Merge "Support device-specific DeviceStatePolicy"
diff --git a/Android.bp b/Android.bp
index cc754f2..72bd642 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",
@@ -177,6 +178,7 @@
     soong_config_variables: {
         include_nonpublic_framework_api: {
             static_libs: [
+                "framework-auxiliary.impl",
                 "framework-supplementalapi.impl",
             ],
         },
@@ -538,7 +540,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 +550,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 +577,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 +611,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..ca211c1 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",
@@ -116,6 +119,7 @@
         ":i18n.module.public.api{.public.stubs.source}",
 
         ":framework-appsearch-sources",
+        ":framework-auxiliary-sources",
         ":framework-connectivity-sources",
         ":framework-connectivity-tiramisu-updatable-sources",
         ":framework-graphics-srcs",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 77b26f8..7f7380a 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: {
@@ -285,6 +286,7 @@
     soong_config_variables: {
         include_nonpublic_framework_api: {
             libs: [
+                "framework-auxiliary.stubs",
                 "framework-supplementalapi.stubs",
             ],
         },
@@ -302,6 +304,7 @@
     soong_config_variables: {
         include_nonpublic_framework_api: {
             libs: [
+                "framework-auxiliary.stubs",
                 "framework-supplementalapi.stubs",
             ],
         },
@@ -334,6 +337,7 @@
     soong_config_variables: {
         include_nonpublic_framework_api: {
             libs: [
+                "framework-auxiliary.stubs",
                 "framework-supplementalapi.stubs",
             ],
         },
@@ -362,6 +366,7 @@
     soong_config_variables: {
         include_nonpublic_framework_api: {
             static_libs: [
+                "framework-auxiliary.stubs",
                 "framework-supplementalapi.stubs",
             ],
         },
@@ -378,6 +383,7 @@
     soong_config_variables: {
         include_nonpublic_framework_api: {
             static_libs: [
+                "framework-auxiliary.stubs",
                 "framework-supplementalapi.stubs",
             ],
         },
@@ -410,6 +416,7 @@
     soong_config_variables: {
         include_nonpublic_framework_api: {
             static_libs: [
+                "framework-auxiliary.stubs",
                 "framework-supplementalapi.stubs",
             ],
         },
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/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..0ad70e4 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 : 3 * 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..9428fcc 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -128,11 +128,13 @@
         "i18n.module.public.api",
     ],
     conditional_bootclasspath: [
+        "framework-auxiliary",
         "framework-supplementalapi",
     ],
     system_server_classpath: [
         "service-media-s",
         "service-permission",
+        "service-supplementalprocess",
     ],
 }
 
diff --git a/boot/Android.bp b/boot/Android.bp
index 3273f2c..8958d70 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -56,6 +56,10 @@
             module: "art-bootclasspath-fragment",
         },
         {
+            apex: "com.android.auxiliary",
+            module: "com.android.auxiliary-bootclasspath-fragment",
+        },
+        {
             apex: "com.android.conscrypt",
             module: "com.android.conscrypt-bootclasspath-fragment",
         },
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 a11f4ba..86fbcee 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -125,11 +125,13 @@
     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_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
@@ -824,6 +826,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 +953,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 +1136,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
@@ -7291,10 +7296,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(int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @NonNull public android.graphics.drawable.Drawable getDrawable(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
     method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
     method @NonNull public String getEnrollmentSpecificId();
     method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
@@ -7311,6 +7316,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);
@@ -7351,6 +7357,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[]);
@@ -7455,6 +7462,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);
@@ -7503,6 +7511,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);
@@ -7598,6 +7607,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
@@ -7672,6 +7682,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
@@ -7859,6 +7873,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 {
@@ -8851,11 +8877,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();
@@ -9719,6 +9746,7 @@
     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);
@@ -9757,6 +9785,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
@@ -10932,10 +10961,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);
@@ -11063,8 +11092,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";
@@ -13183,6 +13212,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
@@ -13475,6 +13505,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();
@@ -13489,6 +13520,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();
@@ -13513,6 +13545,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>);
@@ -17586,12 +17619,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 {
@@ -18047,6 +18084,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
@@ -20923,6 +20961,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
@@ -22073,14 +22112,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);
   }
@@ -22492,6 +22547,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";
@@ -23266,6 +23322,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";
@@ -23283,6 +23340,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";
@@ -23343,12 +23402,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 {
@@ -25776,12 +25841,12 @@
   }
 
   public final class MidiManager {
-    method public android.media.midi.MidiDeviceInfo[] getDevices();
-    method @NonNull public java.util.Collection<android.media.midi.MidiDeviceInfo> getDevicesForTransport(int);
+    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 public void registerDeviceCallbackForTransport(@NonNull android.media.midi.MidiManager.DeviceCallback, @Nullable android.os.Handler, int);
+    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
@@ -26845,16 +26910,43 @@
 
 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();
+  }
+
+  public abstract class TvInteractiveAppService extends android.app.Service {
+    ctor public TvInteractiveAppService();
     method public final android.os.IBinder onBind(android.content.Intent);
-    field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService";
+    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 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 setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback);
+    method public void startInteractiveApp();
+  }
+
+  public abstract static class TvInteractiveAppView.TvInteractiveAppCallback {
+    ctor public TvInteractiveAppView.TvInteractiveAppCallback();
+  }
+
 }
 
 package android.mtp {
@@ -32232,7 +32324,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);
@@ -32242,7 +32334,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);
@@ -32512,6 +32604,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();
@@ -32952,9 +33045,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);
@@ -33280,6 +33377,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 {
@@ -40430,6 +40538,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();
@@ -40450,7 +40559,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);
@@ -40493,7 +40604,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>);
@@ -40509,11 +40623,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 {
@@ -40521,6 +40646,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();
@@ -40614,6 +40741,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();
@@ -40883,7 +41038,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
@@ -41017,6 +41171,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
@@ -41045,6 +41201,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);
@@ -41307,11 +41464,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();
@@ -41327,10 +41485,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";
@@ -41374,6 +41534,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";
@@ -41385,6 +41546,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
@@ -41751,11 +41913,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";
@@ -42001,6 +42163,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";
@@ -42015,11 +42184,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";
@@ -42261,7 +42432,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";
@@ -42283,6 +42453,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 {
@@ -44668,6 +44839,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";
@@ -44918,6 +45090,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;
   }
@@ -44958,6 +45147,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 {
@@ -48549,6 +48759,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);
@@ -50156,6 +50367,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();
@@ -50273,6 +50485,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();
@@ -50515,6 +50728,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);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 2be0c5b..04fc64b 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -63,6 +63,8 @@
 
   public class DevicePolicyManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void acknowledgeNewUserDisclaimer();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void clearLogoutUser();
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getLogoutUser();
     field public static final String ACTION_SHOW_NEW_USER_DISCLAIMER = "android.app.action.SHOW_NEW_USER_DISCLAIMER";
   }
 
@@ -73,12 +75,35 @@
   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 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 +159,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 +278,32 @@
     method public int getResourceId();
   }
 
+  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 +331,30 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStateSnapshot> CREATOR;
   }
 
+  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 +410,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 +445,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 +473,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 +508,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/system-current.txt b/core/api/system-current.txt
index dea3ed5..ce2feac 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";
@@ -37,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";
@@ -139,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";
@@ -158,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";
@@ -366,6 +371,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
@@ -753,7 +759,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 {
@@ -1037,6 +1053,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();
@@ -1049,25 +1067,28 @@
     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 resetStrings(@NonNull String[]);
     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_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
@@ -1121,6 +1142,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();
@@ -1192,6 +1255,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 {
@@ -2041,6 +2185,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);
   }
@@ -2217,6 +2363,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);
@@ -2269,6 +2416,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);
@@ -2287,6 +2443,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();
@@ -2296,15 +2456,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";
@@ -2325,6 +2491,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
@@ -2366,6 +2541,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;
@@ -2582,10 +2758,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>);
   }
 
 }
@@ -2643,6 +2841,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";
@@ -3639,6 +3838,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
   }
 
 }
@@ -4074,6 +4274,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);
@@ -5033,8 +5234,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 {
@@ -5042,8 +5262,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;
@@ -5055,9 +5278,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
   }
 
 }
@@ -6573,6 +6806,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);
@@ -7013,7 +7247,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();
@@ -7713,6 +7947,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();
@@ -7755,6 +7990,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
@@ -8646,6 +8882,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);
@@ -9643,12 +9880,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
   }
@@ -9666,6 +9908,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);
@@ -9674,9 +9917,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);
@@ -9884,6 +10127,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";
@@ -9926,6 +10170,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";
@@ -10154,6 +10399,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";
@@ -10454,6 +10700,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 {
@@ -10905,6 +11163,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 {
@@ -11045,6 +11312,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();
@@ -14328,18 +14596,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);
@@ -14496,6 +14762,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
@@ -14772,9 +15039,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
   }
 
 }
@@ -14924,11 +15188,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 {
@@ -15206,6 +15465,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 {
@@ -15793,3 +16054,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 9a06423..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`
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0e52c5d..ff26ae8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -38,6 +38,7 @@
     field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
     field public static final String REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL = "android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL";
     field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
+    field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
     field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
@@ -1180,9 +1181,23 @@
 
 package android.hardware.input {
 
+  public final class InputDeviceIdentifier implements android.os.Parcelable {
+    ctor public InputDeviceIdentifier(@NonNull String, int, int);
+    method public int describeContents();
+    method @NonNull public String getDescriptor();
+    method public int getProductId();
+    method public int getVendorId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.InputDeviceIdentifier> CREATOR;
+  }
+
   public final class InputManager {
     method public int getBlockUntrustedTouchesMode(@NonNull android.content.Context);
+    method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
+    method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+    method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setBlockUntrustedTouchesMode(@NonNull android.content.Context, int);
+    method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setMaximumObscuringOpacityForTouch(@FloatRange(from=0, to=1) float);
     field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
   }
@@ -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 {
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/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 1e0b143..a7b96a6 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1524,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++) {
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/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/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/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/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/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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 658dcf3..96d037c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1578,6 +1578,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.
@@ -3012,6 +3084,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
      */
@@ -3282,14 +3402,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)
@@ -3304,8 +3425,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";
@@ -9890,14 +10019,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();
         }
@@ -9911,7 +10043,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) {
@@ -14503,6 +14637,105 @@
     }
 
     /**
+     * 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
@@ -14535,11 +14768,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
@@ -14567,9 +14795,6 @@
      *
      * @param drawableIds The list of IDs  to remove.
      *
-     * @throws IllegalArgumentException if IDs are not defined in
-     * {@link DevicePolicyResources.Drawable}
-     *
      * @hide
      */
     @SystemApi
@@ -14593,6 +14818,9 @@
      * <p>Also returns the drawable from {@code defaultDrawableLoader} if
      * {@link DevicePolicyResources.Drawable#INVALID_ID} 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)}.
@@ -14608,7 +14836,7 @@
      * @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,
@@ -14622,6 +14850,9 @@
      * could result in returning a different drawable than {@link #getDrawable(int, int, 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.
      *
@@ -14631,7 +14862,7 @@
      * @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,
@@ -14669,6 +14900,9 @@
      * Similar to {@link #getDrawable(int, int, 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.
      *
@@ -14680,7 +14914,7 @@
      * @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,
@@ -14698,6 +14932,9 @@
      * Similar to {@link #getDrawable(int, int, int, 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.
      *
@@ -14710,7 +14947,7 @@
      * @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,
@@ -14740,4 +14977,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..46e2cce 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -16,8 +16,123 @@
 
 package android.app.admin;
 
+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.IntDef;
+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 +142,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 {
 
@@ -78,6 +193,74 @@
     })
     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.
@@ -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..f663c17 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -18,6 +18,7 @@
 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;
@@ -532,8 +533,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 setDrawables(in List<DevicePolicyDrawableResource> drawables);
     void resetDrawables(in int[] drawableIds);
     ParcelableResource getDrawable(int drawableId, int drawableStyle, int 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/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/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/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/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..b4f2302 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -48,10 +48,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 +137,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 +738,47 @@
     }
 
     boolean checkUser(int pid, int uid, Context context) {
-        if (UserHandle.getUserId(uid) == context.getUserId() || mSingleUser) {
+        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(context.getUser())) {
+                        mUsersRedirectedToOwner.put(callingUser.getIdentifier(), true);
+                        return true;
+                    }
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // ignore
+            }
+
+            mUsersRedirectedToOwner.put(UserHandle.getUserId(uid), false);
+            return false;
+        }
+
+        return false;
     }
 
     /**
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..6ae768a 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -1016,8 +1016,8 @@
     }
 
     @Override
-    public void selfRevokePermissions(@NonNull Collection<String> permissions) {
-        mBase.selfRevokePermissions(permissions);
+    public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) {
+        mBase.revokeOwnPermissionsOnKill(permissions);
     }
 
     @Override
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/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/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c8f88f2..1021d3e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4077,6 +4077,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/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 7d4f7ec..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,6 +2240,44 @@
         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();
 
@@ -2225,6 +2327,15 @@
         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/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/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/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/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..7295b72
--- /dev/null
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.StatusBarManager;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.inputmethodservice.navigationbar.NavigationBarFrame;
+import android.inputmethodservice.navigationbar.NavigationBarView;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManagerPolicyConstants;
+import android.widget.FrameLayout;
+
+import java.util.Objects;
+
+/**
+ * This class hides details behind {@link InputMethodService#canImeRenderGesturalNavButtons()} from
+ * {@link InputMethodService}.
+ *
+ * <p>All the package-private methods are no-op when
+ * {@link InputMethodService#canImeRenderGesturalNavButtons()} returns {@code false}.</p>
+ */
+final class NavigationBarController {
+
+    private interface Callback {
+        default void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
+                @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
+        }
+
+        default void onViewInitialized() {
+        }
+
+        default void onWindowShown() {
+        }
+
+        default void onDestroy() {
+        }
+
+        default String toDebugString() {
+            return "No-op implementation";
+        }
+
+        Callback NOOP = new Callback() {
+        };
+    }
+
+    private final Callback mImpl;
+
+    NavigationBarController(@NonNull InputMethodService inputMethodService) {
+        mImpl = InputMethodService.canImeRenderGesturalNavButtons()
+                ? new Impl(inputMethodService) : Callback.NOOP;
+    }
+
+    void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
+            @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
+        mImpl.updateTouchableInsets(originalInsets, dest);
+    }
+
+    void onViewInitialized() {
+        mImpl.onViewInitialized();
+    }
+
+    void onWindowShown() {
+        mImpl.onWindowShown();
+    }
+
+    void onDestroy() {
+        mImpl.onDestroy();
+    }
+
+    String toDebugString() {
+        return mImpl.toDebugString();
+    }
+
+    private static final class Impl implements Callback {
+        @NonNull
+        private final InputMethodService mService;
+
+        private boolean mDestroyed = false;
+
+        private boolean mRenderGesturalNavButtons;
+
+        @Nullable
+        private NavigationBarFrame mNavigationBarFrame;
+        @Nullable
+        Insets mLastInsets;
+
+        Impl(@NonNull InputMethodService inputMethodService) {
+            mService = inputMethodService;
+        }
+
+        @Nullable
+        private Insets getSystemInsets() {
+            if (mService.mWindow == null) {
+                return null;
+            }
+            final View decorView = mService.mWindow.getWindow().getDecorView();
+            if (decorView == null) {
+                return null;
+            }
+            final WindowInsets windowInsets = decorView.getRootWindowInsets();
+            if (windowInsets == null) {
+                return null;
+            }
+            final Insets stableBarInsets =
+                    windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
+            return Insets.min(windowInsets.getInsets(WindowInsets.Type.systemBars()
+                    | WindowInsets.Type.displayCutout()), stableBarInsets);
+        }
+
+        private void installNavigationBarFrameIfNecessary() {
+            if (!mRenderGesturalNavButtons) {
+                return;
+            }
+            final View rawDecorView = mService.mWindow.getWindow().getDecorView();
+            if (!(rawDecorView instanceof ViewGroup)) {
+                return;
+            }
+            final ViewGroup decorView = (ViewGroup) rawDecorView;
+            mNavigationBarFrame = decorView.findViewByPredicate(
+                    NavigationBarFrame.class::isInstance);
+            final Insets systemInsets = getSystemInsets();
+            if (mNavigationBarFrame == null) {
+                mNavigationBarFrame = new NavigationBarFrame(mService);
+                LayoutInflater.from(mService).inflate(
+                        com.android.internal.R.layout.input_method_navigation_bar,
+                        mNavigationBarFrame);
+                if (systemInsets != null) {
+                    decorView.addView(mNavigationBarFrame, new FrameLayout.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            systemInsets.bottom, Gravity.BOTTOM));
+                    mLastInsets = systemInsets;
+                } else {
+                    decorView.addView(mNavigationBarFrame);
+                }
+                final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate(
+                        NavigationBarView.class::isInstance);
+                if (navigationBarView != null) {
+                    // TODO(b/213337792): Support InputMethodService#setBackDisposition().
+                    // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary.
+                    final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
+                            | StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+                    navigationBarView.setNavigationIconHints(hints);
+                }
+            } else {
+                mNavigationBarFrame.setLayoutParams(new FrameLayout.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT, systemInsets.bottom, Gravity.BOTTOM));
+                mLastInsets = systemInsets;
+            }
+
+            mNavigationBarFrame.setBackground(null);
+        }
+
+        @Override
+        public void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
+                @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
+            if (!mRenderGesturalNavButtons || mNavigationBarFrame == null
+                    || mService.isExtractViewShown()) {
+                return;
+            }
+
+            final Insets systemInsets = getSystemInsets();
+            if (systemInsets != null) {
+                final Window window = mService.mWindow.getWindow();
+                final View decor = window.getDecorView();
+                Region touchableRegion = null;
+                final View inputFrame = mService.mInputFrame;
+                switch (originalInsets.touchableInsets) {
+                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
+                        if (inputFrame.getVisibility() == View.VISIBLE) {
+                            touchableRegion = new Region(inputFrame.getLeft(),
+                                    inputFrame.getTop(), inputFrame.getRight(),
+                                    inputFrame.getBottom());
+                        }
+                        break;
+                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
+                        if (inputFrame.getVisibility() == View.VISIBLE) {
+                            touchableRegion = new Region(inputFrame.getLeft(),
+                                    originalInsets.contentTopInsets, inputFrame.getRight(),
+                                    inputFrame.getBottom());
+                        }
+                        break;
+                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
+                        if (inputFrame.getVisibility() == View.VISIBLE) {
+                            touchableRegion = new Region(inputFrame.getLeft(),
+                                    originalInsets.visibleTopInsets, inputFrame.getRight(),
+                                    inputFrame.getBottom());
+                        }
+                        break;
+                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION:
+                        touchableRegion = new Region();
+                        touchableRegion.set(originalInsets.touchableRegion);
+                        break;
+                }
+                final Rect navBarRect = new Rect(decor.getLeft(),
+                        decor.getBottom() - systemInsets.bottom,
+                        decor.getRight(), decor.getBottom());
+                if (touchableRegion == null) {
+                    touchableRegion = new Region(navBarRect);
+                } else {
+                    touchableRegion.union(navBarRect);
+                }
+
+                dest.touchableRegion.set(touchableRegion);
+                dest.setTouchableInsets(
+                        ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+
+                // TODO(b/205803355): See if we can use View#OnLayoutChangeListener().
+                // TODO(b/205803355): See if we can replace DecorView#mNavigationColorViewState.view
+                boolean zOrderChanged = false;
+                if (decor instanceof ViewGroup) {
+                    ViewGroup decorGroup = (ViewGroup) decor;
+                    final View navbarBackgroundView = window.getNavigationBarBackgroundView();
+                    zOrderChanged = navbarBackgroundView != null
+                            && decorGroup.indexOfChild(navbarBackgroundView)
+                            > decorGroup.indexOfChild(mNavigationBarFrame);
+                }
+                final boolean insetChanged = !Objects.equals(systemInsets, mLastInsets);
+                if (zOrderChanged || insetChanged) {
+                    final NavigationBarFrame that = mNavigationBarFrame;
+                    that.post(() -> {
+                        if (!that.isAttachedToWindow()) {
+                            return;
+                        }
+                        final Insets currentSystemInsets = getSystemInsets();
+                        if (!Objects.equals(currentSystemInsets, mLastInsets)) {
+                            that.setLayoutParams(
+                                    new FrameLayout.LayoutParams(
+                                            ViewGroup.LayoutParams.MATCH_PARENT,
+                                            currentSystemInsets.bottom, Gravity.BOTTOM));
+                            mLastInsets = currentSystemInsets;
+                        }
+                        if (decor instanceof ViewGroup) {
+                            ViewGroup decorGroup = (ViewGroup) decor;
+                            final View navbarBackgroundView =
+                                    window.getNavigationBarBackgroundView();
+                            if (navbarBackgroundView != null
+                                    && decorGroup.indexOfChild(navbarBackgroundView)
+                                    > decorGroup.indexOfChild(that)) {
+                                decorGroup.bringChildToFront(that);
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        private boolean isGesturalNavigationEnabled() {
+            final Resources resources = mService.getResources();
+            if (resources == null) {
+                return false;
+            }
+            return resources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode)
+                    == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+        }
+
+        @Override
+        public void onViewInitialized() {
+            if (mDestroyed) {
+                return;
+            }
+            mRenderGesturalNavButtons = isGesturalNavigationEnabled();
+            installNavigationBarFrameIfNecessary();
+        }
+
+        @Override
+        public void onDestroy() {
+            mDestroyed = true;
+        }
+
+        @Override
+        public void onWindowShown() {
+            if (mDestroyed || !mRenderGesturalNavButtons || mNavigationBarFrame == null) {
+                return;
+            }
+            final Insets systemInsets = getSystemInsets();
+            if (systemInsets != null) {
+                if (!Objects.equals(systemInsets, mLastInsets)) {
+                    mNavigationBarFrame.setLayoutParams(new NavigationBarFrame.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            systemInsets.bottom, Gravity.BOTTOM));
+                    mLastInsets = systemInsets;
+                }
+                final Window window = mService.mWindow.getWindow();
+                View rawDecorView = window.getDecorView();
+                if (rawDecorView instanceof ViewGroup) {
+                    final ViewGroup decor = (ViewGroup) rawDecorView;
+                    final View navbarBackgroundView = window.getNavigationBarBackgroundView();
+                    if (navbarBackgroundView != null
+                            && decor.indexOfChild(navbarBackgroundView)
+                            > decor.indexOfChild(mNavigationBarFrame)) {
+                        decor.bringChildToFront(mNavigationBarFrame);
+                    }
+                }
+                mNavigationBarFrame.setVisibility(View.VISIBLE);
+            }
+        }
+
+        @Override
+        public String toDebugString() {
+            return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons + "}";
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java
new file mode 100644
index 0000000..3f26fa4
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.ArrayList;
+
+/**
+ * Dispatches common view calls to multiple views.  This is used to handle
+ * multiples of the same nav bar icon appearing.
+ */
+final class ButtonDispatcher {
+    private static final int FADE_DURATION_IN = 150;
+    private static final int FADE_DURATION_OUT = 250;
+    public static final Interpolator LINEAR = new LinearInterpolator();
+
+    private final ArrayList<View> mViews = new ArrayList<>();
+
+    private final int mId;
+
+    private View.OnClickListener mClickListener;
+    private View.OnTouchListener mTouchListener;
+    private View.OnLongClickListener mLongClickListener;
+    private View.OnHoverListener mOnHoverListener;
+    private Boolean mLongClickable;
+    private float mAlpha = 1.0f;
+    private Float mDarkIntensity;
+    private int mVisibility = View.VISIBLE;
+    private Boolean mDelayTouchFeedback;
+    private KeyButtonDrawable mImageDrawable;
+    private View mCurrentView;
+    private ValueAnimator mFadeAnimator;
+    private AccessibilityDelegate mAccessibilityDelegate;
+
+    private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation ->
+            setAlpha(
+                    (float) animation.getAnimatedValue(),
+                    false /* animate */,
+                    false /* cancelAnimator */);
+
+    private final AnimatorListenerAdapter mFadeListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mFadeAnimator = null;
+            setVisibility(getAlpha() == 1 ? View.VISIBLE : View.INVISIBLE);
+        }
+    };
+
+    public ButtonDispatcher(int id) {
+        mId = id;
+    }
+
+    public void clear() {
+        mViews.clear();
+    }
+
+    public void addView(View view) {
+        mViews.add(view);
+        view.setOnClickListener(mClickListener);
+        view.setOnTouchListener(mTouchListener);
+        view.setOnLongClickListener(mLongClickListener);
+        view.setOnHoverListener(mOnHoverListener);
+        if (mLongClickable != null) {
+            view.setLongClickable(mLongClickable);
+        }
+        view.setAlpha(mAlpha);
+        view.setVisibility(mVisibility);
+        if (mAccessibilityDelegate != null) {
+            view.setAccessibilityDelegate(mAccessibilityDelegate);
+        }
+        if (view instanceof ButtonInterface) {
+            final ButtonInterface button = (ButtonInterface) view;
+            if (mDarkIntensity != null) {
+                button.setDarkIntensity(mDarkIntensity);
+            }
+            if (mImageDrawable != null) {
+                button.setImageDrawable(mImageDrawable);
+            }
+            if (mDelayTouchFeedback != null) {
+                button.setDelayTouchFeedback(mDelayTouchFeedback);
+            }
+        }
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public int getVisibility() {
+        return mVisibility;
+    }
+
+    public boolean isVisible() {
+        return getVisibility() == View.VISIBLE;
+    }
+
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    public KeyButtonDrawable getImageDrawable() {
+        return mImageDrawable;
+    }
+
+    public void setImageDrawable(KeyButtonDrawable drawable) {
+        mImageDrawable = drawable;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            if (mViews.get(i) instanceof ButtonInterface) {
+                ((ButtonInterface) mViews.get(i)).setImageDrawable(mImageDrawable);
+            }
+        }
+        if (mImageDrawable != null) {
+            mImageDrawable.setCallback(mCurrentView);
+        }
+    }
+
+    public void setVisibility(int visibility) {
+        if (mVisibility == visibility) return;
+        if (mFadeAnimator != null) {
+            mFadeAnimator.cancel();
+        }
+
+        mVisibility = visibility;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setVisibility(mVisibility);
+        }
+    }
+
+    public void setAlpha(float alpha) {
+        setAlpha(alpha, false /* animate */);
+    }
+
+    public void setAlpha(float alpha, boolean animate) {
+        setAlpha(alpha, animate, true /* cancelAnimator */);
+    }
+
+    public void setAlpha(float alpha, boolean animate, long duration) {
+        setAlpha(alpha, animate, duration, true /* cancelAnimator */);
+    }
+
+    public void setAlpha(float alpha, boolean animate, boolean cancelAnimator) {
+        setAlpha(
+                alpha,
+                animate,
+                (getAlpha() < alpha) ? FADE_DURATION_IN : FADE_DURATION_OUT,
+                cancelAnimator);
+    }
+
+    public void setAlpha(float alpha, boolean animate, long duration, boolean cancelAnimator) {
+        if (mFadeAnimator != null && (cancelAnimator || animate)) {
+            mFadeAnimator.cancel();
+        }
+        if (animate) {
+            setVisibility(View.VISIBLE);
+            mFadeAnimator = ValueAnimator.ofFloat(getAlpha(), alpha);
+            mFadeAnimator.setDuration(duration);
+            mFadeAnimator.setInterpolator(LINEAR);
+            mFadeAnimator.addListener(mFadeListener);
+            mFadeAnimator.addUpdateListener(mAlphaListener);
+            mFadeAnimator.start();
+        } else {
+            // Discretize the alpha updates to prevent too frequent updates when there is a long
+            // alpha animation
+            int prevAlpha = (int) (getAlpha() * 255);
+            int nextAlpha = (int) (alpha * 255);
+            if (prevAlpha != nextAlpha) {
+                mAlpha = nextAlpha / 255f;
+                final int N = mViews.size();
+                for (int i = 0; i < N; i++) {
+                    mViews.get(i).setAlpha(mAlpha);
+                }
+            }
+        }
+    }
+
+    public void setDarkIntensity(float darkIntensity) {
+        mDarkIntensity = darkIntensity;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            if (mViews.get(i) instanceof ButtonInterface) {
+                ((ButtonInterface) mViews.get(i)).setDarkIntensity(darkIntensity);
+            }
+        }
+    }
+
+    public void setDelayTouchFeedback(boolean delay) {
+        mDelayTouchFeedback = delay;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            if (mViews.get(i) instanceof ButtonInterface) {
+                ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay);
+            }
+        }
+    }
+
+    public void setOnClickListener(View.OnClickListener clickListener) {
+        mClickListener = clickListener;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setOnClickListener(mClickListener);
+        }
+    }
+
+    public void setOnTouchListener(View.OnTouchListener touchListener) {
+        mTouchListener = touchListener;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setOnTouchListener(mTouchListener);
+        }
+    }
+
+    public void setLongClickable(boolean isLongClickable) {
+        mLongClickable = isLongClickable;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setLongClickable(mLongClickable);
+        }
+    }
+
+    public void setOnLongClickListener(View.OnLongClickListener longClickListener) {
+        mLongClickListener = longClickListener;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setOnLongClickListener(mLongClickListener);
+        }
+    }
+
+    public void setOnHoverListener(View.OnHoverListener hoverListener) {
+        mOnHoverListener = hoverListener;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setOnHoverListener(mOnHoverListener);
+        }
+    }
+
+    public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
+        mAccessibilityDelegate = delegate;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setAccessibilityDelegate(delegate);
+        }
+    }
+
+    public void setTranslation(int x, int y, int z) {
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            final View view = mViews.get(i);
+            view.setTranslationX(x);
+            view.setTranslationY(y);
+            view.setTranslationZ(z);
+        }
+    }
+
+    public ArrayList<View> getViews() {
+        return mViews;
+    }
+
+    public View getCurrentView() {
+        return mCurrentView;
+    }
+
+    public void setCurrentView(View currentView) {
+        mCurrentView = currentView.findViewById(mId);
+        if (mImageDrawable != null) {
+            mImageDrawable.setCallback(mCurrentView);
+        }
+        if (mCurrentView != null) {
+            mCurrentView.setTranslationX(0);
+            mCurrentView.setTranslationY(0);
+            mCurrentView.setTranslationZ(0);
+        }
+    }
+
+    /**
+     * Executes when button is detached from window.
+     */
+    public void onDestroy() {
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java b/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java
new file mode 100644
index 0000000..1c9c86d
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import android.annotation.Nullable;
+import android.graphics.drawable.Drawable;
+
+interface ButtonInterface {
+
+    void setImageDrawable(@Nullable Drawable drawable);
+
+    void setDarkIntensity(float intensity);
+
+    void setDelayTouchFeedback(boolean shouldDelay);
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/DeadZone.java b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
new file mode 100644
index 0000000..cd85736
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_DECAY;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_HOLD;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_SIZE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_SIZE_MAX;
+import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
+
+import android.animation.ObjectAnimator;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+/**
+ * The "dead zone" consumes unintentional taps along the top edge of the navigation bar.
+ * When users are typing quickly on an IME they may attempt to hit the space bar, overshoot, and
+ * accidentally hit the home button. The DeadZone expands temporarily after each tap in the UI
+ * outside the navigation bar (since this is when accidental taps are more likely), then contracts
+ * back over time (since a later tap might be intended for the top of the bar).
+ */
+final class DeadZone {
+    public static final String TAG = "DeadZone";
+
+    public static final boolean DEBUG = false;
+    public static final int HORIZONTAL = 0;  // Consume taps along the top edge.
+    public static final int VERTICAL = 1;  // Consume taps along the left edge.
+
+    private static final boolean CHATTY = true; // print to logcat when we eat a click
+    private final NavigationBarView mNavigationBarView;
+
+    private boolean mShouldFlash;
+    private float mFlashFrac = 0f;
+
+    private int mSizeMax;
+    private int mSizeMin;
+    // Upon activity elsewhere in the UI, the dead zone will hold steady for
+    // mHold ms, then move back over the course of mDecay ms
+    private int mHold, mDecay;
+    private boolean mVertical;
+    private long mLastPokeTime;
+    private int mDisplayRotation;
+
+    private final Runnable mDebugFlash = new Runnable() {
+        @Override
+        public void run() {
+            ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start();
+        }
+    };
+
+    public DeadZone(NavigationBarView view) {
+        mNavigationBarView = view;
+        onConfigurationChanged(Surface.ROTATION_0);
+    }
+
+    static float lerp(float a, float b, float f) {
+        return (b - a) * f + a;
+    }
+
+    private float getSize(long now) {
+        if (mSizeMax == 0)
+            return 0;
+        long dt = (now - mLastPokeTime);
+        if (dt > mHold + mDecay)
+            return mSizeMin;
+        if (dt < mHold)
+            return mSizeMax;
+        return (int) lerp(mSizeMax, mSizeMin, (float) (dt - mHold) / mDecay);
+    }
+
+    public void setFlashOnTouchCapture(boolean dbg) {
+        mShouldFlash = dbg;
+        mFlashFrac = 0f;
+        mNavigationBarView.postInvalidate();
+    }
+
+    public void onConfigurationChanged(int rotation) {
+        mDisplayRotation = rotation;
+
+        final Resources res = mNavigationBarView.getResources();
+        mHold = NAVIGATION_BAR_DEADZONE_HOLD;
+        mDecay = NAVIGATION_BAR_DEADZONE_DECAY;
+
+        mSizeMin = dpToPx(NAVIGATION_BAR_DEADZONE_SIZE, res);
+        mSizeMax = dpToPx(NAVIGATION_BAR_DEADZONE_SIZE_MAX, res);
+        mVertical = (res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
+
+        if (DEBUG) {
+            Log.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold
+                    + (mVertical ? " vertical" : " horizontal"));
+        }
+        setFlashOnTouchCapture(false);   // hard-coded from "bool/config_dead_zone_flash"
+    }
+
+    // I made you a touch event...
+    public boolean onTouchEvent(MotionEvent event) {
+        if (DEBUG) {
+            Log.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction()));
+        }
+
+        // Don't consume events for high precision pointing devices. For this purpose a stylus is
+        // considered low precision (like a finger), so its events may be consumed.
+        if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+            return false;
+        }
+
+        final int action = event.getAction();
+        if (action == MotionEvent.ACTION_OUTSIDE) {
+            poke(event);
+            return true;
+        } else if (action == MotionEvent.ACTION_DOWN) {
+            if (DEBUG) {
+                Log.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY());
+            }
+            //TODO(b/205803355): call mNavBarController.touchAutoDim(mDisplayId); here
+            int size = (int) getSize(event.getEventTime());
+            // In the vertical orientation consume taps along the left edge.
+            // In horizontal orientation consume taps along the top edge.
+            final boolean consumeEvent;
+            if (mVertical) {
+                if (mDisplayRotation == Surface.ROTATION_270) {
+                    consumeEvent = event.getX() > mNavigationBarView.getWidth() - size;
+                } else {
+                    consumeEvent = event.getX() < size;
+                }
+            } else {
+                consumeEvent = event.getY() < size;
+            }
+            if (consumeEvent) {
+                if (CHATTY) {
+                    Log.v(TAG, "consuming errant click: (" + event.getX() + ","
+                            + event.getY() + ")");
+                }
+                if (mShouldFlash) {
+                    mNavigationBarView.post(mDebugFlash);
+                    mNavigationBarView.postInvalidate();
+                }
+                return true; // ...but I eated it
+            }
+        }
+        return false;
+    }
+
+    private void poke(MotionEvent event) {
+        mLastPokeTime = event.getEventTime();
+        if (DEBUG)
+            Log.v(TAG, "poked! size=" + getSize(mLastPokeTime));
+        if (mShouldFlash) mNavigationBarView.postInvalidate();
+    }
+
+    public void setFlash(float f) {
+        mFlashFrac = f;
+        mNavigationBarView.postInvalidate();
+    }
+
+    public float getFlash() {
+        return mFlashFrac;
+    }
+
+    public void onDraw(Canvas can) {
+        if (!mShouldFlash || mFlashFrac <= 0f) {
+            return;
+        }
+
+        final int size = (int) getSize(SystemClock.uptimeMillis());
+        if (mVertical) {
+            if (mDisplayRotation == Surface.ROTATION_270) {
+                can.clipRect(can.getWidth() - size, 0, can.getWidth(), can.getHeight());
+            } else {
+                can.clipRect(0, 0, size, can.getHeight());
+            }
+        } else {
+            can.clipRect(0, 0, can.getWidth(), size);
+        }
+
+        final float frac = DEBUG ? (mFlashFrac - 0.5f) + 0.5f : mFlashFrac;
+        can.drawARGB((int) (frac * 0xFF), 0xDD, 0xEE, 0xAA);
+
+        if (DEBUG && size > mSizeMin) {
+            // Very aggressive redrawing here, for debugging only
+            mNavigationBarView.postInvalidateDelayed(100);
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java
new file mode 100644
index 0000000..25a443d
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_COLOR;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_OFFSET_X;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_OFFSET_Y;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_RADIUS;
+import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
+
+import android.animation.ArgbEvaluator;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.BlurMaskFilter.Blur;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+import android.view.View;
+
+
+/**
+ * Drawable for {@link KeyButtonView}s that supports tinting between two colors, rotation and shows
+ * a shadow. AnimatedVectorDrawable will only support tinting from intensities but has no support
+ * for shadows nor rotations.
+ */
+final class KeyButtonDrawable extends Drawable {
+
+    public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_ROTATE =
+        new FloatProperty<KeyButtonDrawable>("KeyButtonRotation") {
+            @Override
+            public void setValue(KeyButtonDrawable drawable, float degree) {
+                drawable.setRotation(degree);
+            }
+
+            @Override
+            public Float get(KeyButtonDrawable drawable) {
+                return drawable.getRotation();
+            }
+        };
+
+    public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_TRANSLATE_Y =
+        new FloatProperty<KeyButtonDrawable>("KeyButtonTranslateY") {
+            @Override
+            public void setValue(KeyButtonDrawable drawable, float y) {
+                drawable.setTranslationY(y);
+            }
+
+            @Override
+            public Float get(KeyButtonDrawable drawable) {
+                return drawable.getTranslationY();
+            }
+        };
+
+    private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+    private final Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+    private final ShadowDrawableState mState;
+    private AnimatedVectorDrawable mAnimatedDrawable;
+    private final Callback mAnimatedDrawableCallback = new Callback() {
+        @Override
+        public void invalidateDrawable(@NonNull Drawable who) {
+            invalidateSelf();
+        }
+
+        @Override
+        public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+            scheduleSelf(what, when);
+        }
+
+        @Override
+        public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+            unscheduleSelf(what);
+        }
+    };
+
+    public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor,
+            boolean horizontalFlip, Color ovalBackgroundColor) {
+        this(d, new ShadowDrawableState(lightColor, darkColor,
+                d instanceof AnimatedVectorDrawable, horizontalFlip, ovalBackgroundColor));
+    }
+
+    private KeyButtonDrawable(Drawable d, ShadowDrawableState state) {
+        mState = state;
+        if (d != null) {
+            mState.mBaseHeight = d.getIntrinsicHeight();
+            mState.mBaseWidth = d.getIntrinsicWidth();
+            mState.mChangingConfigurations = d.getChangingConfigurations();
+            mState.mChildState = d.getConstantState();
+        }
+        if (canAnimate()) {
+            mAnimatedDrawable = (AnimatedVectorDrawable) mState.mChildState.newDrawable().mutate();
+            mAnimatedDrawable.setCallback(mAnimatedDrawableCallback);
+            setDrawableBounds(mAnimatedDrawable);
+        }
+    }
+
+    public void setDarkIntensity(float intensity) {
+        mState.mDarkIntensity = intensity;
+        final int color = (int) ArgbEvaluator.getInstance()
+                .evaluate(intensity, mState.mLightColor, mState.mDarkColor);
+        updateShadowAlpha();
+        setColorFilter(new PorterDuffColorFilter(color, Mode.SRC_ATOP));
+    }
+
+    public void setRotation(float degrees) {
+        if (canAnimate()) {
+            // AnimatedVectorDrawables will not support rotation
+            return;
+        }
+        if (mState.mRotateDegrees != degrees) {
+            mState.mRotateDegrees = degrees;
+            invalidateSelf();
+        }
+    }
+
+    public void setTranslationX(float x) {
+        setTranslation(x, mState.mTranslationY);
+    }
+
+    public void setTranslationY(float y) {
+        setTranslation(mState.mTranslationX, y);
+    }
+
+    public void setTranslation(float x, float y) {
+        if (mState.mTranslationX != x || mState.mTranslationY != y) {
+            mState.mTranslationX = x;
+            mState.mTranslationY = y;
+            invalidateSelf();
+        }
+    }
+
+    public void setShadowProperties(int x, int y, int size, int color) {
+        if (canAnimate()) {
+            // AnimatedVectorDrawables will not support shadows
+            return;
+        }
+        if (mState.mShadowOffsetX != x || mState.mShadowOffsetY != y
+                || mState.mShadowSize != size || mState.mShadowColor != color) {
+            mState.mShadowOffsetX = x;
+            mState.mShadowOffsetY = y;
+            mState.mShadowSize = size;
+            mState.mShadowColor = color;
+            mShadowPaint.setColorFilter(
+                    new PorterDuffColorFilter(mState.mShadowColor, Mode.SRC_ATOP));
+            updateShadowAlpha();
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        boolean changed = super.setVisible(visible, restart);
+        if (changed) {
+            // End any existing animations when the visibility changes
+            jumpToCurrentState();
+        }
+        return changed;
+    }
+
+    @Override
+    public void jumpToCurrentState() {
+        super.jumpToCurrentState();
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.jumpToCurrentState();
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mState.mAlpha = alpha;
+        mIconPaint.setAlpha(alpha);
+        updateShadowAlpha();
+        invalidateSelf();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mIconPaint.setColorFilter(colorFilter);
+        if (mAnimatedDrawable != null) {
+            if (hasOvalBg()) {
+                mAnimatedDrawable.setColorFilter(
+                        new PorterDuffColorFilter(mState.mLightColor, PorterDuff.Mode.SRC_IN));
+            } else {
+                mAnimatedDrawable.setColorFilter(colorFilter);
+            }
+        }
+        invalidateSelf();
+    }
+
+    public float getDarkIntensity() {
+        return mState.mDarkIntensity;
+    }
+
+    public float getRotation() {
+        return mState.mRotateDegrees;
+    }
+
+    public float getTranslationX() {
+        return mState.mTranslationX;
+    }
+
+    public float getTranslationY() {
+        return mState.mTranslationY;
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        return mState;
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mState.mBaseHeight + (mState.mShadowSize + Math.abs(mState.mShadowOffsetY)) * 2;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mState.mBaseWidth + (mState.mShadowSize + Math.abs(mState.mShadowOffsetX)) * 2;
+    }
+
+    public boolean canAnimate() {
+        return mState.mSupportsAnimation;
+    }
+
+    public void startAnimation() {
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.start();
+        }
+    }
+
+    public void resetAnimation() {
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.reset();
+        }
+    }
+
+    public void clearAnimationCallbacks() {
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.clearAnimationCallbacks();
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        Rect bounds = getBounds();
+        if (bounds.isEmpty()) {
+            return;
+        }
+
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.draw(canvas);
+        } else {
+            // If no cache or previous cached bitmap is hardware/software acceleration does not
+            // match the current canvas on draw then regenerate
+            boolean hwBitmapChanged = mState.mIsHardwareBitmap != canvas.isHardwareAccelerated();
+            if (hwBitmapChanged) {
+                mState.mIsHardwareBitmap = canvas.isHardwareAccelerated();
+            }
+            if (mState.mLastDrawnIcon == null || hwBitmapChanged) {
+                regenerateBitmapIconCache();
+            }
+            canvas.save();
+            canvas.translate(mState.mTranslationX, mState.mTranslationY);
+            canvas.rotate(mState.mRotateDegrees, getIntrinsicWidth() / 2, getIntrinsicHeight() / 2);
+
+            if (mState.mShadowSize > 0) {
+                if (mState.mLastDrawnShadow == null || hwBitmapChanged) {
+                    regenerateBitmapShadowCache();
+                }
+
+                // Translate (with rotation offset) before drawing the shadow
+                final float radians = (float) (mState.mRotateDegrees * Math.PI / 180);
+                final float shadowOffsetX = (float) (Math.sin(radians) * mState.mShadowOffsetY
+                        + Math.cos(radians) * mState.mShadowOffsetX) - mState.mTranslationX;
+                final float shadowOffsetY = (float) (Math.cos(radians) * mState.mShadowOffsetY
+                        - Math.sin(radians) * mState.mShadowOffsetX) - mState.mTranslationY;
+                canvas.drawBitmap(mState.mLastDrawnShadow, shadowOffsetX, shadowOffsetY,
+                        mShadowPaint);
+            }
+            canvas.drawBitmap(mState.mLastDrawnIcon, null, bounds, mIconPaint);
+            canvas.restore();
+        }
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return mState.canApplyTheme();
+    }
+
+    @ColorInt int getDrawableBackgroundColor() {
+        return mState.mOvalBackgroundColor.toArgb();
+    }
+
+    boolean hasOvalBg() {
+        return mState.mOvalBackgroundColor != null;
+    }
+
+    private void regenerateBitmapIconCache() {
+        final int width = getIntrinsicWidth();
+        final int height = getIntrinsicHeight();
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(bitmap);
+
+        // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
+        final Drawable d = mState.mChildState.newDrawable().mutate();
+        setDrawableBounds(d);
+        canvas.save();
+        if (mState.mHorizontalFlip) {
+            canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f);
+        }
+        d.draw(canvas);
+        canvas.restore();
+
+        if (mState.mIsHardwareBitmap) {
+            bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
+        }
+        mState.mLastDrawnIcon = bitmap;
+    }
+
+    private void regenerateBitmapShadowCache() {
+        if (mState.mShadowSize == 0) {
+            // No shadow
+            mState.mLastDrawnIcon = null;
+            return;
+        }
+
+        final int width = getIntrinsicWidth();
+        final int height = getIntrinsicHeight();
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
+        final Drawable d = mState.mChildState.newDrawable().mutate();
+        setDrawableBounds(d);
+        canvas.save();
+        if (mState.mHorizontalFlip) {
+            canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f);
+        }
+        d.draw(canvas);
+        canvas.restore();
+
+        // Draws the shadow from original drawable
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, Blur.NORMAL));
+        int[] offset = new int[2];
+        final Bitmap shadow = bitmap.extractAlpha(paint, offset);
+        paint.setMaskFilter(null);
+        bitmap.eraseColor(Color.TRANSPARENT);
+        canvas.drawBitmap(shadow, offset[0], offset[1], paint);
+
+        if (mState.mIsHardwareBitmap) {
+            bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
+        }
+        mState.mLastDrawnShadow = bitmap;
+    }
+
+    /**
+     * Set the alpha of the shadow. As dark intensity increases, drop the alpha of the shadow since
+     * dark color and shadow should not be visible at the same time.
+     */
+    private void updateShadowAlpha() {
+        // Update the color from the original color's alpha as the max
+        int alpha = Color.alpha(mState.mShadowColor);
+        mShadowPaint.setAlpha(
+                Math.round(alpha * (mState.mAlpha / 255f) * (1 - mState.mDarkIntensity)));
+    }
+
+    /**
+     * Prevent shadow clipping by offsetting the drawable bounds by the shadow and its offset
+     * @param d the drawable to set the bounds
+     */
+    private void setDrawableBounds(Drawable d) {
+        final int offsetX = mState.mShadowSize + Math.abs(mState.mShadowOffsetX);
+        final int offsetY = mState.mShadowSize + Math.abs(mState.mShadowOffsetY);
+        d.setBounds(offsetX, offsetY, getIntrinsicWidth() - offsetX,
+                getIntrinsicHeight() - offsetY);
+    }
+
+    private static class ShadowDrawableState extends ConstantState {
+        int mChangingConfigurations;
+        int mBaseWidth;
+        int mBaseHeight;
+        float mRotateDegrees;
+        float mTranslationX;
+        float mTranslationY;
+        int mShadowOffsetX;
+        int mShadowOffsetY;
+        int mShadowSize;
+        int mShadowColor;
+        float mDarkIntensity;
+        int mAlpha;
+        boolean mHorizontalFlip;
+
+        boolean mIsHardwareBitmap;
+        Bitmap mLastDrawnIcon;
+        Bitmap mLastDrawnShadow;
+        ConstantState mChildState;
+
+        final int mLightColor;
+        final int mDarkColor;
+        final boolean mSupportsAnimation;
+        final Color mOvalBackgroundColor;
+
+        public ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor,
+                boolean animated, boolean horizontalFlip, Color ovalBackgroundColor) {
+            mLightColor = lightColor;
+            mDarkColor = darkColor;
+            mSupportsAnimation = animated;
+            mAlpha = 255;
+            mHorizontalFlip = horizontalFlip;
+            mOvalBackgroundColor = ovalBackgroundColor;
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new KeyButtonDrawable(null, this);
+        }
+
+        @Override
+        public int getChangingConfigurations() {
+            return mChangingConfigurations;
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return true;
+        }
+    }
+
+    /**
+     * Creates a KeyButtonDrawable with a shadow given its icon. For more information, see
+     * {@link #create(Context, int, boolean, boolean)}.
+     */
+    public static KeyButtonDrawable create(Context context, @ColorInt int lightColor,
+            @ColorInt int darkColor, @DrawableRes int iconResId, boolean hasShadow,
+            Color ovalBackgroundColor) {
+        final Resources res = context.getResources();
+        boolean isRtl = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+        Drawable d = context.getDrawable(iconResId);
+        final KeyButtonDrawable drawable = new KeyButtonDrawable(d, lightColor, darkColor,
+                isRtl && d.isAutoMirrored(), ovalBackgroundColor);
+        if (hasShadow) {
+            int offsetX = dpToPx(NAV_KEY_BUTTON_SHADOW_OFFSET_X, res);
+            int offsetY = dpToPx(NAV_KEY_BUTTON_SHADOW_OFFSET_Y, res);
+            int radius = dpToPx(NAV_KEY_BUTTON_SHADOW_RADIUS, res);
+            int color = NAV_KEY_BUTTON_SHADOW_COLOR;
+            drawable.setShadowProperties(offsetX, offsetY, radius, color);
+        }
+        return drawable;
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java
new file mode 100644
index 0000000..38a63b6
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.DimenRes;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RecordingCanvas;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Trace;
+import android.view.RenderNodeAnimator;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+final class KeyButtonRipple extends Drawable {
+
+    private static final float GLOW_MAX_SCALE_FACTOR = 1.35f;
+    private static final float GLOW_MAX_ALPHA = 0.2f;
+    private static final float GLOW_MAX_ALPHA_DARK = 0.1f;
+    private static final int ANIMATION_DURATION_SCALE = 350;
+    private static final int ANIMATION_DURATION_FADE = 450;
+    private static final Interpolator ALPHA_OUT_INTERPOLATOR =
+            new PathInterpolator(0f, 0f, 0.8f, 1f);
+
+    @DimenRes
+    private final int mMaxWidthResource;
+
+    private Paint mRipplePaint;
+    private CanvasProperty<Float> mLeftProp;
+    private CanvasProperty<Float> mTopProp;
+    private CanvasProperty<Float> mRightProp;
+    private CanvasProperty<Float> mBottomProp;
+    private CanvasProperty<Float> mRxProp;
+    private CanvasProperty<Float> mRyProp;
+    private CanvasProperty<Paint> mPaintProp;
+    private float mGlowAlpha = 0f;
+    private float mGlowScale = 1f;
+    private boolean mPressed;
+    private boolean mVisible;
+    private boolean mDrawingHardwareGlow;
+    private int mMaxWidth;
+    private boolean mLastDark;
+    private boolean mDark;
+    private boolean mDelayTouchFeedback;
+
+    private final Interpolator mInterpolator = new LogInterpolator();
+    private boolean mSupportHardware;
+    private final View mTargetView;
+    private final Handler mHandler = new Handler();
+
+    private final HashSet<Animator> mRunningAnimations = new HashSet<>();
+    private final ArrayList<Animator> mTmpArray = new ArrayList<>();
+
+    private final TraceAnimatorListener mExitHwTraceAnimator =
+            new TraceAnimatorListener("exitHardware");
+    private final TraceAnimatorListener mEnterHwTraceAnimator =
+            new TraceAnimatorListener("enterHardware");
+
+    public enum Type {
+        OVAL,
+        ROUNDED_RECT
+    }
+
+    private Type mType = Type.ROUNDED_RECT;
+
+    public KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) {
+        mMaxWidthResource = maxWidthResource;
+        mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource);
+        mTargetView = targetView;
+    }
+
+    public void updateResources() {
+        mMaxWidth = mTargetView.getContext().getResources()
+                .getDimensionPixelSize(mMaxWidthResource);
+        invalidateSelf();
+    }
+
+    public void setDarkIntensity(float darkIntensity) {
+        mDark = darkIntensity >= 0.5f;
+    }
+
+    public void setDelayTouchFeedback(boolean delay) {
+        mDelayTouchFeedback = delay;
+    }
+
+    public void setType(Type type) {
+        mType = type;
+    }
+
+    private Paint getRipplePaint() {
+        if (mRipplePaint == null) {
+            mRipplePaint = new Paint();
+            mRipplePaint.setAntiAlias(true);
+            mRipplePaint.setColor(mLastDark ? 0xff000000 : 0xffffffff);
+        }
+        return mRipplePaint;
+    }
+
+    private void drawSoftware(Canvas canvas) {
+        if (mGlowAlpha > 0f) {
+            final Paint p = getRipplePaint();
+            p.setAlpha((int)(mGlowAlpha * 255f));
+
+            final float w = getBounds().width();
+            final float h = getBounds().height();
+            final boolean horizontal = w > h;
+            final float diameter = getRippleSize() * mGlowScale;
+            final float radius = diameter * .5f;
+            final float cx = w * .5f;
+            final float cy = h * .5f;
+            final float rx = horizontal ? radius : cx;
+            final float ry = horizontal ? cy : radius;
+            final float corner = horizontal ? cy : cx;
+
+            if (mType == Type.ROUNDED_RECT) {
+                canvas.drawRoundRect(cx - rx, cy - ry, cx + rx, cy + ry, corner, corner, p);
+            } else {
+                canvas.save();
+                canvas.translate(cx, cy);
+                float r = Math.min(rx, ry);
+                canvas.drawOval(-r, -r, r, r, p);
+                canvas.restore();
+            }
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        mSupportHardware = canvas.isHardwareAccelerated();
+        if (mSupportHardware) {
+            drawHardware((RecordingCanvas) canvas);
+        } else {
+            drawSoftware(canvas);
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        // Not supported.
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        // Not supported.
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    private boolean isHorizontal() {
+        return getBounds().width() > getBounds().height();
+    }
+
+    private void drawHardware(RecordingCanvas c) {
+        if (mDrawingHardwareGlow) {
+            if (mType == Type.ROUNDED_RECT) {
+                c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp,
+                        mPaintProp);
+            } else {
+                CanvasProperty<Float> cx = CanvasProperty.createFloat(getBounds().width() / 2);
+                CanvasProperty<Float> cy = CanvasProperty.createFloat(getBounds().height() / 2);
+                int d = Math.min(getBounds().width(), getBounds().height());
+                CanvasProperty<Float> r = CanvasProperty.createFloat(1.0f * d / 2);
+                c.drawCircle(cx, cy, r, mPaintProp);
+            }
+        }
+    }
+
+    /** Gets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+    public float getGlowAlpha() {
+        return mGlowAlpha;
+    }
+
+    /** Sets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+    public void setGlowAlpha(float x) {
+        mGlowAlpha = x;
+        invalidateSelf();
+    }
+
+    /** Gets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+    public float getGlowScale() {
+        return mGlowScale;
+    }
+
+    /** Sets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+    public void setGlowScale(float x) {
+        mGlowScale = x;
+        invalidateSelf();
+    }
+
+    private float getMaxGlowAlpha() {
+        return mLastDark ? GLOW_MAX_ALPHA_DARK : GLOW_MAX_ALPHA;
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        boolean pressed = false;
+        for (int i = 0; i < state.length; i++) {
+            if (state[i] == android.R.attr.state_pressed) {
+                pressed = true;
+                break;
+            }
+        }
+        if (pressed != mPressed) {
+            setPressed(pressed);
+            mPressed = pressed;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        boolean changed = super.setVisible(visible, restart);
+        if (changed) {
+            // End any existing animations when the visibility changes
+            jumpToCurrentState();
+        }
+        return changed;
+    }
+
+    @Override
+    public void jumpToCurrentState() {
+        endAnimations("jumpToCurrentState", false /* cancel */);
+    }
+
+    @Override
+    public boolean isStateful() {
+        return true;
+    }
+
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return true;
+    }
+
+    public void setPressed(boolean pressed) {
+        if (mDark != mLastDark && pressed) {
+            mRipplePaint = null;
+            mLastDark = mDark;
+        }
+        if (mSupportHardware) {
+            setPressedHardware(pressed);
+        } else {
+            setPressedSoftware(pressed);
+        }
+    }
+
+    /**
+     * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch
+     * is enabled.
+     */
+    public void abortDelayedRipple() {
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    private void endAnimations(String reason, boolean cancel) {
+        Trace.beginSection("KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel);
+        Trace.endSection();
+        mVisible = false;
+        mTmpArray.addAll(mRunningAnimations);
+        int size = mTmpArray.size();
+        for (int i = 0; i < size; i++) {
+            Animator a = mTmpArray.get(i);
+            if (cancel) {
+                a.cancel();
+            } else {
+                a.end();
+            }
+        }
+        mTmpArray.clear();
+        mRunningAnimations.clear();
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    private void setPressedSoftware(boolean pressed) {
+        if (pressed) {
+            if (mDelayTouchFeedback) {
+                if (mRunningAnimations.isEmpty()) {
+                    mHandler.removeCallbacksAndMessages(null);
+                    mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout());
+                } else if (mVisible) {
+                    enterSoftware();
+                }
+            } else {
+                enterSoftware();
+            }
+        } else {
+            exitSoftware();
+        }
+    }
+
+    private void enterSoftware() {
+        endAnimations("enterSoftware", true /* cancel */);
+        mVisible = true;
+        mGlowAlpha = getMaxGlowAlpha();
+        ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale",
+                0f, GLOW_MAX_SCALE_FACTOR);
+        scaleAnimator.setInterpolator(mInterpolator);
+        scaleAnimator.setDuration(ANIMATION_DURATION_SCALE);
+        scaleAnimator.addListener(mAnimatorListener);
+        scaleAnimator.start();
+        mRunningAnimations.add(scaleAnimator);
+
+        // With the delay, it could eventually animate the enter animation with no pressed state,
+        // then immediately show the exit animation. If this is skipped there will be no ripple.
+        if (mDelayTouchFeedback && !mPressed) {
+            exitSoftware();
+        }
+    }
+
+    private void exitSoftware() {
+        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f);
+        alphaAnimator.setInterpolator(ALPHA_OUT_INTERPOLATOR);
+        alphaAnimator.setDuration(ANIMATION_DURATION_FADE);
+        alphaAnimator.addListener(mAnimatorListener);
+        alphaAnimator.start();
+        mRunningAnimations.add(alphaAnimator);
+    }
+
+    private void setPressedHardware(boolean pressed) {
+        if (pressed) {
+            if (mDelayTouchFeedback) {
+                if (mRunningAnimations.isEmpty()) {
+                    mHandler.removeCallbacksAndMessages(null);
+                    mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout());
+                } else if (mVisible) {
+                    enterHardware();
+                }
+            } else {
+                enterHardware();
+            }
+        } else {
+            exitHardware();
+        }
+    }
+
+    /**
+     * Sets the left/top property for the round rect to {@code prop} depending on whether we are
+     * horizontal or vertical mode.
+     */
+    private void setExtendStart(CanvasProperty<Float> prop) {
+        if (isHorizontal()) {
+            mLeftProp = prop;
+        } else {
+            mTopProp = prop;
+        }
+    }
+
+    private CanvasProperty<Float> getExtendStart() {
+        return isHorizontal() ? mLeftProp : mTopProp;
+    }
+
+    /**
+     * Sets the right/bottom property for the round rect to {@code prop} depending on whether we are
+     * horizontal or vertical mode.
+     */
+    private void setExtendEnd(CanvasProperty<Float> prop) {
+        if (isHorizontal()) {
+            mRightProp = prop;
+        } else {
+            mBottomProp = prop;
+        }
+    }
+
+    private CanvasProperty<Float> getExtendEnd() {
+        return isHorizontal() ? mRightProp : mBottomProp;
+    }
+
+    private int getExtendSize() {
+        return isHorizontal() ? getBounds().width() : getBounds().height();
+    }
+
+    private int getRippleSize() {
+        int size = isHorizontal() ? getBounds().width() : getBounds().height();
+        return Math.min(size, mMaxWidth);
+    }
+
+    private void enterHardware() {
+        endAnimations("enterHardware", true /* cancel */);
+        mVisible = true;
+        mDrawingHardwareGlow = true;
+        setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2));
+        final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(),
+                getExtendSize()/2 - GLOW_MAX_SCALE_FACTOR * getRippleSize()/2);
+        startAnim.setDuration(ANIMATION_DURATION_SCALE);
+        startAnim.setInterpolator(mInterpolator);
+        startAnim.addListener(mAnimatorListener);
+        startAnim.setTarget(mTargetView);
+
+        setExtendEnd(CanvasProperty.createFloat(getExtendSize() / 2));
+        final RenderNodeAnimator endAnim = new RenderNodeAnimator(getExtendEnd(),
+                getExtendSize()/2 + GLOW_MAX_SCALE_FACTOR * getRippleSize()/2);
+        endAnim.setDuration(ANIMATION_DURATION_SCALE);
+        endAnim.setInterpolator(mInterpolator);
+        endAnim.addListener(mAnimatorListener);
+        endAnim.addListener(mEnterHwTraceAnimator);
+        endAnim.setTarget(mTargetView);
+
+        if (isHorizontal()) {
+            mTopProp = CanvasProperty.createFloat(0f);
+            mBottomProp = CanvasProperty.createFloat(getBounds().height());
+            mRxProp = CanvasProperty.createFloat(getBounds().height()/2);
+            mRyProp = CanvasProperty.createFloat(getBounds().height()/2);
+        } else {
+            mLeftProp = CanvasProperty.createFloat(0f);
+            mRightProp = CanvasProperty.createFloat(getBounds().width());
+            mRxProp = CanvasProperty.createFloat(getBounds().width()/2);
+            mRyProp = CanvasProperty.createFloat(getBounds().width()/2);
+        }
+
+        mGlowScale = GLOW_MAX_SCALE_FACTOR;
+        mGlowAlpha = getMaxGlowAlpha();
+        mRipplePaint = getRipplePaint();
+        mRipplePaint.setAlpha((int) (mGlowAlpha * 255));
+        mPaintProp = CanvasProperty.createPaint(mRipplePaint);
+
+        startAnim.start();
+        endAnim.start();
+        mRunningAnimations.add(startAnim);
+        mRunningAnimations.add(endAnim);
+
+        invalidateSelf();
+
+        // With the delay, it could eventually animate the enter animation with no pressed state,
+        // then immediately show the exit animation. If this is skipped there will be no ripple.
+        if (mDelayTouchFeedback && !mPressed) {
+            exitHardware();
+        }
+    }
+
+    private void exitHardware() {
+        mPaintProp = CanvasProperty.createPaint(getRipplePaint());
+        final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp,
+                RenderNodeAnimator.PAINT_ALPHA, 0);
+        opacityAnim.setDuration(ANIMATION_DURATION_FADE);
+        opacityAnim.setInterpolator(ALPHA_OUT_INTERPOLATOR);
+        opacityAnim.addListener(mAnimatorListener);
+        opacityAnim.addListener(mExitHwTraceAnimator);
+        opacityAnim.setTarget(mTargetView);
+
+        opacityAnim.start();
+        mRunningAnimations.add(opacityAnim);
+
+        invalidateSelf();
+    }
+
+    private final AnimatorListenerAdapter mAnimatorListener =
+            new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mRunningAnimations.remove(animation);
+                    if (mRunningAnimations.isEmpty() && !mPressed) {
+                        mVisible = false;
+                        mDrawingHardwareGlow = false;
+                        invalidateSelf();
+                    }
+                }
+            };
+
+    private static final class TraceAnimatorListener extends AnimatorListenerAdapter {
+        private final String mName;
+        TraceAnimatorListener(String name) {
+            mName = name;
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            Trace.beginSection("KeyButtonRipple.start." + mName);
+            Trace.endSection();
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            Trace.beginSection("KeyButtonRipple.cancel." + mName);
+            Trace.endSection();
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            Trace.beginSection("KeyButtonRipple.end." + mName);
+            Trace.endSection();
+        }
+    }
+
+    /**
+     * Interpolator with a smooth log deceleration
+     */
+    private static final class LogInterpolator implements Interpolator {
+        @Override
+        public float getInterpolation(float input) {
+            return 1 - (float) Math.pow(400, -input * 1.4);
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
new file mode 100644
index 0000000..74d30f8
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.KeyEvent.KEYCODE_BACK;
+import static android.view.KeyEvent.KEYCODE_UNKNOWN;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.inputmethodservice.InputMethodService;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.ImageView;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * @hide
+ */
+public class KeyButtonView extends ImageView implements ButtonInterface {
+    private static final String TAG = KeyButtonView.class.getSimpleName();
+
+    private final boolean mPlaySounds;
+    private long mDownTime;
+    private boolean mTracking;
+    private int mCode;
+    private int mTouchDownX;
+    private int mTouchDownY;
+    private AudioManager mAudioManager;
+    private boolean mGestureAborted;
+    @VisibleForTesting boolean mLongClicked;
+    private OnClickListener mOnClickListener;
+    private final KeyButtonRipple mRipple;
+    private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+    private float mDarkIntensity;
+    private boolean mHasOvalBg = false;
+
+    private final Runnable mCheckLongPress = new Runnable() {
+        public void run() {
+            if (isPressed()) {
+                // Log.d("KeyButtonView", "longpressed: " + this);
+                if (isLongClickable()) {
+                    // Just an old-fashioned ImageView
+                    performLongClick();
+                    mLongClicked = true;
+                } else {
+                    if (mCode != KEYCODE_UNKNOWN) {
+                        sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
+                        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+                    }
+                    mLongClicked = true;
+                }
+            }
+        }
+    };
+
+    public KeyButtonView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // TODO(b/205803355): Figure out better place to set this.
+        switch (getId()) {
+            case com.android.internal.R.id.input_method_nav_back:
+                mCode = KEYCODE_BACK;
+                break;
+            default:
+                mCode = KEYCODE_UNKNOWN;
+                break;
+        }
+
+        mPlaySounds = true;
+
+        setClickable(true);
+        mAudioManager = context.getSystemService(AudioManager.class);
+
+        mRipple = new KeyButtonRipple(context, this,
+                com.android.internal.R.dimen.input_method_nav_key_button_ripple_max_width);
+        setBackground(mRipple);
+        setWillNotDraw(false);
+        forceHasOverlappingRendering(false);
+    }
+
+    @Override
+    public boolean isClickable() {
+        return mCode != KEYCODE_UNKNOWN || super.isClickable();
+    }
+
+    public void setCode(int code) {
+        mCode = code;
+    }
+
+    @Override
+    public void setOnClickListener(OnClickListener onClickListener) {
+        super.setOnClickListener(onClickListener);
+        mOnClickListener = onClickListener;
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        if (mCode != KEYCODE_UNKNOWN) {
+            info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
+            if (isLongClickable()) {
+                info.addAction(
+                        new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
+            }
+        }
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        if (visibility != View.VISIBLE) {
+            jumpDrawablesToCurrentState();
+        }
+    }
+
+    @Override
+    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+        if (action == ACTION_CLICK && mCode != KEYCODE_UNKNOWN) {
+            sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis());
+            sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0);
+            mTracking = false;
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+            playSoundEffect(SoundEffectConstants.CLICK);
+            return true;
+        } else if (action == ACTION_LONG_CLICK && mCode != KEYCODE_UNKNOWN) {
+            sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
+            sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0);
+            mTracking = false;
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+            return true;
+        }
+        return super.performAccessibilityActionInternal(action, arguments);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        final boolean showSwipeUI = false; // mOverviewProxyService.shouldShowSwipeUpUI();
+        final int action = ev.getAction();
+        int x, y;
+        if (action == MotionEvent.ACTION_DOWN) {
+            mGestureAborted = false;
+        }
+        if (mGestureAborted) {
+            setPressed(false);
+            return false;
+        }
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mDownTime = SystemClock.uptimeMillis();
+                mLongClicked = false;
+                setPressed(true);
+
+                // Use raw X and Y to detect gestures in case a parent changes the x and y values
+                mTouchDownX = (int) ev.getRawX();
+                mTouchDownY = (int) ev.getRawY();
+                if (mCode != KEYCODE_UNKNOWN) {
+                    sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
+                } else {
+                    // Provide the same haptic feedback that the system offers for virtual keys.
+                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+                }
+                if (!showSwipeUI) {
+                    playSoundEffect(SoundEffectConstants.CLICK);
+                }
+                removeCallbacks(mCheckLongPress);
+                postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
+                break;
+            case MotionEvent.ACTION_MOVE:
+                x = (int)ev.getRawX();
+                y = (int)ev.getRawY();
+
+                float slop = getQuickStepTouchSlopPx(getContext());
+                if (Math.abs(x - mTouchDownX) > slop || Math.abs(y - mTouchDownY) > slop) {
+                    // When quick step is enabled, prevent animating the ripple triggered by
+                    // setPressed and decide to run it on touch up
+                    setPressed(false);
+                    removeCallbacks(mCheckLongPress);
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                setPressed(false);
+                if (mCode != KEYCODE_UNKNOWN) {
+                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
+                }
+                removeCallbacks(mCheckLongPress);
+                break;
+            case MotionEvent.ACTION_UP:
+                final boolean doIt = isPressed() && !mLongClicked;
+                setPressed(false);
+                final boolean doHapticFeedback = (SystemClock.uptimeMillis() - mDownTime) > 150;
+                if (showSwipeUI) {
+                    if (doIt) {
+                        // Apply haptic feedback on touch up since there is none on touch down
+                        performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+                        playSoundEffect(SoundEffectConstants.CLICK);
+                    }
+                } else if (doHapticFeedback && !mLongClicked) {
+                    // Always send a release ourselves because it doesn't seem to be sent elsewhere
+                    // and it feels weird to sometimes get a release haptic and other times not.
+                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
+                }
+                if (mCode != KEYCODE_UNKNOWN) {
+                    if (doIt) {
+                        sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0);
+                        mTracking = false;
+                        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+                    } else {
+                        sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
+                    }
+                } else {
+                    // no key code, just a regular ImageView
+                    if (doIt && mOnClickListener != null) {
+                        mOnClickListener.onClick(this);
+                        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+                    }
+                }
+                removeCallbacks(mCheckLongPress);
+                break;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+        super.setImageDrawable(drawable);
+
+        if (drawable == null) {
+            return;
+        }
+        KeyButtonDrawable keyButtonDrawable = (KeyButtonDrawable) drawable;
+        keyButtonDrawable.setDarkIntensity(mDarkIntensity);
+        mHasOvalBg = keyButtonDrawable.hasOvalBg();
+        if (mHasOvalBg) {
+            mOvalBgPaint.setColor(keyButtonDrawable.getDrawableBackgroundColor());
+        }
+        mRipple.setType(keyButtonDrawable.hasOvalBg() ? KeyButtonRipple.Type.OVAL
+                : KeyButtonRipple.Type.ROUNDED_RECT);
+    }
+
+    public void playSoundEffect(int soundConstant) {
+        if (!mPlaySounds) return;
+        mAudioManager.playSoundEffect(soundConstant);
+    }
+
+    public void sendEvent(int action, int flags) {
+        sendEvent(action, flags, SystemClock.uptimeMillis());
+    }
+
+    private void sendEvent(int action, int flags, long when) {
+        if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
+            if (action == MotionEvent.ACTION_UP) {
+                // TODO(b/205803355): Implement notifyBackAction();
+            }
+        }
+
+        // TODO(b/205803355): Consolidate this logic to somewhere else.
+        if (mContext instanceof InputMethodService) {
+            final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
+            final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
+                    0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+                    flags | KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+                    InputDevice.SOURCE_KEYBOARD);
+            int displayId = INVALID_DISPLAY;
+
+            // Make KeyEvent work on multi-display environment
+            if (getDisplay() != null) {
+                displayId = getDisplay().getDisplayId();
+            }
+            if (displayId != INVALID_DISPLAY) {
+                ev.setDisplayId(displayId);
+            }
+            final InputMethodService ims = (InputMethodService) mContext;
+            final boolean handled;
+            switch (action) {
+                case KeyEvent.ACTION_DOWN:
+                    handled = ims.onKeyDown(ev.getKeyCode(), ev);
+                    mTracking = handled && ev.getRepeatCount() == 0 &&
+                            (ev.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0;
+                    break;
+                case KeyEvent.ACTION_UP:
+                    handled = ims.onKeyUp(ev.getKeyCode(), ev);
+                    break;
+                default:
+                    handled = false;
+                    break;
+            }
+            if (!handled) {
+                final InputConnection ic = ims.getCurrentInputConnection();
+                if (ic != null) {
+                    ic.sendKeyEvent(ev);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setDarkIntensity(float darkIntensity) {
+        mDarkIntensity = darkIntensity;
+
+        Drawable drawable = getDrawable();
+        if (drawable != null) {
+            ((KeyButtonDrawable) drawable).setDarkIntensity(darkIntensity);
+            // Since we reuse the same drawable for multiple views, we need to invalidate the view
+            // manually.
+            invalidate();
+        }
+        mRipple.setDarkIntensity(darkIntensity);
+    }
+
+    @Override
+    public void setDelayTouchFeedback(boolean shouldDelay) {
+        mRipple.setDelayTouchFeedback(shouldDelay);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mHasOvalBg) {
+            int d = Math.min(getWidth(), getHeight());
+            canvas.drawOval(0, 0, d, d, mOvalBgPaint);
+        }
+        super.draw(canvas);
+    }
+
+    /**
+     * Ratio of quickstep touch slop (when system takes over the touch) to view touch slop
+     */
+    public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
+
+    /**
+     * Touch slop for quickstep gesture
+     */
+    private static float getQuickStepTouchSlopPx(Context context) {
+        return QUICKSTEP_TOUCH_SLOP_RATIO * ViewConfiguration.get(context).getScaledTouchSlop();
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
new file mode 100644
index 0000000..93c5439
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import android.annotation.ColorInt;
+
+final class NavigationBarConstants {
+    private NavigationBarConstants() {
+        // Not intended to be instantiated.
+    }
+
+    // Copied from "navbar_back_button_ime_offset"
+    // TODO(b/215443343): Handle this in the drawable then remove this constant.
+    static final float NAVBAR_BACK_BUTTON_IME_OFFSET = 2.0f;
+
+    // Copied from "light_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
+    @ColorInt
+    static final int LIGHT_MODE_ICON_COLOR_SINGLE_TONE = 0xffffffff;
+
+    // Copied from "dark_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
+    @ColorInt
+    static final int DARK_MODE_ICON_COLOR_SINGLE_TONE = 0x99000000;
+
+    // Copied from "navigation_bar_deadzone_hold"
+    static final int NAVIGATION_BAR_DEADZONE_HOLD = 333;
+
+    // Copied from "navigation_bar_deadzone_hold"
+    static final int NAVIGATION_BAR_DEADZONE_DECAY = 333;
+
+    // Copied from "navigation_bar_deadzone_size"
+    static final float NAVIGATION_BAR_DEADZONE_SIZE = 12.0f;
+
+    // Copied from "navigation_bar_deadzone_size_max"
+    static final float NAVIGATION_BAR_DEADZONE_SIZE_MAX = 32.0f;
+
+    // Copied from "nav_key_button_shadow_offset_x"
+    static final float NAV_KEY_BUTTON_SHADOW_OFFSET_X = 0.0f;
+
+    // Copied from "nav_key_button_shadow_offset_y"
+    static final float NAV_KEY_BUTTON_SHADOW_OFFSET_Y = 1.0f;
+
+    // Copied from "nav_key_button_shadow_radius"
+    static final float NAV_KEY_BUTTON_SHADOW_RADIUS = 0.5f;
+
+    // Copied from "nav_key_button_shadow_color"
+    @ColorInt
+    static final int NAV_KEY_BUTTON_SHADOW_COLOR = 0x30000000;
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java
new file mode 100644
index 0000000..f01173e
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import static android.view.MotionEvent.ACTION_OUTSIDE;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.FrameLayout;
+
+/**
+ * @hide
+ */
+public final class NavigationBarFrame extends FrameLayout {
+
+    private DeadZone mDeadZone = null;
+
+    public NavigationBarFrame(@NonNull Context context) {
+        super(context);
+    }
+
+    public NavigationBarFrame(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public NavigationBarFrame(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public void setDeadZone(@NonNull DeadZone deadZone) {
+        mDeadZone = deadZone;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        if (event.getAction() == ACTION_OUTSIDE) {
+            if (mDeadZone != null) {
+                return mDeadZone.onTouchEvent(event);
+            }
+        }
+        return super.dispatchTouchEvent(event);
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java
new file mode 100644
index 0000000..d488890
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.inputmethodservice.navigationbar.ReverseLinearLayout.ReverseRelativeLayout;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.Space;
+
+/**
+ * @hide
+ */
+public final class NavigationBarInflaterView extends FrameLayout {
+
+    private static final String TAG = "NavBarInflater";
+
+    public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
+    public static final String NAV_BAR_LEFT = "sysui_nav_bar_left";
+    public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right";
+
+    public static final String MENU_IME_ROTATE = "menu_ime";
+    public static final String BACK = "back";
+    public static final String HOME = "home";
+    public static final String RECENT = "recent";
+    public static final String NAVSPACE = "space";
+    public static final String CLIPBOARD = "clipboard";
+    public static final String HOME_HANDLE = "home_handle";
+    public static final String KEY = "key";
+    public static final String LEFT = "left";
+    public static final String RIGHT = "right";
+    public static final String CONTEXTUAL = "contextual";
+    public static final String IME_SWITCHER = "ime_switcher";
+
+    public static final String GRAVITY_SEPARATOR = ";";
+    public static final String BUTTON_SEPARATOR = ",";
+
+    public static final String SIZE_MOD_START = "[";
+    public static final String SIZE_MOD_END = "]";
+
+    public static final String KEY_CODE_START = "(";
+    public static final String KEY_IMAGE_DELIM = ":";
+    public static final String KEY_CODE_END = ")";
+    private static final String WEIGHT_SUFFIX = "W";
+    private static final String WEIGHT_CENTERED_SUFFIX = "WC";
+    private static final String ABSOLUTE_SUFFIX = "A";
+    private static final String ABSOLUTE_VERTICAL_CENTERED_SUFFIX = "C";
+
+    // Copied from "config_navBarLayoutHandle:
+    private static final String CONFIG_NAV_BAR_LAYOUT_HANDLE =
+            "back[70AC];home_handle;ime_switcher[70AC]";
+
+    protected LayoutInflater mLayoutInflater;
+    protected LayoutInflater mLandscapeInflater;
+
+    protected FrameLayout mHorizontal;
+
+    SparseArray<ButtonDispatcher> mButtonDispatchers;
+
+    private View mLastPortrait;
+    private View mLastLandscape;
+
+    private boolean mAlternativeOrder;
+
+    public NavigationBarInflaterView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        createInflaters();
+    }
+
+    void createInflaters() {
+        mLayoutInflater = LayoutInflater.from(mContext);
+        Configuration landscape = new Configuration();
+        landscape.setTo(mContext.getResources().getConfiguration());
+        landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
+        mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        inflateChildren();
+        clearViews();
+        inflateLayout(getDefaultLayout());
+    }
+
+    private void inflateChildren() {
+        removeAllViews();
+        mHorizontal = (FrameLayout) mLayoutInflater.inflate(
+                com.android.internal.R.layout.input_method_navigation_layout,
+                this /* root */, false /* attachToRoot */);
+        addView(mHorizontal);
+        updateAlternativeOrder();
+    }
+
+    String getDefaultLayout() {
+        return CONFIG_NAV_BAR_LAYOUT_HANDLE;
+    }
+
+    public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) {
+        mButtonDispatchers = buttonDispatchers;
+        for (int i = 0; i < buttonDispatchers.size(); i++) {
+            initiallyFill(buttonDispatchers.valueAt(i));
+        }
+    }
+
+    void updateButtonDispatchersCurrentView() {
+        if (mButtonDispatchers != null) {
+            View view = mHorizontal;
+            for (int i = 0; i < mButtonDispatchers.size(); i++) {
+                final ButtonDispatcher dispatcher = mButtonDispatchers.valueAt(i);
+                dispatcher.setCurrentView(view);
+            }
+        }
+    }
+
+    void setAlternativeOrder(boolean alternativeOrder) {
+        if (alternativeOrder != mAlternativeOrder) {
+            mAlternativeOrder = alternativeOrder;
+            updateAlternativeOrder();
+        }
+    }
+
+    private void updateAlternativeOrder() {
+        updateAlternativeOrder(mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_ends_group));
+        updateAlternativeOrder(mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_center_group));
+    }
+
+    private void updateAlternativeOrder(View v) {
+        if (v instanceof ReverseLinearLayout) {
+            ((ReverseLinearLayout) v).setAlternativeOrder(mAlternativeOrder);
+        }
+    }
+
+    private void initiallyFill(
+            ButtonDispatcher buttonDispatcher) {
+        addAll(buttonDispatcher, mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_ends_group));
+        addAll(buttonDispatcher, mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_center_group));
+    }
+
+    private void addAll(ButtonDispatcher buttonDispatcher, ViewGroup parent) {
+        for (int i = 0; i < parent.getChildCount(); i++) {
+            // Need to manually search for each id, just in case each group has more than one
+            // of a single id.  It probably mostly a waste of time, but shouldn't take long
+            // and will only happen once.
+            if (parent.getChildAt(i).getId() == buttonDispatcher.getId()) {
+                buttonDispatcher.addView(parent.getChildAt(i));
+            }
+            if (parent.getChildAt(i) instanceof ViewGroup) {
+                addAll(buttonDispatcher, (ViewGroup) parent.getChildAt(i));
+            }
+        }
+    }
+
+    protected void inflateLayout(String newLayout) {
+        if (newLayout == null) {
+            newLayout = getDefaultLayout();
+        }
+        String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);
+        if (sets.length != 3) {
+            Log.d(TAG, "Invalid layout.");
+            newLayout = getDefaultLayout();
+            sets = newLayout.split(GRAVITY_SEPARATOR, 3);
+        }
+        String[] start = sets[0].split(BUTTON_SEPARATOR);
+        String[] center = sets[1].split(BUTTON_SEPARATOR);
+        String[] end = sets[2].split(BUTTON_SEPARATOR);
+        // Inflate these in start to end order or accessibility traversal will be messed up.
+        inflateButtons(start, mHorizontal.findViewById(
+                        com.android.internal.R.id.input_method_nav_ends_group),
+                false /* landscape */, true /* start */);
+
+        inflateButtons(center, mHorizontal.findViewById(
+                        com.android.internal.R.id.input_method_nav_center_group),
+                false /* landscape */, false /* start */);
+
+        addGravitySpacer(mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_ends_group));
+
+        inflateButtons(end, mHorizontal.findViewById(
+                        com.android.internal.R.id.input_method_nav_ends_group),
+                false /* landscape */, false /* start */);
+
+        updateButtonDispatchersCurrentView();
+    }
+
+    private void addGravitySpacer(LinearLayout layout) {
+        layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1));
+    }
+
+    private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
+            boolean start) {
+        for (int i = 0; i < buttons.length; i++) {
+            inflateButton(buttons[i], parent, landscape, start);
+        }
+    }
+
+    private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) {
+        if (layoutParams instanceof LinearLayout.LayoutParams) {
+            return new LinearLayout.LayoutParams(layoutParams.width, layoutParams.height,
+                    ((LinearLayout.LayoutParams) layoutParams).weight);
+        }
+        return new LayoutParams(layoutParams.width, layoutParams.height);
+    }
+
+    @Nullable
+    protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
+            boolean start) {
+        LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
+        View v = createView(buttonSpec, parent, inflater);
+        if (v == null) return null;
+
+        v = applySize(v, buttonSpec, landscape, start);
+        parent.addView(v);
+        addToDispatchers(v);
+        View lastView = landscape ? mLastLandscape : mLastPortrait;
+        View accessibilityView = v;
+        if (v instanceof ReverseRelativeLayout) {
+            accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
+        }
+        if (lastView != null) {
+            accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
+        }
+        if (landscape) {
+            mLastLandscape = accessibilityView;
+        } else {
+            mLastPortrait = accessibilityView;
+        }
+        return v;
+    }
+
+    private View applySize(View v, String buttonSpec, boolean landscape, boolean start) {
+        String sizeStr = extractSize(buttonSpec);
+        if (sizeStr == null) return v;
+
+        if (sizeStr.contains(WEIGHT_SUFFIX) || sizeStr.contains(ABSOLUTE_SUFFIX)) {
+            // To support gravity, wrap in RelativeLayout and apply gravity to it.
+            // Children wanting to use gravity must be smaller than the frame.
+            ReverseRelativeLayout frame = new ReverseRelativeLayout(mContext);
+            LayoutParams childParams = new LayoutParams(v.getLayoutParams());
+
+            // Compute gravity to apply
+            int gravity = (landscape) ? (start ? Gravity.TOP : Gravity.BOTTOM)
+                    : (start ? Gravity.START : Gravity.END);
+            if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) {
+                gravity = Gravity.CENTER;
+            } else if (sizeStr.endsWith(ABSOLUTE_VERTICAL_CENTERED_SUFFIX)) {
+                gravity = Gravity.CENTER_VERTICAL;
+            }
+
+            // Set default gravity, flipped if needed in reversed layouts (270 RTL and 90 LTR)
+            frame.setDefaultGravity(gravity);
+            frame.setGravity(gravity); // Apply gravity to root
+
+            frame.addView(v, childParams);
+
+            if (sizeStr.contains(WEIGHT_SUFFIX)) {
+                // Use weighting to set the width of the frame
+                float weight = Float.parseFloat(
+                        sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX)));
+                frame.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, weight));
+            } else {
+                int width = (int) convertDpToPx(mContext,
+                        Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(ABSOLUTE_SUFFIX))));
+                frame.setLayoutParams(new LinearLayout.LayoutParams(width, MATCH_PARENT));
+            }
+
+            // Ensure ripples can be drawn outside bounds
+            frame.setClipChildren(false);
+            frame.setClipToPadding(false);
+
+            return frame;
+        }
+
+        float size = Float.parseFloat(sizeStr);
+        ViewGroup.LayoutParams params = v.getLayoutParams();
+        params.width = (int) (params.width * size);
+        return v;
+    }
+
+    View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
+        View v = null;
+        String button = extractButton(buttonSpec);
+        if (LEFT.equals(button)) {
+            button = extractButton(NAVSPACE);
+        } else if (RIGHT.equals(button)) {
+            button = extractButton(MENU_IME_ROTATE);
+        }
+        if (HOME.equals(button)) {
+            //v = inflater.inflate(R.layout.home, parent, false);
+        } else if (BACK.equals(button)) {
+            v = inflater.inflate(com.android.internal.R.layout.input_method_nav_back, parent,
+                    false);
+        } else if (RECENT.equals(button)) {
+            //v = inflater.inflate(R.layout.recent_apps, parent, false);
+        } else if (MENU_IME_ROTATE.equals(button)) {
+            //v = inflater.inflate(R.layout.menu_ime, parent, false);
+        } else if (NAVSPACE.equals(button)) {
+            //v = inflater.inflate(R.layout.nav_key_space, parent, false);
+        } else if (CLIPBOARD.equals(button)) {
+            //v = inflater.inflate(R.layout.clipboard, parent, false);
+        } else if (CONTEXTUAL.equals(button)) {
+            //v = inflater.inflate(R.layout.contextual, parent, false);
+        } else if (HOME_HANDLE.equals(button)) {
+            v = inflater.inflate(com.android.internal.R.layout.input_method_nav_home_handle,
+                    parent, false);
+        } else if (IME_SWITCHER.equals(button)) {
+            v = inflater.inflate(com.android.internal.R.layout.input_method_nav_ime_switcher,
+                    parent, false);
+        } else if (button.startsWith(KEY)) {
+            /*
+            String uri = extractImage(button);
+            int code = extractKeycode(button);
+            v = inflater.inflate(R.layout.custom_key, parent, false);
+            ((KeyButtonView) v).setCode(code);
+            if (uri != null) {
+                if (uri.contains(":")) {
+                    ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
+                } else if (uri.contains("/")) {
+                    int index = uri.indexOf('/');
+                    String pkg = uri.substring(0, index);
+                    int id = Integer.parseInt(uri.substring(index + 1));
+                    ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
+                }
+            }
+         */
+        }
+        return v;
+    }
+
+    /*
+    public static String extractImage(String buttonSpec) {
+        if (!buttonSpec.contains(KEY_IMAGE_DELIM)) {
+            return null;
+        }
+        final int start = buttonSpec.indexOf(KEY_IMAGE_DELIM);
+        String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_CODE_END));
+        return subStr;
+    }
+
+    public static int extractKeycode(String buttonSpec) {
+        if (!buttonSpec.contains(KEY_CODE_START)) {
+            return 1;
+        }
+        final int start = buttonSpec.indexOf(KEY_CODE_START);
+        String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_IMAGE_DELIM));
+        return Integer.parseInt(subStr);
+    }
+    */
+
+    public static String extractSize(String buttonSpec) {
+        if (!buttonSpec.contains(SIZE_MOD_START)) {
+            return null;
+        }
+        final int sizeStart = buttonSpec.indexOf(SIZE_MOD_START);
+        return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END));
+    }
+
+    public static String extractButton(String buttonSpec) {
+        if (!buttonSpec.contains(SIZE_MOD_START)) {
+            return buttonSpec;
+        }
+        return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START));
+    }
+
+    private void addToDispatchers(View v) {
+        if (mButtonDispatchers != null) {
+            final int indexOfKey = mButtonDispatchers.indexOfKey(v.getId());
+            if (indexOfKey >= 0) {
+                mButtonDispatchers.valueAt(indexOfKey).addView(v);
+            }
+            if (v instanceof ViewGroup) {
+                final ViewGroup viewGroup = (ViewGroup)v;
+                final int N = viewGroup.getChildCount();
+                for (int i = 0; i < N; i++) {
+                    addToDispatchers(viewGroup.getChildAt(i));
+                }
+            }
+        }
+    }
+
+    private void clearViews() {
+        if (mButtonDispatchers != null) {
+            for (int i = 0; i < mButtonDispatchers.size(); i++) {
+                mButtonDispatchers.valueAt(i).clear();
+            }
+        }
+        clearAllChildren(mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_buttons));
+    }
+
+    private void clearAllChildren(ViewGroup group) {
+        for (int i = 0; i < group.getChildCount(); i++) {
+            ((ViewGroup) group.getChildAt(i)).removeAllViews();
+        }
+    }
+
+    private static float convertDpToPx(Context context, float dp) {
+        return dp * context.getResources().getDisplayMetrics().density;
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java
new file mode 100644
index 0000000..c6096d7
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
+import android.content.res.Resources;
+import android.util.TypedValue;
+
+final class NavigationBarUtils {
+    private NavigationBarUtils() {
+        // Not intended to be instantiated.
+    }
+
+    /**
+     * A utility method to convert "dp" to "pixel".
+     *
+     * <p>TODO(b/215443343): Remove this method by migrating DP values from
+     * {@link NavigationBarConstants} to resource files.</p>
+     *
+     * @param dpValue "dp" value to be converted to "pixel"
+     * @param res {@link Resources} to be used when dealing with "dp".
+     * @return the pixels for a given dp value.
+     */
+    static int dpToPx(float dpValue, Resources res) {
+        return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, res.getDisplayMetrics());
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
new file mode 100644
index 0000000..4284778
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET;
+import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.DrawableRes;
+import android.annotation.FloatRange;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.FrameLayout;
+
+import java.util.function.Consumer;
+
+/**
+ * @hide
+ */
+public final class NavigationBarView extends FrameLayout {
+    final static boolean DEBUG = false;
+    final static String TAG = "NavBarView";
+
+    // Copied from com.android.systemui.animation.Interpolators#FAST_OUT_SLOW_IN
+    private static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+    // The current view is always mHorizontal.
+    View mCurrentView = null;
+    private View mHorizontal;
+
+    private int mCurrentRotation = -1;
+
+    int mDisabledFlags = 0;
+    int mNavigationIconHints = StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+    private final int mNavBarMode = NAV_BAR_MODE_GESTURAL;
+
+    private KeyButtonDrawable mBackIcon;
+    private KeyButtonDrawable mImeSwitcherIcon;
+    private Context mLightContext;
+    private final int mLightIconColor;
+    private final int mDarkIconColor;
+
+    private final android.inputmethodservice.navigationbar.DeadZone mDeadZone;
+    private boolean mDeadZoneConsuming = false;
+
+    private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
+    private Configuration mConfiguration;
+    private Configuration mTmpLastConfiguration;
+
+    private NavigationBarInflaterView mNavigationInflaterView;
+
+    public NavigationBarView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mLightContext = context;
+        mLightIconColor = LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
+        mDarkIconColor = DARK_MODE_ICON_COLOR_SINGLE_TONE;
+
+        mConfiguration = new Configuration();
+        mTmpLastConfiguration = new Configuration();
+        mConfiguration.updateFrom(context.getResources().getConfiguration());
+
+        mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_back,
+                new ButtonDispatcher(com.android.internal.R.id.input_method_nav_back));
+        mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_ime_switcher,
+                new ButtonDispatcher(com.android.internal.R.id.input_method_nav_ime_switcher));
+        mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_home_handle,
+                new ButtonDispatcher(com.android.internal.R.id.input_method_nav_home_handle));
+
+        mDeadZone = new android.inputmethodservice.navigationbar.DeadZone(this);
+
+        getImeSwitchButton().setOnClickListener(view -> view.getContext()
+                .getSystemService(InputMethodManager.class).showInputMethodPicker());
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        shouldDeadZoneConsumeTouchEvents(event);
+        return super.onTouchEvent(event);
+    }
+
+    private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) {
+        int action = event.getActionMasked();
+        if (action == MotionEvent.ACTION_DOWN) {
+            mDeadZoneConsuming = false;
+        }
+        if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) {
+            switch (action) {
+                case MotionEvent.ACTION_DOWN:
+                    mDeadZoneConsuming = true;
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    mDeadZoneConsuming = false;
+                    break;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public View getCurrentView() {
+        return mCurrentView;
+    }
+
+    /**
+     * Applies {@param consumer} to each of the nav bar views.
+     */
+    public void forEachView(Consumer<View> consumer) {
+        if (mHorizontal != null) {
+            consumer.accept(mHorizontal);
+        }
+    }
+
+    public ButtonDispatcher getBackButton() {
+        return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_back);
+    }
+
+    public ButtonDispatcher getImeSwitchButton() {
+        return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_ime_switcher);
+    }
+
+    public ButtonDispatcher getHomeHandle() {
+        return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_home_handle);
+    }
+
+    public SparseArray<ButtonDispatcher> getButtonDispatchers() {
+        return mButtonDispatchers;
+    }
+
+    private void reloadNavIcons() {
+        updateIcons(Configuration.EMPTY);
+    }
+
+    private void updateIcons(Configuration oldConfig) {
+        final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation;
+        final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi;
+        final boolean dirChange =
+                oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection();
+
+        if (densityChange || dirChange) {
+            mImeSwitcherIcon = getDrawable(com.android.internal.R.drawable.ic_ime_switcher);
+        }
+        if (orientationChange || densityChange || dirChange) {
+            mBackIcon = getBackDrawable();
+        }
+    }
+
+    public KeyButtonDrawable getBackDrawable() {
+        KeyButtonDrawable drawable = getDrawable(com.android.internal.R.drawable.ic_ime_nav_back);
+        orientBackButton(drawable);
+        return drawable;
+    }
+
+    /**
+     * @return whether this nav bar mode is edge to edge
+     */
+    public static boolean isGesturalMode(int mode) {
+        return mode == NAV_BAR_MODE_GESTURAL;
+    }
+
+    private void orientBackButton(KeyButtonDrawable drawable) {
+        final boolean useAltBack =
+                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+        final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+        float degrees = useAltBack ? (isRtl ? 90 : -90) : 0;
+        if (drawable.getRotation() == degrees) {
+            return;
+        }
+
+        if (isGesturalMode(mNavBarMode)) {
+            drawable.setRotation(degrees);
+            return;
+        }
+
+        // Animate the back button's rotation to the new degrees and only in portrait move up the
+        // back button to line up with the other buttons
+        float targetY = useAltBack
+                ? - dpToPx(NAVBAR_BACK_BUTTON_IME_OFFSET, getResources())
+                : 0;
+        ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable,
+                PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees),
+                PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_TRANSLATE_Y, targetY));
+        navBarAnimator.setInterpolator(FAST_OUT_SLOW_IN);
+        navBarAnimator.setDuration(200);
+        navBarAnimator.start();
+    }
+
+    private KeyButtonDrawable getDrawable(@DrawableRes int icon) {
+        return KeyButtonDrawable.create(mLightContext, mLightIconColor, mDarkIconColor, icon,
+                true /* hasShadow */, null /* ovalBackgroundColor */);
+    }
+
+    @Override
+    public void setLayoutDirection(int layoutDirection) {
+        reloadNavIcons();
+
+        super.setLayoutDirection(layoutDirection);
+    }
+
+    public void setNavigationIconHints(int hints) {
+        if (hints == mNavigationIconHints) return;
+        final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+        final boolean oldBackAlt =
+                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+        if (newBackAlt != oldBackAlt) {
+            //onImeVisibilityChanged(newBackAlt);
+        }
+
+        if (DEBUG) {
+            android.widget.Toast.makeText(getContext(), "Navigation icon hints = " + hints, 500)
+                    .show();
+        }
+        mNavigationIconHints = hints;
+        updateNavButtonIcons();
+    }
+
+    public void setDisabledFlags(int disabledFlags) {
+        if (mDisabledFlags == disabledFlags) return;
+
+        mDisabledFlags = disabledFlags;
+
+        updateNavButtonIcons();
+    }
+
+    public void updateNavButtonIcons() {
+        // We have to replace or restore the back and home button icons when exiting or entering
+        // carmode, respectively. Recents are not available in CarMode in nav bar so change
+        // to recent icon is not required.
+        KeyButtonDrawable backIcon = mBackIcon;
+        orientBackButton(backIcon);
+        getBackButton().setImageDrawable(backIcon);
+
+        getImeSwitchButton().setImageDrawable(mImeSwitcherIcon);
+
+        // Update IME button visibility, a11y and rotate button always overrides the appearance
+        final boolean imeSwitcherVisible =
+                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0;
+        getImeSwitchButton().setVisibility(imeSwitcherVisible ? View.VISIBLE : View.INVISIBLE);
+
+        getBackButton().setVisibility(View.VISIBLE);
+        getHomeHandle().setVisibility(View.INVISIBLE);
+
+        // We used to be reporting the touch regions via notifyActiveTouchRegions() here.
+        // TODO(b/215593010): Consider taking care of this in the Launcher side.
+    }
+
+    private Display getContextDisplay() {
+        return getContext().getDisplay();
+    }
+
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
+        mNavigationInflaterView = findViewById(com.android.internal.R.id.input_method_nav_inflater);
+        mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
+
+        updateOrientationViews();
+        reloadNavIcons();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        mDeadZone.onDraw(canvas);
+        super.onDraw(canvas);
+    }
+
+    private void updateOrientationViews() {
+        mHorizontal = findViewById(com.android.internal.R.id.input_method_nav_horizontal);
+
+        updateCurrentView();
+    }
+
+    private void updateCurrentView() {
+        resetViews();
+        mCurrentView = mHorizontal;
+        mCurrentView.setVisibility(View.VISIBLE);
+        mCurrentRotation = getContextDisplay().getRotation();
+        mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90);
+        mNavigationInflaterView.updateButtonDispatchersCurrentView();
+    }
+
+    private void resetViews() {
+        mHorizontal.setVisibility(View.GONE);
+    }
+
+    public void reorient() {
+        updateCurrentView();
+
+        final android.inputmethodservice.navigationbar.NavigationBarFrame frame =
+                getRootView().findViewByPredicate(view -> view instanceof NavigationBarFrame);
+        frame.setDeadZone(mDeadZone);
+        mDeadZone.onConfigurationChanged(mCurrentRotation);
+
+        if (DEBUG) {
+            Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
+        }
+
+        // Resolve layout direction if not resolved since components changing layout direction such
+        // as changing languages will recreate this view and the direction will be resolved later
+        if (!isLayoutDirectionResolved()) {
+            resolveLayoutDirection();
+        }
+        updateNavButtonIcons();
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mTmpLastConfiguration.updateFrom(mConfiguration);
+        final int changes = mConfiguration.updateFrom(newConfig);
+
+        updateIcons(mTmpLastConfiguration);
+        if (mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
+                || mTmpLastConfiguration.getLayoutDirection()
+                        != mConfiguration.getLayoutDirection()) {
+            // If car mode or density changes, we need to reset the icons.
+            updateNavButtonIcons();
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        // This needs to happen first as it can changed the enabled state which can affect whether
+        // the back button is visible
+        requestApplyInsets();
+        reorient();
+        updateNavButtonIcons();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        for (int i = 0; i < mButtonDispatchers.size(); ++i) {
+            mButtonDispatchers.valueAt(i).onDestroy();
+        }
+    }
+
+    public void setDarkIntensity(@FloatRange(from = 0.0f, to = 1.0f) float intensity) {
+        for (int i = 0; i < mButtonDispatchers.size(); ++i) {
+            mButtonDispatchers.valueAt(i).setDarkIntensity(intensity);
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java b/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java
new file mode 100644
index 0000000..273cafb
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * TODO(b/215443343): Remove this file, as IME actually doesn't use this.
+ *
+ * @hide
+ */
+public class NavigationHandle extends View implements ButtonInterface {
+
+    public NavigationHandle(Context context) {
+        this(context, null);
+    }
+
+    public NavigationHandle(Context context, AttributeSet attr) {
+        super(context, attr);
+        setFocusable(false);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        return false;
+    }
+
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+    }
+
+    @Override
+    public void setDarkIntensity(float intensity) {
+    }
+
+    @Override
+    public void setDelayTouchFeedback(boolean shouldDelay) {
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java
new file mode 100644
index 0000000..68163c3
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.navigationbar;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import java.util.ArrayList;
+
+/**
+ * Automatically reverses the order of children as they are added.
+ * Also reverse the width and height values of layout params
+ * @hide
+ */
+public class ReverseLinearLayout extends LinearLayout {
+
+    /** If true, the layout is reversed vs. a regular linear layout */
+    private boolean mIsLayoutReverse;
+
+    /** If true, the layout is opposite to it's natural reversity from the layout direction */
+    private boolean mIsAlternativeOrder;
+
+    public ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        updateOrder();
+    }
+
+    @Override
+    public void addView(View child) {
+        reverseParams(child.getLayoutParams(), child, mIsLayoutReverse);
+        if (mIsLayoutReverse) {
+            super.addView(child, 0);
+        } else {
+            super.addView(child);
+        }
+    }
+
+    @Override
+    public void addView(View child, ViewGroup.LayoutParams params) {
+        reverseParams(params, child, mIsLayoutReverse);
+        if (mIsLayoutReverse) {
+            super.addView(child, 0, params);
+        } else {
+            super.addView(child, params);
+        }
+    }
+
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+        updateOrder();
+    }
+
+    public void setAlternativeOrder(boolean alternative) {
+        mIsAlternativeOrder = alternative;
+        updateOrder();
+    }
+
+    /**
+     * In landscape, the LinearLayout is not auto mirrored since it is vertical. Therefore we
+     * have to do it manually
+     */
+    private void updateOrder() {
+        boolean isLayoutRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        boolean isLayoutReverse = isLayoutRtl ^ mIsAlternativeOrder;
+
+        if (mIsLayoutReverse != isLayoutReverse) {
+            // reversity changed, swap the order of all views.
+            int childCount = getChildCount();
+            ArrayList<View> childList = new ArrayList<>(childCount);
+            for (int i = 0; i < childCount; i++) {
+                childList.add(getChildAt(i));
+            }
+            removeAllViews();
+            for (int i = childCount - 1; i >= 0; i--) {
+                final View child = childList.get(i);
+                super.addView(child);
+            }
+            mIsLayoutReverse = isLayoutReverse;
+        }
+    }
+
+    private static void reverseParams(ViewGroup.LayoutParams params, View child,
+            boolean isLayoutReverse) {
+        if (child instanceof Reversible) {
+            ((Reversible) child).reverse(isLayoutReverse);
+        }
+        if (child.getPaddingLeft() == child.getPaddingRight()
+                && child.getPaddingTop() == child.getPaddingBottom()) {
+            child.setPadding(child.getPaddingTop(), child.getPaddingLeft(),
+                    child.getPaddingTop(), child.getPaddingLeft());
+        }
+        if (params == null) {
+            return;
+        }
+        int width = params.width;
+        params.width = params.height;
+        params.height = width;
+    }
+
+    interface Reversible {
+        void reverse(boolean isLayoutReverse);
+    }
+
+    public static class ReverseRelativeLayout extends RelativeLayout implements Reversible {
+
+        public ReverseRelativeLayout(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void reverse(boolean isLayoutReverse) {
+            updateGravity(isLayoutReverse);
+            reverseGroup(this, isLayoutReverse);
+        }
+
+        private int mDefaultGravity = Gravity.NO_GRAVITY;
+        public void setDefaultGravity(int gravity) {
+            mDefaultGravity = gravity;
+        }
+
+        public void updateGravity(boolean isLayoutReverse) {
+            // Flip gravity if top of bottom is used
+            if (mDefaultGravity != Gravity.TOP && mDefaultGravity != Gravity.BOTTOM) return;
+
+            // Use the default (intended for 270 LTR and 90 RTL) unless layout is otherwise
+            int gravityToApply = mDefaultGravity;
+            if (isLayoutReverse) {
+                gravityToApply = mDefaultGravity == Gravity.TOP ? Gravity.BOTTOM : Gravity.TOP;
+            }
+
+            if (getGravity() != gravityToApply) setGravity(gravityToApply);
+        }
+    }
+
+    private static void reverseGroup(ViewGroup group, boolean isLayoutReverse) {
+        for (int i = 0; i < group.getChildCount(); i++) {
+            final View child = group.getChildAt(i);
+            reverseParams(child.getLayoutParams(), child, isLayoutReverse);
+
+            // Recursively reverse all children
+            if (child instanceof ViewGroup) {
+                reverseGroup((ViewGroup) child, isLayoutReverse);
+            }
+        }
+    }
+}
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index a6830b7..2339656 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -549,8 +549,8 @@
          * networks, a network will be chosen arbitrarily amongst the networks matching the highest
          * priority rule.
          *
-         * <p>If all networks fail to match the rules provided, an underlying network will still be
-         * selected (at random if necessary).
+         * <p>If all networks fail to match the rules provided, a carrier-owned underlying network
+         * will still be selected (if available, at random if necessary).
          *
          * @param underlyingNetworkTemplates a list of unique VcnUnderlyingNetworkTemplates that are
          *     ordered from most to least preferred, or an empty list to use the default
diff --git a/core/java/android/os/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/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/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/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..f18c9c9 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);
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index eba9ff1..23baa5d 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -31,6 +31,7 @@
 import android.hardware.vibrator.IVibrator;
 import android.media.AudioAttributes;
 import android.os.vibrator.VibrationConfig;
+import android.os.vibrator.VibratorFrequencyProfile;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -208,8 +209,8 @@
     /**
      * Check whether the vibrator has independent frequency control.
      *
-     * @return True if the hardware can control the frequency of the vibrations, otherwise false.
-     * @hide
+     * @return True if the hardware can control the frequency of the vibrations independently of
+     * the vibration amplitude, false otherwise.
      */
     public boolean hasFrequencyControl() {
         // We currently can only control frequency of the vibration using the compose PWLE method.
@@ -229,28 +230,48 @@
     }
 
     /**
-     * Gets the resonant frequency of the vibrator.
+     * Gets the resonant frequency of the vibrator, if applicable.
      *
-     * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
-     * this vibrator is a composite of multiple physical devices.
-     * @hide
+     * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown, not
+     * applicable, or if this vibrator is a composite of multiple physical devices with different
+     * frequencies.
      */
     public float getResonantFrequency() {
-        return getInfo().getResonantFrequency();
+        return getInfo().getResonantFrequencyHz();
     }
 
     /**
      * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
      *
-     * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
-     *         this vibrator is a composite of multiple physical devices.
-     * @hide
+     * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown, not
+     * applicable, or if this vibrator is a composite of multiple physical devices with different
+     * Q factors.
      */
     public float getQFactor() {
         return getInfo().getQFactor();
     }
 
     /**
+     * Gets the profile that describes the vibrator output across the supported frequency range.
+     *
+     * <p>The profile describes the relative output acceleration that the device can reach when it
+     * vibrates at different frequencies.
+     *
+     * @return The frequency profile for this vibrator, or null if the vibrator does not have
+     * frequency control. If this vibrator is a composite of multiple physical devices then this
+     * will return a profile supported in all devices, or null if the intersection is empty or not
+     * available.
+     */
+    @Nullable
+    public VibratorFrequencyProfile getFrequencyProfile() {
+        VibratorInfo.FrequencyProfile frequencyProfile = getInfo().getFrequencyProfile();
+        if (frequencyProfile.isEmpty()) {
+            return null;
+        }
+        return new VibratorFrequencyProfile(frequencyProfile);
+    }
+
+    /**
      * Return the maximum amplitude the vibrator can play using the audio haptic channels.
      *
      * <p>This is a positive value, or {@link Float#NaN NaN} if it's unknown. If this returns a
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 5271c4d..00ce14f 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -16,7 +16,6 @@
 
 package android.os;
 
-import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.vibrator.Braking;
@@ -26,6 +25,8 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -56,7 +57,7 @@
     private final int mPwlePrimitiveDurationMax;
     private final int mPwleSizeMax;
     private final float mQFactor;
-    private final FrequencyMapping mFrequencyMapping;
+    private final FrequencyProfile mFrequencyProfile;
 
     VibratorInfo(Parcel in) {
         mId = in.readInt();
@@ -69,7 +70,15 @@
         mPwlePrimitiveDurationMax = in.readInt();
         mPwleSizeMax = in.readInt();
         mQFactor = in.readFloat();
-        mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader(), android.os.VibratorInfo.FrequencyMapping.class);
+        mFrequencyProfile = FrequencyProfile.CREATOR.createFromParcel(in);
+    }
+
+    public VibratorInfo(int id, @NonNull VibratorInfo baseVibratorInfo) {
+        this(id, baseVibratorInfo.mCapabilities, baseVibratorInfo.mSupportedEffects,
+                baseVibratorInfo.mSupportedBraking, baseVibratorInfo.mSupportedPrimitives,
+                baseVibratorInfo.mPrimitiveDelayMax, baseVibratorInfo.mCompositionSizeMax,
+                baseVibratorInfo.mPwlePrimitiveDurationMax, baseVibratorInfo.mPwleSizeMax,
+                baseVibratorInfo.mQFactor, baseVibratorInfo.mFrequencyProfile);
     }
 
     /**
@@ -92,7 +101,7 @@
      * @param pwleSizeMax              The maximum number of primitives supported by a PWLE
      *                                 composition.
      * @param qFactor                  The vibrator quality factor.
-     * @param frequencyMapping         The description of the vibrator supported frequencies and max
+     * @param frequencyProfile         The description of the vibrator supported frequencies and max
      *                                 amplitude mappings.
      * @hide
      */
@@ -100,7 +109,9 @@
             @Nullable SparseBooleanArray supportedBraking,
             @NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax,
             int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax,
-            float qFactor, @NonNull FrequencyMapping frequencyMapping) {
+            float qFactor, @NonNull FrequencyProfile frequencyProfile) {
+        Preconditions.checkNotNull(supportedPrimitives);
+        Preconditions.checkNotNull(frequencyProfile);
         mId = id;
         mCapabilities = capabilities;
         mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone();
@@ -111,14 +122,7 @@
         mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax;
         mPwleSizeMax = pwleSizeMax;
         mQFactor = qFactor;
-        mFrequencyMapping = frequencyMapping;
-    }
-
-    protected VibratorInfo(int id, int capabilities, VibratorInfo baseVibrator) {
-        this(id, capabilities, baseVibrator.mSupportedEffects, baseVibrator.mSupportedBraking,
-                baseVibrator.mSupportedPrimitives, baseVibrator.mPrimitiveDelayMax,
-                baseVibrator.mCompositionSizeMax, baseVibrator.mPwlePrimitiveDurationMax,
-                baseVibrator.mPwleSizeMax, baseVibrator.mQFactor, baseVibrator.mFrequencyMapping);
+        mFrequencyProfile = frequencyProfile;
     }
 
     @Override
@@ -133,7 +137,7 @@
         dest.writeInt(mPwlePrimitiveDurationMax);
         dest.writeInt(mPwleSizeMax);
         dest.writeFloat(mQFactor);
-        dest.writeParcelable(mFrequencyMapping, flags);
+        mFrequencyProfile.writeToParcel(dest, flags);
     }
 
     @Override
@@ -170,13 +174,13 @@
                 && Objects.equals(mSupportedEffects, that.mSupportedEffects)
                 && Objects.equals(mSupportedBraking, that.mSupportedBraking)
                 && Objects.equals(mQFactor, that.mQFactor)
-                && Objects.equals(mFrequencyMapping, that.mFrequencyMapping);
+                && Objects.equals(mFrequencyProfile, that.mFrequencyProfile);
     }
 
     @Override
     public int hashCode() {
         int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
-                mQFactor, mFrequencyMapping);
+                mQFactor, mFrequencyProfile);
         for (int i = 0; i < mSupportedPrimitives.size(); i++) {
             hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i);
             hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i);
@@ -198,7 +202,7 @@
                 + ", mPwlePrimitiveDurationMax=" + mPwlePrimitiveDurationMax
                 + ", mPwleSizeMax=" + mPwleSizeMax
                 + ", mQFactor=" + mQFactor
-                + ", mFrequencyMapping=" + mFrequencyMapping
+                + ", mFrequencyProfile=" + mFrequencyProfile
                 + '}';
     }
 
@@ -234,6 +238,30 @@
         return Braking.NONE;
     }
 
+    /** @hide */
+    @Nullable
+    public SparseBooleanArray getSupportedBraking() {
+        if (mSupportedBraking == null) {
+            return null;
+        }
+        return mSupportedBraking.clone();
+    }
+
+    /** @hide */
+    public boolean isBrakingSupportKnown() {
+        return mSupportedBraking != null;
+    }
+
+    /** @hide */
+    public boolean hasBrakingSupport(@Braking int braking) {
+        return (mSupportedBraking != null) && mSupportedBraking.get(braking);
+    }
+
+    /** @hide */
+    public boolean isEffectSupportKnown() {
+        return mSupportedEffects != null;
+    }
+
     /**
      * Query whether the vibrator supports the given effect.
      *
@@ -252,6 +280,15 @@
                 : Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
     }
 
+    /** @hide */
+    @Nullable
+    public SparseBooleanArray getSupportedEffects() {
+        if (mSupportedEffects == null) {
+            return null;
+        }
+        return mSupportedEffects.clone();
+    }
+
     /**
      * Query whether the vibrator supports the given primitive.
      *
@@ -276,6 +313,11 @@
         return mSupportedPrimitives.get(primitiveId);
     }
 
+    /** @hide */
+    public SparseIntArray getSupportedPrimitives() {
+        return mSupportedPrimitives.clone();
+    }
+
     /**
      * Query the maximum delay supported for a primitive in a composed effect.
      *
@@ -329,8 +371,8 @@
      * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
      *         this vibrator is a composite of multiple physical devices.
      */
-    public float getResonantFrequency() {
-        return mFrequencyMapping.mResonantFrequencyHz;
+    public float getResonantFrequencyHz() {
+        return mFrequencyProfile.mResonantFrequencyHz;
     }
 
     /**
@@ -344,31 +386,14 @@
     }
 
     /**
-     * Return a range of frequency values supported by the vibrator.
+     * Gets the profile of supported frequencies, including the measurements of maximum relative
+     * output acceleration for supported vibration frequencies.
      *
-     * @return A range of frequency values supported, in hertz. The range will always contain the
-     * device resonant frequency. Devices without frequency control will return null.
-     * @hide
+     * <p>If the devices does not have frequency control then the profile should be empty.
      */
-    @Nullable
-    public Range<Float> getFrequencyRangeHz() {
-        return mFrequencyMapping.mFrequencyRangeHz;
-    }
-
-    /**
-     * Return the maximum amplitude the vibrator can play at given frequency.
-     *
-     * @param frequencyHz The frequency, in hertz, for query.
-
-     * @return a value in [0,1] representing the maximum amplitude the device can play at given
-     * frequency. Devices without frequency control will return 0 to any input. Devices with
-     * frequency control will return the supported value, for input in
-     * {@link #getFrequencyRangeHz()}, and 0 for any other input.
-     * @hide
-     */
-    @FloatRange(from = 0, to = 1)
-    public float getMaxAmplitude(float frequencyHz) {
-        return mFrequencyMapping.getMaxAmplitude(frequencyHz);
+    @NonNull
+    public FrequencyProfile getFrequencyProfile() {
+        return mFrequencyProfile;
     }
 
     protected long getCapabilities() {
@@ -452,7 +477,7 @@
      * Describes the maximum relative output acceleration that can be achieved for each supported
      * frequency in a specific vibrator.
      *
-     * <p>This mapping is defined by the following parameters:
+     * <p>This profile is defined by the following parameters:
      *
      * <ol>
      *     <li>{@code minFrequencyHz}, {@code resonantFrequencyHz} and {@code frequencyResolutionHz}
@@ -466,7 +491,7 @@
      *
      * @hide
      */
-    public static final class FrequencyMapping implements Parcelable {
+    public static final class FrequencyProfile implements Parcelable {
         @Nullable
         private final Range<Float> mFrequencyRangeHz;
         private final float mMinFrequencyHz;
@@ -474,7 +499,7 @@
         private final float mFrequencyResolutionHz;
         private final float[] mMaxAmplitudes;
 
-        FrequencyMapping(Parcel in) {
+        FrequencyProfile(Parcel in) {
             this(in.readFloat(), in.readFloat(), in.readFloat(), in.createFloatArray());
         }
 
@@ -484,13 +509,13 @@
          * @param resonantFrequencyHz   The vibrator resonant frequency, in hertz.
          * @param minFrequencyHz        Minimum supported frequency, in hertz.
          * @param frequencyResolutionHz The frequency resolution, in hertz, used by the max
-         *                              amplitudes mapping.
+         *                              amplitude measurements.
          * @param maxAmplitudes         The max amplitude supported by each supported frequency,
          *                              starting at minimum frequency with jumps of frequency
          *                              resolution.
          * @hide
          */
-        public FrequencyMapping(float resonantFrequencyHz, float minFrequencyHz,
+        public FrequencyProfile(float resonantFrequencyHz, float minFrequencyHz,
                 float frequencyResolutionHz, float[] maxAmplitudes) {
             mMinFrequencyHz = minFrequencyHz;
             mResonantFrequencyHz = resonantFrequencyHz;
@@ -500,18 +525,25 @@
                 System.arraycopy(maxAmplitudes, 0, mMaxAmplitudes, 0, maxAmplitudes.length);
             }
 
-            // If any required field is undefined then leave this mapping empty.
+            // If any required field is undefined or has a bad value then this profile is invalid.
             boolean isValid = !Float.isNaN(resonantFrequencyHz)
+                    && (resonantFrequencyHz > 0)
                     && !Float.isNaN(minFrequencyHz)
+                    && (minFrequencyHz > 0)
                     && !Float.isNaN(frequencyResolutionHz)
+                    && (frequencyResolutionHz > 0)
                     && (mMaxAmplitudes.length > 0);
 
+            // If any max amplitude is outside the allowed range then this profile is invalid.
+            for (int i = 0; i < mMaxAmplitudes.length; i++) {
+                isValid &= (mMaxAmplitudes[i] >= 0) && (mMaxAmplitudes[i] <= 1);
+            }
+
             float maxFrequencyHz = isValid
                     ? minFrequencyHz + frequencyResolutionHz * (mMaxAmplitudes.length - 1)
                     : Float.NaN;
 
-            // If the non-empty mapping does not have min < resonant < max frequency respected
-            // then leave this mapping empty.
+            // If the constraint min < resonant < max is not met then it is invalid.
             isValid &= !Float.isNaN(maxFrequencyHz)
                     && (resonantFrequencyHz >= minFrequencyHz)
                     && (resonantFrequencyHz <= maxFrequencyHz)
@@ -520,14 +552,17 @@
             mFrequencyRangeHz = isValid ? Range.create(minFrequencyHz, maxFrequencyHz) : null;
         }
 
-        /**
-         * Returns true if this frequency mapping is empty, i.e. the only supported is the resonant
-         * frequency.
-         */
+        /** Returns true if the supported frequency range is empty. */
         public boolean isEmpty() {
             return mFrequencyRangeHz == null;
         }
 
+        /** Returns the supported frequency range, in hertz. */
+        @Nullable
+        public Range<Float> getFrequencyRangeHz() {
+            return mFrequencyRangeHz;
+        }
+
         /**
          * Returns the maximum relative amplitude the vibrator can reach while playing at the
          * given frequency.
@@ -535,24 +570,43 @@
          * @param frequencyHz frequency, in hertz, for query.
          * @return A value in [0,1] representing the max relative amplitude supported at the given
          * frequency. This will return 0 if the frequency is outside the supported range, or if the
-         * mapping is empty.
+         * supported frequency range is empty.
          */
         public float getMaxAmplitude(float frequencyHz) {
-            if (isEmpty() || Float.isNaN(frequencyHz)) {
+            if (isEmpty() || Float.isNaN(frequencyHz) || !mFrequencyRangeHz.contains(frequencyHz)) {
                 // Unsupported frequency requested, vibrator cannot play at this frequency.
                 return 0;
             }
-            float position = (frequencyHz - mMinFrequencyHz) / mFrequencyResolutionHz;
-            int floorIndex = (int) Math.floor(position);
-            int ceilIndex = (int) Math.ceil(position);
-            if (floorIndex < 0 || floorIndex >= mMaxAmplitudes.length) {
-                return 0;
-            }
-            if (floorIndex != ceilIndex && ceilIndex < mMaxAmplitudes.length) {
-                // Value in between two mapped frequency values, use the lowest supported one.
-                return MathUtils.min(mMaxAmplitudes[floorIndex], mMaxAmplitudes[ceilIndex]);
-            }
-            return mMaxAmplitudes[floorIndex];
+
+            // Subtract minFrequencyHz to simplify offset calculations.
+            float mappingFreq = frequencyHz - mMinFrequencyHz;
+
+            // Find the bucket to interpolate within.
+            // Any calculated index should be safe, except exactly equal to max amplitude can be
+            // one step too high, so constrain it to guarantee safety.
+            int startIdx = MathUtils.constrain(
+                    /* amount= */ (int) Math.floor(mappingFreq / mFrequencyResolutionHz),
+                    /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1);
+            int nextIdx = MathUtils.constrain(
+                    /* amount= */ startIdx + 1,
+                    /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1);
+
+            // Linearly interpolate the amplitudes based on the frequency range of the bucket.
+            return MathUtils.constrainedMap(
+                    mMaxAmplitudes[startIdx], mMaxAmplitudes[nextIdx],
+                    startIdx * mFrequencyResolutionHz, nextIdx * mFrequencyResolutionHz,
+                    mappingFreq);
+        }
+
+        /** Returns the raw list of maximum relative output accelerations from the vibrator. */
+        @NonNull
+        public float[] getMaxAmplitudes() {
+            return Arrays.copyOf(mMaxAmplitudes, mMaxAmplitudes.length);
+        }
+
+        /** Returns the raw frequency resolution used for max amplitude measurements, in hertz. */
+        public float getFrequencyResolutionHz() {
+            return mFrequencyResolutionHz;
         }
 
         @Override
@@ -573,10 +627,10 @@
             if (this == o) {
                 return true;
             }
-            if (!(o instanceof FrequencyMapping)) {
+            if (!(o instanceof FrequencyProfile)) {
                 return false;
             }
-            FrequencyMapping that = (FrequencyMapping) o;
+            FrequencyProfile that = (FrequencyProfile) o;
             return Float.compare(mMinFrequencyHz, that.mMinFrequencyHz) == 0
                     && Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0
                     && Float.compare(mFrequencyResolutionHz, that.mFrequencyResolutionHz) == 0
@@ -593,7 +647,7 @@
 
         @Override
         public String toString() {
-            return "FrequencyMapping{"
+            return "FrequencyProfile{"
                     + "mFrequencyRange=" + mFrequencyRangeHz
                     + ", mMinFrequency=" + mMinFrequencyHz
                     + ", mResonantFrequency=" + mResonantFrequencyHz
@@ -603,16 +657,16 @@
         }
 
         @NonNull
-        public static final Creator<FrequencyMapping> CREATOR =
-                new Creator<FrequencyMapping>() {
+        public static final Creator<FrequencyProfile> CREATOR =
+                new Creator<FrequencyProfile>() {
                     @Override
-                    public FrequencyMapping createFromParcel(Parcel in) {
-                        return new FrequencyMapping(in);
+                    public FrequencyProfile createFromParcel(Parcel in) {
+                        return new FrequencyProfile(in);
                     }
 
                     @Override
-                    public FrequencyMapping[] newArray(int size) {
-                        return new FrequencyMapping[size];
+                    public FrequencyProfile[] newArray(int size) {
+                        return new FrequencyProfile[size];
                     }
                 };
     }
@@ -629,8 +683,8 @@
         private int mPwlePrimitiveDurationMax;
         private int mPwleSizeMax;
         private float mQFactor = Float.NaN;
-        private FrequencyMapping mFrequencyMapping =
-                new FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null);
+        private FrequencyProfile mFrequencyProfile =
+                new FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
 
         /** A builder class for a {@link VibratorInfo}. */
         public Builder(int id) {
@@ -702,8 +756,8 @@
 
         /** Configure the vibrator frequency information like resonant frequency and bandwidth. */
         @NonNull
-        public Builder setFrequencyMapping(FrequencyMapping frequencyMapping) {
-            mFrequencyMapping = frequencyMapping;
+        public Builder setFrequencyProfile(@NonNull FrequencyProfile frequencyProfile) {
+            mFrequencyProfile = frequencyProfile;
             return this;
         }
 
@@ -712,7 +766,7 @@
         public VibratorInfo build() {
             return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
                     mSupportedPrimitives, mPrimitiveDelayMax, mCompositionSizeMax,
-                    mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyMapping);
+                    mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyProfile);
         }
 
         /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to core/java/android/os/logcat/ILogcatManagerService.aidl
index 2b3e961..68b5679 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/core/java/android/os/logcat/ILogcatManagerService.aidl
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package android.os.logcat;
 
-import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
+/**
+ * @hide
+ */
+interface ILogcatManagerService {
+    void startThread(in int uid, in int gid, in int pid, in int fd);
+    void finishThread(in int uid, in int gid, in int pid, in int fd);
+}
 
-// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
-interface ISmartspaceTransitionController {
-    oneway void setSmartspace(ISmartspaceCallback callback);
-}
\ No newline at end of file
diff --git a/core/java/android/os/logcat/OWNERS b/core/java/android/os/logcat/OWNERS
new file mode 100644
index 0000000..cb21a6f
--- /dev/null
+++ b/core/java/android/os/logcat/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/services/core/java/com/android/server/logcat/OWNERS
diff --git a/core/java/android/os/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/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..e4aee76 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -562,12 +562,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/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/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/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/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/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/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/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index bff75a6..dd4355d 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -278,6 +278,7 @@
         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;
@@ -1122,6 +1123,11 @@
                             mWindow, mLayout, mWidth, mHeight,
                             View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl,
                             mInsetsState, mTempControls, mSurfaceSize);
+
+                    final int transformHint = SurfaceControl.rotationToBufferTransform(
+                            (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
+                    mSurfaceControl.setTransformHint(transformHint);
+
                     if (mSurfaceControl.isValid()) {
                         if (mBbqSurfaceControl == null) {
                             mBbqSurfaceControl = new SurfaceControl.Builder()
@@ -1135,9 +1141,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
@@ -1377,6 +1383,7 @@
             mWallpaperDimAmount = mDefaultDimAmount;
             mPreviousWallpaperDimAmount = mWallpaperDimAmount;
             mDisplayState = mDisplay.getState();
+            mDisplayInstallOrientation = mDisplay.getInstallOrientation();
 
             if (DEBUG) Log.v(TAG, "onCreate(): " + this);
             onCreate(mSurfaceHolder);
@@ -1629,6 +1636,7 @@
                 return;
             }
             Surface surface = mSurfaceHolder.getSurface();
+            if (!surface.isValid()) return;
             boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y;
             int smaller = widthIsLarger ? mSurfaceSize.x
                     : mSurfaceSize.y;
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/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/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/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/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/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 678c80a..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) {
@@ -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..6226566 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;
 
@@ -290,6 +291,11 @@
     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.
     */
@@ -328,4 +334,12 @@
      */
     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);
 }
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/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/View.java b/core/java/android/view/View.java
index 2126fd5..93fdee0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -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);
             }
         }
     }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cec8d4c..97b5a31 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -398,6 +398,8 @@
     final DisplayManager mDisplayManager;
     final String mBasePackageName;
 
+    private @Surface.Rotation int mDisplayInstallOrientation;
+
     final int[] mTmpLocation = new int[2];
 
     final TypedValue mTmpValue = new TypedValue();
@@ -749,7 +751,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;
 
@@ -1025,6 +1030,7 @@
                 mView = view;
 
                 mAttachInfo.mDisplayState = mDisplay.getState();
+                mDisplayInstallOrientation = mDisplay.getInstallOrientation();
                 mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
                 mFallbackEventHandler.setView(view);
                 mWindowAttributes.copyFrom(attrs);
@@ -4767,7 +4773,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);
     }
 
@@ -4777,7 +4783,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();
+            }
+        }
     }
 
     /**
@@ -5273,6 +5298,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 {
@@ -5341,6 +5367,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);
         }
@@ -5564,7 +5592,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;
@@ -7909,6 +7940,10 @@
                 mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                 mTempControls, mSurfaceSize);
 
+        final int transformHint = SurfaceControl.rotationToBufferTransform(
+                (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
+        mSurfaceControl.setTransformHint(transformHint);
+
         if (mAttachInfo.mContentCaptureManager != null) {
             MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
                     .getMainContentCaptureSession();
@@ -7927,7 +7962,6 @@
                 mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
                 mAttachInfo.mThreadedRenderer.setBlastBufferQueue(mBlastBufferQueue);
             }
-            int transformHint = mSurfaceControl.getTransformHint();
             if (mPreviousTransformHint != transformHint) {
                 mPreviousTransformHint = transformHint;
                 dispatchTransformHintChanged(transformHint);
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/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..998498b 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 import android.util.MergedConfiguration;
 import android.window.ClientWindowFrames;
+import android.window.IOnBackInvokedCallback;
 
 import java.util.HashMap;
 import java.util.Objects;
@@ -459,6 +460,11 @@
     }
 
     @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,
             InputChannel outInputChannel) {
@@ -496,6 +502,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/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/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 6fc246e..6a22023 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2139,7 +2139,7 @@
             view.onInputConnectionOpenedInternal(ic, tba, icHandler);
             final ViewRootImpl viewRoot = view.getViewRootImpl();
             if (viewRoot != null) {
-                viewRoot.getHandwritingInitiator().onInputConnectionCreated(view, tba);
+                viewRoot.getHandwritingInitiator().onInputConnectionCreated(view);
             }
         }
 
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/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%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
rename 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/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 53734eb..2978604 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -17,11 +17,19 @@
 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}.
  *
@@ -39,6 +47,18 @@
 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
@@ -47,7 +67,9 @@
     public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) {
         mWindowSession = windowSession;
         mWindow = window;
-        // TODO(b/209867448): Send the top callback to WM (if one exists).
+        if (mTopCallback != null) {
+            setTopOnBackInvokedCallback(mTopCallback);
+        }
     }
 
     /** Detaches the dispatcher instance from its window. */
@@ -56,20 +78,124 @@
         mWindowSession = null;
     }
 
+    // TODO: Take an Executor for the callback to run on.
     @Override
     public void registerOnBackInvokedCallback(
             @NonNull OnBackInvokedCallback callback, @Priority int priority) {
-        // TODO(b/209867448): To be implemented.
+        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) {
-        // TODO(b/209867448): To be implemented.
+    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() {
-        // TODO(b/209867448): To be implemented.
+        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/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/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/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/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/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/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/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/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/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_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/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..11560a5 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -225,6 +225,7 @@
     repeated InsetsSourceProviderProto insets_source_providers = 35;
     optional bool is_sleeping = 36;
     repeated string sleep_tokens = 37;
+    repeated .android.graphics.RectProto keep_clear_areas = 38;
 
 }
 
@@ -443,6 +444,7 @@
     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 0fd4629..3a842ee 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4251,7 +4251,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" />
 
@@ -6043,10 +6044,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.
@@ -6105,11 +6111,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"
@@ -6123,7 +6139,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
     -->
@@ -6151,6 +6167,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. -->
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/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 a6cd42d..249881e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4077,6 +4077,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"
@@ -4247,7 +4253,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>
@@ -5616,4 +5622,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 d2ac8a3..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">
@@ -3324,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..1a5d8b7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3277,6 +3277,11 @@
     <!-- Title for EditText context menu [CHAR LIMIT=20] -->
     <string name="editTextMenuTitle">Text actions</string>
 
+    <!-- Content description of the back button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="input_method_nav_back_button_desc">Back</string>
+    <!-- Content description of the switch input method button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="input_method_ime_switch_button_desc">Switch input method</string>
+
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the title of that notification. -->
     <string name="low_internal_storage_view_title">Storage space running out</string>
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the message of that notification. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 35e619e..bcc3a6d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2439,6 +2439,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 +3670,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" />
 
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/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/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/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/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 6f5951b..de086df 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"/>
@@ -571,6 +575,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..8f73b9a1 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",
@@ -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",
@@ -745,6 +733,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",
@@ -1597,12 +1591,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",
@@ -1867,6 +1855,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 +1945,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 +2005,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",
@@ -2767,6 +2761,12 @@
       "group": "WM_SHOW_SURFACE_ALLOC",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
+    "751854538": {
+      "message": "DisplayArea keep clear rects changed name =%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
+    },
     "765395228": {
       "message": "onAnimationFinished(): controller=%s reorderMode=%d",
       "level": "DEBUG",
@@ -2797,6 +2797,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",
@@ -3211,6 +3217,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 +3283,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 +3403,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 +3709,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 +3841,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 +3936,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/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/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/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/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/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/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/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/AudioOutputFlags.aidl b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl
index a46229d..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.
      */
@@ -98,7 +98,11 @@
      */
     GAPLESS_OFFLOAD = 15,
     /**
+     * Output is used for spatial audio.
+     */
+    SPATIALIZER = 16,
+    /**
      * Output is used for transmitting ultrasound audio.
      */
-    ULTRASOUND = 16,
+    ULTRASOUND = 17,
 }
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/AudioOutputFlags.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl
index e2f286e..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,5 +51,6 @@
   VOIP_RX = 13,
   INCALL_MUSIC = 14,
   GAPLESS_OFFLOAD = 15,
-  ULTRASOUND = 16,
+  SPATIALIZER = 16,
+  ULTRASOUND = 17,
 }
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 211a50e..dd17dc6 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -177,6 +177,11 @@
      */
     public static final int TYPE_HDMI_EARC         = 29;
 
+    /**
+     * A device type describing a Bluetooth Low Energy (BLE) broadcast group.
+     */
+    public static final int TYPE_BLE_BROADCAST   = 30;
+
     /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
             TYPE_BUILTIN_EARPIECE,
@@ -207,7 +212,8 @@
             TYPE_REMOTE_SUBMIX,
             TYPE_BLE_HEADSET,
             TYPE_BLE_SPEAKER,
-            TYPE_ECHO_REFERENCE}
+            TYPE_ECHO_REFERENCE,
+            TYPE_BLE_BROADCAST}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceType {}
@@ -264,7 +270,8 @@
             TYPE_HEARING_AID,
             TYPE_BUILTIN_SPEAKER_SAFE,
             TYPE_BLE_HEADSET,
-            TYPE_BLE_SPEAKER}
+            TYPE_BLE_SPEAKER,
+            TYPE_BLE_BROADCAST}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceTypeOut {}
@@ -296,6 +303,7 @@
             case TYPE_BUILTIN_SPEAKER_SAFE:
             case TYPE_BLE_HEADSET:
             case TYPE_BLE_SPEAKER:
+            case TYPE_BLE_BROADCAST:
                 return true;
             default:
                 return false;
@@ -636,6 +644,7 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_HEADSET, TYPE_BLE_HEADSET);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_SPEAKER, TYPE_BLE_SPEAKER);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_BROADCAST, TYPE_BLE_BROADCAST);
 
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -690,6 +699,7 @@
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_HEADSET);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_SPEAKER, AudioSystem.DEVICE_OUT_BLE_SPEAKER);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_BROADCAST, AudioSystem.DEVICE_OUT_BLE_BROADCAST);
 
         // privileges mapping to input device
         EXT_TO_INT_INPUT_DEVICE_MAPPING = new SparseIntArray();
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index ebe0882..9211c53 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -90,7 +90,8 @@
      * {@link AudioManager#DEVICE_OUT_BLE_HEADSET}, {@link AudioManager#DEVICE_OUT_BLE_SPEAKER})
      * use the MAC address of the bluetooth device in the form "00:11:22:AA:BB:CC" as reported by
      * {@link BluetoothDevice#getAddress()}.
-     * - Deivces that do not have an address will indicate an empty string "".
+     * - Bluetooth LE broadcast group ({@link AudioManager#DEVICE_OUT_BLE_BROADCAST} use the group number.
+     * - Devices that do not have an address will indicate an empty string "".
      */
     public String address() {
         return mAddress;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 68e5d94..c4cef4c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5440,6 +5440,10 @@
      */
     public static final int DEVICE_OUT_BLE_SPEAKER = AudioSystem.DEVICE_OUT_BLE_SPEAKER;
     /** @hide
+     * The audio output device code for a BLE audio brodcast group.
+     */
+    public static final int DEVICE_OUT_BLE_BROADCAST = AudioSystem.DEVICE_OUT_BLE_BROADCAST;
+    /** @hide
      * This is not used as a returned value from {@link #getDevicesForStream}, but could be
      *  used in the future in a set method to select whatever default device is chosen by the
      *  platform-specific implementation.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 50bf1e5..306479a 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -989,6 +989,8 @@
     public static final int DEVICE_OUT_BLE_HEADSET = 0x20000000;
     /** @hide */
     public static final int DEVICE_OUT_BLE_SPEAKER = 0x20000001;
+    /** @hide */
+    public static final int DEVICE_OUT_BLE_BROADCAST = 0x20000002;
 
     /** @hide */
     public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT;
@@ -1049,6 +1051,7 @@
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_ECHO_CANCELLER);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_HEADSET);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_SPEAKER);
+        DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_BROADCAST);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_DEFAULT);
 
         DEVICE_OUT_ALL_A2DP_SET = new HashSet<>();
@@ -1079,6 +1082,7 @@
         DEVICE_OUT_ALL_BLE_SET = new HashSet<>();
         DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_HEADSET);
         DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_SPEAKER);
+        DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_BROADCAST);
     }
 
     // input devices
@@ -1262,6 +1266,7 @@
     /** @hide */ public static final String DEVICE_OUT_ECHO_CANCELLER_NAME = "echo_canceller";
     /** @hide */ public static final String DEVICE_OUT_BLE_HEADSET_NAME = "ble_headset";
     /** @hide */ public static final String DEVICE_OUT_BLE_SPEAKER_NAME = "ble_speaker";
+    /** @hide */ public static final String DEVICE_OUT_BLE_BROADCAST_NAME = "ble_broadcast";
 
     /** @hide */ public static final String DEVICE_IN_COMMUNICATION_NAME = "communication";
     /** @hide */ public static final String DEVICE_IN_AMBIENT_NAME = "ambient";
@@ -1361,6 +1366,8 @@
             return DEVICE_OUT_BLE_HEADSET_NAME;
         case DEVICE_OUT_BLE_SPEAKER:
             return DEVICE_OUT_BLE_SPEAKER_NAME;
+        case DEVICE_OUT_BLE_BROADCAST:
+            return DEVICE_OUT_BLE_BROADCAST_NAME;
         case DEVICE_OUT_DEFAULT:
         default:
             return "0x" + Integer.toHexString(device);
diff --git a/media/java/android/media/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/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/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index b888fb0..48c50f0 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -159,7 +159,7 @@
             PROTOCOL_UNKNOWN
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface DefaultProtocol {}
+    public @interface Protocol {}
 
     /**
      * Bundle key for the device's user visible name property.
@@ -429,7 +429,7 @@
      *
      * @return the device's default protocol.
      */
-    @DefaultProtocol
+    @Protocol
     public int getDefaultProtocol() {
         return mDefaultProtocol;
     }
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index a049a88..5348d4e 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -18,7 +18,6 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.SystemService;
 import android.bluetooth.BluetoothDevice;
@@ -29,13 +28,19 @@
 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.ArrayList;
-import java.util.Collection;
+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.
@@ -108,18 +113,30 @@
     private class DeviceListener extends IMidiDeviceListener.Stub {
         private final DeviceCallback mCallback;
         private final Handler mHandler;
+        private final Executor mExecutor;
         private final int mTransport;
 
         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 (shouldInvokeCallback(device)) {
-                if (mHandler != null) {
+                if (mExecutor != null) {
+                    mExecutor.execute(() ->
+                            mCallback.onDeviceAdded(device));
+                } else if (mHandler != null) {
                     final MidiDeviceInfo deviceF = device;
                     mHandler.post(new Runnable() {
                             @Override public void run() {
@@ -135,7 +152,10 @@
         @Override
         public void onDeviceRemoved(MidiDeviceInfo device) {
             if (shouldInvokeCallback(device)) {
-                if (mHandler != null) {
+                if (mExecutor != null) {
+                    mExecutor.execute(() ->
+                            mCallback.onDeviceRemoved(device));
+                } else if (mHandler != null) {
                     final MidiDeviceInfo deviceF = device;
                     mHandler.post(new Runnable() {
                             @Override public void run() {
@@ -150,7 +170,10 @@
 
         @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() {
@@ -234,7 +257,7 @@
     /**
      * 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 #registerDeviceCallbackForTransport} instead.
+     * 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
@@ -247,9 +270,19 @@
      * @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) {
-        registerDeviceCallbackForTransport(callback, handler, TRANSPORT_MIDI_BYTE_STREAM);
+        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);
     }
 
     /**
@@ -263,16 +296,16 @@
      * Applications should call {@link #getDevicesForTransport} before registering the callback
      * to get a list of devices already added.
      *
-     * @param callback a {@link DeviceCallback} for MIDI device notifications
-     * @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.
      * @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 registerDeviceCallbackForTransport(@NonNull DeviceCallback callback,
-            @Nullable Handler handler, @Transport int transport) {
-        DeviceListener deviceListener = new DeviceListener(callback, handler, transport);
+    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) {
@@ -303,7 +336,9 @@
      * {@link #getDevicesForTransport} instead.
      *
      * @return an array of MIDI devices
+     * @deprecated Use {@link #getDevicesForTransport} instead.
      */
+    @Deprecated
     public MidiDeviceInfo[] getDevices() {
         try {
            return mService.getDevices();
@@ -322,14 +357,13 @@
      *                  TRANSPORT_UNIVERSAL_MIDI_PACKETS.
      * @return a collection of MIDI devices
      */
-    public @NonNull Collection<MidiDeviceInfo> getDevicesForTransport(@Transport int transport) {
+    public @NonNull Set<MidiDeviceInfo> getDevicesForTransport(@Transport int transport) {
         try {
             MidiDeviceInfo[] devices = mService.getDevicesForTransport(transport);
-            Collection<MidiDeviceInfo> out = new ArrayList<MidiDeviceInfo>(devices.length);
-            for (int i = 0; i < devices.length; i++) {
-                out.add(devices[i]);
+            if (devices == null) {
+                return Collections.emptySet();
             }
-            return out;
+            return new ArraySet<>(devices);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -399,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 {
@@ -423,6 +459,15 @@
         }
     }
 
+    /** @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,
diff --git a/media/java/android/media/tv/AitInfo.java b/media/java/android/media/tv/AitInfo.java
index ff4c625..71b1634 100644
--- a/media/java/android/media/tv/AitInfo.java
+++ b/media/java/android/media/tv/AitInfo.java
@@ -17,11 +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.
+ * AIT (Application Information Table) info.
  * @hide
  */
 public final class AitInfo implements Parcelable {
@@ -50,14 +51,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/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..9bc7367 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -945,7 +945,13 @@
         }
 
         /**
-         * Notifies AIT info updated.
+         * Informs the app that the AIT (Application Information Table) is updated.
+         *
+         * <p>This method should also be call when
+         * {@link #onSetInteractiveAppNotificationEnabled(boolean)} is called to send the first AIT
+         * info.
+         *
+         * @see #onSetInteractiveAppNotificationEnabled(boolean)
          * @hide
          */
         public void notifyAitInfoUpdated(@NonNull final AitInfo aitInfo) {
@@ -1198,7 +1204,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.
+         *
+         * @see TvView#setInteractiveAppNotificationEnabled(boolean)
+         * @see Session#notifyAitInfoUpdated(android.media.tv.AitInfo)
          * @hide
          */
         public void onSetInteractiveAppNotificationEnabled(boolean enabled) {
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 71f6ad6..d2086c5 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -481,9 +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.
+     *
+     * @see TvInputService.Session#notifyAitInfoUpdated(android.media.tv.AitInfo)
+     * @see android.media.tv.interactive.TvInteractiveAppView#setTvView(TvView)
      * @hide
      */
     public void setInteractiveAppNotificationEnabled(boolean enabled) {
@@ -1062,12 +1071,12 @@
         }
 
         /**
-         * 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 87%
rename from media/java/android/media/tv/interactive/TvIAppManager.java
rename to media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index f819438..39be501 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,128 @@
 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.
  */
-@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.
+     * Unrealized state of interactive app service.
      * @hide
      */
-    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.
+     * Preparing state of interactive app service.
      * @hide
      */
-    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.
+     * Ready state of interactive app service.
      * @hide
      */
-    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.
+     * Error state of interactive app service.
      * @hide
      */
-    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.
+     * @hide
+     */
+    public static final int INTERACTIVE_APP_STATE_STOPPED = 1;
+    /**
+     * Running state of interactive application.
+     * @hide
+     */
+    public static final int INTERACTIVE_APP_STATE_RUNNING = 2;
+    /**
+     * Error state of interactive application.
+     * @hide
+     */
+    public static final int INTERACTIVE_APP_STATE_ERROR = 3;
+
+
+    /** @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.
+     * @hide
+     */
+    public static final int ERROR_NONE = 0;
+    /**
+     * Unknown error code.
+     * @hide
+     */
+    public static final int ERROR_UNKNOWN = 1;
+    /**
+     * Error code for an unsupported channel.
+     * @hide
+     */
+    public static final int ERROR_NOT_SUPPORTED = 2;
+    /**
+     * Error code for weak signal.
+     * @hide
+     */
+    public static final int ERROR_WEAK_SIGNAL = 3;
+    /**
+     * Error code when resource (e.g. tuner) is unavailable.
+     * @hide
+     */
+    public static final int ERROR_RESOURCE_UNAVAILABLE = 4;
+    /**
+     * Error code for blocked contents.
+     * @hide
+     */
+    public static final int ERROR_BLOCKED = 5;
+    /**
+     * Error code when the key or module is missing for the encrypted channel.
+     * @hide
+     */
+    public static final int ERROR_ENCRYPTED = 6;
+    /**
+     * Error code when the current channel is an unknown channel.
+     * @hide
+     */
+    public static final int ERROR_UNKNOWN_CHANNEL = 7;
+
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -190,7 +274,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 +293,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 +369,7 @@
 
             @Override
             public void onCommandRequest(
-                    @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                    @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
                     Bundle parameters,
                     int seq) {
                 synchronized (mSessionCallbackRecordMap) {
@@ -383,14 +467,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 +542,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);
                     }
                 }
             }
@@ -484,9 +568,10 @@
          * 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 +583,7 @@
          * App service package.
          *
          * @param iAppServiceId The ID of the TV Interactive App service.
+         * @hide
          */
         public void onInteractiveAppServiceRemoved(@NonNull String iAppServiceId) {
         }
@@ -509,6 +595,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 +611,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 +646,7 @@
         }
 
         public void postInteractiveAppServiceAdded(final String iAppServiceId) {
-            mHandler.post(new Runnable() {
+            mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
                     mCallback.onInteractiveAppServiceAdded(iAppServiceId);
@@ -560,7 +655,7 @@
         }
 
         public void postInteractiveAppServiceRemoved(final String iAppServiceId) {
-            mHandler.post(new Runnable() {
+            mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
                     mCallback.onInteractiveAppServiceRemoved(iAppServiceId);
@@ -569,7 +664,7 @@
         }
 
         public void postInteractiveAppServiceUpdated(final String iAppServiceId) {
-            mHandler.post(new Runnable() {
+            mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
                     mCallback.onInteractiveAppServiceUpdated(iAppServiceId);
@@ -578,7 +673,7 @@
         }
 
         public void postTvInteractiveAppInfoUpdated(final TvInteractiveAppInfo iAppInfo) {
-            mHandler.post(new Runnable() {
+            mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
                     mCallback.onTvInteractiveAppInfoUpdated(iAppInfo);
@@ -586,11 +681,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 +731,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 +757,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 +771,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 +795,16 @@
      * 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.
+     * @param executor A {@link Executor} that the status change will be delivered to.
      * @hide
      */
     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));
         }
     }
 
@@ -742,7 +839,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 +856,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 +1239,7 @@
         }
 
         /**
-         * Notifies IAPP session when video is available.
+         * Notifies Interactive APP session when video is available.
          */
         public void notifyVideoAvailable() {
             if (mToken == null) {
@@ -1157,7 +1254,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 +1269,7 @@
         }
 
         /**
-         * Notifies IAPP session when content is allowed.
+         * Notifies Interactive APP session when content is allowed.
          */
         public void notifyContentAllowed() {
             if (mToken == null) {
@@ -1187,7 +1284,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 +1575,7 @@
         }
 
         void postCommandRequest(
-                final @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                final @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
                 final Bundle parameters) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1553,11 +1650,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 +1684,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 +1715,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 +1811,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 92%
rename from media/java/android/media/tv/interactive/TvIAppService.java
rename to media/java/android/media/tv/interactive/TvInteractiveAppService.java
index c0ec76b..d599d0a 100755
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -65,11 +65,11 @@
 import java.util.List;
 
 /**
- * The TvIAppService class represents a TV interactive applications RTE.
+ * The TvInteractiveAppService class represents a TV interactive applications RTE.
  */
-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 +83,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 +131,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 +182,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);
             }
 
@@ -196,15 +203,14 @@
      * Prepares TV Interactive App service for the given type.
      * @hide
      */
-    public void onPrepare(int type) {
-        // TODO: make it abstract when unhide
+    public 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
     }
 
@@ -236,20 +242,32 @@
      * @hide
      */
     @Nullable
-    public Session onCreateSession(@NonNull String iAppServiceId, int type) {
-        // TODO: make it abstract when unhide
+    public Session onCreateSession(
+            @NonNull String iAppServiceId,
+            @TvInteractiveAppInfo.InteractiveAppType int type) {
         return null;
     }
 
     /**
-     * Notifies the system when the state of the interactive app has been changed.
-     * @param state the current state
+     * 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}.
      * @hide
      */
     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();
     }
 
     /**
@@ -298,6 +316,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 +338,13 @@
         }
 
         /**
-         * Starts TvIAppService session.
-         * @hide
+         * Starts TvInteractiveAppService session.
          */
         public void onStartInteractiveApp() {
         }
 
         /**
-         * Stops TvIAppService session.
-         * @hide
+         * Stops TvInteractiveAppService session.
          */
         public void onStopInteractiveApp() {
         }
@@ -364,6 +381,7 @@
         /**
          * To toggle Digital Teletext Application if there is one in AIT app list.
          * @param enable
+         * @hide
          */
         public void onSetTeletextAppEnabled(boolean enable) {
         }
@@ -438,6 +456,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 +466,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 +474,7 @@
         }
 
         /**
-         * Releases TvIAppService session.
+         * Releases TvInteractiveAppService session.
          * @hide
          */
         public void onRelease() {
@@ -620,6 +640,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 +665,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 +691,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 +716,7 @@
 
         /**
          * Sets broadcast video bounds.
+         * @hide
          */
         public void setVideoBounds(Rect rect) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -715,6 +739,7 @@
 
         /**
          * Requests the URI of the current channel.
+         * @hide
          */
         public void requestCurrentChannelUri() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -737,6 +762,7 @@
 
         /**
          * Requests the logic channel number (LCN) of the current channel.
+         * @hide
          */
         public void requestCurrentChannelLcn() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -759,6 +785,7 @@
 
         /**
          * Requests stream volume.
+         * @hide
          */
         public void requestStreamVolume() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -781,6 +808,7 @@
 
         /**
          * Requests the list of {@link TvTrackInfo}.
+         * @hide
          */
         public void requestTrackInfoList() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -829,6 +857,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 +1016,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 +1032,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);
@@ -1039,8 +1073,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 +1104,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 +1113,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 +1479,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 +1493,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 +1527,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 +1555,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..12e2199 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,16 @@
 
     /**
      * Prepares the interactive application.
+     *
+     * @param iAppServiceId the interactive app service ID, which can be found in
+     *                      {@link TvInteractiveAppInfo#getId()}.
+     *
+     * @see android.media.tv.interactive.TvInteractiveAppManager#getTvInteractiveAppServiceList()
      * @hide
      */
-    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");
@@ -552,10 +560,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
+     * @return The result of the operation.
      * @hide
      */
     public int setTvView(@Nullable TvView tvView) {
@@ -583,6 +591,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,7 +618,7 @@
          */
         public void onCommandRequest(
                 @NonNull String iAppServiceId,
-                @NonNull @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                @NonNull @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
                 @Nullable Bundle parameters) {
         }
 
@@ -617,10 +626,16 @@
          * This is called when the session state is changed.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
-         * @param state current session state.
+         * @param state the current state.
+         * @param err the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE}
+         *              is used when the state is not
+         *              {@link TvInteractiveAppManager#INTERACTIVE_APP_STATE_ERROR}.
          * @hide
          */
-        public void onSessionStateChanged(@NonNull String iAppServiceId, int state) {
+        public void onStateChanged(
+                @NonNull String iAppServiceId,
+                @TvInteractiveAppManager.InteractiveAppState int state,
+                @TvInteractiveAppManager.ErrorCode int err) {
         }
 
         /**
@@ -642,13 +657,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 +674,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 +684,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 +694,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 +704,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 +717,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 +819,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 +843,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 +859,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..3157375 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -1002,7 +1002,7 @@
     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);
 
@@ -1565,6 +1565,36 @@
     }
 
     /**
+     * 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 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/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..41f3a678 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1622,6 +1622,15 @@
     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);
+}
+
 jobject JTuner::openLnbByHandle(int handle) {
     if (mTunerClient == nullptr) {
         return nullptr;
@@ -2610,6 +2619,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 +4384,11 @@
     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 jint android_media_tv_Tuner_close_frontend(JNIEnv* env, jobject thiz, jint /* handle */) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->closeFrontend();
@@ -4676,6 +4708,8 @@
              (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 },
 };
 
 static const JNINativeMethod gFilterMethods[] = {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 502bd6b..e9475dc 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -205,6 +205,7 @@
     Result getFrontendHardwareInfo(string& info);
     jint setMaxNumberOfFrontends(int32_t frontendType, int32_t maxNumber);
     int32_t getMaxNumberOfFrontends(int32_t frontendType);
+    jint removeOutputPid(int32_t pid);
 
     jweak getObject();
 
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 0fdd8d8..bea0342 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -143,6 +143,15 @@
     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;
+}
+
 shared_ptr<ITunerFrontend> FrontendClient::getAidlFrontend() {
     return mTunerFrontend;
 }
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index 77d9098..c6838c8 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -120,6 +120,11 @@
      */
     Result getHardwareInfo(string& info);
 
+    /**
+     * Filter out unnecessary PID from frontend output.
+     */
+    Result removeOutputPid(int32_t pid);
+
     int32_t getId();
 
     shared_ptr<ITunerFrontend> getAidlFrontend();
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index 2dd9525..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();
@@ -310,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..453e8e6 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,82 @@
     }
 
     /**
-     * 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.
+     * @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 +803,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 +836,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 +866,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 +1003,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/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/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/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
index 9f9d73f..58ca21f 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
@@ -32,6 +32,8 @@
 
 import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Binder;
 import android.service.NetworkStatsCollectionKeyProto;
 import android.service.NetworkStatsCollectionProto;
@@ -70,6 +72,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Collection of {@link NetworkStatsHistory}, stored based on combined key of
@@ -77,6 +80,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 +104,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 +129,7 @@
         mDirty = false;
     }
 
+    /** @hide */
     public long getStartMillis() {
         return mStartMillis;
     }
@@ -124,6 +137,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 +147,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 +188,7 @@
         }
     }
 
+    /** @hide */
     @VisibleForTesting
     public long roundDown(long time) {
         if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
@@ -182,10 +203,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 +229,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 +355,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 +402,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 +413,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 +433,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 +466,7 @@
         }
     }
 
+    /** @hide */
     @Override
     public void read(InputStream in) throws IOException {
         read((DataInput) new DataInputStream(in));
@@ -472,6 +506,7 @@
         }
     }
 
+    /** @hide */
     @Override
     public void write(OutputStream out) throws IOException {
         write((DataOutput) new DataOutputStream(out));
@@ -514,6 +549,7 @@
      * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
      *
      * @deprecated
+     * @hide
      */
     @Deprecated
     public void readLegacyNetwork(File file) throws IOException {
@@ -559,6 +595,7 @@
      * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
      *
      * @deprecated
+     * @hide
      */
     @Deprecated
     public void readLegacyUid(File file, boolean onlyTags) throws IOException {
@@ -629,6 +666,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 +703,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 +722,7 @@
         }
     }
 
+    /** @hide */
     public void dumpDebug(ProtoOutputStream proto, long tag) {
         final long start = proto.start(tag);
 
@@ -706,6 +746,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 +809,37 @@
         return false;
     }
 
-    private static class Key implements Comparable<Key> {
+    /**
+     * 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 +852,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 +861,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..77b7f16 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,8 +28,8 @@
 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 com.android.server.NetworkManagementSocketTagger;
@@ -37,8 +38,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 +176,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 +196,38 @@
     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);
+        sStatsService = statsManager.getBinder();
+    }
+
+    /**
+     * Attach the socket tagger implementation to the current process, to
+     * get notified when a socket's {@link FileDescriptor} is assigned to
+     * a thread. See {@link SocketTagger#set(SocketTagger)}.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void attachSocketTagger() {
+        NetworkManagementSocketTagger.install();
+    }
+
+    /**
      * Set active tag to use when accounting {@link Socket} traffic originating
      * from the current thread. Only one active tag per thread is supported.
      * <p>
diff --git a/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl b/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl
new file mode 100644
index 0000000..4e8a5b2
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats;
+
+import android.net.DataUsageRequest;
+
+/**
+ * Interface for NetworkStatsService to notify events to the callers of registerUsageCallback.
+ *
+ * @hide
+ */
+oneway interface IUsageCallback {
+    void onThresholdReached(in DataUsageRequest request);
+    void onCallbackReleased(in DataUsageRequest request);
+}
diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp
index 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/NetworkStatsObservers.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
index b57a4f9..e85a59e 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) {
+    public DataUsageRequest register(DataUsageRequest inputRequest, IUsageCallback callback,
+            int callingUid, @NetworkStatsAccess.Level int accessLevel) {
         DataUsageRequest request = buildRequest(inputRequest);
-        RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid,
+        RequestInfo requestInfo = buildRequestInfo(request, callback, callingUid,
                 accessLevel);
 
         if (LOGV) Log.v(TAG, "Registering observer for " + request);
@@ -206,11 +205,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 +216,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 +228,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 +253,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 +266,7 @@
         }
 
         private void unlinkDeathRecipient() {
-            if (mBinder != null) {
-                mBinder.unlinkToDeath(this, 0);
-            }
+            mCallback.asBinder().unlinkToDeath(this, 0);
         }
 
         /**
@@ -294,17 +288,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 +330,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 +372,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..d78c2c4 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(netd),
                 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);
         }
     }
 
@@ -968,11 +1020,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 +1148,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 +1386,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 +1412,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 +1427,9 @@
                 if (isMobile) {
                     mobileIfaces.add(baseIface);
                 }
+                if (isWifi) {
+                    wifiIfaces.add(baseIface);
+                }
             }
 
             // Traffic occurring on stacked interfaces is usually clatd.
@@ -1411,6 +1471,9 @@
                     if (isMobile) {
                         mobileIfaces.add(iface);
                     }
+                    if (isWifi) {
+                        wifiIfaces.add(iface);
+                    }
 
                     mStatsFactory.noteStackedIface(iface, baseIface);
                 }
@@ -1418,11 +1481,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 +1508,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 +2259,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 +2271,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 +2295,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/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/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/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 1edb7d1..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
@@ -64,7 +64,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -436,7 +435,6 @@
     }
 
     @Test
-    @Ignore
     public void noAppRemoved_noWorkprofile_doResumeIfNeededLocked_shouldNotClearEntries()
             throws RemoteException {
         // scenario: only owner user
@@ -630,7 +628,6 @@
     }
 
     @Test
-    @Ignore
     public void noAppRemoved_workprofileExists_doResumeIfNeededLocked_shouldNotClearEntries()
             throws RemoteException {
         if (!MU_ENABLED) {
@@ -711,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 09540d1..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
@@ -40,7 +40,6 @@
 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -85,7 +84,6 @@
         when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice);
         mProfileManager = new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter,
                 mDeviceManager, mEventManager);
-        mEventManager.registerProfileIntentReceiverForTest();
     }
 
     /**
@@ -152,7 +150,6 @@
      * profile connection state changed callback
      */
     @Test
-    @Ignore
     public void stateChangedHandler_receiveA2dpConnectionStateChanged_shouldDispatchCallback() {
         mShadowBluetoothAdapter.setSupportedProfiles(generateList(
                 new int[] {BluetoothProfile.A2DP}));
@@ -174,7 +171,6 @@
      * profile connection state changed callback
      */
     @Test
-    @Ignore
     public void stateChangedHandler_receiveHeadsetConnectionStateChanged_shouldDispatchCallback() {
         mShadowBluetoothAdapter.setSupportedProfiles(generateList(
                 new int[] {BluetoothProfile.HEADSET}));
@@ -196,7 +192,6 @@
      * CachedBluetoothDeviceManager method
      */
     @Test
-    @Ignore
     public void stateChangedHandler_receiveHAPConnectionStateChanged_shouldDispatchDeviceManager() {
         mShadowBluetoothAdapter.setSupportedProfiles(generateList(
                 new int[] {BluetoothProfile.HEARING_AID}));
@@ -219,7 +214,6 @@
      * profile connection state changed callback
      */
     @Test
-    @Ignore
     public void stateChangedHandler_receivePanConnectionStateChanged_shouldNotDispatchCallback() {
         mShadowBluetoothAdapter.setSupportedProfiles(generateList(
                 new int[] {BluetoothProfile.PAN}));
@@ -261,7 +255,6 @@
      * handler and refresh CachedBluetoothDevice
      */
     @Test
-    @Ignore
     public void stateChangedHandler_receivePanConnectionStateChangedWithProfile_shouldRefresh() {
         mShadowBluetoothAdapter.setSupportedProfiles(generateList(
                 new int[] {BluetoothProfile.PAN}));
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 aa11952..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;
@@ -37,7 +36,6 @@
 import com.android.settingslib.RestrictedLockUtils;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -67,9 +65,8 @@
     }
 
     @Test
-    @Ignore
     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()));
 
@@ -85,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/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 a31f24a..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,14 +19,17 @@
 
 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.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -37,7 +40,7 @@
 @RunWith(RobolectricTestRunner.class)
 public class AccessPointPreferenceTest {
 
-    private Context mContext = RuntimeEnvironment.application;
+    private Context mContext;
 
     @Mock
     private AccessPoint mockAccessPoint;
@@ -54,12 +57,13 @@
     @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());
     }
 
     @Test
-    @Ignore
     public void refresh_openNetwork_updateContentDescription() {
         final String ssid = "ssid";
         final String summary = "connected";
@@ -90,7 +94,6 @@
     }
 
     @Test
-    @Ignore
     public void refresh_setTitle_shouldUseSsidString() {
         final String ssid = "ssid";
         final String summary = "connected";
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 5d7f8ba..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;
@@ -44,7 +46,6 @@
 import com.android.settingslib.R;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -75,10 +76,10 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mock(WifiManager.class));
     }
 
     @Test
-    @Ignore
     public void testVerboseSummaryString_showsScanResultSpeedLabel() {
         WifiTracker.sVerboseLogging = true;
 
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 c16cae0..46e24fa 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -250,6 +250,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 +549,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" />
 
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index e1da744..3ae85e7 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
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/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/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/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..ceaacfc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -984,10 +984,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..41d5735 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -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>
@@ -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>
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..f2f382d 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
@@ -310,53 +317,48 @@
                 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 (mOpeningLeashes != null) {
+                    // TODO: the launcher animation should handle this
+                    for (int i = 0; i < mOpeningLeashes.size(); ++i) {
+                        t.show(mOpeningLeashes.get(i));
+                        t.setAlpha(mOpeningLeashes.get(i), 1.f);
+                    }
+                }
+                if (mPipTask != null && mPipTransaction != null) {
+                    t.show(mInfo.getChange(mPipTask).getLeash());
+                    PictureInPictureSurfaceTransaction.apply(mPipTransaction,
+                            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/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/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/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/ComplicationProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java
index 099e379..e5d6319 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java
@@ -16,8 +16,14 @@
 
 package com.android.systemui.dreams;
 
+import android.annotation.IntDef;
 import android.content.Context;
 
+import com.android.settingslib.dream.DreamBackend;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * {@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
@@ -25,6 +31,27 @@
  */
 public interface ComplicationProvider {
     /**
+     * The type of dream complications which can be provided by a {@link ComplicationProvider}.
+     */
+    @IntDef(prefix = {"COMPLICATION_TYPE_"}, flag = true, value = {
+            COMPLICATION_TYPE_NONE,
+            COMPLICATION_TYPE_TIME,
+            COMPLICATION_TYPE_DATE,
+            COMPLICATION_TYPE_WEATHER,
+            COMPLICATION_TYPE_AIR_QUALITY,
+            COMPLICATION_TYPE_CAST_INFO
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ComplicationType {}
+
+    int COMPLICATION_TYPE_NONE = 0;
+    int COMPLICATION_TYPE_TIME = 1;
+    int COMPLICATION_TYPE_DATE = 1 << 1;
+    int COMPLICATION_TYPE_WEATHER = 1 << 2;
+    int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3;
+    int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
+
+    /**
      * Called when the {@link ComplicationHost} requests the associated complication be produced.
      *
      * @param context The {@link Context} used to construct the view.
@@ -33,4 +60,26 @@
      */
     void onCreateComplication(Context context, ComplicationHost.CreationCallback creationCallback,
             ComplicationHost.InteractionCallback interactionCallback);
+
+    /**
+     * Converts a {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to
+     * {@link ComplicationType}.
+     */
+    @ComplicationType
+    default int convertComplicationType(@DreamBackend.ComplicationType int type) {
+        switch (type) {
+            case DreamBackend.COMPLICATION_TYPE_TIME:
+                return COMPLICATION_TYPE_TIME;
+            case DreamBackend.COMPLICATION_TYPE_DATE:
+                return COMPLICATION_TYPE_DATE;
+            case DreamBackend.COMPLICATION_TYPE_WEATHER:
+                return COMPLICATION_TYPE_WEATHER;
+            case DreamBackend.COMPLICATION_TYPE_AIR_QUALITY:
+                return COMPLICATION_TYPE_AIR_QUALITY;
+            case DreamBackend.COMPLICATION_TYPE_CAST_INFO:
+                return COMPLICATION_TYPE_CAST_INFO;
+            default:
+                return COMPLICATION_TYPE_NONE;
+        }
+    }
 }
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/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 0d4688e..072f50d 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,12 @@
 
 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})
 public interface DreamModule {
-}
+}
\ No newline at end of file
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/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/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..b84b382
--- /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 if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.key)) {
+                    // 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/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..6eff799 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
@@ -552,14 +552,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/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 9c755e9..3cdaa9a 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;
     }
 
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/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 4249d76..48048b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -707,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;
             }
@@ -806,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);
     }
 
@@ -1330,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 fdced64..ae4a19e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -268,6 +268,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 =
@@ -618,6 +619,8 @@
     protected boolean mDozing;
     private boolean mIsFullscreen;
 
+    boolean mCloseQsBeforeScreenOff;
+
     private final NotificationMediaManager mMediaManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final NotificationRemoteInputManager mRemoteInputManager;
@@ -1123,6 +1126,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 +2647,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 +2934,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 +2970,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 +2978,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 +3100,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 +3158,7 @@
         // bar.
         mKeyguardStateController.notifyKeyguardGoingAway(true);
         mCommandQueue.appTransitionPending(mDisplayId, true /* forced */);
+        updateScrimController();
     }
 
     /**
@@ -3306,7 +3330,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
@@ -3644,6 +3671,10 @@
         public void onScreenTurnedOff() {
             mFalsingCollector.onScreenOff();
             mScrimController.onScreenTurnedOff();
+            if (mCloseQsBeforeScreenOff) {
+                mNotificationPanelViewController.closeQs();
+                mCloseQsBeforeScreenOff = false;
+            }
             updateIsKeyguard();
         }
     };
@@ -3736,17 +3767,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();
@@ -3758,7 +3786,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/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/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 4f037a0..aaf35af 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -136,6 +136,8 @@
 
     override fun shouldAnimateClockChange(): Boolean = !isAnimationPlaying()
 
+    override fun shouldDelayDisplayDozeTransition(): Boolean = shouldPlayAnimation()
+
     /** Called when AOD status is changed */
     override fun onAlwaysOnChanged(alwaysOn: Boolean) {
         alwaysOnEnabled = alwaysOn
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/ComplicationProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java
new file mode 100644
index 0000000..ada7ddb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.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;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.dream.DreamBackend;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationProviderTest {
+    private TestComplicationProvider mComplicationProvider;
+
+    @Before
+    public void setup() {
+        mComplicationProvider = new TestComplicationProvider();
+    }
+
+    @Test
+    public void testConvertComplicationType() {
+        assertEquals(ComplicationProvider.COMPLICATION_TYPE_TIME,
+                mComplicationProvider.convertComplicationType(DreamBackend.COMPLICATION_TYPE_TIME));
+        assertEquals(ComplicationProvider.COMPLICATION_TYPE_DATE,
+                mComplicationProvider.convertComplicationType(DreamBackend.COMPLICATION_TYPE_DATE));
+        assertEquals(ComplicationProvider.COMPLICATION_TYPE_WEATHER,
+                mComplicationProvider.convertComplicationType(
+                        DreamBackend.COMPLICATION_TYPE_WEATHER));
+        assertEquals(ComplicationProvider.COMPLICATION_TYPE_AIR_QUALITY,
+                mComplicationProvider.convertComplicationType(
+                        DreamBackend.COMPLICATION_TYPE_AIR_QUALITY));
+        assertEquals(ComplicationProvider.COMPLICATION_TYPE_CAST_INFO,
+                mComplicationProvider.convertComplicationType(
+                        DreamBackend.COMPLICATION_TYPE_CAST_INFO));
+    }
+
+    private static class TestComplicationProvider implements ComplicationProvider {
+        @Override
+        public void onCreateComplication(Context context,
+                ComplicationHost.CreationCallback creationCallback,
+                ComplicationHost.InteractionCallback interactionCallback) {
+        }
+    }
+}
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/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..a1ec38f 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,16 +21,14 @@
 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.Test
@@ -53,11 +51,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 +69,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 +110,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 +196,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/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index ecc4c46..509ae33 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,19 @@
 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.Test
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import java.util.concurrent.Future
 
 @SmallTest
 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 +49,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 +142,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 +300,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 +313,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..11b727e 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,7 +4,9 @@
 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
@@ -17,8 +19,7 @@
 @SmallTest
 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 +31,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/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..77065b2 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
@@ -819,31 +819,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/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/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index 8f15d5c..77e3ee5 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,29 @@
     private void unregister(int displayId) {
         mController.getFullScreenMagnificationController().unregister(displayId);
     }
+
+    /** Dumps {@link MagnificationConfig} and magnification region of magnifiers on the displays. */
+    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();
+        }
+    }
+
+    private int getIdOfLastServiceToMagnify(int mode, int displayId) {
+        return (mode == MAGNIFICATION_MODE_FULLSCREEN)
+                ? mController.getFullScreenMagnificationController()
+                .getIdOfLastServiceToMagnify(displayId)
+                : mController.getWindowMagnificationMgr().getIdOfLastServiceToMagnify(
+                        displayId);
+    }
 }
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..2fbefa4 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -319,6 +319,22 @@
     }
 
     /**
+     * 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.
      *
      * The tracking typing focus should be set to enabled with the following conditions:
@@ -466,6 +482,7 @@
         boolean previousEnabled;
         synchronized (mLock) {
             if (mConnectionWrapper == null) {
+                Slog.w(TAG, "enableWindowMagnification failed: connection null");
                 return false;
             }
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
@@ -508,6 +525,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/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
index a4fa1c1..dbe866b 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -16,6 +16,8 @@
 
 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;
@@ -71,11 +73,11 @@
      */
     @Override
     public void onDeviceConnected(@NonNull BluetoothDevice device) {
-        if (DEBUG) Log.i(TAG, "onDevice_Connected() " + toString(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 " + toString(device) + " is already connected.");
+            if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected.");
             return;
         }
 
@@ -91,13 +93,15 @@
     public void onDeviceDisconnected(@NonNull BluetoothDevice device,
             @DisconnectReason int reason) {
         if (DEBUG) {
-            Log.i(TAG, "onDevice_Disconnected() " + toString(device));
+            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 " + toString(device));
+            if (DEBUG) {
+                Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device));
+            }
             return;
         }
 
@@ -109,7 +113,7 @@
                 mAssociationStore.getAssociationsByAddress(device.getAddress());
 
         if (DEBUG) {
-            Log.d(TAG, "onDevice_ConnectivityChanged() " + toString(device)
+            Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
                     + " connected=" + connected);
             if (associations.isEmpty()) {
                 Log.d(TAG, "  > No CDM associations");
@@ -138,6 +142,12 @@
     }
 
     @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
@@ -153,23 +163,4 @@
         // This will be implemented when CDM support updating addresses.
         throw new IllegalArgumentException("Address changes are not supported.");
     }
-
-    private static String toString(@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();
-    }
 }
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..5c8fb2e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,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;
@@ -50,11 +55,11 @@
 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.Set;
 
 
 final class VirtualDeviceImpl extends IVirtualDevice.Stub
@@ -69,10 +74,34 @@
     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 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 +112,22 @@
 
     VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
             IBinder token, int ownerUid, OnDeviceCloseListener listener,
-            PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
+            PendingTrampolineCallback pendingTrampolineCallback,
+            IVirtualDeviceActivityListener activityListener,
+            VirtualDeviceParams params) {
         this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener,
-                pendingTrampolineCallback, params);
+                pendingTrampolineCallback, activityListener, params);
     }
 
     @VisibleForTesting
     VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
             int ownerUid, InputController inputController, OnDeviceCloseListener listener,
-            PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
+            PendingTrampolineCallback pendingTrampolineCallback,
+            IVirtualDeviceActivityListener activityListener, VirtualDeviceParams params) {
         mContext = context;
         mAssociationInfo = associationInfo;
         mPendingTrampolineCallback = pendingTrampolineCallback;
+        mActivityListener = activityListener;
         mOwnerUid = ownerUid;
         mAppToken = token;
         mParams = params;
@@ -202,7 +235,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 +261,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 +288,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             mInputController.createTouchscreen(deviceName, vendorId, productId,
-                    deviceToken, screenSize);
+                    deviceToken, displayId, screenSize);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -324,10 +358,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) {
@@ -343,9 +388,15 @@
                     "Virtual device already have a virtual display with ID " + displayId);
         }
         mVirtualDisplayIds.add(displayId);
+        LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture(
+                displayId, false);
         final GenericWindowPolicyController dwpc =
                 new GenericWindowPolicyController(FLAG_SECURE,
-                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles());
+                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        getAllowedUserHandles(),
+                        mParams.getAllowedActivities(),
+                        mParams.getBlockedActivities(),
+                        createListenerAdapter(displayId));
         mWindowPolicyControllers.put(displayId, dwpc);
         return dwpc;
     }
@@ -374,6 +425,8 @@
                     "Virtual device doesn't have a virtual display with ID " + displayId);
         }
         mVirtualDisplayIds.remove(displayId);
+        LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture(
+                displayId, true);
         mWindowPolicyControllers.remove(displayId);
     }
 
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/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/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/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..3c5b872 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,6 +1039,7 @@
      * 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);
@@ -1041,6 +1055,17 @@
                             | PackageManager.MATCH_DIRECT_BOOT_AWARE
                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
                     userId);
+            if (cpi == null && isAuthorityRedirectedForCloneProfile(authority)) {
+                // This might be a provider that's running only in the system user that's
+                // also serving clone profiles
+                cpi = AppGlobals.getPackageManager().resolveContentProvider(authority,
+                        ActivityManagerService.STOCK_PM_FLAGS
+                                | PackageManager.GET_URI_PERMISSION_PATTERNS
+                                | PackageManager.MATCH_DISABLED_COMPONENTS
+                                | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                        UserHandle.USER_SYSTEM);
+            }
         } catch (RemoteException ignored) {
         }
         if (cpi == null) {
@@ -1048,6 +1073,16 @@
                     + "; expected to find a valid ContentProvider for this authority";
         }
 
+        if (isAuthorityRedirectedForCloneProfile(authority)) {
+            UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
+            UserInfo userInfo = umInternal.getUserInfo(userId);
+
+            if (userInfo != null && userInfo.isCloneProfile()) {
+                userId = umInternal.getProfileParentId(userId);
+                checkUser = false;
+            }
+        }
+
         final int callingPid = Binder.getCallingPid();
         ProcessRecord r;
         final String appName;
@@ -1060,7 +1095,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/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 0961fcb3..2dd6bf5 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1360,6 +1360,9 @@
             case AudioSystem.DEVICE_OUT_USB_HEADSET:
                 connType = AudioRoutesInfo.MAIN_USB;
                 break;
+            case AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET:
+                connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS;
+                break;
         }
 
         synchronized (mCurAudioRoutes) {
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 33a26ba..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;
@@ -305,6 +306,9 @@
 
         @Override
         public void onFixedRotationFinished(int displayId) { }
+
+        @Override
+        public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearArea) { }
     }
 
 
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/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..c6d3829 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;
@@ -1382,7 +1380,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 +1472,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 +1596,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 +1635,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 +2137,7 @@
     private void setCurrentScreenBrightness(float brightnessValue) {
         if (brightnessValue != mCurrentScreenBrightnessSetting) {
             mCurrentScreenBrightnessSetting = brightnessValue;
-            postBrightnessChangeRunnable();
+            mHandler.post(mOnBrightnessChangeRunnable);
         }
     }
 
@@ -2215,7 +2189,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 +2299,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 +2466,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 +2763,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/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/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ec49b47..0d41a37 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;
@@ -168,7 +167,6 @@
 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 +218,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 +235,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;
@@ -343,7 +328,7 @@
 
     static class SessionState {
         final ClientState client;
-        final IInputMethod method;
+        final IInputMethodInvoker method;
 
         IInputMethodSession session;
         InputChannel channel;
@@ -352,14 +337,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 +612,7 @@
      */
     @GuardedBy("ImfLock.class")
     @Nullable
-    private IInputMethod getCurMethodLocked() {
+    private IInputMethodInvoker getCurMethodLocked() {
         return mBindingController.getCurMethod();
     }
 
@@ -654,9 +639,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 +690,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 +770,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;
@@ -1575,7 +1560,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,
@@ -1782,8 +1767,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 +1950,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 +2185,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,8 +2199,14 @@
         }
     }
 
-    private void executeOrSendMessage(IInterface target, Message msg) {
+    private void executeOrSendMessage(IInputMethodClient target, Message msg) {
          if (target.asBinder() instanceof Binder) {
+             // This is supposed to be emulating the one-way semantics when the IME client is
+             // 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.
              mCaller.sendMessage(msg);
          } else {
              handleMessage(msg);
@@ -2232,10 +2221,9 @@
                     + 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();
                 }
             }
 
@@ -2284,16 +2272,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 +2299,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 +2317,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 +2352,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,11 +2493,15 @@
         }
     }
 
-    @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
@@ -2515,7 +2511,8 @@
     }
 
     @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 +2522,7 @@
                     channel.dispose();
                     return;
                 }
-                IInputMethod curMethod = getCurMethodLocked();
+                IInputMethodInvoker curMethod = getCurMethodLocked();
                 if (curMethod != null && method != null
                         && curMethod.asBinder() == method.asBinder()) {
                     if (mCurClient != null) {
@@ -2579,22 +2576,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 +2988,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 +3068,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 +3121,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 +3162,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 +3203,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 +3214,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 +3235,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 +3334,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 +3585,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;
             }
@@ -3679,8 +3702,7 @@
             if (!calledFromValidUserLocked()) {
                 return;
             }
-            executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageO(
-                    MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
+            showInputMethodAndSubtypeEnabler(inputMethodId);
         }
     }
 
@@ -4104,7 +4126,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 +4177,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 +4220,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 +4229,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 +4260,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 +4333,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 +4342,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 +4354,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);
         }
     }
 
@@ -4741,14 +4616,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);
-    }
-
     // ----------------------------------------------------------------------
 
     /**
@@ -5221,7 +5088,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/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..e555c13 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -63,7 +63,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;
@@ -1498,13 +1497,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 +1734,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 +1966,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 +2074,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 +2273,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 +2687,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");
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/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 2aa0e01..69c475a 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.
@@ -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/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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 6a5d76b..80699ac 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2245,6 +2245,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) {
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..e00f4f5 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;
@@ -234,8 +233,6 @@
 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:
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..e03cf0a 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;
@@ -560,8 +560,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/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/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/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..9b3d6d6 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -113,6 +113,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;
@@ -1592,7 +1593,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 +1609,8 @@
                         + permName + " because it does not hold that permission");
             }
         }
-        mPermissionControllerManager.selfRevokePermissions(packageName, permissions);
+        mPermissionControllerManager.revokeOwnPermissionsOnKill(packageName, permissions,
+                callback);
     }
 
     private boolean mayManageRolePermission(int uid) {
@@ -3181,9 +3184,13 @@
                     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)) {
+                    // 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);
                     }
                 }
             }
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/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..1f21938 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
@@ -492,6 +492,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 +548,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) {
@@ -2371,6 +2376,11 @@
     }
 
     @Override
+    public boolean shouldInheritKeyStoreKeys() {
+        return getBoolean(Booleans.INHERIT_KEYSTORE_KEYS);
+    }
+
+    @Override
     public ParsingPackageImpl setBaseRevisionCode(int value) {
         baseRevisionCode = value;
         return this;
@@ -2514,6 +2524,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..bf7c55f 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
@@ -868,7 +868,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();
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..b7ca4de 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -121,7 +121,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;
@@ -231,7 +230,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;
@@ -2332,11 +2330,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 +2347,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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 86ef8d2..76434c7 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7364,12 +7364,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 +7929,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 +7948,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 1681348..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.Disabled;
 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
-    @Disabled
     static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;
 
     private static final String TAG = "ActivityRecordInputSink";
@@ -116,8 +114,7 @@
     private InputWindowHandle createInputWindowHandle() {
         InputWindowHandle inputWindowHandle = new InputWindowHandle(null,
                 mActivityRecord.getDisplayId());
-        inputWindowHandle.replaceTouchableRegionWithCrop(
-                mActivityRecord.getParentSurfaceControl());
+        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/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..e449dde 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -122,6 +122,7 @@
 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.OPENING_APPS;
 import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
 import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
@@ -169,6 +170,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;
@@ -838,7 +840,6 @@
             }
             w.mSurfacePlacementNeeded = true;
             w.mLayoutNeeded = false;
-            w.prelayout();
             final boolean firstLayout = !w.isLaidOut();
             getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames);
             w.mLayoutSeq = mLayoutSeq;
@@ -881,7 +882,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()
@@ -2001,6 +2001,7 @@
     }
 
     void configureDisplayPolicy() {
+        mRootWindowContainer.updateDisplayImePolicyCache();
         mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors();
         mDisplayRotation.configure(mBaseDisplayWidth, mBaseDisplayHeight);
     }
@@ -2718,6 +2719,7 @@
     void onDisplayChanged(DisplayContent dc) {
         super.onDisplayChanged(dc);
         updateSystemGestureExclusionLimit();
+        updateKeepClearAreas();
     }
 
     void updateSystemGestureExclusionLimit() {
@@ -3327,6 +3329,9 @@
             }
         }
         proto.write(IME_POLICY, getImePolicy());
+        for (Rect r : getKeepClearAreas()) {
+            r.dumpDebug(proto, KEEP_CLEAR_AREAS);
+        }
         proto.end(token);
     }
 
@@ -3386,6 +3391,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 +3625,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 +3952,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;
@@ -3987,11 +4003,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 +5494,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/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/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/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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 5a420ca..d031bec 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -165,6 +165,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 +987,7 @@
         forAllDisplays(dc -> {
             dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
             dc.updateSystemGestureExclusion();
+            dc.updateKeepClearAreas();
             dc.updateTouchExcludeRegion();
         });
 
@@ -2530,9 +2532,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();
@@ -3665,7 +3674,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..7acc0c5 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -73,6 +73,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;
@@ -490,6 +491,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);
@@ -863,4 +874,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/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..18df316 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -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;
@@ -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);
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/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4006848..1205dee 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;
@@ -3331,7 +3331,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..026b9e1 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);
         }
 
@@ -2438,23 +2444,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 */);
             }
@@ -3838,6 +3827,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 +4339,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 +4920,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 +4982,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 +5680,11 @@
     }
 
     @Override
+    public void saveWindowTraceToFile() {
+        mWindowTracing.saveForBugreport(null /* printwriter */);
+    }
+
+    @Override
     public boolean isWindowTraceEnabled() {
         return mWindowTracing.isEnabled();
     }
@@ -6908,6 +6909,7 @@
     void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) {
         synchronized (mGlobalLock) {
             mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays;
+            mRoot.updateDisplayImePolicyCache();
         }
     }
 
@@ -6963,23 +6965,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 +7051,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 +7324,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 +7680,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 +7718,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 +7821,7 @@
 
         @Override
         public @DisplayImePolicy int getDisplayImePolicy(int displayId) {
-            synchronized (mGlobalLock) {
-                return WindowManagerService.this.getDisplayImePolicy(displayId);
-            }
+            return WindowManagerService.this.getDisplayImePolicy(displayId);
         }
 
         @Override
@@ -8475,6 +8477,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 +8486,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);
@@ -8714,20 +8728,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 +8787,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..1f83767 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -172,6 +172,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 +197,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;
@@ -442,9 +444,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 +472,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};
@@ -1012,6 +1019,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 +1147,7 @@
             mSubLayer = 0;
             mWinAnimator = null;
             mWpcForDisplayAreaConfigChanges = null;
+            mOverrideScale = 1f;
             return;
         }
         mDeathRecipient = deathRecipient;
@@ -1138,6 +1195,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 +1225,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 +1250,7 @@
      * @see ActivityRecord#hasSizeCompatBounds()
      */
     boolean hasCompatScale() {
-        return mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord);
+        return hasCompatScale(mAttrs, mActivityRecord, mOverrideScale);
     }
 
     /**
@@ -1183,11 +1258,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;
     }
 
     /**
@@ -1691,21 +1771,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
@@ -2928,7 +2993,6 @@
         @Override
         public void binderDied() {
             try {
-                boolean resetSplitScreenResizing = false;
                 synchronized (mWmService.mGlobalLock) {
                     final WindowState win = mWmService
                             .windowForClientLocked(mSession, mClient, false);
@@ -2944,16 +3008,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.
             }
@@ -4063,6 +4117,9 @@
         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);
     }
 
@@ -4231,6 +4288,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 +5317,6 @@
             mLastVScale != newVScale ) {
             getPendingTransaction().setMatrix(getSurfaceControl(),
                 newHScale, 0, 0, newVScale);
-            mLastGlobalScale = mGlobalScale;
             mLastHScale = newHScale;
             mLastVScale = newVScale;
         }
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/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index df9ab50..f19202a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -145,6 +145,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 +241,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;
@@ -298,6 +310,8 @@
     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 +588,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 +849,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 +1215,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..9b87b9d 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;
@@ -177,4 +178,15 @@
             int drawableId, int drawableStyle, int 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..9a98235 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
@@ -20,6 +20,7 @@
 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.Strings.UPDATABLE_STRING_IDS;
 
 import static java.util.Objects.requireNonNull;
 
@@ -27,6 +28,7 @@
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyDrawableResource;
 import android.app.admin.DevicePolicyResources;
+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";
 
-
+    /**
+     * Map of <drawable_id, <style_id, resource_value>>
+     */
     private final Map<Integer, Map<Integer, ParcelableResource>>
             mUpdatedDrawablesForStyle = new HashMap<>();
 
+    /**
+     * Map of <drawable_id, <source_id, resource_value>>
+     */
     private final Map<Integer, Map<Integer, 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;
 
@@ -114,12 +128,10 @@
     private boolean updateDrawable(
             int drawableId, int 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)) {
@@ -139,12 +151,10 @@
     private boolean updateDrawableForSource(
             int drawableId, int 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)) {
@@ -183,19 +193,15 @@
     ParcelableResource getDrawable(
             int drawableId, int drawableStyle, int 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 +222,73 @@
         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(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();
@@ -362,6 +435,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(
@@ -401,6 +486,12 @@
                                 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..40196db 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,7 @@
 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.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 +189,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;
@@ -2234,7 +2249,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 +2257,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 +3176,7 @@
             case SystemService.PHASE_ACTIVITY_MANAGER_READY:
                 synchronized (getLockObject()) {
                     migrateToProfileOnOrganizationOwnedDeviceIfCompLocked();
-                    applyManagedProfileRestrictionIfDeviceOwnerLocked();
+                    applyProfileRestrictionsIfDeviceOwnerLocked();
                 }
                 maybeStartSecurityLogMonitorOnActivityManagerReady();
                 break;
@@ -3776,6 +3801,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 +6958,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 +7010,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 +7106,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 +7114,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);
     }
@@ -7305,12 +7350,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 +7367,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 +8518,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 +8996,7 @@
         mOwners.writeProfileOwner(userId);
         deleteTransferOwnershipBundleLocked(userId);
         toggleBackupServiceActive(userId, true);
-        applyManagedProfileRestrictionIfDeviceOwnerLocked();
+        applyProfileRestrictionsIfDeviceOwnerLocked();
         setNetworkLoggingActiveInternal(false);
     }
 
@@ -12395,8 +12451,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 +12462,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 +13064,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 +15608,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 +15628,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 +17132,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 +17145,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 +17173,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");
@@ -17850,7 +17968,12 @@
                 ? 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(() ->
@@ -17977,6 +18100,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(
@@ -18026,4 +18260,50 @@
             mContext.sendBroadcastAsUser(intent, user);
         }
     }
+
+    @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) {
+        final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
+        intent.putExtra(EXTRA_RESOURCE_ID, stringIds);
+        intent.putExtra(EXTRA_RESOURCE_TYPE_STRING, /* value= */ true);
+        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+
+        List<UserInfo> users = mUserManager.getAliveUsers();
+        for (int i = 0; i < users.size(); i++) {
+            UserHandle user = users.get(i).getUserHandle();
+            mContext.sendBroadcastAsUser(intent, user);
+        }
+    }
 }
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 715fe6e..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;
     }
 
@@ -701,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;
 
@@ -788,7 +864,15 @@
         if (device == null) {
             throw new IllegalArgumentException("no such device for " + deviceInfo);
         }
-        return device.getDeviceStatus();
+        int uid = Binder.getCallingUid();
+        if (device.isUidAllowed(uid)) {
+            return device.getDeviceStatus();
+        } else {
+            Log.e(TAG, "getDeviceStatus() invalid UID = " + uid);
+            EventLog.writeEvent(0x534e4554, "203549963",
+                    uid, "getDeviceStatus: invalid uid");
+            return null;
+        }
     }
 
     @Override
diff --git a/services/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/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/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..555f4b8 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)
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/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..72100e44 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -18,19 +18,24 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doNothing;
 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.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;
@@ -73,6 +78,12 @@
     private DisplayManagerInternal mDisplayManagerInternalMock;
     @Mock
     private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManagerMock;
+    @Mock
+    private InputManagerInternal mInputManagerInternalMock;
+    @Mock
+    private IVirtualDeviceActivityListener mActivityListener;
 
     @Before
     public void setUp() {
@@ -81,17 +92,32 @@
         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);
+
         mInputController = new InputController(new Object(), mNativeWrapperMock);
         mDeviceImpl = new VirtualDeviceImpl(mContext,
                 /* association info */ null, new Binder(), /* uid */ 0, mInputController,
-                (int associationId) -> {}, mPendingTrampolineCallback,
+                (int associationId) -> {}, mPendingTrampolineCallback, mActivityListener,
                 new VirtualDeviceParams.Builder().build());
     }
 
     @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 createVirtualKeyboard_noDisplay_failsSecurityException() {
         assertThrows(
                 SecurityException.class,
@@ -154,7 +180,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 +190,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 +200,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 +220,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 +245,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 +256,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 +286,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 +327,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 +338,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 +375,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 +394,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..842a438 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,17 @@
 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.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;
@@ -91,6 +95,7 @@
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.SystemUpdatePolicy;
+import android.app.admin.WifiSsidPolicy;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -1112,6 +1117,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 +1402,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 +4136,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);
@@ -7209,8 +7223,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 +7237,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 +7840,128 @@
                 () -> 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));
+    }
+
     private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
         final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage,
                 userVpnUid, List.of(new AppOpsManager.OpEntry(
@@ -7863,18 +7997,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..6eb2085 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -232,6 +232,8 @@
                 return mMockSystemServices.crossProfileApps;
             case Context.VPN_MANAGEMENT_SERVICE:
                 return mMockSystemServices.vpnManager;
+            case Context.DEVICE_POLICY_SERVICE:
+                return mMockSystemServices.devicePolicyManager;
         }
         throw new UnsupportedOperationException();
     }
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..597a165 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;
@@ -125,6 +126,7 @@
     public final AppOpsManager appOpsManager;
     public final UsbManager usbManager;
     public final VpnManager vpnManager;
+    public final DevicePolicyManager devicePolicyManager;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
     public final BuildMock buildMock = new BuildMock();
@@ -172,6 +174,7 @@
         appOpsManager = mock(AppOpsManager.class);
         usbManager = mock(UsbManager.class);
         vpnManager = mock(VpnManager.class);
+        devicePolicyManager = mock(DevicePolicyManager.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/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index c544f5c..81c9871 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -2046,7 +2046,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/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..7e5fe04 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -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..774e5b9 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;
@@ -571,7 +570,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 +615,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)
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..8d58ec0 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(),
@@ -1982,6 +1983,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 +2000,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 +2437,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/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
index 407f9cf..d64bf12 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -26,11 +26,14 @@
 
 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.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
 
 import android.app.ActivityThread;
 import android.content.Context;
@@ -43,6 +46,7 @@
 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;
@@ -130,15 +134,31 @@
     @Test
     public void testGetSettingsContextOnDualDisplayContent() {
         final Context context = mController.getSettingsContext(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/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/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 141588a..ae3ce04 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -75,7 +75,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
@@ -466,12 +468,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)));
     }
 
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/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/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/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
index 9d4db00..85b1de5 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -41,6 +41,7 @@
 
     private final boolean mIsInputHeadset;
     private final boolean mIsOutputHeadset;
+    private final boolean mIsDock;
 
     private boolean mSelected = false;
     private int mOutputState;
@@ -53,7 +54,7 @@
 
     public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress,
             boolean hasOutput, boolean hasInput,
-            boolean isInputHeadset, boolean isOutputHeadset) {
+            boolean isInputHeadset, boolean isOutputHeadset, boolean isDock) {
         mAudioService = audioService;
         mCardNum = card;
         mDeviceNum = device;
@@ -62,31 +63,32 @@
         mHasInput = hasInput;
         mIsInputHeadset = isInputHeadset;
         mIsOutputHeadset = isOutputHeadset;
+        mIsDock = isDock;
     }
 
     /**
-     * @returns the ALSA card number associated with this peripheral.
+     * @return the ALSA card number associated with this peripheral.
      */
     public int getCardNum() {
         return mCardNum;
     }
 
     /**
-     * @returns the ALSA device number associated with this peripheral.
+     * @return the ALSA device number associated with this peripheral.
      */
     public int getDeviceNum() {
         return mDeviceNum;
     }
 
     /**
-     * @returns the USB device device address associated with this peripheral.
+     * @return the USB device device address associated with this peripheral.
      */
     public String getDeviceAddress() {
         return mDeviceAddress;
     }
 
     /**
-     * @returns the ALSA card/device address string.
+     * @return the ALSA card/device address string.
      */
     public String getAlsaCardDeviceString() {
         if (mCardNum < 0 || mDeviceNum < 0) {
@@ -98,35 +100,42 @@
     }
 
     /**
-     * @returns true if the device supports output.
+     * @return true if the device supports output.
      */
     public boolean hasOutput() {
         return mHasOutput;
     }
 
     /**
-     * @returns true if the device supports input (recording).
+     * @return true if the device supports input (recording).
      */
     public boolean hasInput() {
         return mHasInput;
     }
 
     /**
-     * @returns true if the device is a headset for purposes of input.
+     * @return true if the device is a headset for purposes of input.
      */
     public boolean isInputHeadset() {
         return mIsInputHeadset;
     }
 
     /**
-     * @returns true if the device is a headset for purposes of output.
+     * @return true if the device is a headset for purposes of output.
      */
     public boolean isOutputHeadset() {
         return mIsOutputHeadset;
     }
 
     /**
-     * @returns true if input jack is detected or jack detection is not supported.
+     * @return true if the device is a USB dock.
+     */
+    public boolean isDock() {
+        return mIsDock;
+    }
+
+    /**
+     * @return true if input jack is detected or jack detection is not supported.
      */
     private synchronized boolean isInputJackConnected() {
         if (mJackDetector == null) {
@@ -136,7 +145,7 @@
     }
 
     /**
-     * @returns true if input jack is detected or jack detection is not supported.
+     * @return true if input jack is detected or jack detection is not supported.
      */
     private synchronized boolean isOutputJackConnected() {
         if (mJackDetector == null) {
@@ -190,9 +199,10 @@
         try {
             // Output Device
             if (mHasOutput) {
-                int device = mIsOutputHeadset
-                        ? AudioSystem.DEVICE_OUT_USB_HEADSET
-                        : AudioSystem.DEVICE_OUT_USB_DEVICE;
+                int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
+                        : (mIsOutputHeadset
+                            ? AudioSystem.DEVICE_OUT_USB_HEADSET
+                            : AudioSystem.DEVICE_OUT_USB_DEVICE);
                 if (DEBUG) {
                     Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device)
                             + " addr:" + alsaCardDeviceString
@@ -231,7 +241,7 @@
 
     /**
      * @Override
-     * @returns a string representation of the object.
+     * @return a string representation of the object.
      */
     public synchronized String toString() {
         return "UsbAlsaDevice: [card: " + mCardNum
@@ -273,7 +283,7 @@
 
     /**
      * @Override
-     * @returns true if the objects are equivalent.
+     * @return true if the objects are equivalent.
      */
     public boolean equals(Object obj) {
         if (!(obj instanceof UsbAlsaDevice)) {
@@ -285,12 +295,13 @@
                 && mHasOutput == other.mHasOutput
                 && mHasInput == other.mHasInput
                 && mIsInputHeadset == other.mIsInputHeadset
-                && mIsOutputHeadset == other.mIsOutputHeadset);
+                && mIsOutputHeadset == other.mIsOutputHeadset
+                && mIsDock == other.mIsDock);
     }
 
     /**
      * @Override
-     * @returns a hash code generated from the object contents.
+     * @return a hash code generated from the object contents.
      */
     public int hashCode() {
         final int prime = 31;
@@ -301,6 +312,7 @@
         result = prime * result + (mHasInput ? 0 : 1);
         result = prime * result + (mIsInputHeadset ? 0 : 1);
         result = prime * result + (mIsOutputHeadset ? 0 : 1);
+        result = prime * result + (mIsDock ? 0 : 1);
 
         return result;
     }
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 1c72eb8..fd9b995 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -237,6 +237,7 @@
         if (hasInput || hasOutput) {
             boolean isInputHeadset = parser.isInputHeadset();
             boolean isOutputHeadset = parser.isOutputHeadset();
+            boolean isDock = parser.isDock();
 
             if (mAudioService == null) {
                 Slog.e(TAG, "no AudioService");
@@ -246,7 +247,7 @@
             UsbAlsaDevice alsaDevice =
                     new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
                                       deviceAddress, hasOutput, hasInput,
-                                      isInputHeadset, isOutputHeadset);
+                                      isInputHeadset, isOutputHeadset, isDock);
             if (alsaDevice != null) {
                 alsaDevice.setDeviceNameAndDescription(
                           cardRec.getCardName(), cardRec.getCardDescription());
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 9ac270f..94cc826 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -165,7 +165,7 @@
                 pw.println("manfacturer:0x" + Integer.toHexString(deviceDescriptor.getVendorID())
                         + " product:" + Integer.toHexString(deviceDescriptor.getProductID()));
                 pw.println("isHeadset[in: " + parser.isInputHeadset()
-                        + " , out: " + parser.isOutputHeadset() + "]");
+                        + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock());
             } else {
                 pw.println(formatTime() + " Disconnect " + mDeviceAddress);
             }
@@ -179,9 +179,8 @@
                 UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
                 descriptorTree.parse(parser);
                 descriptorTree.report(new TextReportCanvas(parser, stringBuilder));
-
                 stringBuilder.append("isHeadset[in: " + parser.isInputHeadset()
-                        + " , out: " + parser.isOutputHeadset() + "]");
+                        + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock());
                 pw.println(stringBuilder.toString());
             } else {
                 pw.println(formatTime() + " Disconnect " + mDeviceAddress);
@@ -198,9 +197,8 @@
                     descriptor.report(canvas);
                 }
                 pw.println(stringBuilder.toString());
-
                 pw.println("isHeadset[in: " + parser.isInputHeadset()
-                        + " , out: " + parser.isOutputHeadset() + "]");
+                        + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock());
             } else {
                 pw.println(formatTime() + " Disconnect " + mDeviceAddress);
             }
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/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 3412a6f..6e68a91 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -870,4 +870,35 @@
         return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER;
     }
 
+    /**
+     * isDock() indicates if the connected USB output peripheral is a docking station with
+     * audio output.
+     * A valid audio dock must declare only one audio output control terminal of type
+     * TERMINAL_EXTERN_DIGITAL.
+     */
+    public boolean isDock() {
+        if (hasMIDIInterface() || hasHIDInterface()) {
+            return false;
+        }
+
+        ArrayList<UsbDescriptor> acDescriptors =
+                getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
+                        UsbACInterface.AUDIO_AUDIOCONTROL);
+
+        if (acDescriptors.size() != 1) {
+            return false;
+        }
+
+        if (acDescriptors.get(0) instanceof UsbACTerminal) {
+            UsbACTerminal outDescr = (UsbACTerminal) acDescriptors.get(0);
+            if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_DIGITAL) {
+                return true;
+            }
+        } else {
+            Log.w(TAG, "Undefined Audio Output terminal l: " + acDescriptors.get(0).getLength()
+                    + " t:0x" + Integer.toHexString(acDescriptors.get(0).getType()));
+        }
+        return false;
+    }
+
 }
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/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/telecomm/java/android/telecom/CallEndpoint.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to telecomm/java/android/telecom/CallEndpoint.aidl
index 2b3e961..5030ffd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/telecomm/java/android/telecom/CallEndpoint.aidl
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package android.telecom;
 
-import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
-
-// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
-interface ISmartspaceTransitionController {
-    oneway void setSmartspace(ISmartspaceCallback callback);
-}
\ No newline at end of file
+/**
+ * {@hide}
+  */
+parcelable CallEndpoint;
diff --git a/telecomm/java/android/telecom/CallEndpoint.java b/telecomm/java/android/telecom/CallEndpoint.java
new file mode 100644
index 0000000..dc70656
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpoint.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Represents the endpoint on which a call can be carried by the user.
+ *
+ * For example, the user may be able to carry out a call on another device on their local network
+ * using a call streaming solution, or may be able to carry out a call on another device registered
+ * with the same mobile line of service.
+ */
+public final class CallEndpoint implements Parcelable {
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"ENDPOINT_TYPE_"},
+            value = {ENDPOINT_TYPE_TETHERED, ENDPOINT_TYPE_UNTETHERED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EndpointType {}
+
+    /** Indicates the endpoint contains a complete calling stack and is capable of carrying out a
+     *  call on its own. Untethered endpoints are typically other devices which share the same
+     *  mobile line of service as the current device.
+     */
+    public static final int ENDPOINT_TYPE_UNTETHERED = 1;
+
+    /** Indicates the endpoint itself doesn't have the required calling infrastructure in order to
+     *  complete a call on its own. Tethered endpoints depend on a call streaming solution to
+     *  transport the media and control for a call to another device, while depending on the current
+     *  device to connect the call to the mobile network.
+     */
+    public static final int ENDPOINT_TYPE_TETHERED = 2;
+
+    private final ParcelUuid mUuid;
+    private CharSequence mDescription;
+    private final int mType;
+    private final ComponentName mComponentName;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mUuid.writeToParcel(dest, flags);
+        dest.writeCharSequence(mDescription);
+        dest.writeInt(mType);
+        mComponentName.writeToParcel(dest, flags);
+    }
+
+    public static final @android.annotation.NonNull Creator<CallEndpoint> CREATOR =
+            new Creator<CallEndpoint>() {
+                @Override
+                public CallEndpoint createFromParcel(Parcel in) {
+                    return new CallEndpoint(in);
+                }
+
+                @Override
+                public CallEndpoint[] newArray(int size) {
+                    return new CallEndpoint[size];
+                }
+            };
+
+    public CallEndpoint(@NonNull ParcelUuid uuid, @NonNull CharSequence description, int type,
+            @NonNull ComponentName componentName) {
+        mUuid = uuid;
+        mDescription = description;
+        mType = type;
+        mComponentName = componentName;
+    }
+
+    private CallEndpoint(@NonNull Parcel in) {
+        this(ParcelUuid.CREATOR.createFromParcel(in), in.readCharSequence(), in.readInt(),
+                ComponentName.CREATOR.createFromParcel(in));
+    }
+
+    /**
+     * A unique identifier for this call endpoint. An endpoint provider should take care to use an
+     * identifier which is stable for the current association between an endpoint and the current
+     * device, but which is not globally identifying.
+     * @return the unique identifier.
+     */
+    public @NonNull ParcelUuid getIdentifier() {
+        return mUuid;
+    }
+
+    /**
+     * A human-readable description of this {@link CallEndpoint}. An {@link InCallService} uses
+     * when informing the user of the endpoint.
+     * @return the description.
+     */
+    public @NonNull CharSequence getDescription() {
+        return mDescription;
+    }
+
+    public @EndpointType int getType() {
+        return mType;
+    }
+
+    /**
+     * @hide
+     */
+    public @NonNull ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof CallEndpoint) {
+            CallEndpoint d = (CallEndpoint) o;
+            return Objects.equals(mUuid, d.mUuid)
+                    && Objects.equals(mDescription, d.mDescription)
+                    && Objects.equals(mType, d.mType)
+                    && Objects.equals(mComponentName, d.mComponentName);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mUuid, mDescription, mType, mComponentName);
+    }
+}
diff --git a/telecomm/java/android/telecom/CallEndpointCallback.java b/telecomm/java/android/telecom/CallEndpointCallback.java
new file mode 100644
index 0000000..6ba55f1
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * Provides callbacks from telecom to the cross device call streaming app with lifecycle events
+ * related to an {@link CallEndpointSession}.
+ */
+public interface CallEndpointCallback {
+    /**
+     * Invoked by telecom when a {@link CallEndpointSession} is started but the streaming app has
+     * not activated the endpoint in a timely manner and the framework deems the activation request
+     * to have timed out.
+     */
+    void onCallEndpointSessionActivationTimeout();
+
+    /**
+     * Invoked by telecom when {@link CallEndpointSession#setCallEndpointSessionDeactivated()}
+     * called by a cross device call streaming app, or when the app uninstalled. When a tethered
+     * {@link CallEndpoint} is deactivated, the call streaming app should clean up any
+     * audio/network resources and stop relaying call controls from the endpoint.
+     */
+    void onCallEndpointSessionDeactivated();
+}
diff --git a/telecomm/java/android/telecom/CallEndpointSession.java b/telecomm/java/android/telecom/CallEndpointSession.java
new file mode 100644
index 0000000..1e7b30c
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointSession.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.IntDef;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import com.android.internal.telecom.ICallEndpointSession;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * Provides method and necessary information for cross device call streaming app to streams calls
+ * and updates to the status of the endpoint.
+ *
+ */
+public class CallEndpointSession {
+    /**
+     * Indicates that this call endpoint session is activated by
+     * {@link Call#answerCall(CallEndpoint, int)} from the original device.
+     */
+    public static final int ANSWER_REQUEST = 1;
+
+    /**
+     * Indicates that this call endpoint session is activated by {@link Call#pushCall(CallEndpoint)}
+     * from the original device.
+     */
+    public static final int PUSH_REQUEST = 2;
+
+    /**
+     * Indicates that this call endpoint session is activated by
+     * {@link TelecomManager#placeCall(Uri, Bundle)} with extra
+     * {@link TelecomManager#EXTRA_START_CALL_ON_ENDPOINT} set.
+     */
+    public static final int PLACE_REQUEST = 3;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"ACTIVATION_FAILURE_"},
+            value = {ACTIVATION_FAILURE_REJECTED, ACTIVATION_FAILURE_UNAVAILABLE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ActivationFailureReason {}
+    /**
+     * Used as reason for {@link #setCallEndpointSessionActivationFailed(int)} to inform the
+     * endpoint is no longer present on the network.
+     */
+    public static final int ACTIVATION_FAILURE_UNAVAILABLE = 0;
+
+    /**
+     * Used as reason for {@link #setCallEndpointSessionActivationFailed(int)} to inform the
+     * remote endpoint rejected the request to start streaming a cross device call.
+     */
+    public static final int ACTIVATION_FAILURE_REJECTED = 1;
+
+    private final ICallEndpointSession mCallEndpointSession;
+
+    /**
+     * {@hide}
+     */
+    public CallEndpointSession(ICallEndpointSession callEndpointSession) {
+        mCallEndpointSession = callEndpointSession;
+    }
+
+    /**
+     * Invoked by cross device call streaming app to inform telecom stack that the call endpoint is
+     * now activated and that the call is being streamed to the endpoint.
+     */
+    public void setCallEndpointSessionActivated() {
+        try {
+            mCallEndpointSession.setCallEndpointSessionActivated();
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Invoked by cross device call streaming app to inform telecom stack that the call endpoint
+     * could not be activated due to error.
+     * Possible errors are:
+     * <ul>
+     *     <li>{@link #ACTIVATION_FAILURE_UNAVAILABLE}</li>
+     *     <li>{@link #ACTIVATION_FAILURE_REJECTED}</li>
+     * </ul>
+     *
+     * @param reason The reason for activation failure
+     */
+    public void setCallEndpointSessionActivationFailed(@ActivationFailureReason int reason) {
+        try {
+            mCallEndpointSession.setCallEndpointSessionActivationFailed(reason);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Invoked by cross device call streaming app to inform telecom stack that the call endpoint is
+     * no longer active.
+     */
+    public void setCallEndpointSessionDeactivated() {
+        try {
+            mCallEndpointSession.setCallEndpointSessionDeactivated();
+        } catch (RemoteException e) {
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 30d4959..21a1804 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -561,15 +561,6 @@
      */
     public static final int PROPERTY_CROSS_SIM = 1 << 13;
 
-    /**
-     * Connection is a tethered external call.
-     * <p>
-     * Indicates that the {@link Connection} is fixed on this device but the audio streams are
-     * re-routed to another device.
-     * <p>
-     */
-    public static final int PROPERTY_TETHERED_CALL = 1 << 14;
-
     //**********************************************************************************************
     // Next PROPERTY value: 1<<14
     //**********************************************************************************************
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 0f034ad..63b9548 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -111,6 +111,22 @@
      */
     public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED";
 
+    /**
+     * This reason is set when an call is ended due to {@link CallEndpoint} rejection.
+     * This reason string should only be associated with the {@link #LOCAL} disconnect code returned
+     * from {@link #getCode()}.
+     */
+    public static final String REASON_ENDPOINT_REJECTED = "REASON_ENDPOINT_REJECTED";
+
+    /**
+     * This reason is set when a call is ended due to {@link CallEndpoint} deactivated by
+     * call disconnection or user terminated streaming.
+     * This reason string should only be associated with the {@link #LOCAL} disconnect code returned
+     * from {@link #getCode()}
+     */
+    public static final String REASON_ENDPOINT_SESSION_DEACTIVATED =
+            "REASON_ENDPOINT_SESSION_DEACTIVATED";
+
     private int mDisconnectCode;
     private CharSequence mDisconnectLabel;
     private CharSequence mDisconnectDescription;
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index ab35aff..34e9942 100755
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -373,6 +373,34 @@
     }
 
     /**
+     * Instructs Telecom to push a call to the given endpoint.
+     *
+     * @param callId The callId to push.
+     * @param callEndpoint The endpoint to which the call will be pushed.
+     */
+    public void pushCall(String callId, CallEndpoint callEndpoint) {
+        try {
+            mAdapter.pushCall(callId, callEndpoint);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to answer a call via the given endpoint.
+     *
+     * @param callId The callId to push.
+     * @param callEndpoint The endpoint on which the call will be answered.
+     * @param videoState The video state in which to answer the call.
+     */
+    public void answerCall(String callId, CallEndpoint callEndpoint,
+            @VideoProfile.VideoState int videoState) {
+        try {
+            mAdapter.answerCallViaEndpoint(callId, callEndpoint, videoState);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
      * Intructs Telecom to send a call event.
      *
      * @param callId The callId to send the event for.
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 0ddd52d..ecd6596 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -30,9 +30,12 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.view.Surface;
 
 import com.android.internal.os.SomeArgs;
+import com.android.internal.telecom.ICallEndpointCallback;
+import com.android.internal.telecom.ICallEndpointSession;
 import com.android.internal.telecom.IInCallAdapter;
 import com.android.internal.telecom.IInCallService;
 
@@ -258,6 +261,10 @@
     private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
     private static final int MSG_ON_HANDOVER_FAILED = 12;
     private static final int MSG_ON_HANDOVER_COMPLETE = 13;
+    private static final int MSG_ON_PUSH_FAILED = 14;
+    private static final int MSG_ON_PULL_FAILED = 15;
+    private static final int MSG_ON_ANSWER_EXTERNAL_FAILED = 16;
+    private static final int MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST = 17;
 
     /** Default Handler used to consolidate binder method calls onto a single thread. */
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -339,6 +346,66 @@
                     mPhone.internalOnHandoverComplete(callId);
                     break;
                 }
+                case MSG_ON_PUSH_FAILED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId = (String) args.arg1;
+                        CallEndpoint callEndpoint = (CallEndpoint) args.arg2;
+                        int reason = (int) args.arg3;
+                        mPhone.internalOnCallPushFailed(callId, callEndpoint, reason);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_ON_PULL_FAILED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId = (String) args.arg1;
+                        int reason = (int) args.arg2;
+                        mPhone.internalOnCallPullFailed(callId, reason);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_ON_ANSWER_EXTERNAL_FAILED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId = (String) args.arg1;
+                        CallEndpoint callEndpoint = (CallEndpoint) args.arg2;
+                        int reason = (int) args.arg3;
+                        mPhone.internalOnAnswerFailed(callId, callEndpoint, reason);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        CallEndpoint callEndpoint = (CallEndpoint) args.arg1;
+                        ICallEndpointSession iCallEndpointSession =
+                                (ICallEndpointSession) args.arg2;
+                        try {
+                            mCallEndpointCallback = onCallEndpointActivationRequested(callEndpoint,
+                                    new CallEndpointSession(iCallEndpointSession));
+                        } catch (UnsupportedOperationException e) {
+                            // This InCallService neglected to implement
+                            // onCallEndpointActivationRequested, immediately signal back to Telecom
+                            // that the activation failed.
+                            try {
+                                iCallEndpointSession.setCallEndpointSessionActivationFailed(
+                                        CallEndpointSession.ACTIVATION_FAILURE_UNAVAILABLE);
+                            } catch (RemoteException re) {
+                                // Ignore
+                            }
+                        }
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
                 default:
                     break;
             }
@@ -353,6 +420,36 @@
         }
 
         @Override
+        public ICallEndpointCallback requestCallEndpointActivation(CallEndpoint callEndpoint,
+                ICallEndpointSession callEndpointSession) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callEndpoint;
+            args.arg2 = callEndpointSession;
+            mHandler.obtainMessage(MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST, args).sendToTarget();
+
+            return new ICallEndpointCallback.Stub() {
+                @Override
+                public void onCallEndpointSessionActivationTimeout() throws RemoteException {
+                    if (mCallEndpointCallback != null) {
+                        mCallEndpointCallback.onCallEndpointSessionActivationTimeout();
+                    }
+                }
+
+                @Override
+                public void onCallEndpointSessionDeactivated() throws RemoteException {
+                    if (mCallEndpointCallback != null) {
+                        mCallEndpointCallback.onCallEndpointSessionDeactivated();
+                    }
+                }
+
+                @Override
+                public IBinder asBinder() {
+                    return this;
+                }
+            };
+        }
+
+        @Override
         public void addCall(ParcelableCall call) {
             mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();
         }
@@ -424,6 +521,32 @@
         public void onHandoverComplete(String callId) {
             mHandler.obtainMessage(MSG_ON_HANDOVER_COMPLETE, callId).sendToTarget();
         }
+
+        @Override
+        public void onCallPullFailed(String callId, int reason) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = reason;
+            mHandler.obtainMessage(MSG_ON_PULL_FAILED, args).sendToTarget();
+        }
+
+        @Override
+        public void onCallPushFailed(String callId, CallEndpoint endpoint, int reason) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = endpoint;
+            args.arg3 = reason;
+            mHandler.obtainMessage(MSG_ON_PUSH_FAILED, args).sendToTarget();
+        }
+
+        @Override
+        public void onAnswerFailed(String callId, CallEndpoint endpoint, int reason) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = endpoint;
+            args.arg3 = reason;
+            mHandler.obtainMessage(MSG_ON_ANSWER_EXTERNAL_FAILED, args).sendToTarget();
+        }
     }
 
     private Phone.Listener mPhoneListener = new Phone.Listener() {
@@ -470,6 +593,8 @@
     };
 
     private Phone mPhone;
+    private CallEndpointSession mCallEndpointSession;
+    private CallEndpointCallback mCallEndpointCallback;
 
     public InCallService() {
     }
@@ -494,6 +619,14 @@
             onPhoneDestroyed(oldPhone);
         }
 
+        if (mCallEndpointCallback != null) {
+            mCallEndpointCallback = null;
+        }
+
+        if (mCallEndpointSession != null) {
+            mCallEndpointSession = null;
+        }
+
         return false;
     }
 
@@ -704,6 +837,21 @@
     }
 
     /**
+     * To handle the request from telecom to activate an endpoint session. Streaming app with
+     * meta-data {@link TelecomManager#METADATA_STREAMING_TETHERED_CALLS}.
+     * @param endpoint The endpoint which is to be activated.
+     * @param session An instance of {@link CallEndpointSession} to let streaming app report updates
+     *                of the endpoint.
+     * @return CallEndpointCallback The implementation provided by streaming app. Telecom use this
+     *                              to report events related to the call endpoint session.
+     */
+    public @NonNull CallEndpointCallback onCallEndpointActivationRequested(
+            @NonNull CallEndpoint endpoint, @NonNull CallEndpointSession session)
+            throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Used to issue commands to the {@link Connection.VideoProvider} associated with a
      * {@link Call}.
      */
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index f412a18..c429183 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
@@ -29,8 +30,11 @@
 import com.android.internal.telecom.IVideoProvider;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Information about a call that is used between InCallService and Telecom.
@@ -69,6 +73,8 @@
         private int mCallerNumberVerificationStatus;
         private String mContactDisplayName;
         private String mActiveChildCallId;
+        private CallEndpoint mActiveCallEndpoint;
+        private Set<CallEndpoint> mAvailableCallEndpoints = new HashSet<>();
 
         public ParcelableCallBuilder setId(String id) {
             mId = id;
@@ -224,6 +230,27 @@
             return this;
         }
 
+        /**
+         * Set active call endpoint
+         * @param callEndpoint
+         * @return
+         */
+        public ParcelableCallBuilder setActiveCallEndpoint(CallEndpoint callEndpoint) {
+            mActiveCallEndpoint = callEndpoint;
+            return this;
+        }
+
+        /**
+         * Set available call endpoints
+         * @param availableCallEndpoints
+         * @return
+         */
+        public ParcelableCallBuilder setAvailableCallEndpoints(
+                Set<CallEndpoint> availableCallEndpoints) {
+            mAvailableCallEndpoints = availableCallEndpoints;
+            return this;
+        }
+
         public ParcelableCall createParcelableCall() {
             return new ParcelableCall(
                     mId,
@@ -255,7 +282,9 @@
                     mCallDirection,
                     mCallerNumberVerificationStatus,
                     mContactDisplayName,
-                    mActiveChildCallId);
+                    mActiveChildCallId,
+                    mActiveCallEndpoint,
+                    mAvailableCallEndpoints);
         }
 
         public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
@@ -292,6 +321,8 @@
                     parcelableCall.mCallerNumberVerificationStatus;
             newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
             newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
+            newBuilder.mActiveCallEndpoint = parcelableCall.mActiveCallEndpoint;
+            newBuilder.mAvailableCallEndpoints = parcelableCall.mAvailableCallEndpoints;
             return newBuilder;
         }
     }
@@ -327,6 +358,8 @@
     private final int mCallerNumberVerificationStatus;
     private final String mContactDisplayName;
     private final String mActiveChildCallId; // Only valid for CDMA conferences
+    private final CallEndpoint mActiveCallEndpoint;
+    private final Set<CallEndpoint> mAvailableCallEndpoints;
 
     public ParcelableCall(
             String id,
@@ -358,7 +391,9 @@
             int callDirection,
             int callerNumberVerificationStatus,
             String contactDisplayName,
-            String activeChildCallId
+            String activeChildCallId,
+            CallEndpoint activeCallEndpoint,
+            Set<CallEndpoint> availableCallEndpoints
     ) {
         mId = id;
         mState = state;
@@ -390,6 +425,8 @@
         mCallerNumberVerificationStatus = callerNumberVerificationStatus;
         mContactDisplayName = contactDisplayName;
         mActiveChildCallId = activeChildCallId;
+        mActiveCallEndpoint = activeCallEndpoint;
+        mAvailableCallEndpoints = availableCallEndpoints;
     }
 
     /** The unique ID of the call. */
@@ -614,6 +651,21 @@
         return mActiveChildCallId;
     }
 
+    /**
+     * @return The {@link CallEndpoint} which is currently active for this call, or null if the call
+     * does not take place via an {@link CallEndpoint}.
+     */
+    public @Nullable CallEndpoint getActiveCallEndpoint() {
+        return mActiveCallEndpoint;
+    }
+
+    /**
+     * @return A set of available {@link CallEndpoint}
+     */
+    public @NonNull Set<CallEndpoint> getAvailableCallEndpoints() {
+        return mAvailableCallEndpoints;
+    }
+
     /** Responsible for creating ParcelableCall objects for deserialized Parcels. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCall> CREATOR =
@@ -655,6 +707,9 @@
             int callerNumberVerificationStatus = source.readInt();
             String contactDisplayName = source.readString();
             String activeChildCallId = source.readString();
+            CallEndpoint activeCallEndpoint = source.readParcelable(classLoader);
+            List<CallEndpoint> availablableCallEndpoints = new ArrayList<>();
+            source.readList(availablableCallEndpoints, classLoader);
             return new ParcelableCallBuilder()
                     .setId(id)
                     .setState(state)
@@ -686,6 +741,8 @@
                     .setCallerNumberVerificationStatus(callerNumberVerificationStatus)
                     .setContactDisplayName(contactDisplayName)
                     .setActiveChildCallId(activeChildCallId)
+                    .setActiveCallEndpoint(activeCallEndpoint)
+                    .setAvailableCallEndpoints(new HashSet<>(availablableCallEndpoints))
                     .createParcelableCall();
         }
 
@@ -735,6 +792,8 @@
         destination.writeInt(mCallerNumberVerificationStatus);
         destination.writeString(mContactDisplayName);
         destination.writeString(mActiveChildCallId);
+        destination.writeParcelable(mActiveCallEndpoint, 0);
+        destination.writeList(Arrays.asList(mAvailableCallEndpoints.toArray()));
     }
 
     @Override
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index bc0a146..ac91a92 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -292,6 +292,29 @@
         }
     }
 
+    void internalOnCallPullFailed(String callId, @Call.Callback.PullFailedReason int reason) {
+        Call call = getCallById(callId);
+        if (call != null) {
+            call.internalOnCallPullFailed(reason);
+        }
+    }
+
+    void internalOnAnswerFailed(String callId, CallEndpoint callEndpoint,
+            @Call.Callback.AnswerFailedReason int reason) {
+        Call call = getCallById(callId);
+        if (call != null) {
+            call.internalOnAnswerFailed(callEndpoint, reason);
+        }
+    }
+
+    void internalOnCallPushFailed(String callId, CallEndpoint callEndpoint,
+            @Call.Callback.PushFailedReason int reason) {
+        Call call = getCallById(callId);
+        if (call != null) {
+            call.internalOnCallPushFailed(callEndpoint, reason);
+        }
+    }
+
     /**
      * Called to destroy the phone and cleanup any lingering calls.
      */
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 6279bf8..e560f34 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -54,8 +54,10 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -586,6 +588,14 @@
             "android.telecom.extra.START_CALL_WITH_RTT";
 
     /**
+     * A parcelable extra, which when set on the bundle passed into {@link #placeCall(Uri, Bundle)},
+     * indicates that the call should be initiated with an active {@link CallEndpoint} to stream
+     * the call as a tethered call.
+     */
+    public static final String EXTRA_START_CALL_ON_ENDPOINT =
+            "android.telecom.extra.START_CALL_ON_ENDPOINT";
+
+    /**
      * Start an activity indicating that the completion of an outgoing call or an incoming call
      * which was not blocked by the {@link CallScreeningService}, and which was NOT terminated
      * while the call was in {@link Call#STATE_AUDIO_PROCESSING}.
@@ -745,6 +755,23 @@
             "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
 
     /**
+     * A boolean meta-data value indicating this {@link InCallService} implementation is aimed at
+     * working as a streaming app for a tethered call. When there's a tethered call
+     * requesting to a {@link CallEndpoint} registered with this app, Telecom will bind to this
+     * streaming app and let the app streaming the call to the requested endpoint.
+     * <p>
+     * This meta-data can only be set for an {@link InCallService} which doesn't set neither
+     * {@link #METADATA_IN_CALL_SERVICE_UI} nor {@link #METADATA_IN_CALL_SERVICE_CAR_MODE_UI}.
+     * Otherwise, the app will be treated as a phone/dialer app or a car-mode app.
+     * <p>
+     * The {@link InCallService} declared this meta-data must implement
+     * {@link InCallService#onCallEndpointActivationRequested(CallEndpoint, CallEndpointSession)}.
+     * See this method for more information.
+     */
+    public static final String METADATA_STREAMING_TETHERED_CALLS =
+            "android.telecom.STREAMING_TETHERED_CALLS";
+
+    /**
      * The dual tone multi-frequency signaling character sent to indicate the dialing system should
      * pause for a predefined period.
      */
@@ -1294,14 +1321,24 @@
      * {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.
      * <p>
      * Requires permission {@link android.Manifest.permission#READ_PHONE_STATE}, or that the caller
-     * is the default dialer app.
+     * is the default dialer app to get all phone account handles.
+     * <P>
+     * If the caller doesn't meet any of the above requirements and has {@link
+     * android.Manifest.permission#MANAGE_OWN_CALLS}, the caller can get only the phone account
+     * handles they have registered.
      * <p>
-     * A {@link SecurityException} will be thrown if a called is not the default dialer, or lacks
-     * the {@link android.Manifest.permission#READ_PHONE_STATE} permission.
+     * A {@link SecurityException} will be thrown if the caller is not the default dialer
+     * or the caller does not have at least one of the following permissions:
+     * {@link android.Manifest.permission#READ_PHONE_STATE} permission,
+     * {@link android.Manifest.permission#MANAGE_OWN_CALLS} permission
      *
      * @return A list of {@code PhoneAccountHandle} objects.
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(anyOf = {
+            READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.MANAGE_OWN_CALLS
+    })
     public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
         ITelecomService service = getTelecomService();
         if (service != null) {
@@ -2250,6 +2287,7 @@
      *   <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li>
      *   <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li>
      *   <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li>
+     *   <li>{@link #EXTRA_START_CALL_ON_ENDPOINT}</li>
      * </ul>
      * <p>
      * An app which implements the self-managed {@link ConnectionService} API uses
@@ -2579,6 +2617,79 @@
         }
     }
 
+    /**
+     * Register a set of {@link CallEndpoint} to telecom. All registered {@link CallEndpoint} can
+     * be provided as options for push, place or answer call externally.
+     *
+     * @param endpoints Endpoints to be registered.
+     */
+    // TODO: add permission requirements
+    // @RequiresPermission{}
+    public void registerCallEndpoints(@NonNull Set<CallEndpoint> endpoints) {
+        ITelecomService service = getTelecomService();
+        List<CallEndpoint> endpointList = new ArrayList<>(endpoints);
+        if (service != null) {
+            try {
+                service.registerCallEndpoints(endpointList, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException registerCallEndpoints: " + e);
+                e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException("Telecom service is null.");
+        }
+    }
+
+    /**
+     * Unregister all {@link CallEndpoint} from telecom in the set provided. After un-registration,
+     * telecom will stop tracking and maintaining these {@link CallEndpoint}, user can no longer
+     * carry a call on them.
+     *
+     * @param endpoints
+     */
+    // TODO: add permission requirements
+    // @RequiresPermission{}
+    public void unregisterCallEndpoints(@NonNull Set<CallEndpoint> endpoints) {
+        ITelecomService service = getTelecomService();
+        List<CallEndpoint> endpointList = new ArrayList<>(endpoints);
+        if (service != null) {
+            try {
+                service.unregisterCallEndpoints(endpointList, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException unregisterCallEndpoints: " + e);
+                e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException("Telecom service is null.");
+        }
+    }
+
+    /**
+     * Return a set all registered {@link CallEndpoint} that can be used to stream and carry an
+     * external call.
+     *
+     * @return A set of all available {@link CallEndpoint}.
+     */
+    // TODO: add permission requirements
+    // @RequiresPermission{}
+    public @NonNull Set<CallEndpoint> getCallEndpoints() {
+        Set<CallEndpoint> endpoints = new HashSet<>();
+        List<CallEndpoint> endpointList;
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                endpointList = service.getCallEndpoints(mContext.getOpPackageName());
+                return new HashSet<>(endpointList);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException registerCallEndpoints: " + e);
+                e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException("Telecom service is null.");
+        }
+        return endpoints;
+    }
+
     private boolean isSystemProcess() {
         return Process.myUid() == Process.SYSTEM_UID;
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl
index 2b3e961..dc1cc0f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl
@@ -14,11 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package com.android.internal.telecom;
 
-import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
+/**
+ * Internal remote CallEndpointCallback interface for Telecom framework to report event related to
+ * the endpoint session.
+ *
+ * {@hide}
+ */
+oneway interface ICallEndpointCallback {
+    void onCallEndpointSessionActivationTimeout();
 
-// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
-interface ISmartspaceTransitionController {
-    oneway void setSmartspace(ISmartspaceCallback callback);
+    void onCallEndpointSessionDeactivated();
 }
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl b/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl
new file mode 100644
index 0000000..1c1c29a
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telecom;
+
+/**
+ * Internal remote CallEndpointSession interface for streaming app to update the status of the
+ * endpoint.
+ *
+ * @see android.telecom.CallEndpointSession
+ *
+ * {@hide}
+ */
+
+oneway interface ICallEndpointSession {
+    void setCallEndpointSessionActivated();
+
+    void setCallEndpointSessionActivationFailed(int reason);
+
+    void setCallEndpointSessionDeactivated();
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index d72f8aa..986871f 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
 import android.telecom.Logging.Session;
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index edf1cf4..ecca835 100755
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -18,6 +18,7 @@
 
 import android.net.Uri;
 import android.os.Bundle;
+import android.telecom.CallEndpoint;
 import android.telecom.PhoneAccountHandle;
 
 /**
@@ -95,4 +96,8 @@
 
     void handoverTo(String callId, in PhoneAccountHandle destAcct, int videoState,
             in Bundle extras);
+
+    void pushCall(String callId, in CallEndpoint endpoint);
+
+    void answerCallViaEndpoint(String callId, in CallEndpoint endpoint, int videoState);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index b9563fa..93d9f28 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -19,9 +19,12 @@
 import android.app.PendingIntent;
 import android.os.Bundle;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.ParcelableCall;
 
 import com.android.internal.telecom.IInCallAdapter;
+import com.android.internal.telecom.ICallEndpointCallback;
+import com.android.internal.telecom.ICallEndpointSession;
 
 /**
  * Internal remote interface for in-call services.
@@ -30,9 +33,12 @@
  *
  * {@hide}
  */
-oneway interface IInCallService {
+interface IInCallService {
     void setInCallAdapter(in IInCallAdapter inCallAdapter);
 
+    ICallEndpointCallback requestCallEndpointActivation(in CallEndpoint callEndpoint,
+        in ICallEndpointSession callEndpointSession);
+
     void addCall(in ParcelableCall call);
 
     void updateCall(in ParcelableCall call);
@@ -58,4 +64,10 @@
     void onHandoverFailed(String callId, int error);
 
     void onHandoverComplete(String callId);
+
+    void onCallPullFailed(String callId, int reason);
+
+    void onCallPushFailed(String callId, in CallEndpoint endpoint, int reason);
+
+    void onAnswerFailed(String callId, in CallEndpoint endpoint, int reason);
 }
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index b9936ce..985f6bc 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
+import android.telecom.CallEndpoint;
 import android.telecom.TelecomAnalytics;
 import android.telecom.PhoneAccountHandle;
 import android.net.Uri;
@@ -368,4 +369,19 @@
      * @see TelecomServiceImpl#setTestCallDiagnosticService
      */
     void setTestCallDiagnosticService(in String packageName);
+
+    /**
+     * @see TelecomServiceImpl#registerCallEndpoints(in List<CallEndpoint>, in String);
+     */
+    void registerCallEndpoints(in List<CallEndpoint> endpoints, in String packageName);
+
+    /**
+     * @see TelecomServiceImpl#unregisterCallEndpoints(in List<CallEndpoint>, String);
+     */
+    void unregisterCallEndpoints(in List<CallEndpoint> endpoints, in String packageName);
+
+    /**
+     * @see TelecomServiceImpl#getCallEndpoints(in String packageName);
+     */
+    List<CallEndpoint> getCallEndpoints(in String packageName);
 }
diff --git a/telephony/java/android/telephony/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/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/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/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/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/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/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/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> {