Merge "Import translations. DO NOT MERGE ANYWHERE"
diff --git a/Android.bp b/Android.bp
index 72bd642..8bc5d7a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -77,7 +77,6 @@
         // Java/AIDL sources under frameworks/base
         ":framework-annotations",
         ":framework-blobstore-sources",
-        ":framework-bluetooth-sources", // TODO(b/214988855) : Remove once framework-bluetooth jar is ready
         ":framework-connectivity-tiramisu-sources",
         ":framework-core-sources",
         ":framework-drm-sources",
@@ -178,7 +177,6 @@
     soong_config_variables: {
         include_nonpublic_framework_api: {
             static_libs: [
-                "framework-auxiliary.impl",
                 "framework-supplementalapi.impl",
             ],
         },
@@ -298,6 +296,7 @@
     defaults: ["framework-aidl-export-defaults"],
     srcs: [
         ":framework-non-updatable-sources",
+        ":framework-bluetooth-sources", // TODO(b/214988855) : Remove once framework-bluetooth jar is ready
         "core/java/**/*.logtags",
         ":apex-info-list",
     ],
@@ -603,6 +602,7 @@
     libs: [
         "art.module.public.api",
         "sdk_module-lib_current_framework-tethering",
+        "sdk_public_current_framework-bluetooth",
         // There are a few classes from modules used by the core that
         // need to be resolved by metalava. We use a prebuilt stub of the
         // full sdk to ensure we can resolve them. If a new class gets added,
diff --git a/ApiDocs.bp b/ApiDocs.bp
index ca211c1..c50d446 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -74,39 +74,21 @@
 
 stubs_defaults {
     name: "framework-doc-stubs-default",
+    defaults: ["android-non-updatable-stubs-defaults"],
     srcs: [
-        ":android-non-updatable-stub-sources",
-
         // No longer part of the stubs, but are included in the docs.
         ":android-test-base-sources",
         ":android-test-mock-sources",
         ":android-test-runner-sources",
     ],
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-    ],
     libs: framework_docs_only_libs,
     create_doc_stubs: true,
-    annotations_enabled: true,
-    filter_packages: packages_to_document,
     api_levels_annotations_enabled: true,
     api_levels_annotations_dirs: [
         "sdk-dir",
         "api-versions-jars-dir",
     ],
-    previous_api: ":android.api.public.latest",
-    merge_annotations_dirs: [
-        "metalava-manual",
-    ],
     write_sdk_values: true,
-    // TODO(b/169090544): remove below aidl includes.
-    aidl: {
-        local_include_dirs: ["media/aidl"],
-        include_dirs: [
-            "frameworks/av/aidl",
-            "frameworks/native/libs/permission/aidl",
-        ],
-    },
 }
 
 // Defaults module for doc-stubs targets that use module source code as input.
@@ -119,8 +101,8 @@
         ":i18n.module.public.api{.public.stubs.source}",
 
         ":framework-appsearch-sources",
-        ":framework-auxiliary-sources",
         ":framework-connectivity-sources",
+        ":framework-bluetooth-sources",
         ":framework-connectivity-tiramisu-updatable-sources",
         ":framework-graphics-srcs",
         ":framework-mediaprovider-sources",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 7f7380a..a0a426e 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -215,52 +215,6 @@
 // from stub sources
 /////////////////////////////////////////////////////////////////////
 
-modules_public_stubs = [
-    "android.net.ipsec.ike.stubs",
-    "art.module.public.api.stubs",
-    "conscrypt.module.public.api.stubs",
-    "framework-appsearch.stubs",
-    "framework-connectivity.stubs",
-    "framework-connectivity-tiramisu.stubs",
-    "framework-graphics.stubs",
-    "framework-media.stubs",
-    "framework-mediaprovider.stubs",
-    "framework-nearby.stubs",
-    "framework-permission.stubs",
-    "framework-permission-s.stubs",
-    "framework-scheduling.stubs",
-    "framework-sdkextensions.stubs",
-    "framework-statsd.stubs",
-    "framework-supplementalprocess.stubs",
-    "framework-tethering.stubs",
-    "framework-uwb.stubs",
-    "framework-wifi.stubs",
-    "i18n.module.public.api.stubs",
-]
-
-modules_system_stubs = [
-    "android.net.ipsec.ike.stubs.system",
-    "art.module.public.api.stubs.system",
-    "conscrypt.module.public.api.stubs", // Only has public stubs
-    "framework-appsearch.stubs.system",
-    "framework-connectivity.stubs.system",
-    "framework-connectivity-tiramisu.stubs.system",
-    "framework-graphics.stubs.system",
-    "framework-media.stubs.system",
-    "framework-mediaprovider.stubs.system",
-    "framework-nearby.stubs.system",
-    "framework-permission.stubs.system",
-    "framework-permission-s.stubs.system",
-    "framework-scheduling.stubs.system",
-    "framework-sdkextensions.stubs.system",
-    "framework-statsd.stubs.system",
-    "framework-supplementalprocess.stubs",
-    "framework-tethering.stubs.system",
-    "framework-uwb.stubs.system",
-    "framework-wifi.stubs.system",
-    "i18n.module.public.api.stubs", // Only has public stubs
-]
-
 java_defaults {
     name: "android-non-updatable_defaults_stubs_current",
     libs: ["stub-annotations"],
@@ -282,15 +236,7 @@
     name: "android-non-updatable.stubs",
     defaults: ["android-non-updatable_defaults_stubs_current"],
     srcs: [":api-stubs-docs-non-updatable"],
-    libs: modules_public_stubs,
-    soong_config_variables: {
-        include_nonpublic_framework_api: {
-            libs: [
-                "framework-auxiliary.stubs",
-                "framework-supplementalapi.stubs",
-            ],
-        },
-    },
+    libs: ["all-modules-public-stubs"],
     dist: {
         dir: "apistubs/android/public",
     },
@@ -300,15 +246,7 @@
     name: "android-non-updatable.stubs.system",
     defaults: ["android-non-updatable_defaults_stubs_current"],
     srcs: [":system-api-stubs-docs-non-updatable"],
-    libs: modules_system_stubs,
-    soong_config_variables: {
-        include_nonpublic_framework_api: {
-            libs: [
-                "framework-auxiliary.stubs",
-                "framework-supplementalapi.stubs",
-            ],
-        },
-    },
+    libs: ["all-modules-system-stubs"],
     dist: {
         dir: "apistubs/android/system",
     },
@@ -320,6 +258,8 @@
     srcs: [":module-lib-api-stubs-docs-non-updatable"],
     libs: [
         "sdk_module-lib_current_framework-tethering",
+        "sdk_public_current_framework-bluetooth",
+        // NOTE: The below can be removed once the prebuilt stub contains bluetooth.
         "sdk_system_current_android",
         // NOTE: The below can be removed once the prebuilt stub contains IKE.
         "sdk_system_current_android.net.ipsec.ike",
@@ -333,15 +273,7 @@
     name: "android-non-updatable.stubs.test",
     defaults: ["android-non-updatable_defaults_stubs_current"],
     srcs: [":test-api-stubs-docs-non-updatable"],
-    libs: modules_system_stubs,
-    soong_config_variables: {
-        include_nonpublic_framework_api: {
-            libs: [
-                "framework-auxiliary.stubs",
-                "framework-supplementalapi.stubs",
-            ],
-        },
-    },
+    libs: ["all-modules-system-stubs"],
     dist: {
         dir: "apistubs/android/test",
     },
@@ -359,35 +291,21 @@
 
 java_library_with_nonpublic_deps {
     name: "android_stubs_current",
-    static_libs: modules_public_stubs + [
+    static_libs: [
+        "all-modules-public-stubs",
         "android-non-updatable.stubs",
         "private-stub-annotations-jar",
     ],
-    soong_config_variables: {
-        include_nonpublic_framework_api: {
-            static_libs: [
-                "framework-auxiliary.stubs",
-                "framework-supplementalapi.stubs",
-            ],
-        },
-    },
     defaults: ["android.jar_defaults"],
 }
 
 java_library_with_nonpublic_deps {
     name: "android_system_stubs_current",
-    static_libs: modules_system_stubs + [
+    static_libs: [
+        "all-modules-system-stubs",
         "android-non-updatable.stubs.system",
         "private-stub-annotations-jar",
     ],
-    soong_config_variables: {
-        include_nonpublic_framework_api: {
-            static_libs: [
-                "framework-auxiliary.stubs",
-                "framework-supplementalapi.stubs",
-            ],
-        },
-    },
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
@@ -409,18 +327,11 @@
     name: "android_test_stubs_current",
     // Modules do not have test APIs, but we want to include their SystemApis, like we include
     // the SystemApi of framework-non-updatable-sources.
-    static_libs: modules_system_stubs + [
+    static_libs: [
+        "all-modules-system-stubs",
         "android-non-updatable.stubs.test",
         "private-stub-annotations-jar",
     ],
-    soong_config_variables: {
-        include_nonpublic_framework_api: {
-            static_libs: [
-                "framework-auxiliary.stubs",
-                "framework-supplementalapi.stubs",
-            ],
-        },
-    },
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index e873514..67a3380 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -218,7 +218,7 @@
             })
 
         override fun parseImpl(file: File) =
-                parser.parsePackage(input.get()!!.reset(), file, 0).result
+                parser.parsePackage(input.get()!!.reset(), file, 0, null).result
                         as ParsingPackageImpl
     }
 
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index 1be68f5..4ad015d 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -20,7 +20,6 @@
 
 import android.app.Activity;
 import android.content.Context;
-import android.graphics.Point;
 import android.os.RemoteException;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
@@ -133,12 +132,10 @@
         final WindowManager.LayoutParams mParams;
         final int mWidth;
         final int mHeight;
-        final Point mOutSurfaceSize = new Point();
         final SurfaceControl mOutSurfaceControl;
 
         final IntSupplier mViewVisibility;
 
-        int mFrameNumber;
         int mFlags;
 
         RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) {
@@ -155,9 +152,8 @@
             final IWindowSession session = WindowManagerGlobal.getWindowSession();
             while (state.keepRunning()) {
                 session.relayout(mWindow, mParams, mWidth, mHeight,
-                        mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrames,
-                        mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls,
-                        mOutSurfaceSize);
+                        mViewVisibility.getAsInt(), mFlags, mOutFrames,
+                        mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls);
             }
         }
     }
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 9fb1227..6dd79e8 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -350,6 +350,21 @@
      * @hide
      */
     public static final int REASON_MEDIA_SESSION_CALLBACK = 317;
+    /**
+     * Dialer app.
+     * @hide
+     */
+    public static final int REASON_ROLE_DIALER = 318;
+    /**
+     * Emergency app.
+     * @hide
+     */
+    public static final int REASON_ROLE_EMERGENCY = 319;
+    /**
+     * System Module.
+     * @hide
+     */
+    public static final int REASON_SYSTEM_MODULE = 320;
 
     /** @hide The app requests out-out. */
     public static final int REASON_OPT_OUT_REQUESTED = 1000;
@@ -423,6 +438,9 @@
             REASON_EVENT_MMS,
             REASON_SHELL,
             REASON_MEDIA_SESSION_CALLBACK,
+            REASON_ROLE_DIALER,
+            REASON_ROLE_EMERGENCY,
+            REASON_SYSTEM_MODULE,
             REASON_OPT_OUT_REQUESTED,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -726,6 +744,12 @@
                 return "SHELL";
             case REASON_MEDIA_SESSION_CALLBACK:
                 return "MEDIA_SESSION_CALLBACK";
+            case REASON_ROLE_DIALER:
+                return "ROLE_DIALER";
+            case REASON_ROLE_EMERGENCY:
+                return "ROLE_EMERGENCY";
+            case REASON_SYSTEM_MODULE:
+                return "SYSTEM_MODULE";
             case REASON_OPT_OUT_REQUESTED:
                 return "REASON_OPT_OUT_REQUESTED";
             default:
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 0f36d32..9b1f2d0 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -4,8 +4,8 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.usage.AppStandbyInfo;
+import android.app.usage.UsageStatsManager.ForcedReasons;
 import android.app.usage.UsageStatsManager.StandbyBuckets;
-import android.app.usage.UsageStatsManager.SystemForcedReasons;
 import android.content.Context;
 import android.util.IndentingPrintWriter;
 
@@ -152,7 +152,7 @@
      *                       UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_* reasons.
      */
     void restrictApp(@NonNull String packageName, int userId,
-            @SystemForcedReasons int restrictReason);
+            @ForcedReasons int restrictReason);
 
     /**
      * Put the specified app in the
@@ -169,7 +169,29 @@
      *                       UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_* reasons.
      */
     void restrictApp(@NonNull String packageName, int userId, int mainReason,
-            @SystemForcedReasons int restrictReason);
+            @ForcedReasons int restrictReason);
+
+    /**
+     * Unrestrict an app if there is no other reason to restrict it.
+     *
+     * <p>
+     * The {@code prevMainReasonRestrict} and {@code prevSubReasonRestrict} are the previous
+     * reasons of why it was restricted, but the caller knows that these conditions are not true
+     * anymore; therefore if there is no other reasons to restrict it (as there could bemultiple
+     * reasons to restrict it), lift the restriction.
+     * </p>
+     *
+     * @param packageName            The package name of the app.
+     * @param userId                 The user id that this app runs in.
+     * @param prevMainReasonRestrict The main reason that why it was restricted, must be either
+     *                               {@link android.app.usage.UsageStatsManager#REASON_MAIN_FORCED_BY_SYSTEM}
+     *                               or {@link android.app.usage.UsageStatsManager#REASON_MAIN_FORCED_BY_USER}.
+     * @param prevSubReasonRestrict  The subreason that why it was restricted before.
+     * @param mainReasonUnrestrict   The main reason that why it could be unrestricted now.
+     * @param subReasonUnrestrict    The subreason that why it could be unrestricted now.
+     */
+    void maybeUnrestrictApp(@NonNull String packageName, int userId, int prevMainReasonRestrict,
+            int prevSubReasonRestrict, int mainReasonUnrestrict, int subReasonUnrestrict);
 
     void addActiveDeviceAdmin(String adminPkg, int userId);
 
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index d21a0ea..70d5038 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.PackageOps;
 import android.app.IActivityManager;
@@ -280,6 +281,14 @@
         }
     }
 
+    private final AppBackgroundRestrictionListener mAppBackgroundRestrictionListener =
+            new AppBackgroundRestrictionListener() {
+        @Override
+        public void onAutoRestrictedBucketFeatureFlagChanged(boolean autoRestrictedBucket) {
+            mHandler.notifyAutoRestrictedBucketFeatureFlagChanged(autoRestrictedBucket);
+        }
+    };
+
     /**
      * Listener for any state changes that affect any app's eligibility to run.
      */
@@ -370,6 +379,18 @@
         }
 
         /**
+         * Called when toggling the feature flag of moving to restricted standby bucket
+         * automatically on background-restricted.
+         */
+        private void onAutoRestrictedBucketFeatureFlagChanged(AppStateTrackerImpl sender,
+                boolean autoRestrictedBucket) {
+            updateAllJobs();
+            if (autoRestrictedBucket) {
+                unblockAllUnrestrictedAlarms();
+            }
+        }
+
+        /**
          * Called when the job restrictions for multiple UIDs might have changed, so the job
          * scheduler should re-evaluate all restrictions for all jobs.
          */
@@ -499,6 +520,8 @@
                     mFlagsObserver.isForcedAppStandbyForSmallBatteryEnabled();
             mStandbyTracker = new StandbyTracker();
             mAppStandbyInternal.addListener(mStandbyTracker);
+            mActivityManagerInternal.addAppBackgroundRestrictionListener(
+                    mAppBackgroundRestrictionListener);
 
             try {
                 mIActivityManager.registerUidObserver(new UidObserver(),
@@ -802,6 +825,7 @@
         private static final int MSG_USER_REMOVED = 8;
         private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9;
         private static final int MSG_EXEMPTED_BUCKET_CHANGED = 10;
+        private static final int MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED = 11;
 
         private static final int MSG_ON_UID_ACTIVE = 12;
         private static final int MSG_ON_UID_GONE = 13;
@@ -849,6 +873,12 @@
             obtainMessage(MSG_EXEMPTED_BUCKET_CHANGED).sendToTarget();
         }
 
+        public void notifyAutoRestrictedBucketFeatureFlagChanged(boolean autoRestrictedBucket) {
+            removeMessages(MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED);
+            obtainMessage(MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED,
+                    autoRestrictedBucket ? 1 : 0, 0).sendToTarget();
+        }
+
         public void doUserRemoved(int userId) {
             obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
         }
@@ -952,6 +982,13 @@
                     handleUserRemoved(msg.arg1);
                     return;
 
+                case MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED:
+                    final boolean autoRestrictedBucket = msg.arg1 == 1;
+                    for (Listener l : cloneListeners()) {
+                        l.onAutoRestrictedBucketFeatureFlagChanged(sender, autoRestrictedBucket);
+                    }
+                    return;
+
                 case MSG_ON_UID_ACTIVE:
                     handleUidActive(msg.arg1);
                     return;
@@ -1120,7 +1157,12 @@
             if (ArrayUtils.contains(mPowerExemptAllAppIds, appId)) {
                 return false;
             }
-            return (mForcedAppStandbyEnabled && isRunAnyRestrictedLocked(uid, packageName));
+            // If apps will be put into restricted standby bucket automatically on user-forced
+            // app standby, instead of blocking alarms completely, let the restricted standby bucket
+            // policy take care of it.
+            return (mForcedAppStandbyEnabled
+                    && !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+                    && isRunAnyRestrictedLocked(uid, packageName));
         }
     }
 
@@ -1161,7 +1203,12 @@
                     || ArrayUtils.contains(mTempExemptAppIds, appId)) {
                 return false;
             }
-            if (mForcedAppStandbyEnabled && isRunAnyRestrictedLocked(uid, packageName)) {
+            // If apps will be put into restricted standby bucket automatically on user-forced
+            // app standby, instead of blocking jobs completely, let the restricted standby bucket
+            // policy take care of it.
+            if (mForcedAppStandbyEnabled
+                    && !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+                    && isRunAnyRestrictedLocked(uid, packageName)) {
                 return true;
             }
             if (hasForegroundExemption) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index 0ceab35..65d7121 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -19,6 +19,7 @@
 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
+import android.app.ActivityManagerInternal;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
@@ -59,6 +60,7 @@
     static final int KNOWN_ACTIVE = 1;
     static final int KNOWN_INACTIVE = 2;
 
+    private final ActivityManagerInternal mActivityManagerInternal;
     private final AppStateTrackerImpl mAppStateTracker;
 
     private final UpdateJobFunctor mUpdateJobFunctor = new UpdateJobFunctor();
@@ -66,6 +68,8 @@
     public BackgroundJobsController(JobSchedulerService service) {
         super(service);
 
+        mActivityManagerInternal = (ActivityManagerInternal) Objects.requireNonNull(
+                LocalServices.getService(ActivityManagerInternal.class));
         mAppStateTracker = (AppStateTrackerImpl) Objects.requireNonNull(
                 LocalServices.getService(AppStateTracker.class));
         mAppStateTracker.addListener(mForceAppStandbyListener);
@@ -216,7 +220,8 @@
         }
         boolean didChange =
                 jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun,
-                        !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
+                        !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+                        && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
         didChange |= jobStatus.setUidActive(isActive);
         return didChange;
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 0ad70e4..8b26397 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -63,8 +63,8 @@
 import android.app.ActivityManager;
 import android.app.usage.AppStandbyInfo;
 import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManager.ForcedReasons;
 import android.app.usage.UsageStatsManager.StandbyBuckets;
-import android.app.usage.UsageStatsManager.SystemForcedReasons;
 import android.app.usage.UsageStatsManagerInternal;
 import android.appwidget.AppWidgetManager;
 import android.content.BroadcastReceiver;
@@ -183,7 +183,7 @@
             COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR,
             COMPRESS_TIME ? 4 * ONE_MINUTE : 24 * ONE_HOUR,
             COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR,
-            COMPRESS_TIME ? 32 * ONE_MINUTE : 3 * ONE_DAY
+            COMPRESS_TIME ? 32 * ONE_MINUTE : 8 * ONE_DAY
     };
 
     /** The minimum allowed values for each index in {@link #DEFAULT_ELAPSED_TIME_THRESHOLDS}. */
@@ -1406,13 +1406,13 @@
 
     @Override
     public void restrictApp(@NonNull String packageName, int userId,
-            @SystemForcedReasons int restrictReason) {
+            @ForcedReasons int restrictReason) {
         restrictApp(packageName, userId, REASON_MAIN_FORCED_BY_SYSTEM, restrictReason);
     }
 
     @Override
     public void restrictApp(@NonNull String packageName, int userId, int mainReason,
-            @SystemForcedReasons int restrictReason) {
+            @ForcedReasons int restrictReason) {
         if (mainReason != REASON_MAIN_FORCED_BY_SYSTEM
                 && mainReason != REASON_MAIN_FORCED_BY_USER) {
             Slog.e(TAG, "Tried to restrict app " + packageName + " for an unsupported reason");
@@ -1799,27 +1799,36 @@
      * bucket if it was forced into the bucket by the system because it was buggy.
      */
     @VisibleForTesting
-    void maybeUnrestrictBuggyApp(String packageName, int userId) {
+    void maybeUnrestrictBuggyApp(@NonNull String packageName, int userId) {
+        maybeUnrestrictApp(packageName, userId,
+                REASON_MAIN_FORCED_BY_SYSTEM, REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY,
+                REASON_MAIN_DEFAULT, REASON_SUB_DEFAULT_APP_UPDATE);
+    }
+
+    @Override
+    public void maybeUnrestrictApp(@NonNull String packageName, int userId,
+            int prevMainReasonRestrict, int prevSubReasonRestrict,
+            int mainReasonUnrestrict, int subReasonUnrestrict) {
         synchronized (mAppIdleLock) {
             final long elapsedRealtime = mInjector.elapsedRealtime();
             final AppIdleHistory.AppUsageHistory app =
                     mAppIdleHistory.getAppUsageHistory(packageName, userId, elapsedRealtime);
             if (app.currentBucket != STANDBY_BUCKET_RESTRICTED
-                    || (app.bucketingReason & REASON_MAIN_MASK) != REASON_MAIN_FORCED_BY_SYSTEM) {
+                    || (app.bucketingReason & REASON_MAIN_MASK) != prevMainReasonRestrict) {
                 return;
             }
 
             final int newBucket;
             final int newReason;
-            if ((app.bucketingReason & REASON_SUB_MASK) == REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY) {
-                // If bugginess was the only reason the app should be restricted, then lift it out.
+            if ((app.bucketingReason & REASON_SUB_MASK) == prevSubReasonRestrict) {
+                // If it was the only reason the app should be restricted, then lift it out.
                 newBucket = STANDBY_BUCKET_RARE;
-                newReason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_UPDATE;
+                newReason = mainReasonUnrestrict | subReasonUnrestrict;
             } else {
-                // There's another reason the app was restricted. Remove the buggy bit and call
+                // There's another reason the app was restricted. Remove the subreason bit and call
                 // it a day.
                 newBucket = STANDBY_BUCKET_RESTRICTED;
-                newReason = app.bucketingReason & ~REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY;
+                newReason = app.bucketingReason & ~prevSubReasonRestrict;
             }
             mAppIdleHistory.setAppStandbyBucket(
                     packageName, userId, elapsedRealtime, newBucket, newReason);
diff --git a/api/Android.bp b/api/Android.bp
index 9428fcc..d8727f9 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -110,6 +110,7 @@
         "art.module.public.api",
         "conscrypt.module.public.api",
         "framework-appsearch",
+        "framework-bluetooth",
         "framework-connectivity",
         "framework-connectivity-tiramisu",
         "framework-graphics",
@@ -128,7 +129,6 @@
         "i18n.module.public.api",
     ],
     conditional_bootclasspath: [
-        "framework-auxiliary",
         "framework-supplementalapi",
     ],
     system_server_classpath: [
diff --git a/api/api.go b/api/api.go
index 4b6ebc1..aa9e399e 100644
--- a/api/api.go
+++ b/api/api.go
@@ -27,6 +27,7 @@
 const art = "art.module.public.api"
 const conscrypt = "conscrypt.module.public.api"
 const i18n = "i18n.module.public.api"
+var modules_with_only_public_scope = []string{i18n, conscrypt}
 
 // The intention behind this soong plugin is to generate a number of "merged"
 // API-related modules that would otherwise require a large amount of very
@@ -183,6 +184,27 @@
 	ctx.CreateModule(genrule.GenRuleFactory, &props)
 }
 
+func createMergedPublicStubs(ctx android.LoadHookContext, modules []string) {
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("all-modules-public-stubs")
+	props.Static_libs = transformArray(modules, "", ".stubs")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
+func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
+	props := libraryProps{}
+	modules_with_system_stubs := removeAll(modules, modules_with_only_public_scope)
+	props.Name = proptools.StringPtr("all-modules-system-stubs")
+	props.Static_libs = append(
+		transformArray(modules_with_only_public_scope, "", ".stubs"),
+		transformArray(modules_with_system_stubs, "", ".stubs.system")...)
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedModuleLibStubs(ctx android.LoadHookContext, modules []string) {
 	// The user of this module compiles against the "core" SDK, so remove core libraries to avoid dupes.
 	modules = removeAll(modules, []string{art, conscrypt, i18n})
@@ -205,7 +227,7 @@
 func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
 	var textFiles []MergedTxtDefinition
 	// Two module libraries currently do not support @SystemApi so only have the public scope.
-	bcpWithSystemApi := removeAll(bootclasspath, []string{conscrypt, i18n})
+	bcpWithSystemApi := removeAll(bootclasspath, modules_with_only_public_scope)
 
 	tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
 	for i, f := range []string{"current.txt", "removed.txt"} {
@@ -253,6 +275,8 @@
 
 	createMergedStubsSrcjar(ctx, bootclasspath)
 
+	createMergedPublicStubs(ctx, bootclasspath)
+	createMergedSystemStubs(ctx, bootclasspath)
 	createMergedModuleLibStubs(ctx, bootclasspath)
 
 	createMergedAnnotations(ctx, bootclasspath)
diff --git a/boot/Android.bp b/boot/Android.bp
index 8958d70..3273f2c 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -56,10 +56,6 @@
             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/core/api/current.txt b/core/api/current.txt
index 86fbcee..e7d9f61 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -50,6 +50,7 @@
     field public static final String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
     field public static final String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
     field public static final String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
+    field public static final String BIND_TV_INTERACTIVE_APP = "android.permission.BIND_TV_INTERACTIVE_APP";
     field public static final String BIND_VISUAL_VOICEMAIL_SERVICE = "android.permission.BIND_VISUAL_VOICEMAIL_SERVICE";
     field public static final String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION";
     field public static final String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
@@ -134,6 +135,9 @@
     field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA";
     field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
     field public static final String READ_LOGS = "android.permission.READ_LOGS";
+    field public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO";
+    field public static final String READ_MEDIA_IMAGE = "android.permission.READ_MEDIA_IMAGE";
+    field public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO";
     field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
     field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
     field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
@@ -215,6 +219,8 @@
     field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES";
     field public static final String NOTIFICATIONS = "android.permission-group.NOTIFICATIONS";
     field public static final String PHONE = "android.permission-group.PHONE";
+    field public static final String READ_MEDIA_AURAL = "android.permission-group.READ_MEDIA_AURAL";
+    field public static final String READ_MEDIA_VISUAL = "android.permission-group.READ_MEDIA_VISUAL";
     field public static final String SENSORS = "android.permission-group.SENSORS";
     field public static final String SMS = "android.permission-group.SMS";
     field public static final String STORAGE = "android.permission-group.STORAGE";
@@ -356,6 +362,7 @@
     field public static final int authorities = 16842776; // 0x1010018
     field public static final int autoAdvanceViewId = 16843535; // 0x101030f
     field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+    field public static final int autoHandwritingEnabled;
     field public static final int autoLink = 16842928; // 0x10100b0
     field public static final int autoMirrored = 16843754; // 0x10103ea
     field public static final int autoRemoveFromRecents = 16843847; // 0x1010447
@@ -3139,22 +3146,22 @@
   public static final class AccessibilityService.MagnificationController {
     method public void addListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
     method public void addListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, @Nullable android.os.Handler);
-    method public float getCenterX();
-    method public float getCenterY();
+    method @Deprecated public float getCenterX();
+    method @Deprecated public float getCenterY();
     method @NonNull public android.graphics.Region getCurrentMagnificationRegion();
     method @Nullable public android.accessibilityservice.MagnificationConfig getMagnificationConfig();
-    method @NonNull public android.graphics.Region getMagnificationRegion();
-    method public float getScale();
+    method @Deprecated @NonNull public android.graphics.Region getMagnificationRegion();
+    method @Deprecated public float getScale();
     method public boolean removeListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
     method public boolean reset(boolean);
     method public boolean resetCurrentMagnification(boolean);
-    method public boolean setCenter(float, float, boolean);
+    method @Deprecated public boolean setCenter(float, float, boolean);
     method public boolean setMagnificationConfig(@NonNull android.accessibilityservice.MagnificationConfig, boolean);
-    method public boolean setScale(float, boolean);
+    method @Deprecated public boolean setScale(float, boolean);
   }
 
   public static interface AccessibilityService.MagnificationController.OnMagnificationChangedListener {
-    method public void onMagnificationChanged(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController, @NonNull android.graphics.Region, float, float, float);
+    method @Deprecated public void onMagnificationChanged(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController, @NonNull android.graphics.Region, float, float, float);
     method public default void onMagnificationChanged(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController, @NonNull android.graphics.Region, @NonNull android.accessibilityservice.MagnificationConfig);
   }
 
@@ -7296,10 +7303,10 @@
     method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
     method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
     method public CharSequence getDeviceOwnerLockScreenInfo();
-    method @NonNull public android.graphics.drawable.Drawable getDrawable(int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawable(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
     method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
     method @NonNull public String getEnrollmentSpecificId();
     method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
@@ -7343,6 +7350,7 @@
     method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName);
     method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName);
     method public int getPersonalAppsSuspendedReasons(@NonNull android.content.ComponentName);
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig();
     method public int getRequiredPasswordComplexity();
     method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName);
     method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName);
@@ -7487,6 +7495,7 @@
     method public boolean setPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName, @Nullable java.util.List<java.lang.String>);
     method public boolean setPermittedInputMethods(@NonNull android.content.ComponentName, java.util.List<java.lang.String>);
     method public void setPersonalAppsSuspended(@NonNull android.content.ComponentName, boolean);
+    method public void setPreferentialNetworkServiceConfig(@NonNull android.app.admin.PreferentialNetworkServiceConfig);
     method public void setPreferentialNetworkServiceEnabled(boolean);
     method public void setProfileEnabled(@NonNull android.content.ComponentName);
     method public void setProfileName(@NonNull android.content.ComponentName, String);
@@ -7710,29 +7719,29 @@
     ctor public DevicePolicyResources();
   }
 
-  public static final class DevicePolicyResources.Drawable {
-    field public static final int INVALID_ID = -1; // 0xffffffff
-    field public static final int WORK_PROFILE_ICON = 1; // 0x1
-    field public static final int WORK_PROFILE_ICON_BADGE = 0; // 0x0
-    field public static final int WORK_PROFILE_OFF_ICON = 2; // 0x2
-    field public static final int WORK_PROFILE_USER_ICON = 3; // 0x3
+  public static final class DevicePolicyResources.Drawables {
+    field public static final String UNDEFINED = "UNDEFINED";
+    field public static final String WORK_PROFILE_ICON = "WORK_PROFILE_ICON";
+    field public static final String WORK_PROFILE_ICON_BADGE = "WORK_PROFILE_ICON_BADGE";
+    field public static final String WORK_PROFILE_OFF_ICON = "WORK_PROFILE_OFF_ICON";
+    field public static final String WORK_PROFILE_USER_ICON = "WORK_PROFILE_USER_ICON";
   }
 
-  public static final class DevicePolicyResources.Drawable.Source {
-    field public static final int HOME_WIDGET = 2; // 0x2
-    field public static final int LAUNCHER_OFF_BUTTON = 3; // 0x3
-    field public static final int NOTIFICATION = 0; // 0x0
-    field public static final int PROFILE_SWITCH_ANIMATION = 1; // 0x1
-    field public static final int QUICK_SETTINGS = 4; // 0x4
-    field public static final int STATUS_BAR = 5; // 0x5
-    field public static final int UNDEFINED = -1; // 0xffffffff
+  public static final class DevicePolicyResources.Drawables.Source {
+    field public static final String HOME_WIDGET = "HOME_WIDGET";
+    field public static final String LAUNCHER_OFF_BUTTON = "LAUNCHER_OFF_BUTTON";
+    field public static final String NOTIFICATION = "NOTIFICATION";
+    field public static final String PROFILE_SWITCH_ANIMATION = "PROFILE_SWITCH_ANIMATION";
+    field public static final String QUICK_SETTINGS = "QUICK_SETTINGS";
+    field public static final String STATUS_BAR = "STATUS_BAR";
+    field public static final String UNDEFINED = "UNDEFINED";
   }
 
-  public static final class DevicePolicyResources.Drawable.Style {
-    field public static final int DEFAULT = -1; // 0xffffffff
-    field public static final int OUTLINE = 2; // 0x2
-    field public static final int SOLID_COLORED = 0; // 0x0
-    field public static final int SOLID_NOT_COLORED = 1; // 0x1
+  public static final class DevicePolicyResources.Drawables.Style {
+    field public static final String DEFAULT = "DEFAULT";
+    field public static final String OUTLINE = "OUTLINE";
+    field public static final String SOLID_COLORED = "SOLID_COLORED";
+    field public static final String SOLID_NOT_COLORED = "SOLID_NOT_COLORED";
   }
 
   public final class DnsEvent extends android.app.admin.NetworkEvent implements android.os.Parcelable {
@@ -7772,6 +7781,32 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.NetworkEvent> CREATOR;
   }
 
+  public final class PreferentialNetworkServiceConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public int[] getExcludedUids();
+    method @NonNull public int[] getIncludedUids();
+    method public int getNetworkId();
+    method public boolean isEnabled();
+    method public boolean isFallbackToDefaultConnectionAllowed();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PreferentialNetworkServiceConfig> CREATOR;
+    field public static final int PREFERENTIAL_NETWORK_ID_1 = 1; // 0x1
+    field public static final int PREFERENTIAL_NETWORK_ID_2 = 2; // 0x2
+    field public static final int PREFERENTIAL_NETWORK_ID_3 = 3; // 0x3
+    field public static final int PREFERENTIAL_NETWORK_ID_4 = 4; // 0x4
+    field public static final int PREFERENTIAL_NETWORK_ID_5 = 5; // 0x5
+  }
+
+  public static final class PreferentialNetworkServiceConfig.Builder {
+    ctor public PreferentialNetworkServiceConfig.Builder();
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig build();
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setEnabled(boolean);
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setExcludedUids(@NonNull int[]);
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setFallbackToDefaultConnectionAllowed(boolean);
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setIncludedUids(@NonNull int[]);
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setNetworkId(int);
+  }
+
   public class SecurityLog {
     ctor public SecurityLog();
     field public static final int LEVEL_ERROR = 3; // 0x3
@@ -8840,1327 +8875,6 @@
 
 }
 
-package android.bluetooth {
-
-  public final class BluetoothA2dp implements android.bluetooth.BluetoothProfile {
-    method public void finalize();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isA2dpPlaying(android.bluetooth.BluetoothDevice);
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_PLAYING_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
-    field public static final int STATE_NOT_PLAYING = 11; // 0xb
-    field public static final int STATE_PLAYING = 10; // 0xa
-  }
-
-  public final class BluetoothAdapter {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean cancelDiscovery();
-    method public static boolean checkBluetoothAddress(String);
-    method public void closeProfileProxy(int, android.bluetooth.BluetoothProfile);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disable();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enable();
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, "android.permission.LOCAL_MAC_ADDRESS"}) public String getAddress();
-    method public android.bluetooth.le.BluetoothLeAdvertiser getBluetoothLeAdvertiser();
-    method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices();
-    method @Deprecated public static android.bluetooth.BluetoothAdapter getDefaultAdapter();
-    method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public java.time.Duration getDiscoverableTimeout();
-    method public int getLeMaximumAdvertisingDataLength();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getProfileConnectionState(int);
-    method public boolean getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int);
-    method public android.bluetooth.BluetoothDevice getRemoteDevice(String);
-    method public android.bluetooth.BluetoothDevice getRemoteDevice(byte[]);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public int getScanMode();
-    method public int getState();
-    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 boolean isMultipleAdvertisementSupported();
-    method public boolean isOffloadedFilteringSupported();
-    method public boolean isOffloadedScanBatchingSupported();
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingInsecureL2capChannel() throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException;
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingL2capChannel() throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setName(String);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startDiscovery();
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startLeScan(java.util.UUID[], android.bluetooth.BluetoothAdapter.LeScanCallback);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LOCAL_NAME_CHANGED = "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_SCAN_MODE_CHANGED = "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
-    field public static final String ACTION_STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED";
-    field public static final int ERROR = -2147483648; // 0x80000000
-    field public static final String EXTRA_CONNECTION_STATE = "android.bluetooth.adapter.extra.CONNECTION_STATE";
-    field public static final String EXTRA_DISCOVERABLE_DURATION = "android.bluetooth.adapter.extra.DISCOVERABLE_DURATION";
-    field public static final String EXTRA_LOCAL_NAME = "android.bluetooth.adapter.extra.LOCAL_NAME";
-    field public static final String EXTRA_PREVIOUS_CONNECTION_STATE = "android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE";
-    field public static final String EXTRA_PREVIOUS_SCAN_MODE = "android.bluetooth.adapter.extra.PREVIOUS_SCAN_MODE";
-    field public static final String EXTRA_PREVIOUS_STATE = "android.bluetooth.adapter.extra.PREVIOUS_STATE";
-    field public static final String EXTRA_SCAN_MODE = "android.bluetooth.adapter.extra.SCAN_MODE";
-    field public static final String EXTRA_STATE = "android.bluetooth.adapter.extra.STATE";
-    field public static final int SCAN_MODE_CONNECTABLE = 21; // 0x15
-    field public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 23; // 0x17
-    field public static final int SCAN_MODE_NONE = 20; // 0x14
-    field public static final int STATE_CONNECTED = 2; // 0x2
-    field public static final int STATE_CONNECTING = 1; // 0x1
-    field public static final int STATE_DISCONNECTED = 0; // 0x0
-    field public static final int STATE_DISCONNECTING = 3; // 0x3
-    field public static final int STATE_OFF = 10; // 0xa
-    field public static final int STATE_ON = 12; // 0xc
-    field public static final int STATE_TURNING_OFF = 13; // 0xd
-    field public static final int STATE_TURNING_ON = 11; // 0xb
-  }
-
-  public static interface BluetoothAdapter.LeScanCallback {
-    method public void onLeScan(android.bluetooth.BluetoothDevice, int, byte[]);
-  }
-
-  public class BluetoothAssignedNumbers {
-    field public static final int AAMP_OF_AMERICA = 190; // 0xbe
-    field public static final int ACCEL_SEMICONDUCTOR = 74; // 0x4a
-    field public static final int ACE_SENSOR = 188; // 0xbc
-    field public static final int ADIDAS = 195; // 0xc3
-    field public static final int ADVANCED_PANMOBIL_SYSTEMS = 145; // 0x91
-    field public static final int AIROHA_TECHNOLOGY = 148; // 0x94
-    field public static final int ALCATEL = 36; // 0x24
-    field public static final int ALPWISE = 154; // 0x9a
-    field public static final int AMICCOM_ELECTRONICS = 192; // 0xc0
-    field public static final int APLIX = 189; // 0xbd
-    field public static final int APPLE = 76; // 0x4c
-    field public static final int APT_LICENSING = 79; // 0x4f
-    field public static final int ARCHOS = 207; // 0xcf
-    field public static final int ARP_DEVICES = 168; // 0xa8
-    field public static final int ATHEROS_COMMUNICATIONS = 69; // 0x45
-    field public static final int ATMEL = 19; // 0x13
-    field public static final int AUSTCO_COMMUNICATION_SYSTEMS = 213; // 0xd5
-    field public static final int AUTONET_MOBILE = 127; // 0x7f
-    field public static final int AVAGO = 78; // 0x4e
-    field public static final int AVM_BERLIN = 31; // 0x1f
-    field public static final int A_AND_D_ENGINEERING = 105; // 0x69
-    field public static final int A_AND_R_CAMBRIDGE = 124; // 0x7c
-    field public static final int BANDSPEED = 32; // 0x20
-    field public static final int BAND_XI_INTERNATIONAL = 100; // 0x64
-    field public static final int BDE_TECHNOLOGY = 180; // 0xb4
-    field public static final int BEATS_ELECTRONICS = 204; // 0xcc
-    field public static final int BEAUTIFUL_ENTERPRISE = 108; // 0x6c
-    field public static final int BEKEY = 178; // 0xb2
-    field public static final int BELKIN_INTERNATIONAL = 92; // 0x5c
-    field public static final int BINAURIC = 203; // 0xcb
-    field public static final int BIOSENTRONICS = 219; // 0xdb
-    field public static final int BLUEGIGA = 71; // 0x47
-    field public static final int BLUERADIOS = 133; // 0x85
-    field public static final int BLUETOOTH_SIG = 63; // 0x3f
-    field public static final int BLUETREK_TECHNOLOGIES = 151; // 0x97
-    field public static final int BOSE = 158; // 0x9e
-    field public static final int BRIARTEK = 109; // 0x6d
-    field public static final int BROADCOM = 15; // 0xf
-    field public static final int CAEN_RFID = 170; // 0xaa
-    field public static final int CAMBRIDGE_SILICON_RADIO = 10; // 0xa
-    field public static final int CATC = 52; // 0x34
-    field public static final int CINETIX = 175; // 0xaf
-    field public static final int CLARINOX_TECHNOLOGIES = 179; // 0xb3
-    field public static final int COLORFY = 156; // 0x9c
-    field public static final int COMMIL = 51; // 0x33
-    field public static final int CONEXANT_SYSTEMS = 28; // 0x1c
-    field public static final int CONNECTBLUE = 113; // 0x71
-    field public static final int CONTINENTAL_AUTOMOTIVE = 75; // 0x4b
-    field public static final int CONWISE_TECHNOLOGY = 66; // 0x42
-    field public static final int CREATIVE_TECHNOLOGY = 118; // 0x76
-    field public static final int C_TECHNOLOGIES = 38; // 0x26
-    field public static final int DANLERS = 225; // 0xe1
-    field public static final int DELORME_PUBLISHING_COMPANY = 128; // 0x80
-    field public static final int DEXCOM = 208; // 0xd0
-    field public static final int DIALOG_SEMICONDUCTOR = 210; // 0xd2
-    field public static final int DIGIANSWER = 12; // 0xc
-    field public static final int ECLIPSE = 53; // 0x35
-    field public static final int ECOTEST = 136; // 0x88
-    field public static final int ELGATO_SYSTEMS = 206; // 0xce
-    field public static final int EM_MICROELECTRONIC_MARIN = 90; // 0x5a
-    field public static final int EQUINOX_AG = 134; // 0x86
-    field public static final int ERICSSON_TECHNOLOGY = 0; // 0x0
-    field public static final int EVLUMA = 201; // 0xc9
-    field public static final int FREE2MOVE = 83; // 0x53
-    field public static final int FUNAI_ELECTRIC = 144; // 0x90
-    field public static final int GARMIN_INTERNATIONAL = 135; // 0x87
-    field public static final int GCT_SEMICONDUCTOR = 45; // 0x2d
-    field public static final int GELO = 200; // 0xc8
-    field public static final int GENEQ = 194; // 0xc2
-    field public static final int GENERAL_MOTORS = 104; // 0x68
-    field public static final int GENNUM = 59; // 0x3b
-    field public static final int GEOFORCE = 157; // 0x9d
-    field public static final int GIBSON_GUITARS = 98; // 0x62
-    field public static final int GN_NETCOM = 103; // 0x67
-    field public static final int GN_RESOUND = 137; // 0x89
-    field public static final int GOOGLE = 224; // 0xe0
-    field public static final int GREEN_THROTTLE_GAMES = 172; // 0xac
-    field public static final int GROUP_SENSE = 115; // 0x73
-    field public static final int HANLYNN_TECHNOLOGIES = 123; // 0x7b
-    field public static final int HARMAN_INTERNATIONAL = 87; // 0x57
-    field public static final int HEWLETT_PACKARD = 101; // 0x65
-    field public static final int HITACHI = 41; // 0x29
-    field public static final int HOSIDEN = 221; // 0xdd
-    field public static final int IBM = 3; // 0x3
-    field public static final int INFINEON_TECHNOLOGIES = 9; // 0x9
-    field public static final int INGENIEUR_SYSTEMGRUPPE_ZAHN = 171; // 0xab
-    field public static final int INTEGRATED_SILICON_SOLUTION = 65; // 0x41
-    field public static final int INTEGRATED_SYSTEM_SOLUTION = 57; // 0x39
-    field public static final int INTEL = 2; // 0x2
-    field public static final int INVENTEL = 30; // 0x1e
-    field public static final int IPEXTREME = 61; // 0x3d
-    field public static final int I_TECH_DYNAMIC_GLOBAL_DISTRIBUTION = 153; // 0x99
-    field public static final int JAWBONE = 138; // 0x8a
-    field public static final int JIANGSU_TOPPOWER_AUTOMOTIVE_ELECTRONICS = 155; // 0x9b
-    field public static final int JOHNSON_CONTROLS = 185; // 0xb9
-    field public static final int J_AND_M = 82; // 0x52
-    field public static final int KAWANTECH = 212; // 0xd4
-    field public static final int KC_TECHNOLOGY = 22; // 0x16
-    field public static final int KENSINGTON_COMPUTER_PRODUCTS_GROUP = 160; // 0xa0
-    field public static final int LAIRD_TECHNOLOGIES = 119; // 0x77
-    field public static final int LESSWIRE = 121; // 0x79
-    field public static final int LG_ELECTRONICS = 196; // 0xc4
-    field public static final int LINAK = 164; // 0xa4
-    field public static final int LUCENT = 7; // 0x7
-    field public static final int LUDUS_HELSINKI = 132; // 0x84
-    field public static final int MACRONIX = 44; // 0x2c
-    field public static final int MAGNETI_MARELLI = 169; // 0xa9
-    field public static final int MANSELLA = 33; // 0x21
-    field public static final int MARVELL = 72; // 0x48
-    field public static final int MATSUSHITA_ELECTRIC = 58; // 0x3a
-    field public static final int MC10 = 202; // 0xca
-    field public static final int MEDIATEK = 70; // 0x46
-    field public static final int MESO_INTERNATIONAL = 182; // 0xb6
-    field public static final int META_WATCH = 163; // 0xa3
-    field public static final int MEWTEL_TECHNOLOGY = 47; // 0x2f
-    field public static final int MICOMMAND = 99; // 0x63
-    field public static final int MICROCHIP_TECHNOLOGY = 205; // 0xcd
-    field public static final int MICROSOFT = 6; // 0x6
-    field public static final int MINDTREE = 106; // 0x6a
-    field public static final int MISFIT_WEARABLES = 223; // 0xdf
-    field public static final int MITEL_SEMICONDUCTOR = 16; // 0x10
-    field public static final int MITSUBISHI_ELECTRIC = 20; // 0x14
-    field public static final int MOBILIAN_CORPORATION = 55; // 0x37
-    field public static final int MONSTER = 112; // 0x70
-    field public static final int MOTOROLA = 8; // 0x8
-    field public static final int MSTAR_SEMICONDUCTOR = 122; // 0x7a
-    field public static final int MUZIK = 222; // 0xde
-    field public static final int NEC = 34; // 0x22
-    field public static final int NEC_LIGHTING = 149; // 0x95
-    field public static final int NEWLOGIC = 23; // 0x17
-    field public static final int NIKE = 120; // 0x78
-    field public static final int NINE_SOLUTIONS = 102; // 0x66
-    field public static final int NOKIA_MOBILE_PHONES = 1; // 0x1
-    field public static final int NORDIC_SEMICONDUCTOR = 89; // 0x59
-    field public static final int NORWOOD_SYSTEMS = 46; // 0x2e
-    field public static final int ODM_TECHNOLOGY = 150; // 0x96
-    field public static final int OMEGAWAVE = 174; // 0xae
-    field public static final int ONSET_COMPUTER = 197; // 0xc5
-    field public static final int OPEN_INTERFACE = 39; // 0x27
-    field public static final int OTL_DYNAMICS = 165; // 0xa5
-    field public static final int PANDA_OCEAN = 166; // 0xa6
-    field public static final int PARROT = 67; // 0x43
-    field public static final int PARTHUS_TECHNOLOGIES = 14; // 0xe
-    field public static final int PASSIF_SEMICONDUCTOR = 176; // 0xb0
-    field public static final int PETER_SYSTEMTECHNIK = 173; // 0xad
-    field public static final int PHILIPS_SEMICONDUCTORS = 37; // 0x25
-    field public static final int PLANTRONICS = 85; // 0x55
-    field public static final int POLAR_ELECTRO = 107; // 0x6b
-    field public static final int POLAR_ELECTRO_EUROPE = 209; // 0xd1
-    field public static final int PROCTER_AND_GAMBLE = 220; // 0xdc
-    field public static final int QUALCOMM = 29; // 0x1d
-    field public static final int QUALCOMM_CONNECTED_EXPERIENCES = 216; // 0xd8
-    field public static final int QUALCOMM_INNOVATION_CENTER = 184; // 0xb8
-    field public static final int QUALCOMM_LABS = 140; // 0x8c
-    field public static final int QUALCOMM_TECHNOLOGIES = 215; // 0xd7
-    field public static final int QUINTIC = 142; // 0x8e
-    field public static final int QUUPPA = 199; // 0xc7
-    field public static final int RALINK_TECHNOLOGY = 91; // 0x5b
-    field public static final int RDA_MICROELECTRONICS = 97; // 0x61
-    field public static final int REALTEK_SEMICONDUCTOR = 93; // 0x5d
-    field public static final int RED_M = 50; // 0x32
-    field public static final int RENESAS_TECHNOLOGY = 54; // 0x36
-    field public static final int RESEARCH_IN_MOTION = 60; // 0x3c
-    field public static final int RF_MICRO_DEVICES = 40; // 0x28
-    field public static final int RIVIERAWAVES = 96; // 0x60
-    field public static final int ROHDE_AND_SCHWARZ = 25; // 0x19
-    field public static final int RTX_TELECOM = 21; // 0x15
-    field public static final int SAMSUNG_ELECTRONICS = 117; // 0x75
-    field public static final int SARIS_CYCLING_GROUP = 177; // 0xb1
-    field public static final int SEERS_TECHNOLOGY = 125; // 0x7d
-    field public static final int SEIKO_EPSON = 64; // 0x40
-    field public static final int SELFLY = 198; // 0xc6
-    field public static final int SEMILINK = 226; // 0xe2
-    field public static final int SENNHEISER_COMMUNICATIONS = 130; // 0x82
-    field public static final int SHANGHAI_SUPER_SMART_ELECTRONICS = 114; // 0x72
-    field public static final int SHENZHEN_EXCELSECU_DATA_TECHNOLOGY = 193; // 0xc1
-    field public static final int SIGNIA_TECHNOLOGIES = 27; // 0x1b
-    field public static final int SILICON_WAVE = 11; // 0xb
-    field public static final int SIRF_TECHNOLOGY = 80; // 0x50
-    field public static final int SOCKET_MOBILE = 68; // 0x44
-    field public static final int SONY_ERICSSON = 86; // 0x56
-    field public static final int SOUND_ID = 111; // 0x6f
-    field public static final int SPORTS_TRACKING_TECHNOLOGIES = 126; // 0x7e
-    field public static final int SR_MEDIZINELEKTRONIK = 161; // 0xa1
-    field public static final int STACCATO_COMMUNICATIONS = 77; // 0x4d
-    field public static final int STALMART_TECHNOLOGY = 191; // 0xbf
-    field public static final int STARKEY_LABORATORIES = 186; // 0xba
-    field public static final int STOLLMAN_E_PLUS_V = 143; // 0x8f
-    field public static final int STONESTREET_ONE = 94; // 0x5e
-    field public static final int ST_MICROELECTRONICS = 48; // 0x30
-    field public static final int SUMMIT_DATA_COMMUNICATIONS = 110; // 0x6e
-    field public static final int SUUNTO = 159; // 0x9f
-    field public static final int SWIRL_NETWORKS = 181; // 0xb5
-    field public static final int SYMBOL_TECHNOLOGIES = 42; // 0x2a
-    field public static final int SYNOPSYS = 49; // 0x31
-    field public static final int SYSTEMS_AND_CHIPS = 62; // 0x3e
-    field public static final int S_POWER_ELECTRONICS = 187; // 0xbb
-    field public static final int TAIXINGBANG_TECHNOLOGY = 211; // 0xd3
-    field public static final int TENOVIS = 43; // 0x2b
-    field public static final int TERAX = 56; // 0x38
-    field public static final int TEXAS_INSTRUMENTS = 13; // 0xd
-    field public static final int THINKOPTICS = 146; // 0x92
-    field public static final int THREECOM = 5; // 0x5
-    field public static final int THREE_DIJOY = 84; // 0x54
-    field public static final int THREE_DSP = 73; // 0x49
-    field public static final int TIMEKEEPING_SYSTEMS = 131; // 0x83
-    field public static final int TIMEX_GROUP_USA = 214; // 0xd6
-    field public static final int TOPCORN_POSITIONING_SYSTEMS = 139; // 0x8b
-    field public static final int TOSHIBA = 4; // 0x4
-    field public static final int TRANSILICA = 24; // 0x18
-    field public static final int TRELAB = 183; // 0xb7
-    field public static final int TTPCOM = 26; // 0x1a
-    field public static final int TXTR = 218; // 0xda
-    field public static final int TZERO_TECHNOLOGIES = 81; // 0x51
-    field public static final int UNIVERSAL_ELECTRONICS = 147; // 0x93
-    field public static final int VERTU = 162; // 0xa2
-    field public static final int VISTEON = 167; // 0xa7
-    field public static final int VIZIO = 88; // 0x58
-    field public static final int VOYETRA_TURTLE_BEACH = 217; // 0xd9
-    field public static final int WAVEPLUS_TECHNOLOGY = 35; // 0x23
-    field public static final int WICENTRIC = 95; // 0x5f
-    field public static final int WIDCOMM = 17; // 0x11
-    field public static final int WUXI_VIMICRO = 129; // 0x81
-    field public static final int ZEEVO = 18; // 0x12
-    field public static final int ZER01_TV = 152; // 0x98
-    field public static final int ZOMM = 116; // 0x74
-    field public static final int ZSCAN_SOFTWARE = 141; // 0x8d
-  }
-
-  public final class BluetoothClass implements android.os.Parcelable {
-    method public int describeContents();
-    method public boolean doesClassMatch(int);
-    method public int getDeviceClass();
-    method public int getMajorDeviceClass();
-    method public boolean hasService(int);
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothClass> CREATOR;
-    field public static final int PROFILE_A2DP = 1; // 0x1
-    field public static final int PROFILE_HEADSET = 0; // 0x0
-    field public static final int PROFILE_HID = 3; // 0x3
-  }
-
-  public static class BluetoothClass.Device {
-    ctor public BluetoothClass.Device();
-    field public static final int AUDIO_VIDEO_CAMCORDER = 1076; // 0x434
-    field public static final int AUDIO_VIDEO_CAR_AUDIO = 1056; // 0x420
-    field public static final int AUDIO_VIDEO_HANDSFREE = 1032; // 0x408
-    field public static final int AUDIO_VIDEO_HEADPHONES = 1048; // 0x418
-    field public static final int AUDIO_VIDEO_HIFI_AUDIO = 1064; // 0x428
-    field public static final int AUDIO_VIDEO_LOUDSPEAKER = 1044; // 0x414
-    field public static final int AUDIO_VIDEO_MICROPHONE = 1040; // 0x410
-    field public static final int AUDIO_VIDEO_PORTABLE_AUDIO = 1052; // 0x41c
-    field public static final int AUDIO_VIDEO_SET_TOP_BOX = 1060; // 0x424
-    field public static final int AUDIO_VIDEO_UNCATEGORIZED = 1024; // 0x400
-    field public static final int AUDIO_VIDEO_VCR = 1068; // 0x42c
-    field public static final int AUDIO_VIDEO_VIDEO_CAMERA = 1072; // 0x430
-    field public static final int AUDIO_VIDEO_VIDEO_CONFERENCING = 1088; // 0x440
-    field public static final int AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER = 1084; // 0x43c
-    field public static final int AUDIO_VIDEO_VIDEO_GAMING_TOY = 1096; // 0x448
-    field public static final int AUDIO_VIDEO_VIDEO_MONITOR = 1080; // 0x438
-    field public static final int AUDIO_VIDEO_WEARABLE_HEADSET = 1028; // 0x404
-    field public static final int COMPUTER_DESKTOP = 260; // 0x104
-    field public static final int COMPUTER_HANDHELD_PC_PDA = 272; // 0x110
-    field public static final int COMPUTER_LAPTOP = 268; // 0x10c
-    field public static final int COMPUTER_PALM_SIZE_PC_PDA = 276; // 0x114
-    field public static final int COMPUTER_SERVER = 264; // 0x108
-    field public static final int COMPUTER_UNCATEGORIZED = 256; // 0x100
-    field public static final int COMPUTER_WEARABLE = 280; // 0x118
-    field public static final int HEALTH_BLOOD_PRESSURE = 2308; // 0x904
-    field public static final int HEALTH_DATA_DISPLAY = 2332; // 0x91c
-    field public static final int HEALTH_GLUCOSE = 2320; // 0x910
-    field public static final int HEALTH_PULSE_OXIMETER = 2324; // 0x914
-    field public static final int HEALTH_PULSE_RATE = 2328; // 0x918
-    field public static final int HEALTH_THERMOMETER = 2312; // 0x908
-    field public static final int HEALTH_UNCATEGORIZED = 2304; // 0x900
-    field public static final int HEALTH_WEIGHING = 2316; // 0x90c
-    field public static final int PHONE_CELLULAR = 516; // 0x204
-    field public static final int PHONE_CORDLESS = 520; // 0x208
-    field public static final int PHONE_ISDN = 532; // 0x214
-    field public static final int PHONE_MODEM_OR_GATEWAY = 528; // 0x210
-    field public static final int PHONE_SMART = 524; // 0x20c
-    field public static final int PHONE_UNCATEGORIZED = 512; // 0x200
-    field public static final int TOY_CONTROLLER = 2064; // 0x810
-    field public static final int TOY_DOLL_ACTION_FIGURE = 2060; // 0x80c
-    field public static final int TOY_GAME = 2068; // 0x814
-    field public static final int TOY_ROBOT = 2052; // 0x804
-    field public static final int TOY_UNCATEGORIZED = 2048; // 0x800
-    field public static final int TOY_VEHICLE = 2056; // 0x808
-    field public static final int WEARABLE_GLASSES = 1812; // 0x714
-    field public static final int WEARABLE_HELMET = 1808; // 0x710
-    field public static final int WEARABLE_JACKET = 1804; // 0x70c
-    field public static final int WEARABLE_PAGER = 1800; // 0x708
-    field public static final int WEARABLE_UNCATEGORIZED = 1792; // 0x700
-    field public static final int WEARABLE_WRIST_WATCH = 1796; // 0x704
-  }
-
-  public static class BluetoothClass.Device.Major {
-    ctor public BluetoothClass.Device.Major();
-    field public static final int AUDIO_VIDEO = 1024; // 0x400
-    field public static final int COMPUTER = 256; // 0x100
-    field public static final int HEALTH = 2304; // 0x900
-    field public static final int IMAGING = 1536; // 0x600
-    field public static final int MISC = 0; // 0x0
-    field public static final int NETWORKING = 768; // 0x300
-    field public static final int PERIPHERAL = 1280; // 0x500
-    field public static final int PHONE = 512; // 0x200
-    field public static final int TOY = 2048; // 0x800
-    field public static final int UNCATEGORIZED = 7936; // 0x1f00
-    field public static final int WEARABLE = 1792; // 0x700
-  }
-
-  public static final class BluetoothClass.Service {
-    ctor public BluetoothClass.Service();
-    field public static final int AUDIO = 2097152; // 0x200000
-    field public static final int CAPTURE = 524288; // 0x80000
-    field public static final int INFORMATION = 8388608; // 0x800000
-    field public static final int LE_AUDIO = 16384; // 0x4000
-    field public static final int LIMITED_DISCOVERABILITY = 8192; // 0x2000
-    field public static final int NETWORKING = 131072; // 0x20000
-    field public static final int OBJECT_TRANSFER = 1048576; // 0x100000
-    field public static final int POSITIONING = 65536; // 0x10000
-    field public static final int RENDER = 262144; // 0x40000
-    field public static final int TELEPHONY = 4194304; // 0x400000
-  }
-
-  public final class BluetoothCodecConfig implements android.os.Parcelable {
-    ctor public BluetoothCodecConfig(int);
-    method public int describeContents();
-    method public int getBitsPerSample();
-    method public int getChannelMode();
-    method public int getCodecPriority();
-    method public long getCodecSpecific1();
-    method public long getCodecSpecific2();
-    method public long getCodecSpecific3();
-    method public long getCodecSpecific4();
-    method public int getCodecType();
-    method public static int getMaxCodecType();
-    method public int getSampleRate();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final int BITS_PER_SAMPLE_16 = 1; // 0x1
-    field public static final int BITS_PER_SAMPLE_24 = 2; // 0x2
-    field public static final int BITS_PER_SAMPLE_32 = 4; // 0x4
-    field public static final int BITS_PER_SAMPLE_NONE = 0; // 0x0
-    field public static final int CHANNEL_MODE_MONO = 1; // 0x1
-    field public static final int CHANNEL_MODE_NONE = 0; // 0x0
-    field public static final int CHANNEL_MODE_STEREO = 2; // 0x2
-    field public static final int CODEC_PRIORITY_DEFAULT = 0; // 0x0
-    field public static final int CODEC_PRIORITY_DISABLED = -1; // 0xffffffff
-    field public static final int CODEC_PRIORITY_HIGHEST = 1000000; // 0xf4240
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothCodecConfig> CREATOR;
-    field public static final int SAMPLE_RATE_176400 = 16; // 0x10
-    field public static final int SAMPLE_RATE_192000 = 32; // 0x20
-    field public static final int SAMPLE_RATE_44100 = 1; // 0x1
-    field public static final int SAMPLE_RATE_48000 = 2; // 0x2
-    field public static final int SAMPLE_RATE_88200 = 4; // 0x4
-    field public static final int SAMPLE_RATE_96000 = 8; // 0x8
-    field public static final int SAMPLE_RATE_NONE = 0; // 0x0
-    field public static final int SOURCE_CODEC_TYPE_AAC = 1; // 0x1
-    field public static final int SOURCE_CODEC_TYPE_APTX = 2; // 0x2
-    field public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; // 0x3
-    field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
-    field public static final int SOURCE_CODEC_TYPE_LC3 = 5; // 0x5
-    field public static final int SOURCE_CODEC_TYPE_LDAC = 4; // 0x4
-    field public static final int SOURCE_CODEC_TYPE_SBC = 0; // 0x0
-  }
-
-  public static final class BluetoothCodecConfig.Builder {
-    ctor public BluetoothCodecConfig.Builder();
-    method @NonNull public android.bluetooth.BluetoothCodecConfig build();
-    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setBitsPerSample(int);
-    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setChannelMode(int);
-    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecPriority(int);
-    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific1(long);
-    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific2(long);
-    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific3(long);
-    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific4(long);
-    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecType(int);
-    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setSampleRate(int);
-  }
-
-  public final class BluetoothCodecStatus implements android.os.Parcelable {
-    ctor public BluetoothCodecStatus(@Nullable android.bluetooth.BluetoothCodecConfig, @Nullable java.util.List<android.bluetooth.BluetoothCodecConfig>, @Nullable java.util.List<android.bluetooth.BluetoothCodecConfig>);
-    method public int describeContents();
-    method @Nullable public android.bluetooth.BluetoothCodecConfig getCodecConfig();
-    method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getCodecsLocalCapabilities();
-    method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getCodecsSelectableCapabilities();
-    method public boolean isCodecConfigSelectable(@Nullable android.bluetooth.BluetoothCodecConfig);
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothCodecStatus> CREATOR;
-    field public static final String EXTRA_CODEC_STATUS = "android.bluetooth.extra.CODEC_STATUS";
-  }
-
-  public final class BluetoothCsipSetCoordinator implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
-    method public void close();
-    method protected void finalize();
-    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
-    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CSIS_CONNECTION_STATE_CHANGED = "android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED";
-  }
-
-  public final class BluetoothDevice implements android.os.Parcelable {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int, android.os.Handler);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBond();
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createInsecureL2capChannel(int) throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createInsecureRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createL2capChannel(int) throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
-    method public int describeContents();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean fetchUuidsWithSdp();
-    method public String getAddress();
-    method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getAlias();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothClass getBluetoothClass();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getBondState();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getType();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.os.ParcelUuid[] getUuids();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int setAlias(@Nullable String);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setPairingConfirmation(boolean);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setPin(byte[]);
-    method public void writeToParcel(android.os.Parcel, int);
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ACL_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ACL_DISCONNECTED = "android.bluetooth.device.action.ACL_DISCONNECTED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ACL_DISCONNECT_REQUESTED = "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ALIAS_CHANGED = "android.bluetooth.device.action.ALIAS_CHANGED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_BOND_STATE_CHANGED = "android.bluetooth.device.action.BOND_STATE_CHANGED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CLASS_CHANGED = "android.bluetooth.device.action.CLASS_CHANGED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_FOUND = "android.bluetooth.device.action.FOUND";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_NAME_CHANGED = "android.bluetooth.device.action.NAME_CHANGED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_UUID = "android.bluetooth.device.action.UUID";
-    field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
-    field public static final int ADDRESS_TYPE_RANDOM = 1; // 0x1
-    field public static final int BOND_BONDED = 12; // 0xc
-    field public static final int BOND_BONDING = 11; // 0xb
-    field public static final int BOND_NONE = 10; // 0xa
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothDevice> CREATOR;
-    field public static final int DEVICE_TYPE_CLASSIC = 1; // 0x1
-    field public static final int DEVICE_TYPE_DUAL = 3; // 0x3
-    field public static final int DEVICE_TYPE_LE = 2; // 0x2
-    field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int ERROR = -2147483648; // 0x80000000
-    field public static final String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE";
-    field public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS";
-    field public static final String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE";
-    field public static final String EXTRA_IS_COORDINATED_SET_MEMBER = "android.bluetooth.extra.IS_COORDINATED_SET_MEMBER";
-    field public static final String EXTRA_NAME = "android.bluetooth.device.extra.NAME";
-    field public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY";
-    field public static final String EXTRA_PAIRING_VARIANT = "android.bluetooth.device.extra.PAIRING_VARIANT";
-    field public static final String EXTRA_PREVIOUS_BOND_STATE = "android.bluetooth.device.extra.PREVIOUS_BOND_STATE";
-    field public static final String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI";
-    field public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
-    field public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; // 0x2
-    field public static final int PAIRING_VARIANT_PIN = 0; // 0x0
-    field public static final int PHY_LE_1M = 1; // 0x1
-    field public static final int PHY_LE_1M_MASK = 1; // 0x1
-    field public static final int PHY_LE_2M = 2; // 0x2
-    field public static final int PHY_LE_2M_MASK = 2; // 0x2
-    field public static final int PHY_LE_CODED = 3; // 0x3
-    field public static final int PHY_LE_CODED_MASK = 4; // 0x4
-    field public static final int PHY_OPTION_NO_PREFERRED = 0; // 0x0
-    field public static final int PHY_OPTION_S2 = 1; // 0x1
-    field public static final int PHY_OPTION_S8 = 2; // 0x2
-    field public static final int TRANSPORT_AUTO = 0; // 0x0
-    field public static final int TRANSPORT_BREDR = 1; // 0x1
-    field public static final int TRANSPORT_LE = 2; // 0x2
-  }
-
-  public final class BluetoothGatt implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void abortReliableWrite();
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void abortReliableWrite(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean beginReliableWrite();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void close();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void disconnect();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean discoverServices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean executeReliableWrite();
-    method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @Deprecated public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method public android.bluetooth.BluetoothDevice getDevice();
-    method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
-    method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readDescriptor(android.bluetooth.BluetoothGattDescriptor);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readRemoteRssi();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean requestConnectionPriority(int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean requestMtu(int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setPreferredPhy(int, int, int);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int writeCharacteristic(@NonNull android.bluetooth.BluetoothGattCharacteristic, @NonNull byte[], int);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int writeDescriptor(@NonNull android.bluetooth.BluetoothGattDescriptor, @NonNull byte[]);
-    field public static final int CONNECTION_PRIORITY_BALANCED = 0; // 0x0
-    field public static final int CONNECTION_PRIORITY_HIGH = 1; // 0x1
-    field public static final int CONNECTION_PRIORITY_LOW_POWER = 2; // 0x2
-    field public static final int GATT_CONNECTION_CONGESTED = 143; // 0x8f
-    field public static final int GATT_FAILURE = 257; // 0x101
-    field public static final int GATT_INSUFFICIENT_AUTHENTICATION = 5; // 0x5
-    field public static final int GATT_INSUFFICIENT_AUTHORIZATION = 8; // 0x8
-    field public static final int GATT_INSUFFICIENT_ENCRYPTION = 15; // 0xf
-    field public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 13; // 0xd
-    field public static final int GATT_INVALID_OFFSET = 7; // 0x7
-    field public static final int GATT_READ_NOT_PERMITTED = 2; // 0x2
-    field public static final int GATT_REQUEST_NOT_SUPPORTED = 6; // 0x6
-    field public static final int GATT_SUCCESS = 0; // 0x0
-    field public static final int GATT_WRITE_NOT_PERMITTED = 3; // 0x3
-  }
-
-  public abstract class BluetoothGattCallback {
-    ctor public BluetoothGattCallback();
-    method @Deprecated public void onCharacteristicChanged(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic);
-    method public void onCharacteristicChanged(@NonNull android.bluetooth.BluetoothGatt, @NonNull android.bluetooth.BluetoothGattCharacteristic, @NonNull byte[]);
-    method @Deprecated public void onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
-    method public void onCharacteristicRead(@NonNull android.bluetooth.BluetoothGatt, @NonNull android.bluetooth.BluetoothGattCharacteristic, @NonNull byte[], int);
-    method public void onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
-    method public void onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int);
-    method @Deprecated public void onDescriptorRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
-    method public void onDescriptorRead(@NonNull android.bluetooth.BluetoothGatt, @NonNull android.bluetooth.BluetoothGattDescriptor, int, @NonNull byte[]);
-    method public void onDescriptorWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
-    method public void onMtuChanged(android.bluetooth.BluetoothGatt, int, int);
-    method public void onPhyRead(android.bluetooth.BluetoothGatt, int, int, int);
-    method public void onPhyUpdate(android.bluetooth.BluetoothGatt, int, int, int);
-    method public void onReadRemoteRssi(android.bluetooth.BluetoothGatt, int, int);
-    method public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt, int);
-    method public void onServiceChanged(@NonNull android.bluetooth.BluetoothGatt);
-    method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int);
-  }
-
-  public class BluetoothGattCharacteristic implements android.os.Parcelable {
-    ctor public BluetoothGattCharacteristic(java.util.UUID, int, int);
-    method public boolean addDescriptor(android.bluetooth.BluetoothGattDescriptor);
-    method public int describeContents();
-    method public android.bluetooth.BluetoothGattDescriptor getDescriptor(java.util.UUID);
-    method public java.util.List<android.bluetooth.BluetoothGattDescriptor> getDescriptors();
-    method @Deprecated public Float getFloatValue(int, int);
-    method public int getInstanceId();
-    method @Deprecated public Integer getIntValue(int, int);
-    method public int getPermissions();
-    method public int getProperties();
-    method public android.bluetooth.BluetoothGattService getService();
-    method @Deprecated public String getStringValue(int);
-    method public java.util.UUID getUuid();
-    method @Deprecated public byte[] getValue();
-    method public int getWriteType();
-    method @Deprecated public boolean setValue(byte[]);
-    method @Deprecated public boolean setValue(int, int, int);
-    method @Deprecated public boolean setValue(int, int, int, int);
-    method @Deprecated public boolean setValue(String);
-    method public void setWriteType(int);
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattCharacteristic> CREATOR;
-    field public static final int FORMAT_FLOAT = 52; // 0x34
-    field public static final int FORMAT_SFLOAT = 50; // 0x32
-    field public static final int FORMAT_SINT16 = 34; // 0x22
-    field public static final int FORMAT_SINT32 = 36; // 0x24
-    field public static final int FORMAT_SINT8 = 33; // 0x21
-    field public static final int FORMAT_UINT16 = 18; // 0x12
-    field public static final int FORMAT_UINT32 = 20; // 0x14
-    field public static final int FORMAT_UINT8 = 17; // 0x11
-    field public static final int PERMISSION_READ = 1; // 0x1
-    field public static final int PERMISSION_READ_ENCRYPTED = 2; // 0x2
-    field public static final int PERMISSION_READ_ENCRYPTED_MITM = 4; // 0x4
-    field public static final int PERMISSION_WRITE = 16; // 0x10
-    field public static final int PERMISSION_WRITE_ENCRYPTED = 32; // 0x20
-    field public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 64; // 0x40
-    field public static final int PERMISSION_WRITE_SIGNED = 128; // 0x80
-    field public static final int PERMISSION_WRITE_SIGNED_MITM = 256; // 0x100
-    field public static final int PROPERTY_BROADCAST = 1; // 0x1
-    field public static final int PROPERTY_EXTENDED_PROPS = 128; // 0x80
-    field public static final int PROPERTY_INDICATE = 32; // 0x20
-    field public static final int PROPERTY_NOTIFY = 16; // 0x10
-    field public static final int PROPERTY_READ = 2; // 0x2
-    field public static final int PROPERTY_SIGNED_WRITE = 64; // 0x40
-    field public static final int PROPERTY_WRITE = 8; // 0x8
-    field public static final int PROPERTY_WRITE_NO_RESPONSE = 4; // 0x4
-    field public static final int WRITE_TYPE_DEFAULT = 2; // 0x2
-    field public static final int WRITE_TYPE_NO_RESPONSE = 1; // 0x1
-    field public static final int WRITE_TYPE_SIGNED = 4; // 0x4
-    field protected java.util.List<android.bluetooth.BluetoothGattDescriptor> mDescriptors;
-  }
-
-  public class BluetoothGattDescriptor implements android.os.Parcelable {
-    ctor public BluetoothGattDescriptor(java.util.UUID, int);
-    method public int describeContents();
-    method public android.bluetooth.BluetoothGattCharacteristic getCharacteristic();
-    method public int getPermissions();
-    method public java.util.UUID getUuid();
-    method @Deprecated public byte[] getValue();
-    method @Deprecated public boolean setValue(byte[]);
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattDescriptor> CREATOR;
-    field public static final byte[] DISABLE_NOTIFICATION_VALUE;
-    field public static final byte[] ENABLE_INDICATION_VALUE;
-    field public static final byte[] ENABLE_NOTIFICATION_VALUE;
-    field public static final int PERMISSION_READ = 1; // 0x1
-    field public static final int PERMISSION_READ_ENCRYPTED = 2; // 0x2
-    field public static final int PERMISSION_READ_ENCRYPTED_MITM = 4; // 0x4
-    field public static final int PERMISSION_WRITE = 16; // 0x10
-    field public static final int PERMISSION_WRITE_ENCRYPTED = 32; // 0x20
-    field public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 64; // 0x40
-    field public static final int PERMISSION_WRITE_SIGNED = 128; // 0x80
-    field public static final int PERMISSION_WRITE_SIGNED_MITM = 256; // 0x100
-  }
-
-  public final class BluetoothGattServer implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean addService(android.bluetooth.BluetoothGattService);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void cancelConnection(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void clearServices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void close();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect(android.bluetooth.BluetoothDevice, boolean);
-    method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
-    method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int notifyCharacteristicChanged(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothGattCharacteristic, boolean, @NonNull byte[]);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeService(android.bluetooth.BluetoothGattService);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setPreferredPhy(android.bluetooth.BluetoothDevice, int, int, int);
-  }
-
-  public abstract class BluetoothGattServerCallback {
-    ctor public BluetoothGattServerCallback();
-    method public void onCharacteristicReadRequest(android.bluetooth.BluetoothDevice, int, int, android.bluetooth.BluetoothGattCharacteristic);
-    method public void onCharacteristicWriteRequest(android.bluetooth.BluetoothDevice, int, android.bluetooth.BluetoothGattCharacteristic, boolean, boolean, int, byte[]);
-    method public void onConnectionStateChange(android.bluetooth.BluetoothDevice, int, int);
-    method public void onDescriptorReadRequest(android.bluetooth.BluetoothDevice, int, int, android.bluetooth.BluetoothGattDescriptor);
-    method public void onDescriptorWriteRequest(android.bluetooth.BluetoothDevice, int, android.bluetooth.BluetoothGattDescriptor, boolean, boolean, int, byte[]);
-    method public void onExecuteWrite(android.bluetooth.BluetoothDevice, int, boolean);
-    method public void onMtuChanged(android.bluetooth.BluetoothDevice, int);
-    method public void onNotificationSent(android.bluetooth.BluetoothDevice, int);
-    method public void onPhyRead(android.bluetooth.BluetoothDevice, int, int, int);
-    method public void onPhyUpdate(android.bluetooth.BluetoothDevice, int, int, int);
-    method public void onServiceAdded(int, android.bluetooth.BluetoothGattService);
-  }
-
-  public class BluetoothGattService implements android.os.Parcelable {
-    ctor public BluetoothGattService(java.util.UUID, int);
-    method public boolean addCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
-    method public boolean addService(android.bluetooth.BluetoothGattService);
-    method public int describeContents();
-    method public android.bluetooth.BluetoothGattCharacteristic getCharacteristic(java.util.UUID);
-    method public java.util.List<android.bluetooth.BluetoothGattCharacteristic> getCharacteristics();
-    method public java.util.List<android.bluetooth.BluetoothGattService> getIncludedServices();
-    method public int getInstanceId();
-    method public int getType();
-    method public java.util.UUID getUuid();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattService> CREATOR;
-    field public static final int SERVICE_TYPE_PRIMARY = 0; // 0x0
-    field public static final int SERVICE_TYPE_SECONDARY = 1; // 0x1
-    field protected java.util.List<android.bluetooth.BluetoothGattCharacteristic> mCharacteristics;
-    field protected java.util.List<android.bluetooth.BluetoothGattService> mIncludedServices;
-  }
-
-  public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isAudioConnected(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isNoiseReductionSupported(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isVoiceRecognitionSupported(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, String, String);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean startVoiceRecognition(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean stopVoiceRecognition(android.bluetooth.BluetoothDevice);
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_AUDIO_STATE_CHANGED = "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
-    field public static final int AT_CMD_TYPE_ACTION = 4; // 0x4
-    field public static final int AT_CMD_TYPE_BASIC = 3; // 0x3
-    field public static final int AT_CMD_TYPE_READ = 0; // 0x0
-    field public static final int AT_CMD_TYPE_SET = 2; // 0x2
-    field public static final int AT_CMD_TYPE_TEST = 1; // 0x1
-    field public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
-    field public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
-    field public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
-    field public static final int STATE_AUDIO_CONNECTED = 12; // 0xc
-    field public static final int STATE_AUDIO_CONNECTING = 11; // 0xb
-    field public static final int STATE_AUDIO_DISCONNECTED = 10; // 0xa
-    field public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
-    field public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = "android.bluetooth.headset.intent.category.companyid";
-  }
-
-  @Deprecated public final class BluetoothHealth implements android.bluetooth.BluetoothProfile {
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.os.ParcelFileDescriptor getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean registerSinkAppConfiguration(String, int, android.bluetooth.BluetoothHealthCallback);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration);
-    field @Deprecated public static final int APP_CONFIG_REGISTRATION_FAILURE = 1; // 0x1
-    field @Deprecated public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0; // 0x0
-    field @Deprecated public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3; // 0x3
-    field @Deprecated public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2; // 0x2
-    field @Deprecated public static final int CHANNEL_TYPE_RELIABLE = 10; // 0xa
-    field @Deprecated public static final int CHANNEL_TYPE_STREAMING = 11; // 0xb
-    field @Deprecated public static final int SINK_ROLE = 2; // 0x2
-    field @Deprecated public static final int SOURCE_ROLE = 1; // 0x1
-    field @Deprecated public static final int STATE_CHANNEL_CONNECTED = 2; // 0x2
-    field @Deprecated public static final int STATE_CHANNEL_CONNECTING = 1; // 0x1
-    field @Deprecated public static final int STATE_CHANNEL_DISCONNECTED = 0; // 0x0
-    field @Deprecated public static final int STATE_CHANNEL_DISCONNECTING = 3; // 0x3
-  }
-
-  @Deprecated public final class BluetoothHealthAppConfiguration implements android.os.Parcelable {
-    method @Deprecated public int describeContents();
-    method @Deprecated public int getDataType();
-    method @Deprecated public String getName();
-    method @Deprecated public int getRole();
-    method @Deprecated public void writeToParcel(android.os.Parcel, int);
-    field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothHealthAppConfiguration> CREATOR;
-  }
-
-  @Deprecated public abstract class BluetoothHealthCallback {
-    ctor @Deprecated public BluetoothHealthCallback();
-    method @Deprecated @BinderThread public void onHealthAppConfigurationStatusChange(android.bluetooth.BluetoothHealthAppConfiguration, int);
-    method @Deprecated @BinderThread public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int);
-  }
-
-  public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    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[]);
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
-  }
-
-  public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean registerApp(android.bluetooth.BluetoothHidDeviceAppSdpSettings, android.bluetooth.BluetoothHidDeviceAppQosSettings, android.bluetooth.BluetoothHidDeviceAppQosSettings, java.util.concurrent.Executor, android.bluetooth.BluetoothHidDevice.Callback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean replyReport(android.bluetooth.BluetoothDevice, byte, byte, byte[]);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean reportError(android.bluetooth.BluetoothDevice, byte);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendReport(android.bluetooth.BluetoothDevice, int, byte[]);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean unregisterApp();
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
-    field public static final byte ERROR_RSP_INVALID_PARAM = 4; // 0x4
-    field public static final byte ERROR_RSP_INVALID_RPT_ID = 2; // 0x2
-    field public static final byte ERROR_RSP_NOT_READY = 1; // 0x1
-    field public static final byte ERROR_RSP_SUCCESS = 0; // 0x0
-    field public static final byte ERROR_RSP_UNKNOWN = 14; // 0xe
-    field public static final byte ERROR_RSP_UNSUPPORTED_REQ = 3; // 0x3
-    field public static final byte PROTOCOL_BOOT_MODE = 0; // 0x0
-    field public static final byte PROTOCOL_REPORT_MODE = 1; // 0x1
-    field public static final byte REPORT_TYPE_FEATURE = 3; // 0x3
-    field public static final byte REPORT_TYPE_INPUT = 1; // 0x1
-    field public static final byte REPORT_TYPE_OUTPUT = 2; // 0x2
-    field public static final byte SUBCLASS1_COMBO = -64; // 0xffffffc0
-    field public static final byte SUBCLASS1_KEYBOARD = 64; // 0x40
-    field public static final byte SUBCLASS1_MOUSE = -128; // 0xffffff80
-    field public static final byte SUBCLASS1_NONE = 0; // 0x0
-    field public static final byte SUBCLASS2_CARD_READER = 6; // 0x6
-    field public static final byte SUBCLASS2_DIGITIZER_TABLET = 5; // 0x5
-    field public static final byte SUBCLASS2_GAMEPAD = 2; // 0x2
-    field public static final byte SUBCLASS2_JOYSTICK = 1; // 0x1
-    field public static final byte SUBCLASS2_REMOTE_CONTROL = 3; // 0x3
-    field public static final byte SUBCLASS2_SENSING_DEVICE = 4; // 0x4
-    field public static final byte SUBCLASS2_UNCATEGORIZED = 0; // 0x0
-  }
-
-  public abstract static class BluetoothHidDevice.Callback {
-    ctor public BluetoothHidDevice.Callback();
-    method public void onAppStatusChanged(android.bluetooth.BluetoothDevice, boolean);
-    method public void onConnectionStateChanged(android.bluetooth.BluetoothDevice, int);
-    method public void onGetReport(android.bluetooth.BluetoothDevice, byte, byte, int);
-    method public void onInterruptData(android.bluetooth.BluetoothDevice, byte, byte[]);
-    method public void onSetProtocol(android.bluetooth.BluetoothDevice, byte);
-    method public void onSetReport(android.bluetooth.BluetoothDevice, byte, byte, byte[]);
-    method public void onVirtualCableUnplug(android.bluetooth.BluetoothDevice);
-  }
-
-  public final class BluetoothHidDeviceAppQosSettings implements android.os.Parcelable {
-    ctor public BluetoothHidDeviceAppQosSettings(int, int, int, int, int, int);
-    method public int describeContents();
-    method public int getDelayVariation();
-    method public int getLatency();
-    method public int getPeakBandwidth();
-    method public int getServiceType();
-    method public int getTokenBucketSize();
-    method public int getTokenRate();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothHidDeviceAppQosSettings> CREATOR;
-    field public static final int MAX = -1; // 0xffffffff
-    field public static final int SERVICE_BEST_EFFORT = 1; // 0x1
-    field public static final int SERVICE_GUARANTEED = 2; // 0x2
-    field public static final int SERVICE_NO_TRAFFIC = 0; // 0x0
-  }
-
-  public final class BluetoothHidDeviceAppSdpSettings implements android.os.Parcelable {
-    ctor public BluetoothHidDeviceAppSdpSettings(String, String, String, byte, byte[]);
-    method public int describeContents();
-    method public String getDescription();
-    method public byte[] getDescriptors();
-    method public String getName();
-    method public String getProvider();
-    method public byte getSubclass();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothHidDeviceAppSdpSettings> CREATOR;
-  }
-
-  public final class BluetoothLeAudio implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
-    method public void close();
-    method protected void finalize();
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothDevice getConnectedGroupLeadDevice(int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getGroupId(@NonNull android.bluetooth.BluetoothDevice);
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
-  }
-
-  public final class BluetoothLeAudioCodecConfig {
-    method @NonNull public String getCodecName();
-    method public int getCodecType();
-    method public static int getMaxCodecType();
-    field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
-    field public static final int SOURCE_CODEC_TYPE_LC3 = 0; // 0x0
-  }
-
-  public static final class BluetoothLeAudioCodecConfig.Builder {
-    ctor public BluetoothLeAudioCodecConfig.Builder();
-    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig build();
-    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setCodecType(int);
-  }
-
-  public final class BluetoothManager {
-    method public android.bluetooth.BluetoothAdapter getAdapter();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice, int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int, int[]);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGattServer openGattServer(android.content.Context, android.bluetooth.BluetoothGattServerCallback);
-  }
-
-  public interface BluetoothProfile {
-    method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method public int getConnectionState(android.bluetooth.BluetoothDevice);
-    method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    field public static final int A2DP = 2; // 0x2
-    field public static final int CSIP_SET_COORDINATOR = 25; // 0x19
-    field public static final String EXTRA_PREVIOUS_STATE = "android.bluetooth.profile.extra.PREVIOUS_STATE";
-    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
-    field public static final int HID_DEVICE = 19; // 0x13
-    field public static final int LE_AUDIO = 22; // 0x16
-    field public static final int SAP = 10; // 0xa
-    field public static final int STATE_CONNECTED = 2; // 0x2
-    field public static final int STATE_CONNECTING = 1; // 0x1
-    field public static final int STATE_DISCONNECTED = 0; // 0x0
-    field public static final int STATE_DISCONNECTING = 3; // 0x3
-  }
-
-  public static interface BluetoothProfile.ServiceListener {
-    method public void onServiceConnected(int, android.bluetooth.BluetoothProfile);
-    method public void onServiceDisconnected(int);
-  }
-
-  public final class BluetoothServerSocket implements java.io.Closeable {
-    method public android.bluetooth.BluetoothSocket accept() throws java.io.IOException;
-    method public android.bluetooth.BluetoothSocket accept(int) throws java.io.IOException;
-    method public void close() throws java.io.IOException;
-    method public int getPsm();
-  }
-
-  public final class BluetoothSocket implements java.io.Closeable {
-    method public void close() throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void connect() throws java.io.IOException;
-    method public int getConnectionType();
-    method public java.io.InputStream getInputStream() throws java.io.IOException;
-    method public int getMaxReceivePacketSize();
-    method public int getMaxTransmitPacketSize();
-    method public java.io.OutputStream getOutputStream() throws java.io.IOException;
-    method public android.bluetooth.BluetoothDevice getRemoteDevice();
-    method public boolean isConnected();
-    field public static final int TYPE_L2CAP = 3; // 0x3
-    field public static final int TYPE_RFCOMM = 1; // 0x1
-    field public static final int TYPE_SCO = 2; // 0x2
-  }
-
-  public final class BluetoothStatusCodes {
-    field public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2; // 0x2
-    field public static final int ERROR_BLUETOOTH_NOT_ENABLED = 1; // 0x1
-    field public static final int ERROR_DEVICE_NOT_BONDED = 3; // 0x3
-    field public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 200; // 0xc8
-    field public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 201; // 0xc9
-    field public static final int ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6; // 0x6
-    field public static final int ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION = 8; // 0x8
-    field public static final int ERROR_PROFILE_SERVICE_NOT_BOUND = 9; // 0x9
-    field public static final int ERROR_UNKNOWN = 2147483647; // 0x7fffffff
-    field public static final int FEATURE_NOT_SUPPORTED = 11; // 0xb
-    field public static final int FEATURE_SUPPORTED = 10; // 0xa
-    field public static final int SUCCESS = 0; // 0x0
-  }
-
-}
-
-package android.bluetooth.le {
-
-  public abstract class AdvertiseCallback {
-    ctor public AdvertiseCallback();
-    method public void onStartFailure(int);
-    method public void onStartSuccess(android.bluetooth.le.AdvertiseSettings);
-    field public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; // 0x3
-    field public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1; // 0x1
-    field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5; // 0x5
-    field public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4; // 0x4
-    field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; // 0x2
-  }
-
-  public final class AdvertiseData implements android.os.Parcelable {
-    method public int describeContents();
-    method public boolean getIncludeDeviceName();
-    method public boolean getIncludeTxPowerLevel();
-    method public android.util.SparseArray<byte[]> getManufacturerSpecificData();
-    method public java.util.Map<android.os.ParcelUuid,byte[]> getServiceData();
-    method @NonNull public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
-    method public java.util.List<android.os.ParcelUuid> getServiceUuids();
-    method @NonNull public java.util.List<android.bluetooth.le.TransportDiscoveryData> getTransportDiscoveryData();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertiseData> CREATOR;
-  }
-
-  public static final class AdvertiseData.Builder {
-    ctor public AdvertiseData.Builder();
-    method public android.bluetooth.le.AdvertiseData.Builder addManufacturerData(int, byte[]);
-    method public android.bluetooth.le.AdvertiseData.Builder addServiceData(android.os.ParcelUuid, byte[]);
-    method @NonNull public android.bluetooth.le.AdvertiseData.Builder addServiceSolicitationUuid(@NonNull android.os.ParcelUuid);
-    method public android.bluetooth.le.AdvertiseData.Builder addServiceUuid(android.os.ParcelUuid);
-    method @NonNull public android.bluetooth.le.AdvertiseData.Builder addTransportDiscoveryData(@NonNull android.bluetooth.le.TransportDiscoveryData);
-    method public android.bluetooth.le.AdvertiseData build();
-    method public android.bluetooth.le.AdvertiseData.Builder setIncludeDeviceName(boolean);
-    method public android.bluetooth.le.AdvertiseData.Builder setIncludeTxPowerLevel(boolean);
-  }
-
-  public final class AdvertiseSettings implements android.os.Parcelable {
-    method public int describeContents();
-    method public int getMode();
-    method public int getTimeout();
-    method public int getTxPowerLevel();
-    method public boolean isConnectable();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final int ADVERTISE_MODE_BALANCED = 1; // 0x1
-    field public static final int ADVERTISE_MODE_LOW_LATENCY = 2; // 0x2
-    field public static final int ADVERTISE_MODE_LOW_POWER = 0; // 0x0
-    field public static final int ADVERTISE_TX_POWER_HIGH = 3; // 0x3
-    field public static final int ADVERTISE_TX_POWER_LOW = 1; // 0x1
-    field public static final int ADVERTISE_TX_POWER_MEDIUM = 2; // 0x2
-    field public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; // 0x0
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertiseSettings> CREATOR;
-  }
-
-  public static final class AdvertiseSettings.Builder {
-    ctor public AdvertiseSettings.Builder();
-    method public android.bluetooth.le.AdvertiseSettings build();
-    method public android.bluetooth.le.AdvertiseSettings.Builder setAdvertiseMode(int);
-    method public android.bluetooth.le.AdvertiseSettings.Builder setConnectable(boolean);
-    method public android.bluetooth.le.AdvertiseSettings.Builder setTimeout(int);
-    method public android.bluetooth.le.AdvertiseSettings.Builder setTxPowerLevel(int);
-  }
-
-  public final class AdvertisingSet {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void enableAdvertising(boolean, int, int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingEnabled(boolean);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setScanResponseData(android.bluetooth.le.AdvertiseData);
-  }
-
-  public abstract class AdvertisingSetCallback {
-    ctor public AdvertisingSetCallback();
-    method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
-    method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
-    method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int, int);
-    method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int, int);
-    method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
-    method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
-    method public void onPeriodicAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
-    method public void onPeriodicAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
-    method public void onScanResponseDataSet(android.bluetooth.le.AdvertisingSet, int);
-    field public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; // 0x3
-    field public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1; // 0x1
-    field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5; // 0x5
-    field public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4; // 0x4
-    field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; // 0x2
-    field public static final int ADVERTISE_SUCCESS = 0; // 0x0
-  }
-
-  public final class AdvertisingSetParameters implements android.os.Parcelable {
-    method public int describeContents();
-    method public int getInterval();
-    method public int getPrimaryPhy();
-    method public int getSecondaryPhy();
-    method public int getTxPowerLevel();
-    method public boolean includeTxPower();
-    method public boolean isAnonymous();
-    method public boolean isConnectable();
-    method public boolean isLegacy();
-    method public boolean isScannable();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertisingSetParameters> CREATOR;
-    field public static final int INTERVAL_HIGH = 1600; // 0x640
-    field public static final int INTERVAL_LOW = 160; // 0xa0
-    field public static final int INTERVAL_MAX = 16777215; // 0xffffff
-    field public static final int INTERVAL_MEDIUM = 400; // 0x190
-    field public static final int INTERVAL_MIN = 160; // 0xa0
-    field public static final int TX_POWER_HIGH = 1; // 0x1
-    field public static final int TX_POWER_LOW = -15; // 0xfffffff1
-    field public static final int TX_POWER_MAX = 1; // 0x1
-    field public static final int TX_POWER_MEDIUM = -7; // 0xfffffff9
-    field public static final int TX_POWER_MIN = -127; // 0xffffff81
-    field public static final int TX_POWER_ULTRA_LOW = -21; // 0xffffffeb
-  }
-
-  public static final class AdvertisingSetParameters.Builder {
-    ctor public AdvertisingSetParameters.Builder();
-    method public android.bluetooth.le.AdvertisingSetParameters build();
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setAnonymous(boolean);
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setConnectable(boolean);
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setIncludeTxPower(boolean);
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setInterval(int);
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setScannable(boolean);
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
-  }
-
-  public final class BluetoothLeAdvertiser {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, int, android.bluetooth.le.AdvertisingSetCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, int, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void stopAdvertisingSet(android.bluetooth.le.AdvertisingSetCallback);
-  }
-
-  public final class BluetoothLeScanner {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void flushPendingScanResults(android.bluetooth.le.ScanCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startScan(android.bluetooth.le.ScanCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startScan(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public int startScan(@Nullable java.util.List<android.bluetooth.le.ScanFilter>, @Nullable android.bluetooth.le.ScanSettings, @NonNull android.app.PendingIntent);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopScan(android.bluetooth.le.ScanCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopScan(android.app.PendingIntent);
-    field public static final String EXTRA_CALLBACK_TYPE = "android.bluetooth.le.extra.CALLBACK_TYPE";
-    field public static final String EXTRA_ERROR_CODE = "android.bluetooth.le.extra.ERROR_CODE";
-    field public static final String EXTRA_LIST_SCAN_RESULT = "android.bluetooth.le.extra.LIST_SCAN_RESULT";
-  }
-
-  public final class PeriodicAdvertisingParameters implements android.os.Parcelable {
-    method public int describeContents();
-    method public boolean getIncludeTxPower();
-    method public int getInterval();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.bluetooth.le.PeriodicAdvertisingParameters> CREATOR;
-  }
-
-  public static final class PeriodicAdvertisingParameters.Builder {
-    ctor public PeriodicAdvertisingParameters.Builder();
-    method public android.bluetooth.le.PeriodicAdvertisingParameters build();
-    method public android.bluetooth.le.PeriodicAdvertisingParameters.Builder setIncludeTxPower(boolean);
-    method public android.bluetooth.le.PeriodicAdvertisingParameters.Builder setInterval(int);
-  }
-
-  public abstract class ScanCallback {
-    ctor public ScanCallback();
-    method public void onBatchScanResults(java.util.List<android.bluetooth.le.ScanResult>);
-    method public void onScanFailed(int);
-    method public void onScanResult(int, android.bluetooth.le.ScanResult);
-    field public static final int SCAN_FAILED_ALREADY_STARTED = 1; // 0x1
-    field public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2; // 0x2
-    field public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 4; // 0x4
-    field public static final int SCAN_FAILED_INTERNAL_ERROR = 3; // 0x3
-  }
-
-  public final class ScanFilter implements android.os.Parcelable {
-    method public int describeContents();
-    method @Nullable public String getDeviceAddress();
-    method @Nullable public String getDeviceName();
-    method @Nullable public byte[] getManufacturerData();
-    method @Nullable public byte[] getManufacturerDataMask();
-    method public int getManufacturerId();
-    method @Nullable public byte[] getServiceData();
-    method @Nullable public byte[] getServiceDataMask();
-    method @Nullable public android.os.ParcelUuid getServiceDataUuid();
-    method @Nullable public android.os.ParcelUuid getServiceSolicitationUuid();
-    method @Nullable public android.os.ParcelUuid getServiceSolicitationUuidMask();
-    method @Nullable public android.os.ParcelUuid getServiceUuid();
-    method @Nullable public android.os.ParcelUuid getServiceUuidMask();
-    method public boolean matches(android.bluetooth.le.ScanResult);
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.ScanFilter> CREATOR;
-  }
-
-  public static final class ScanFilter.Builder {
-    ctor public ScanFilter.Builder();
-    method public android.bluetooth.le.ScanFilter build();
-    method public android.bluetooth.le.ScanFilter.Builder setDeviceAddress(String);
-    method public android.bluetooth.le.ScanFilter.Builder setDeviceName(String);
-    method public android.bluetooth.le.ScanFilter.Builder setManufacturerData(int, byte[]);
-    method public android.bluetooth.le.ScanFilter.Builder setManufacturerData(int, byte[], byte[]);
-    method public android.bluetooth.le.ScanFilter.Builder setServiceData(android.os.ParcelUuid, byte[]);
-    method public android.bluetooth.le.ScanFilter.Builder setServiceData(android.os.ParcelUuid, byte[], byte[]);
-    method @NonNull public android.bluetooth.le.ScanFilter.Builder setServiceSolicitationUuid(@Nullable android.os.ParcelUuid);
-    method @NonNull public android.bluetooth.le.ScanFilter.Builder setServiceSolicitationUuid(@Nullable android.os.ParcelUuid, @Nullable android.os.ParcelUuid);
-    method public android.bluetooth.le.ScanFilter.Builder setServiceUuid(android.os.ParcelUuid);
-    method public android.bluetooth.le.ScanFilter.Builder setServiceUuid(android.os.ParcelUuid, android.os.ParcelUuid);
-  }
-
-  public final class ScanRecord {
-    method public int getAdvertiseFlags();
-    method public byte[] getBytes();
-    method @Nullable public String getDeviceName();
-    method public android.util.SparseArray<byte[]> getManufacturerSpecificData();
-    method @Nullable public byte[] getManufacturerSpecificData(int);
-    method public java.util.Map<android.os.ParcelUuid,byte[]> getServiceData();
-    method @Nullable public byte[] getServiceData(android.os.ParcelUuid);
-    method @NonNull public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
-    method public java.util.List<android.os.ParcelUuid> getServiceUuids();
-    method public int getTxPowerLevel();
-  }
-
-  public final class ScanResult implements android.os.Parcelable {
-    ctor @Deprecated public ScanResult(android.bluetooth.BluetoothDevice, android.bluetooth.le.ScanRecord, int, long);
-    ctor public ScanResult(android.bluetooth.BluetoothDevice, int, int, int, int, int, int, int, android.bluetooth.le.ScanRecord, long);
-    method public int describeContents();
-    method public int getAdvertisingSid();
-    method public int getDataStatus();
-    method public android.bluetooth.BluetoothDevice getDevice();
-    method public int getPeriodicAdvertisingInterval();
-    method public int getPrimaryPhy();
-    method public int getRssi();
-    method @Nullable public android.bluetooth.le.ScanRecord getScanRecord();
-    method public int getSecondaryPhy();
-    method public long getTimestampNanos();
-    method public int getTxPower();
-    method public boolean isConnectable();
-    method public boolean isLegacy();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.ScanResult> CREATOR;
-    field public static final int DATA_COMPLETE = 0; // 0x0
-    field public static final int DATA_TRUNCATED = 2; // 0x2
-    field public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0; // 0x0
-    field public static final int PHY_UNUSED = 0; // 0x0
-    field public static final int SID_NOT_PRESENT = 255; // 0xff
-    field public static final int TX_POWER_NOT_PRESENT = 127; // 0x7f
-  }
-
-  public final class ScanSettings implements android.os.Parcelable {
-    method public int describeContents();
-    method public int getCallbackType();
-    method public boolean getLegacy();
-    method public int getPhy();
-    method public long getReportDelayMillis();
-    method public int getScanMode();
-    method public int getScanResultType();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final int CALLBACK_TYPE_ALL_MATCHES = 1; // 0x1
-    field public static final int CALLBACK_TYPE_FIRST_MATCH = 2; // 0x2
-    field public static final int CALLBACK_TYPE_MATCH_LOST = 4; // 0x4
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.ScanSettings> CREATOR;
-    field public static final int MATCH_MODE_AGGRESSIVE = 1; // 0x1
-    field public static final int MATCH_MODE_STICKY = 2; // 0x2
-    field public static final int MATCH_NUM_FEW_ADVERTISEMENT = 2; // 0x2
-    field public static final int MATCH_NUM_MAX_ADVERTISEMENT = 3; // 0x3
-    field public static final int MATCH_NUM_ONE_ADVERTISEMENT = 1; // 0x1
-    field public static final int PHY_LE_ALL_SUPPORTED = 255; // 0xff
-    field public static final int SCAN_MODE_BALANCED = 1; // 0x1
-    field public static final int SCAN_MODE_LOW_LATENCY = 2; // 0x2
-    field public static final int SCAN_MODE_LOW_POWER = 0; // 0x0
-    field public static final int SCAN_MODE_OPPORTUNISTIC = -1; // 0xffffffff
-  }
-
-  public static final class ScanSettings.Builder {
-    ctor public ScanSettings.Builder();
-    method public android.bluetooth.le.ScanSettings build();
-    method public android.bluetooth.le.ScanSettings.Builder setCallbackType(int);
-    method public android.bluetooth.le.ScanSettings.Builder setLegacy(boolean);
-    method public android.bluetooth.le.ScanSettings.Builder setMatchMode(int);
-    method public android.bluetooth.le.ScanSettings.Builder setNumOfMatches(int);
-    method public android.bluetooth.le.ScanSettings.Builder setPhy(int);
-    method public android.bluetooth.le.ScanSettings.Builder setReportDelay(long);
-    method public android.bluetooth.le.ScanSettings.Builder setScanMode(int);
-  }
-
-  public final class TransportBlock implements android.os.Parcelable {
-    ctor public TransportBlock(int, int, int, @Nullable byte[]);
-    method public int describeContents();
-    method public int getOrgId();
-    method public int getTdsFlags();
-    method @Nullable public byte[] getTransportData();
-    method public int getTransportDataLength();
-    method @Nullable public byte[] toByteArray();
-    method public int totalBytes();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.TransportBlock> CREATOR;
-  }
-
-  public final class TransportDiscoveryData implements android.os.Parcelable {
-    ctor public TransportDiscoveryData(int, @NonNull java.util.List<android.bluetooth.le.TransportBlock>);
-    ctor public TransportDiscoveryData(@NonNull byte[]);
-    method public int describeContents();
-    method @NonNull public java.util.List<android.bluetooth.le.TransportBlock> getTransportBlocks();
-    method public int getTransportDataType();
-    method @Nullable public byte[] toByteArray();
-    method public int totalBytes();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.TransportDiscoveryData> CREATOR;
-  }
-
-}
-
 package android.companion {
 
   public final class AssociationInfo implements android.os.Parcelable {
@@ -10248,13 +8962,11 @@
 
   public abstract class CompanionDeviceService extends android.app.Service {
     ctor public CompanionDeviceService();
-    method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessage(int, int, @NonNull byte[]);
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
     method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
     method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
     method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
-    method @MainThread public void onDispatchMessage(int, int, @NonNull byte[]);
     field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
   }
 
@@ -13503,6 +12215,7 @@
   }
 
   public final class ShortcutInfo implements android.os.Parcelable {
+    method @NonNull public static android.content.pm.ShortcutInfo createFromGenericDocument(@NonNull android.content.Context, @NonNull android.app.appsearch.GenericDocument);
     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);
@@ -18084,7 +16797,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_FRONT_BUFFER = 4294967296L; // 0x100000000L
     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
@@ -19858,7 +18571,6 @@
   public abstract class AbstractInputMethodService extends android.app.Service implements android.view.KeyEvent.Callback {
     ctor public AbstractInputMethodService();
     method public android.view.KeyEvent.DispatcherState getKeyDispatcherState();
-    method public final boolean isUiContext();
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface();
     method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
@@ -21146,6 +19858,7 @@
     method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice();
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public static int getDirectPlaybackSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
+    method @NonNull public java.util.List<android.media.AudioProfile> getDirectProfilesForAttributes(@NonNull android.media.AudioAttributes);
     method public int getEncodedSurroundMode();
     method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException;
     method public int getMode();
@@ -22508,9 +21221,11 @@
     field public static final int COLOR_Format24bitBGR888 = 12; // 0xc
     field @Deprecated public static final int COLOR_Format24bitRGB888 = 11; // 0xb
     field @Deprecated public static final int COLOR_Format25bitARGB1888 = 14; // 0xe
+    field public static final int COLOR_Format32bitABGR2101010 = 2130750114; // 0x7f00aaa2
     field public static final int COLOR_Format32bitABGR8888 = 2130747392; // 0x7f00a000
     field @Deprecated public static final int COLOR_Format32bitARGB8888 = 16; // 0x10
     field @Deprecated public static final int COLOR_Format32bitBGRA8888 = 15; // 0xf
+    field public static final int COLOR_Format64bitABGRFloat = 2130710294; // 0x7f000f16
     field @Deprecated public static final int COLOR_Format8bitRGB332 = 2; // 0x2
     field @Deprecated public static final int COLOR_FormatCbYCrY = 27; // 0x1b
     field @Deprecated public static final int COLOR_FormatCrYCbY = 28; // 0x1c
@@ -22543,12 +21258,14 @@
     field @Deprecated public static final int COLOR_FormatYUV422SemiPlanar = 24; // 0x18
     field public static final int COLOR_FormatYUV444Flexible = 2135181448; // 0x7f444888
     field @Deprecated public static final int COLOR_FormatYUV444Interleaved = 29; // 0x1d
+    field public static final int COLOR_FormatYUVP010 = 54; // 0x36
     field @Deprecated public static final int COLOR_QCOM_FormatYUV420SemiPlanar = 2141391872; // 0x7fa30c00
     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_HdrEditing = "hdr-editing";
     field public static final String FEATURE_IntraRefresh = "intra-refresh";
     field public static final String FEATURE_LowLatency = "low-latency";
     field public static final String FEATURE_MultipleFrames = "multiple-frames";
@@ -24502,6 +23219,7 @@
     method public static android.net.Uri getValidRingtoneUri(android.content.Context);
     method public boolean hasHapticChannels(int);
     method public static boolean hasHapticChannels(@NonNull android.net.Uri);
+    method public static boolean hasHapticChannels(@NonNull android.content.Context, @NonNull android.net.Uri);
     method public int inferStreamType();
     method public static boolean isDefault(android.net.Uri);
     method @Nullable public static android.content.res.AssetFileDescriptor openDefaultRingtoneUri(@NonNull android.content.Context, @NonNull android.net.Uri) throws java.io.FileNotFoundException;
@@ -26177,6 +24895,46 @@
 
 package android.media.tv {
 
+  public final class AdRequest implements android.os.Parcelable {
+    ctor public AdRequest(int, int, @Nullable android.os.ParcelFileDescriptor, long, long, long, @Nullable String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method public long getEchoIntervalMillis();
+    method @Nullable public android.os.ParcelFileDescriptor getFileDescriptor();
+    method public int getId();
+    method @Nullable public String getMediaFileType();
+    method @NonNull public android.os.Bundle getMetadata();
+    method public int getRequestType();
+    method public long getStartTimeMillis();
+    method public long getStopTimeMillis();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AdRequest> CREATOR;
+    field public static final int REQUEST_TYPE_START = 1; // 0x1
+    field public static final int REQUEST_TYPE_STOP = 2; // 0x2
+  }
+
+  public final class AdResponse implements android.os.Parcelable {
+    ctor public AdResponse(int, int, long);
+    method public int describeContents();
+    method public long getElapsedTimeMillis();
+    method public int getId();
+    method public int getResponseType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AdResponse> CREATOR;
+    field public static final int RESPONSE_TYPE_ERROR = 4; // 0x4
+    field public static final int RESPONSE_TYPE_FINISHED = 2; // 0x2
+    field public static final int RESPONSE_TYPE_PLAYING = 1; // 0x1
+    field public static final int RESPONSE_TYPE_STOPPED = 3; // 0x3
+  }
+
+  public final class AitInfo implements android.os.Parcelable {
+    ctor public AitInfo(int, int);
+    method public int describeContents();
+    method public int getType();
+    method public int getVersion();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AitInfo> CREATOR;
+  }
+
   public final class TvContentRating {
     method public boolean contains(@NonNull android.media.tv.TvContentRating);
     method public static android.media.tv.TvContentRating createRating(String, String, String, java.lang.String...);
@@ -26670,6 +25428,9 @@
     field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 1; // 0x1
     field public static final int RECORDING_ERROR_RESOURCE_BUSY = 2; // 0x2
     field public static final int RECORDING_ERROR_UNKNOWN = 0; // 0x0
+    field public static final int SIGNAL_STRENGTH_LOST = 1; // 0x1
+    field public static final int SIGNAL_STRENGTH_STRONG = 3; // 0x3
+    field public static final int SIGNAL_STRENGTH_WEAK = 2; // 0x2
     field public static final long TIME_SHIFT_INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L
     field public static final int TIME_SHIFT_STATUS_AVAILABLE = 3; // 0x3
     field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
@@ -26747,12 +25508,16 @@
   public abstract static class TvInputService.Session implements android.view.KeyEvent.Callback {
     ctor public TvInputService.Session(android.content.Context);
     method public void layoutSurface(int, int, int, int);
+    method public void notifyAdResponse(@NonNull android.media.tv.AdResponse);
+    method public void notifyAitInfoUpdated(@NonNull android.media.tv.AitInfo);
     method public void notifyChannelRetuned(android.net.Uri);
     method public void notifyContentAllowed();
     method public void notifyContentBlocked(@NonNull android.media.tv.TvContentRating);
+    method public void notifySignalStrength(int);
     method public void notifyTimeShiftStatusChanged(int);
     method public void notifyTrackSelected(int, String);
     method public void notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>);
+    method public void notifyTuned(@NonNull android.net.Uri);
     method public void notifyVideoAvailable();
     method public void notifyVideoUnavailable(int);
     method public void onAppPrivateCommand(@NonNull String, android.os.Bundle);
@@ -26764,8 +25529,10 @@
     method public boolean onKeyUp(int, android.view.KeyEvent);
     method public void onOverlayViewSizeChanged(int, int);
     method public abstract void onRelease();
+    method public void onRequestAd(@NonNull android.media.tv.AdRequest);
     method public boolean onSelectTrack(int, @Nullable String);
     method public abstract void onSetCaptionEnabled(boolean);
+    method public void onSetInteractiveAppNotificationEnabled(boolean);
     method public abstract void onSetStreamVolume(@FloatRange(from=0.0, to=1.0) float);
     method public abstract boolean onSetSurface(@Nullable android.view.Surface);
     method public void onSurfaceChanged(int, int, int);
@@ -26867,6 +25634,7 @@
     method public void sendAppPrivateCommand(@NonNull String, android.os.Bundle);
     method public void setCallback(@Nullable android.media.tv.TvView.TvInputCallback);
     method public void setCaptionEnabled(boolean);
+    method public void setInteractiveAppNotificationEnabled(boolean);
     method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener);
     method public void setStreamVolume(@FloatRange(from=0.0, to=1.0) float);
     method public void setTimeShiftPositionCallback(@Nullable android.media.tv.TvView.TimeShiftPositionCallback);
@@ -26893,14 +25661,17 @@
 
   public abstract static class TvView.TvInputCallback {
     ctor public TvView.TvInputCallback();
+    method public void onAitInfoUpdated(@NonNull String, @NonNull android.media.tv.AitInfo);
     method public void onChannelRetuned(String, android.net.Uri);
     method public void onConnectionFailed(String);
     method public void onContentAllowed(String);
     method public void onContentBlocked(String, android.media.tv.TvContentRating);
     method public void onDisconnected(String);
+    method public void onSignalStrength(@NonNull String, int);
     method public void onTimeShiftStatusChanged(String, int);
     method public void onTrackSelected(String, int, String);
     method public void onTracksChanged(String, java.util.List<android.media.tv.TvTrackInfo>);
+    method public void onTuned(@NonNull String, @NonNull android.net.Uri);
     method public void onVideoAvailable(String);
     method public void onVideoSizeChanged(String, int, int);
     method public void onVideoUnavailable(String, int);
@@ -26910,6 +25681,27 @@
 
 package android.media.tv.interactive {
 
+  public final class AppLinkInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getClassName();
+    method @NonNull public String getPackageName();
+    method @Nullable public String getUriHost();
+    method @Nullable public String getUriPrefix();
+    method @Nullable public String getUriScheme();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.interactive.AppLinkInfo> CREATOR;
+  }
+
+  public static final class AppLinkInfo.Builder {
+    ctor public AppLinkInfo.Builder(@NonNull String, @NonNull String);
+    method @NonNull public android.media.tv.interactive.AppLinkInfo build();
+    method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setClassName(@NonNull String);
+    method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setPackageName(@NonNull String);
+    method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriHost(@Nullable String);
+    method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriPrefix(@Nullable String);
+    method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriScheme(@Nullable String);
+  }
+
   public final class TvInteractiveAppInfo implements android.os.Parcelable {
     ctor public TvInteractiveAppInfo(@NonNull android.content.Context, @NonNull android.content.ComponentName);
     method public int describeContents();
@@ -26925,26 +25717,172 @@
 
   public final class TvInteractiveAppManager {
     method @NonNull public java.util.List<android.media.tv.interactive.TvInteractiveAppInfo> getTvInteractiveAppServiceList();
+    method public void prepare(@NonNull String, int);
+    method public void registerAppLinkInfo(@NonNull String, @NonNull android.media.tv.interactive.AppLinkInfo);
+    method public void registerCallback(@NonNull android.media.tv.interactive.TvInteractiveAppManager.TvInteractiveAppCallback, @NonNull java.util.concurrent.Executor);
+    method public void sendAppLinkCommand(@NonNull String, @NonNull android.os.Bundle);
+    method public void unregisterAppLinkInfo(@NonNull String, @NonNull android.media.tv.interactive.AppLinkInfo);
+    method public void unregisterCallback(@NonNull android.media.tv.interactive.TvInteractiveAppManager.TvInteractiveAppCallback);
+    field public static final String ACTION_APP_LINK_COMMAND = "android.media.tv.interactive.action.APP_LINK_COMMAND";
+    field public static final String APP_LINK_KEY_BACK_URI = "back_uri";
+    field public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
+    field public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
+    field public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
+    field public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
+    field public static final int ERROR_BLOCKED = 5; // 0x5
+    field public static final int ERROR_ENCRYPTED = 6; // 0x6
+    field public static final int ERROR_NONE = 0; // 0x0
+    field public static final int ERROR_NOT_SUPPORTED = 2; // 0x2
+    field public static final int ERROR_RESOURCE_UNAVAILABLE = 4; // 0x4
+    field public static final int ERROR_UNKNOWN = 1; // 0x1
+    field public static final int ERROR_UNKNOWN_CHANNEL = 7; // 0x7
+    field public static final int ERROR_WEAK_SIGNAL = 3; // 0x3
+    field public static final String INTENT_KEY_BI_INTERACTIVE_APP_TYPE = "bi_interactive_app_type";
+    field public static final String INTENT_KEY_BI_INTERACTIVE_APP_URI = "bi_interactive_app_uri";
+    field public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
+    field public static final String INTENT_KEY_INTERACTIVE_APP_SERVICE_ID = "interactive_app_id";
+    field public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
+    field public static final int INTERACTIVE_APP_STATE_ERROR = 3; // 0x3
+    field public static final int INTERACTIVE_APP_STATE_RUNNING = 2; // 0x2
+    field public static final int INTERACTIVE_APP_STATE_STOPPED = 1; // 0x1
+    field public static final int SERVICE_STATE_ERROR = 4; // 0x4
+    field public static final int SERVICE_STATE_PREPARING = 2; // 0x2
+    field public static final int SERVICE_STATE_READY = 3; // 0x3
+    field public static final int SERVICE_STATE_UNREALIZED = 1; // 0x1
+    field public static final int TELETEXT_APP_STATE_ERROR = 3; // 0x3
+    field public static final int TELETEXT_APP_STATE_HIDE = 2; // 0x2
+    field public static final int TELETEXT_APP_STATE_SHOW = 1; // 0x1
+  }
+
+  public abstract static class TvInteractiveAppManager.TvInteractiveAppCallback {
+    ctor public TvInteractiveAppManager.TvInteractiveAppCallback();
+    method public void onInteractiveAppServiceAdded(@NonNull String);
+    method public void onInteractiveAppServiceRemoved(@NonNull String);
+    method public void onInteractiveAppServiceUpdated(@NonNull String);
+    method public void onTvInteractiveAppServiceStateChanged(@NonNull String, int, int, int);
   }
 
   public abstract class TvInteractiveAppService extends android.app.Service {
     ctor public TvInteractiveAppService();
-    method public final android.os.IBinder onBind(android.content.Intent);
+    method public final void notifyStateChanged(int, int, int);
+    method public void onAppLinkCommand(@NonNull android.os.Bundle);
+    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method @Nullable public abstract android.media.tv.interactive.TvInteractiveAppService.Session onCreateSession(@NonNull String, int);
+    method public abstract void onPrepare(int);
+    method public void onRegisterAppLinkInfo(@NonNull android.media.tv.interactive.AppLinkInfo);
+    method public void onUnregisterAppLinkInfo(@NonNull android.media.tv.interactive.AppLinkInfo);
+    field public static final String COMMAND_PARAMETER_KEY_CHANGE_CHANNEL_QUIETLY = "command_change_channel_quietly";
+    field public static final String COMMAND_PARAMETER_KEY_CHANNEL_URI = "command_channel_uri";
+    field public static final String COMMAND_PARAMETER_KEY_INPUT_ID = "command_input_id";
+    field public static final String COMMAND_PARAMETER_KEY_TRACK_ID = "command_track_id";
+    field public static final String COMMAND_PARAMETER_KEY_TRACK_TYPE = "command_track_type";
+    field public static final String COMMAND_PARAMETER_KEY_VOLUME = "command_volume";
+    field public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track";
+    field public static final String PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME = "set_stream_volume";
+    field public static final String PLAYBACK_COMMAND_TYPE_STOP = "stop";
+    field public static final String PLAYBACK_COMMAND_TYPE_TUNE = "tune";
+    field public static final String PLAYBACK_COMMAND_TYPE_TUNE_NEXT = "tune_next";
+    field public static final String PLAYBACK_COMMAND_TYPE_TUNE_PREV = "tune_previous";
     field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvInteractiveAppService";
     field public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
   }
 
+  public abstract static class TvInteractiveAppService.Session implements android.view.KeyEvent.Callback {
+    ctor public TvInteractiveAppService.Session(@NonNull android.content.Context);
+    method public void layoutSurface(int, int, int, int);
+    method public final void notifyBiInteractiveAppCreated(@NonNull android.net.Uri, @Nullable String);
+    method public void notifySessionStateChanged(int, int);
+    method public final void notifyTeletextAppStateChanged(int);
+    method public void onAdResponse(@NonNull android.media.tv.AdResponse);
+    method public void onContentAllowed();
+    method public void onContentBlocked(@NonNull android.media.tv.TvContentRating);
+    method public void onCreateBiInteractiveApp(@NonNull android.net.Uri, @Nullable android.os.Bundle);
+    method @Nullable public android.view.View onCreateMediaView();
+    method public void onCurrentChannelLcn(int);
+    method public void onCurrentChannelUri(@Nullable android.net.Uri);
+    method public void onCurrentTvInputId(@Nullable String);
+    method public void onDestroyBiInteractiveApp(@NonNull String);
+    method public boolean onGenericMotionEvent(@NonNull android.view.MotionEvent);
+    method public boolean onKeyDown(int, @NonNull android.view.KeyEvent);
+    method public boolean onKeyLongPress(int, @NonNull android.view.KeyEvent);
+    method public boolean onKeyMultiple(int, int, @NonNull android.view.KeyEvent);
+    method public boolean onKeyUp(int, @NonNull android.view.KeyEvent);
+    method public void onMediaViewSizeChanged(int, int);
+    method public abstract void onRelease();
+    method public void onResetInteractiveApp();
+    method public abstract boolean onSetSurface(@Nullable android.view.Surface);
+    method public void onSetTeletextAppEnabled(boolean);
+    method public void onSignalStrength(int);
+    method public void onStartInteractiveApp();
+    method public void onStopInteractiveApp();
+    method public void onStreamVolume(float);
+    method public void onSurfaceChanged(int, int, int);
+    method public boolean onTouchEvent(@NonNull android.view.MotionEvent);
+    method public void onTrackInfoList(@NonNull java.util.List<android.media.tv.TvTrackInfo>);
+    method public void onTrackSelected(int, @NonNull String);
+    method public boolean onTrackballEvent(@NonNull android.view.MotionEvent);
+    method public void onTracksChanged(@NonNull java.util.List<android.media.tv.TvTrackInfo>);
+    method public void onTuned(@NonNull android.net.Uri);
+    method public void onVideoAvailable();
+    method public void onVideoUnavailable(int);
+    method public void requestAd(@NonNull android.media.tv.AdRequest);
+    method public void requestCurrentChannelLcn();
+    method public void requestCurrentChannelUri();
+    method public void requestCurrentTvInputId();
+    method public void requestStreamVolume();
+    method public void requestTrackInfoList();
+    method public void sendPlaybackCommandRequest(@NonNull String, @Nullable android.os.Bundle);
+    method public void setMediaViewEnabled(boolean);
+    method public void setVideoBounds(@NonNull android.graphics.Rect);
+  }
+
   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 clearOnUnhandledInputEventListener();
+    method public void createBiInteractiveApp(@NonNull android.net.Uri, @Nullable android.os.Bundle);
+    method public void destroyBiInteractiveApp(@NonNull String);
+    method public boolean dispatchUnhandledInputEvent(@NonNull android.view.InputEvent);
+    method public void onAttachedToWindow();
+    method public void onDetachedFromWindow();
+    method public void onLayout(boolean, int, int, int, int);
+    method public void onMeasure(int, int);
+    method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
+    method public void onVisibilityChanged(@NonNull android.view.View, int);
+    method public void prepareInteractiveApp(@NonNull String, int);
+    method public void reset();
+    method public void resetInteractiveApp();
+    method public void sendCurrentChannelLcn(int);
+    method public void sendCurrentChannelUri(@Nullable android.net.Uri);
+    method public void sendCurrentTvInputId(@Nullable String);
+    method public void sendStreamVolume(float);
+    method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
     method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback);
+    method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener);
+    method public void setTeletextAppEnabled(boolean);
+    method public int setTvView(@Nullable android.media.tv.TvView);
     method public void startInteractiveApp();
+    method public void stopInteractiveApp();
+  }
+
+  public static interface TvInteractiveAppView.OnUnhandledInputEventListener {
+    method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
   }
 
   public abstract static class TvInteractiveAppView.TvInteractiveAppCallback {
     ctor public TvInteractiveAppView.TvInteractiveAppCallback();
+    method public void onBiInteractiveAppCreated(@NonNull String, @NonNull android.net.Uri, @Nullable String);
+    method public void onPlaybackCommandRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
+    method public void onRequestCurrentChannelLcn(@NonNull String);
+    method public void onRequestCurrentChannelUri(@NonNull String);
+    method public void onRequestCurrentTvInputId(@NonNull String);
+    method public void onRequestStreamVolume(@NonNull String);
+    method public void onRequestTrackInfoList(@NonNull String);
+    method public void onSetVideoBounds(@NonNull String, @NonNull android.graphics.Rect);
+    method public void onStateChanged(@NonNull String, int, int);
+    method public void onTeletextAppStateChanged(@NonNull String, int);
   }
 
 }
@@ -31509,6 +30447,7 @@
     field public static final int BATTERY_HEALTH_UNKNOWN = 1; // 0x1
     field public static final int BATTERY_HEALTH_UNSPECIFIED_FAILURE = 6; // 0x6
     field public static final int BATTERY_PLUGGED_AC = 1; // 0x1
+    field public static final int BATTERY_PLUGGED_DOCK = 8; // 0x8
     field public static final int BATTERY_PLUGGED_USB = 2; // 0x2
     field public static final int BATTERY_PLUGGED_WIRELESS = 4; // 0x4
     field public static final int BATTERY_PROPERTY_CAPACITY = 4; // 0x4
@@ -33016,6 +31955,9 @@
     method public static android.os.VibrationEffect createWaveform(long[], int[], int);
     method public int describeContents();
     method @NonNull public static android.os.VibrationEffect.Composition startComposition();
+    method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform();
+    method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform(@NonNull android.os.VibrationEffect.VibrationParameter);
+    method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform(@NonNull android.os.VibrationEffect.VibrationParameter, @NonNull android.os.VibrationEffect.VibrationParameter);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
     field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
     field public static final int EFFECT_CLICK = 0; // 0x0
@@ -33025,10 +31967,13 @@
   }
 
   public static final class VibrationEffect.Composition {
+    method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect);
+    method @NonNull public android.os.VibrationEffect.Composition addOffDuration(@NonNull java.time.Duration);
     method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int);
     method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float);
     method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
     method @NonNull public android.os.VibrationEffect compose();
+    method @NonNull public android.os.VibrationEffect.Composition repeatEffectIndefinitely(@NonNull android.os.VibrationEffect);
     field public static final int PRIMITIVE_CLICK = 1; // 0x1
     field public static final int PRIMITIVE_LOW_TICK = 8; // 0x8
     field public static final int PRIMITIVE_QUICK_FALL = 6; // 0x6
@@ -33039,6 +31984,21 @@
     field public static final int PRIMITIVE_TICK = 7; // 0x7
   }
 
+  public static final class VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException extends java.lang.IllegalStateException {
+  }
+
+  public static class VibrationEffect.VibrationParameter {
+    method @NonNull public static android.os.VibrationEffect.VibrationParameter targetAmplitude(@FloatRange(from=0, to=1) float);
+    method @NonNull public static android.os.VibrationEffect.VibrationParameter targetFrequency(@FloatRange(from=1) float);
+  }
+
+  public static final class VibrationEffect.WaveformBuilder {
+    method @NonNull public android.os.VibrationEffect.WaveformBuilder addSustain(@NonNull java.time.Duration);
+    method @NonNull public android.os.VibrationEffect.WaveformBuilder addTransition(@NonNull java.time.Duration, @NonNull android.os.VibrationEffect.VibrationParameter);
+    method @NonNull public android.os.VibrationEffect.WaveformBuilder addTransition(@NonNull java.time.Duration, @NonNull android.os.VibrationEffect.VibrationParameter, @NonNull android.os.VibrationEffect.VibrationParameter);
+    method @NonNull public android.os.VibrationEffect build();
+  }
+
   public abstract class Vibrator {
     method public final int areAllEffectsSupported(@NonNull int...);
     method public final boolean areAllPrimitivesSupported(@NonNull int...);
@@ -41268,8 +40228,10 @@
     field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
     field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
+    field public static final int CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS = 65536; // 0x10000
     field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
     field public static final int CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE = 256; // 0x100
+    field public static final int CAPABILITY_VOICE_CALLING_AVAILABLE = 131072; // 0x20000
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.PhoneAccount> CREATOR;
     field public static final String EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE = "android.telecom.extra.ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE";
     field public static final String EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE = "android.telecom.extra.ALWAYS_USE_VOIP_AUDIO_MODE";
@@ -42209,6 +41171,7 @@
     field public static final String KEY_SIP_TIMER_T1_MILLIS_INT = "ims.sip_timer_t1_millis_int";
     field public static final String KEY_SIP_TIMER_T2_MILLIS_INT = "ims.sip_timer_t2_millis_int";
     field public static final String KEY_SIP_TIMER_T4_MILLIS_INT = "ims.sip_timer_t4_millis_int";
+    field public static final String KEY_SUPPORTED_RATS_INT_ARRAY = "ims.supported_rats_int_array";
     field public static final String KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT = "ims.wifi_off_deferring_time_millis_int";
     field public static final int NETWORK_TYPE_HOME = 0; // 0x0
     field public static final int NETWORK_TYPE_ROAMING = 1; // 0x1
@@ -42229,6 +41192,7 @@
     field public static final String KEY_EMERGENCY_QOS_PRECONDITION_SUPPORTED_BOOL = "imsemergency.emergency_qos_precondition_supported_bool";
     field public static final String KEY_EMERGENCY_REGISTRATION_TIMER_MILLIS_INT = "imsemergency.emergency_registration_timer_millis_int";
     field public static final String KEY_PREFIX = "imsemergency.";
+    field public static final String KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT = "imsemergency.refresh_geolocation_timeout_millis_int";
     field public static final String KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL = "imsemergency.retry_emergency_on_ims_pdn_bool";
   }
 
@@ -42238,7 +41202,6 @@
     field public static final String KEY_T140_PAYLOAD_TYPE_INT = "imsrtt.t140_payload_type_int";
     field public static final String KEY_TEXT_AS_BANDWIDTH_KBPS_INT = "imsrtt.text_as_bandwidth_kbps_int";
     field public static final String KEY_TEXT_CODEC_CAPABILITY_PAYLOAD_TYPES_BUNDLE = "imsrtt.text_codec_capability_payload_types_bundle";
-    field public static final String KEY_TEXT_INACTIVITY_CALL_END_REASONS_INT_ARRAY = "imsrtt.text_inactivity_call_end_reasons_int_array";
     field public static final String KEY_TEXT_ON_DEFAULT_BEARER_SUPPORTED_BOOL = "imsrtt.text_on_default_bearer_supported_bool";
     field public static final String KEY_TEXT_QOS_PRECONDITION_SUPPORTED_BOOL = "imsrtt.text_qos_precondition_supported_bool";
     field public static final String KEY_TEXT_RR_BANDWIDTH_BPS_INT = "imsrtt.text_rr_bandwidth_bps_int";
@@ -42274,6 +41237,7 @@
     field public static final String KEY_UT_REQUIRES_IMS_REGISTRATION_BOOL = "imsss.ut_requires_ims_registration_bool";
     field public static final String KEY_UT_SERVER_BASED_SERVICES_INT_ARRAY = "imsss.ut_server_based_services_int_array";
     field public static final String KEY_UT_SUPPORTED_WHEN_PS_DATA_OFF_BOOL = "imsss.ut_supported_when_ps_data_off_bool";
+    field public static final String KEY_UT_SUPPORTED_WHEN_ROAMING_BOOL = "imsss.ut_supported_when_roaming_bool";
     field public static final String KEY_UT_TERMINAL_BASED_SERVICES_INT_ARRAY = "imsss.ut_terminal_based_services_int_array";
     field public static final String KEY_UT_TRANSPORT_TYPE_INT = "imsss.ut_transport_type_int";
     field public static final String KEY_XCAP_OVER_UT_SUPPORTED_RATS_INT_ARRAY = "imsss.xcap_over_ut_supported_rats_int_array";
@@ -42373,6 +41337,7 @@
     field public static final String KEY_RINGBACK_TIMER_MILLIS_INT = "imsvoice.ringback_timer_millis_int";
     field public static final String KEY_RINGING_TIMER_MILLIS_INT = "imsvoice.ringing_timer_millis_int";
     field public static final String KEY_SESSION_EXPIRES_TIMER_SEC_INT = "imsvoice.session_expires_timer_sec_int";
+    field public static final String KEY_SESSION_PRIVACY_TYPE_INT = "imsvoice.session_privacy_type_int";
     field public static final String KEY_SESSION_REFRESHER_TYPE_INT = "imsvoice.session_refresher_type_int";
     field public static final String KEY_SESSION_REFRESH_METHOD_INT = "imsvoice.session_refresh_method_int";
     field public static final String KEY_SESSION_TIMER_SUPPORTED_BOOL = "imsvoice.session_timer_supported_bool";
@@ -42382,6 +41347,9 @@
     field public static final int MIDCALL_SRVCC_SUPPORT = 3; // 0x3
     field public static final int OCTET_ALIGNED = 1; // 0x1
     field public static final int PREALERTING_SRVCC_SUPPORT = 2; // 0x2
+    field public static final int SESSION_PRIVACY_TYPE_HEADER = 0; // 0x0
+    field public static final int SESSION_PRIVACY_TYPE_ID = 2; // 0x2
+    field public static final int SESSION_PRIVACY_TYPE_NONE = 1; // 0x1
     field public static final int SESSION_REFRESHER_TYPE_UAC = 1; // 0x1
     field public static final int SESSION_REFRESHER_TYPE_UAS = 2; // 0x2
     field public static final int SESSION_REFRESHER_TYPE_UNKNOWN = 0; // 0x0
@@ -48383,6 +47351,7 @@
     method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
     method public default int getBufferTransformHint();
     method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
+    method public default void setTouchableRegion(@Nullable android.graphics.Region);
   }
 
   @UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener {
@@ -50453,6 +49422,7 @@
     method public boolean isAccessibilityHeading();
     method public boolean isActivated();
     method public boolean isAttachedToWindow();
+    method public boolean isAutoHandwritingEnabled();
     method public boolean isClickable();
     method public boolean isContextClickable();
     method public boolean isDirty();
@@ -50636,6 +49606,7 @@
     method public void setAlpha(@FloatRange(from=0.0, to=1.0) float);
     method public void setAnimation(android.view.animation.Animation);
     method public void setAnimationMatrix(@Nullable android.graphics.Matrix);
+    method public void setAutoHandwritingEnabled(boolean);
     method public void setAutofillHints(@Nullable java.lang.String...);
     method public void setAutofillId(@Nullable android.view.autofill.AutofillId);
     method public void setBackground(android.graphics.drawable.Drawable);
@@ -55127,6 +54098,7 @@
   public abstract class WebSettings {
     ctor public WebSettings();
     method @Deprecated public abstract boolean enableSmoothTransition();
+    method public boolean getAllowAlgorithmicDarkening();
     method public abstract boolean getAllowContentAccess();
     method public abstract boolean getAllowFileAccess();
     method public abstract boolean getAllowFileAccessFromFileURLs();
@@ -55148,7 +54120,7 @@
     method public abstract boolean getDomStorageEnabled();
     method public abstract String getFantasyFontFamily();
     method public abstract String getFixedFontFamily();
-    method public int getForceDark();
+    method @Deprecated public int getForceDark();
     method public abstract boolean getJavaScriptCanOpenWindowsAutomatically();
     method public abstract boolean getJavaScriptEnabled();
     method public abstract android.webkit.WebSettings.LayoutAlgorithm getLayoutAlgorithm();
@@ -55171,6 +54143,7 @@
     method public abstract int getTextZoom();
     method public abstract boolean getUseWideViewPort();
     method public abstract String getUserAgentString();
+    method public void setAllowAlgorithmicDarkening(boolean);
     method public abstract void setAllowContentAccess(boolean);
     method public abstract void setAllowFileAccess(boolean);
     method @Deprecated public abstract void setAllowFileAccessFromFileURLs(boolean);
@@ -55192,7 +54165,7 @@
     method @Deprecated public abstract void setEnableSmoothTransition(boolean);
     method public abstract void setFantasyFontFamily(String);
     method public abstract void setFixedFontFamily(String);
-    method public void setForceDark(int);
+    method @Deprecated public void setForceDark(int);
     method @Deprecated public abstract void setGeolocationDatabasePath(String);
     method public abstract void setGeolocationEnabled(boolean);
     method public abstract void setJavaScriptCanOpenWindowsAutomatically(boolean);
@@ -55223,9 +54196,9 @@
     method public abstract void setUserAgentString(@Nullable String);
     method public abstract boolean supportMultipleWindows();
     method public abstract boolean supportZoom();
-    field public static final int FORCE_DARK_AUTO = 1; // 0x1
-    field public static final int FORCE_DARK_OFF = 0; // 0x0
-    field public static final int FORCE_DARK_ON = 2; // 0x2
+    field @Deprecated public static final int FORCE_DARK_AUTO = 1; // 0x1
+    field @Deprecated public static final int FORCE_DARK_OFF = 0; // 0x0
+    field @Deprecated public static final int FORCE_DARK_ON = 2; // 0x2
     field public static final int LOAD_CACHE_ELSE_NETWORK = 1; // 0x1
     field public static final int LOAD_CACHE_ONLY = 3; // 0x3
     field public static final int LOAD_DEFAULT = -1; // 0xffffffff
@@ -57390,8 +56363,8 @@
     method public void setViewOutlinePreferredRadiusDimen(@IdRes int, @DimenRes int);
     method public void setViewPadding(@IdRes int, @Px int, @Px int, @Px int, @Px int);
     method public void setViewVisibility(@IdRes int, int);
-    method public void showNext(@IdRes int);
-    method public void showPrevious(@IdRes int);
+    method @Deprecated public void showNext(@IdRes int);
+    method @Deprecated public void showPrevious(@IdRes int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.widget.RemoteViews> CREATOR;
     field public static final String EXTRA_CHECKED = "android.widget.extra.CHECKED";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 4134223..6c2db43 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 @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getLogoutUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int logoutUser();
     field public static final String ACTION_SHOW_NEW_USER_DISCLAIMER = "android.app.action.SHOW_NEW_USER_DISCLAIMER";
   }
 
@@ -78,23 +80,15 @@
     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);
   }
 
-}
-
-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);
+  public abstract static class NetworkStatsManager.UsageCallback {
+    method public void onThresholdReached(@NonNull android.net.NetworkTemplate);
   }
 
 }
@@ -271,6 +265,10 @@
     method public int getResourceId();
   }
 
+  public class LocalSocket implements java.io.Closeable {
+    ctor public LocalSocket(@NonNull java.io.FileDescriptor);
+  }
+
   public class NetworkIdentity {
     method public int getOemManaged();
     method public int getRatType();
@@ -324,6 +322,20 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStateSnapshot> CREATOR;
   }
 
+  public class NetworkStatsCollection {
+    method @NonNull public java.util.Map<android.net.NetworkStatsCollection.Key,android.net.NetworkStatsHistory> getEntries();
+  }
+
+  public static final class NetworkStatsCollection.Builder {
+    ctor public NetworkStatsCollection.Builder(long);
+    method @NonNull public android.net.NetworkStatsCollection.Builder addEntry(@NonNull android.net.NetworkStatsCollection.Key, @NonNull android.net.NetworkStatsHistory);
+    method @NonNull public android.net.NetworkStatsCollection build();
+  }
+
+  public static class NetworkStatsCollection.Key {
+    ctor public NetworkStatsCollection.Key(@NonNull java.util.Set<android.net.NetworkIdentity>, int, int, int);
+  }
+
   public final class NetworkStatsHistory implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.List<android.net.NetworkStatsHistory.Entry> getEntries();
@@ -358,6 +370,7 @@
     method public int getRoaming();
     method @NonNull public java.util.Set<java.lang.String> getSubscriberIds();
     method @NonNull public java.util.Set<java.lang.String> getWifiNetworkKeys();
+    method public boolean matches(@NonNull android.net.NetworkIdentity);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkTemplate> CREATOR;
     field public static final int MATCH_BLUETOOTH = 8; // 0x8
@@ -404,6 +417,7 @@
   }
 
   public class TrafficStats {
+    method public static void attachSocketTagger();
     method public static void init(@NonNull android.content.Context);
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index da4147b..b0b5edd 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -287,6 +287,7 @@
     field public static final String SECURE_ELEMENT_PRIVILEGED_OPERATION = "android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION";
     field public static final String SEND_CATEGORY_CAR_NOTIFICATIONS = "android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS";
     field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
+    field public static final String SEND_LOST_MODE_LOCATION_UPDATES = "android.permission.SEND_LOST_MODE_LOCATION_UPDATES";
     field public static final String SEND_SAFETY_CENTER_UPDATE = "android.permission.SEND_SAFETY_CENTER_UPDATE";
     field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
     field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
@@ -320,6 +321,7 @@
     field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
     field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
     field public static final String TOGGLE_AUTOMOTIVE_PROJECTION = "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION";
+    field public static final String TRIGGER_LOST_MODE = "android.permission.TRIGGER_LOST_MODE";
     field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
     field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
     field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
@@ -742,6 +744,7 @@
     method public void clearRequireCompatChange();
     method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
     method public static android.app.BroadcastOptions makeBasic();
+    method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
     method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
     method public void setDontSendToRestrictedApps(boolean);
     method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
@@ -1021,13 +1024,13 @@
 package android.app.admin {
 
   public final class DevicePolicyDrawableResource implements android.os.Parcelable {
-    ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, int, @DrawableRes int);
-    ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, @DrawableRes int);
+    ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, @NonNull String, @NonNull String, @NonNull String, @DrawableRes int);
+    ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, @NonNull String, @NonNull String, @DrawableRes int);
     method public int describeContents();
     method @DrawableRes public int getCallingPackageResourceId();
-    method public int getDrawableId();
-    method public int getDrawableSource();
-    method public int getDrawableStyle();
+    method @NonNull public String getDrawableId();
+    method @NonNull public String getDrawableSource();
+    method @NonNull public String getDrawableStyle();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyDrawableResource> CREATOR;
   }
@@ -1066,8 +1069,9 @@
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
-    method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetDrawables(@NonNull int[]);
+    method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetDrawables(@NonNull String[]);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetStrings(@NonNull String[]);
+    method @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setDrawables(@NonNull java.util.Set<android.app.admin.DevicePolicyDrawableResource>);
@@ -1079,6 +1083,7 @@
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
     field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
     field @RequiresPermission(android.Manifest.permission.DISPATCH_PROVISIONING_MESSAGE) public static final String ACTION_ESTABLISH_NETWORK_CONNECTION = "android.app.action.ESTABLISH_NETWORK_CONNECTION";
+    field public static final String ACTION_LOST_MODE_LOCATION_UPDATE = "android.app.action.LOST_MODE_LOCATION_UPDATE";
     field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
     field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
@@ -1105,6 +1110,7 @@
     field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
     field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
     field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
+    field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION";
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -2252,473 +2258,6 @@
 
 }
 
-package android.bluetooth {
-
-  public final class BluetoothA2dp implements android.bluetooth.BluetoothProfile {
-    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BufferConstraints getBufferConstraints();
-    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 getDynamicBufferSupport();
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setBufferLengthMillis(int, int);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
-    field public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1; // 0x1
-    field public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2; // 0x2
-    field public static final int DYNAMIC_BUFFER_SUPPORT_NONE = 0; // 0x0
-    field public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0; // 0x0
-    field public static final int OPTIONAL_CODECS_PREF_DISABLED = 0; // 0x0
-    field public static final int OPTIONAL_CODECS_PREF_ENABLED = 1; // 0x1
-    field public static final int OPTIONAL_CODECS_PREF_UNKNOWN = -1; // 0xffffffff
-    field public static final int OPTIONAL_CODECS_SUPPORTED = 1; // 0x1
-    field public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1; // 0xffffffff
-  }
-
-  public final class BluetoothA2dpSink implements android.bluetooth.BluetoothProfile {
-    method public void finalize();
-    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 boolean isAudioPlaying(@NonNull android.bluetooth.BluetoothDevice);
-    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.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
-  }
-
-  public final class BluetoothActivityEnergyInfo implements android.os.Parcelable {
-    method public int getBluetoothStackState();
-    method public long getControllerEnergyUsed();
-    method public long getControllerIdleTimeMillis();
-    method public long getControllerRxTimeMillis();
-    method public long getControllerTxTimeMillis();
-    method public long getTimestampMillis();
-    method @NonNull public java.util.List<android.bluetooth.UidTraffic> getUidTraffic();
-    method public boolean isValid();
-    field public static final int BT_STACK_STATE_INVALID = 0; // 0x0
-    field public static final int BT_STACK_STATE_STATE_ACTIVE = 1; // 0x1
-    field public static final int BT_STACK_STATE_STATE_IDLE = 3; // 0x3
-    field public static final int BT_STACK_STATE_STATE_SCANNING = 2; // 0x2
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothActivityEnergyInfo> CREATOR;
-  }
-
-  public final class BluetoothAdapter {
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean disable(boolean);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disableBLE();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enableBLE();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enableNoAutoConnect();
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void generateLocalOobData(int, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OobDataCallback);
-    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getActiveDevices(int);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public long getDiscoveryEndMillis();
-    method public boolean isBleScanAlwaysAvailable();
-    method public boolean isLeEnabled();
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean removeActiveDevice(int);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean removeOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setActiveDevice(@NonNull android.bluetooth.BluetoothDevice, int);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int setDiscoverableTimeout(@NonNull java.time.Duration);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int setScanMode(int);
-    field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED";
-    field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
-    field public static final int ACTIVE_DEVICE_ALL = 2; // 0x2
-    field public static final int ACTIVE_DEVICE_AUDIO = 0; // 0x0
-    field public static final int ACTIVE_DEVICE_PHONE_CALL = 1; // 0x1
-  }
-
-  public static interface BluetoothAdapter.OnMetadataChangedListener {
-    method public void onMetadataChanged(@NonNull android.bluetooth.BluetoothDevice, int, @Nullable byte[]);
-  }
-
-  public static interface BluetoothAdapter.OobDataCallback {
-    method public void onError(int);
-    method public void onOobData(int, @NonNull android.bluetooth.OobData);
-  }
-
-  public final class BluetoothClass implements android.os.Parcelable {
-    field public static final int PROFILE_A2DP_SINK = 6; // 0x6
-    field public static final int PROFILE_NAP = 5; // 0x5
-    field public static final int PROFILE_OPP = 2; // 0x2
-    field public static final int PROFILE_PANU = 4; // 0x4
-  }
-
-  public final class BluetoothCsipSetCoordinator implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<java.lang.Integer> getAllGroupIds(@Nullable android.os.ParcelUuid);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.Map getGroupUuidMapByDevice(@Nullable android.bluetooth.BluetoothDevice);
-    method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.UUID groupLock(int, @Nullable java.util.concurrent.Executor, @Nullable android.bluetooth.BluetoothCsipSetCoordinator.ClientLockCallback);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean groupUnlock(@NonNull java.util.UUID);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CSIS_DEVICE_AVAILABLE = "android.bluetooth.action.CSIS_DEVICE_AVAILABLE";
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CSIS_SET_MEMBER_AVAILABLE = "android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE";
-  }
-
-  public static interface BluetoothCsipSetCoordinator.ClientLockCallback {
-    method public void onGroupLockSet(int, int, boolean);
-  }
-
-  public final class BluetoothDevice implements android.os.Parcelable {
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean canBondWithoutDialog();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean cancelBondProcess();
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public int connect();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBond(int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBondOutOfBand(int, @Nullable android.bluetooth.OobData, @Nullable android.bluetooth.OobData);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int disconnect();
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean fetchUuidsWithSdp(int);
-    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public byte[] getMetadata(int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getSimAccessPermission();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isConnected();
-    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);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setSilenceMode(boolean);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setSimAccessPermission(int);
-    field public static final int ACCESS_ALLOWED = 1; // 0x1
-    field public static final int ACCESS_REJECTED = 2; // 0x2
-    field public static final int ACCESS_UNKNOWN = 0; // 0x0
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED";
-    field public static final String DEVICE_TYPE_DEFAULT = "Default";
-    field public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset";
-    field public static final String DEVICE_TYPE_WATCH = "Watch";
-    field public static final int METADATA_COMPANION_APP = 4; // 0x4
-    field public static final int METADATA_DEVICE_TYPE = 17; // 0x11
-    field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10
-    field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3
-    field public static final int METADATA_IS_UNTETHERED_HEADSET = 6; // 0x6
-    field public static final int METADATA_MAIN_BATTERY = 18; // 0x12
-    field public static final int METADATA_MAIN_CHARGING = 19; // 0x13
-    field public static final int METADATA_MAIN_ICON = 5; // 0x5
-    field public static final int METADATA_MAIN_LOW_BATTERY_THRESHOLD = 20; // 0x14
-    field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0
-    field public static final int METADATA_MAX_LENGTH = 2048; // 0x800
-    field public static final int METADATA_MODEL_NAME = 1; // 0x1
-    field public static final int METADATA_SOFTWARE_VERSION = 2; // 0x2
-    field public static final int METADATA_UNTETHERED_CASE_BATTERY = 12; // 0xc
-    field public static final int METADATA_UNTETHERED_CASE_CHARGING = 15; // 0xf
-    field public static final int METADATA_UNTETHERED_CASE_ICON = 9; // 0x9
-    field public static final int METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD = 23; // 0x17
-    field public static final int METADATA_UNTETHERED_LEFT_BATTERY = 10; // 0xa
-    field public static final int METADATA_UNTETHERED_LEFT_CHARGING = 13; // 0xd
-    field public static final int METADATA_UNTETHERED_LEFT_ICON = 7; // 0x7
-    field public static final int METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD = 21; // 0x15
-    field public static final int METADATA_UNTETHERED_RIGHT_BATTERY = 11; // 0xb
-    field public static final int METADATA_UNTETHERED_RIGHT_CHARGING = 14; // 0xe
-    field public static final int METADATA_UNTETHERED_RIGHT_ICON = 8; // 0x8
-    field public static final int METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD = 22; // 0x16
-  }
-
-  public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean connect(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int connectAudio();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int disconnectAudio();
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getAudioState(@NonNull android.bluetooth.BluetoothDevice);
-    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 boolean isInbandRingingEnabled();
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean startScoUsingVirtualVoiceCall();
-    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);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
-  }
-
-  public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
-  }
-
-  public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile {
-    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 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(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
-    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.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();
-    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 boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
-    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 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 @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";
-    field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
-    field public static final String EXTRA_TETHERING_STATE = "android.bluetooth.extra.TETHERING_STATE";
-    field public static final int LOCAL_NAP_ROLE = 1; // 0x1
-    field public static final int LOCAL_PANU_ROLE = 2; // 0x2
-    field public static final int PAN_ROLE_NONE = 0; // 0x0
-    field public static final int REMOTE_NAP_ROLE = 1; // 0x1
-    field public static final int REMOTE_PANU_ROLE = 2; // 0x2
-    field public static final int TETHERING_STATE_OFF = 1; // 0x1
-    field public static final int TETHERING_STATE_ON = 2; // 0x2
-  }
-
-  public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
-    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.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
-    field public static final int CONNECTION_POLICY_ALLOWED = 100; // 0x64
-    field public static final int CONNECTION_POLICY_FORBIDDEN = 0; // 0x0
-    field public static final int CONNECTION_POLICY_UNKNOWN = -1; // 0xffffffff
-    field public static final int HEADSET_CLIENT = 16; // 0x10
-    field public static final int MAP_CLIENT = 18; // 0x12
-    field public static final int PAN = 5; // 0x5
-    field public static final int PBAP_CLIENT = 17; // 0x11
-    field @Deprecated public static final int PRIORITY_OFF = 0; // 0x0
-    field @Deprecated public static final int PRIORITY_ON = 100; // 0x64
-    field public static final int VOLUME_CONTROL = 23; // 0x17
-  }
-
-  public final class BluetoothStatusCodes {
-    field public static final int ERROR_ANOTHER_ACTIVE_OOB_REQUEST = 1000; // 0x3e8
-    field public static final int ERROR_AUDIO_DEVICE_ALREADY_CONNECTED = 1116; // 0x45c
-    field public static final int ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED = 1117; // 0x45d
-    field public static final int ERROR_AUDIO_ROUTE_BLOCKED = 1118; // 0x45e
-    field public static final int ERROR_CALL_ACTIVE = 1119; // 0x45f
-    field public static final int ERROR_NOT_ACTIVE_DEVICE = 12; // 0xc
-    field public static final int ERROR_NO_ACTIVE_DEVICES = 13; // 0xd
-    field public static final int ERROR_PROFILE_NOT_CONNECTED = 14; // 0xe
-    field public static final int ERROR_TIMEOUT = 15; // 0xf
-  }
-
-  public final class BluetoothUuid {
-    method public static boolean containsAnyUuid(@Nullable android.os.ParcelUuid[], @Nullable android.os.ParcelUuid[]);
-    method @NonNull public static android.os.ParcelUuid parseUuidFrom(@Nullable byte[]);
-    field @NonNull public static final android.os.ParcelUuid A2DP_SINK;
-    field @NonNull public static final android.os.ParcelUuid A2DP_SOURCE;
-    field @NonNull public static final android.os.ParcelUuid ADV_AUDIO_DIST;
-    field @NonNull public static final android.os.ParcelUuid AVRCP_CONTROLLER;
-    field @NonNull public static final android.os.ParcelUuid AVRCP_TARGET;
-    field @NonNull public static final android.os.ParcelUuid BASE_UUID;
-    field @NonNull public static final android.os.ParcelUuid BNEP;
-    field @NonNull public static final android.os.ParcelUuid CAP;
-    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;
-    field @NonNull public static final android.os.ParcelUuid HID;
-    field @NonNull public static final android.os.ParcelUuid HOGP;
-    field @NonNull public static final android.os.ParcelUuid HSP;
-    field @NonNull public static final android.os.ParcelUuid HSP_AG;
-    field @NonNull public static final android.os.ParcelUuid LE_AUDIO;
-    field @NonNull public static final android.os.ParcelUuid MAP;
-    field @NonNull public static final android.os.ParcelUuid MAS;
-    field @NonNull public static final android.os.ParcelUuid MEDIA_CONTROL;
-    field @NonNull public static final android.os.ParcelUuid MNS;
-    field @NonNull public static final android.os.ParcelUuid NAP;
-    field @NonNull public static final android.os.ParcelUuid OBEX_OBJECT_PUSH;
-    field @NonNull public static final android.os.ParcelUuid PANU;
-    field @NonNull public static final android.os.ParcelUuid PBAP_PCE;
-    field @NonNull public static final android.os.ParcelUuid PBAP_PSE;
-    field @NonNull public static final android.os.ParcelUuid SAP;
-    field public static final int UUID_BYTES_128_BIT = 16; // 0x10
-    field public static final int UUID_BYTES_16_BIT = 2; // 0x2
-    field public static final int UUID_BYTES_32_BIT = 4; // 0x4
-    field @NonNull public static final android.os.ParcelUuid VOLUME_CONTROL;
-  }
-
-  public final class BluetoothVolumeControl implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void close();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize();
-    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 boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void setVolume(@Nullable android.bluetooth.BluetoothDevice, @IntRange(from=0, to=255) int);
-    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED";
-  }
-
-  public final class BufferConstraint implements android.os.Parcelable {
-    ctor public BufferConstraint(int, int, int);
-    method public int describeContents();
-    method public int getDefaultMillis();
-    method public int getMaxMillis();
-    method public int getMinMillis();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BufferConstraint> CREATOR;
-  }
-
-  public final class BufferConstraints implements android.os.Parcelable {
-    ctor public BufferConstraints(@NonNull java.util.List<android.bluetooth.BufferConstraint>);
-    method public int describeContents();
-    method @Nullable public android.bluetooth.BufferConstraint forCodec(int);
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field public static final int BUFFER_CODEC_MAX_NUM = 32; // 0x20
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BufferConstraints> CREATOR;
-  }
-
-  public final class OobData implements android.os.Parcelable {
-    method @NonNull public byte[] getClassOfDevice();
-    method @NonNull public byte[] getClassicLength();
-    method @NonNull public byte[] getConfirmationHash();
-    method @NonNull public byte[] getDeviceAddressWithType();
-    method @Nullable public byte[] getDeviceName();
-    method @Nullable public byte[] getLeAppearance();
-    method @NonNull public int getLeDeviceRole();
-    method @NonNull public int getLeFlags();
-    method @Nullable public byte[] getLeTemporaryKey();
-    method @NonNull public byte[] getRandomizerHash();
-    field public static final int CLASS_OF_DEVICE_OCTETS = 3; // 0x3
-    field public static final int CONFIRMATION_OCTETS = 16; // 0x10
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.OobData> CREATOR;
-    field public static final int DEVICE_ADDRESS_OCTETS = 7; // 0x7
-    field public static final int LE_APPEARANCE_OCTETS = 2; // 0x2
-    field public static final int LE_DEVICE_FLAG_OCTETS = 1; // 0x1
-    field public static final int LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL = 3; // 0x3
-    field public static final int LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL = 2; // 0x2
-    field public static final int LE_DEVICE_ROLE_CENTRAL_ONLY = 1; // 0x1
-    field public static final int LE_DEVICE_ROLE_OCTETS = 1; // 0x1
-    field public static final int LE_DEVICE_ROLE_PERIPHERAL_ONLY = 0; // 0x0
-    field public static final int LE_FLAG_BREDR_NOT_SUPPORTED = 2; // 0x2
-    field public static final int LE_FLAG_GENERAL_DISCOVERY_MODE = 1; // 0x1
-    field public static final int LE_FLAG_LIMITED_DISCOVERY_MODE = 0; // 0x0
-    field public static final int LE_FLAG_SIMULTANEOUS_CONTROLLER = 3; // 0x3
-    field public static final int LE_FLAG_SIMULTANEOUS_HOST = 4; // 0x4
-    field public static final int LE_TK_OCTETS = 16; // 0x10
-    field public static final int OOB_LENGTH_OCTETS = 2; // 0x2
-    field public static final int RANDOMIZER_OCTETS = 16; // 0x10
-  }
-
-  public static final class OobData.ClassicBuilder {
-    ctor public OobData.ClassicBuilder(@NonNull byte[], @NonNull byte[], @NonNull byte[]);
-    method @NonNull public android.bluetooth.OobData build();
-    method @NonNull public android.bluetooth.OobData.ClassicBuilder setClassOfDevice(@NonNull byte[]);
-    method @NonNull public android.bluetooth.OobData.ClassicBuilder setDeviceName(@NonNull byte[]);
-    method @NonNull public android.bluetooth.OobData.ClassicBuilder setRandomizerHash(@NonNull byte[]);
-  }
-
-  public static final class OobData.LeBuilder {
-    ctor public OobData.LeBuilder(@NonNull byte[], @NonNull byte[], int);
-    method @NonNull public android.bluetooth.OobData build();
-    method @NonNull public android.bluetooth.OobData.LeBuilder setDeviceName(@NonNull byte[]);
-    method @NonNull public android.bluetooth.OobData.LeBuilder setLeFlags(int);
-    method @NonNull public android.bluetooth.OobData.LeBuilder setLeTemporaryKey(@NonNull byte[]);
-    method @NonNull public android.bluetooth.OobData.LeBuilder setRandomizerHash(@NonNull byte[]);
-  }
-
-  public final class UidTraffic implements java.lang.Cloneable android.os.Parcelable {
-    method public long getRxBytes();
-    method public long getTxBytes();
-    method public int getUid();
-    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.UidTraffic> CREATOR;
-  }
-
-}
-
-package android.bluetooth.le {
-
-  public final class AdvertiseSettings implements android.os.Parcelable {
-    method public int getOwnAddressType();
-  }
-
-  public static final class AdvertiseSettings.Builder {
-    method @NonNull public android.bluetooth.le.AdvertiseSettings.Builder setOwnAddressType(int);
-  }
-
-  public final class AdvertisingSetParameters implements android.os.Parcelable {
-    method public int getOwnAddressType();
-    field public static final int ADDRESS_TYPE_DEFAULT = -1; // 0xffffffff
-    field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
-    field public static final int ADDRESS_TYPE_RANDOM = 1; // 0x1
-  }
-
-  public static final class AdvertisingSetParameters.Builder {
-    method @NonNull public android.bluetooth.le.AdvertisingSetParameters.Builder setOwnAddressType(int);
-  }
-
-  public final class BluetoothLeScanner {
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(android.os.WorkSource, android.bluetooth.le.ScanCallback);
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.os.WorkSource, android.bluetooth.le.ScanCallback);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startTruncatedScan(java.util.List<android.bluetooth.le.TruncatedFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
-  }
-
-  @Deprecated public final class ResultStorageDescriptor implements android.os.Parcelable {
-    ctor @Deprecated public ResultStorageDescriptor(int, int, int);
-    method @Deprecated public int describeContents();
-    method @Deprecated public int getLength();
-    method @Deprecated public int getOffset();
-    method @Deprecated public int getType();
-    method @Deprecated public void writeToParcel(android.os.Parcel, int);
-    field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.ResultStorageDescriptor> CREATOR;
-  }
-
-  public final class ScanFilter implements android.os.Parcelable {
-    method public int getAddressType();
-    method @Nullable public byte[] getIrk();
-  }
-
-  public static final class ScanFilter.Builder {
-    method @NonNull public android.bluetooth.le.ScanFilter.Builder setDeviceAddress(@NonNull String, int);
-    method @NonNull public android.bluetooth.le.ScanFilter.Builder setDeviceAddress(@NonNull String, int, @NonNull byte[]);
-    field public static final int LEN_IRK_OCTETS = 16; // 0x10
-  }
-
-  public final class ScanSettings implements android.os.Parcelable {
-    field public static final int SCAN_MODE_AMBIENT_DISCOVERY = 3; // 0x3
-    field public static final int SCAN_RESULT_TYPE_ABBREVIATED = 1; // 0x1
-    field public static final int SCAN_RESULT_TYPE_FULL = 0; // 0x0
-  }
-
-  public static final class ScanSettings.Builder {
-    method public android.bluetooth.le.ScanSettings.Builder setScanResultType(int);
-  }
-
-  @Deprecated public final class TruncatedFilter {
-    ctor @Deprecated public TruncatedFilter(android.bluetooth.le.ScanFilter, java.util.List<android.bluetooth.le.ResultStorageDescriptor>);
-    method @Deprecated public android.bluetooth.le.ScanFilter getFilter();
-    method @Deprecated public java.util.List<android.bluetooth.le.ResultStorageDescriptor> getStorageDescriptors();
-  }
-
-}
-
 package android.companion {
 
   public final class AssociationInfo implements android.os.Parcelable {
@@ -2761,12 +2300,26 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams);
   }
 
+  public static interface VirtualDeviceManager.ActivityListener {
+    method public void onDisplayEmpty(int);
+    method public void onTopActivityChanged(int, @NonNull android.content.ComponentName);
+  }
+
+  public static interface VirtualDeviceManager.LaunchCallback {
+    method public void onLaunchFailed();
+    method public void onLaunchSuccess();
+  }
+
   public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
+    method public void addActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+    method public void addActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener, @NonNull java.util.concurrent.Executor);
     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);
+    method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.LaunchCallback);
+    method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
   }
 
   public final class VirtualDeviceParams implements android.os.Parcelable {
@@ -5238,6 +4791,7 @@
     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 @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int resetUsbPort();
     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
@@ -5790,23 +5344,41 @@
   public final class SatellitePvt implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.location.SatellitePvt.ClockInfo getClockInfo();
+    method public int getEphemerisSource();
     method @FloatRange public double getIonoDelayMeters();
+    method @IntRange(from=0, to=1023) public int getIssueOfDataClock();
+    method @IntRange(from=0, to=255) public int getIssueOfDataEphemeris();
     method @Nullable public android.location.SatellitePvt.PositionEcef getPositionEcef();
+    method @IntRange(from=0, to=604784) public int getTimeOfClock();
+    method @IntRange(from=0, to=604784) public int getTimeOfEphemeris();
     method @FloatRange public double getTropoDelayMeters();
     method @Nullable public android.location.SatellitePvt.VelocityEcef getVelocityEcef();
     method public boolean hasIono();
+    method public boolean hasIssueOfDataClock();
+    method public boolean hasIssueOfDataEphemeris();
     method public boolean hasPositionVelocityClockInfo();
+    method public boolean hasTimeOfClock();
+    method public boolean hasTimeOfEphemeris();
     method public boolean hasTropo();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.SatellitePvt> CREATOR;
+    field public static final int EPHEMERIS_SOURCE_DEMODULATED = 0; // 0x0
+    field public static final int EPHEMERIS_SOURCE_OTHER = 3; // 0x3
+    field public static final int EPHEMERIS_SOURCE_SERVER_LONG_TERM = 2; // 0x2
+    field public static final int EPHEMERIS_SOURCE_SERVER_NORMAL = 1; // 0x1
   }
 
   public static final class SatellitePvt.Builder {
     ctor public SatellitePvt.Builder();
     method @NonNull public android.location.SatellitePvt build();
     method @NonNull public android.location.SatellitePvt.Builder setClockInfo(@NonNull android.location.SatellitePvt.ClockInfo);
+    method @NonNull public android.location.SatellitePvt.Builder setEphemerisSource(int);
     method @NonNull public android.location.SatellitePvt.Builder setIonoDelayMeters(@FloatRange(from=0.0f, to=100.0f) double);
+    method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataClock(@IntRange(from=0, to=1023) int);
+    method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataEphemeris(@IntRange(from=0, to=255) int);
     method @NonNull public android.location.SatellitePvt.Builder setPositionEcef(@NonNull android.location.SatellitePvt.PositionEcef);
+    method @NonNull public android.location.SatellitePvt.Builder setTimeOfClock(@IntRange(from=0, to=604784) int);
+    method @NonNull public android.location.SatellitePvt.Builder setTimeOfEphemeris(@IntRange(from=0, to=604784) int);
     method @NonNull public android.location.SatellitePvt.Builder setTropoDelayMeters(@FloatRange(from=0.0f, to=100.0f) double);
     method @NonNull public android.location.SatellitePvt.Builder setVelocityEcef(@NonNull android.location.SatellitePvt.VelocityEcef);
   }
@@ -5991,6 +5563,7 @@
     method public boolean isAudioServerRunning();
     method public boolean isHdmiSystemAudioSupported();
     method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
+    method public static boolean isUltrasoundSupported();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
@@ -6795,6 +6368,7 @@
     method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
     method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
     method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
+    method @Nullable public android.media.tv.tuner.frontend.FrontendStatusReadiness[] getFrontendStatusReadiness(@NonNull int[]);
     method @IntRange(from=0xffffffff) public int getMaxNumberOfFrontends(int);
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean hasUnusedFrontend(int);
     method public boolean isLowestPriority(int);
@@ -8039,6 +7613,16 @@
     method public boolean isLocked();
   }
 
+  public class FrontendStatusReadiness {
+    method public int getStatusReadiness();
+    method public int getStatusType();
+    field public static final int FRONTEND_STATUS_READINESS_STABLE = 3; // 0x3
+    field public static final int FRONTEND_STATUS_READINESS_UNAVAILABLE = 1; // 0x1
+    field public static final int FRONTEND_STATUS_READINESS_UNDEFINED = 0; // 0x0
+    field public static final int FRONTEND_STATUS_READINESS_UNSTABLE = 2; // 0x2
+    field public static final int FRONTEND_STATUS_READINESS_UNSUPPORTED = 4; // 0x4
+  }
+
   public class Isdbs3FrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
     method public int getCodeRateCapability();
     method public int getModulationCapability();
@@ -9933,6 +9517,8 @@
     method public int checkPermissionForDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
     method public int checkPermissionForDataDeliveryFromDataSource(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
     method public int checkPermissionForPreflight(@NonNull String, @NonNull android.content.AttributionSource);
+    method public int checkPermissionForStartDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
+    method public void finishDataDelivery(@NonNull String, @NonNull android.content.AttributionSource);
     method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages();
     method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages();
     method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public int getRuntimePermissionsVersion();
@@ -11164,6 +10750,7 @@
     method public void onCreate();
     method public void onDestroy();
     method public void onGameTaskFocusChanged(boolean);
+    method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public final boolean restartGame();
     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);
   }
@@ -13383,6 +12970,7 @@
   }
 
   public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
+    ctor public TelephonyManager.ModemActivityInfoException(int);
     method public int getErrorCode();
     field public static final int ERROR_INVALID_INFO_RECEIVED = 2; // 0x2
     field public static final int ERROR_MODEM_RESPONSE_ERROR = 3; // 0x3
@@ -14762,6 +14350,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
@@ -15730,6 +15319,7 @@
     method @Deprecated public abstract void setUseWebViewBackgroundForOverscrollBackground(boolean);
     method @Deprecated public abstract void setUserAgent(int);
     method public abstract void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean);
+    field public static final long ENABLE_SIMPLIFIED_DARK_MODE = 214741472L; // 0xcccb1e0L
   }
 
   public class WebStorage {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ff26ae8..2303ddb 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -103,6 +103,7 @@
 package android.app {
 
   @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+    method public final boolean addDumpable(@NonNull android.util.Dumpable);
     method public void onMovedToDisplay(int, android.content.res.Configuration);
   }
 
@@ -617,14 +618,6 @@
 
 }
 
-package android.bluetooth {
-
-  public final class BluetoothClass implements android.os.Parcelable {
-    method public int getClassOfDevice();
-  }
-
-}
-
 package android.companion {
 
   public abstract class CompanionDeviceService extends android.app.Service {
@@ -1801,7 +1794,6 @@
     method public static android.os.VibrationEffect get(int, boolean);
     method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
     method public abstract long getDuration();
-    method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform();
     field public static final int EFFECT_POP = 4; // 0x4
     field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0
     field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1
@@ -1819,20 +1811,6 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR;
   }
 
-  public static final class VibrationEffect.Composition {
-    method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect);
-    method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect, @IntRange(from=0) int);
-  }
-
-  public static final class VibrationEffect.WaveformBuilder {
-    method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
-    method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=1.0f) float, @IntRange(from=0) int);
-    method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
-    method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=1.0f) float, @IntRange(from=0) int);
-    method @NonNull public android.os.VibrationEffect build();
-    method @NonNull public android.os.VibrationEffect build(int);
-  }
-
   public abstract class Vibrator {
     method public int getDefaultVibrationIntensity(int);
     field public static final int VIBRATION_INTENSITY_HIGH = 3; // 0x3
@@ -2507,6 +2485,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
     method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
     method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
+    method @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
     field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff
   }
 
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 479e6bf..6b0aef8 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -1400,7 +1400,9 @@
          * </p>
          *
          * @return the current magnification scale
+         * @deprecated Use {@link #getMagnificationConfig()} instead
          */
+        @Deprecated
         public float getScale() {
             final IAccessibilityServiceConnection connection =
                     AccessibilityInteractionClient.getInstance(mService).getConnection(
@@ -1435,7 +1437,9 @@
          *
          * @return the unscaled screen-relative X coordinate of the center of
          *         the magnified region
+         * @deprecated Use {@link #getMagnificationConfig()} instead
          */
+        @Deprecated
         public float getCenterX() {
             final IAccessibilityServiceConnection connection =
                     AccessibilityInteractionClient.getInstance(mService).getConnection(
@@ -1470,7 +1474,9 @@
          *
          * @return the unscaled screen-relative Y coordinate of the center of
          *         the magnified region
+         * @deprecated Use {@link #getMagnificationConfig()} instead
          */
+        @Deprecated
         public float getCenterY() {
             final IAccessibilityServiceConnection connection =
                     AccessibilityInteractionClient.getInstance(mService).getConnection(
@@ -1509,7 +1515,9 @@
          *
          * @return the region of the screen currently active for magnification, or an empty region
          * if magnification is not active.
+         * @deprecated Use {@link #getCurrentMagnificationRegion()} instead
          */
+        @Deprecated
         @NonNull
         public Region getMagnificationRegion() {
             final IAccessibilityServiceConnection connection =
@@ -1677,7 +1685,9 @@
          * @param animate {@code true} to animate from the current scale or
          *                {@code false} to set the scale immediately
          * @return {@code true} on success, {@code false} on failure
+         * @deprecated Use {@link #setMagnificationConfig(MagnificationConfig, boolean)} instead
          */
+        @Deprecated
         public boolean setScale(float scale, boolean animate) {
             final IAccessibilityServiceConnection connection =
                     AccessibilityInteractionClient.getInstance(mService).getConnection(
@@ -1717,7 +1727,9 @@
          * @param animate {@code true} to animate from the current viewport
          *                center or {@code false} to set the center immediately
          * @return {@code true} on success, {@code false} on failure
+         * @deprecated Use {@link #setMagnificationConfig(MagnificationConfig, boolean)} instead
          */
+        @Deprecated
         public boolean setCenter(float centerX, float centerY, boolean animate) {
             final IAccessibilityServiceConnection connection =
                     AccessibilityInteractionClient.getInstance(mService).getConnection(
@@ -1754,7 +1766,11 @@
              * magnification is focused
              * @param centerY the new Y coordinate, in unscaled coordinates, around which
              * magnification is focused
+             * @deprecated Override
+             * {@link #onMagnificationChanged(MagnificationController, Region, MagnificationConfig)}
+             * instead
              */
+            @Deprecated
             void onMagnificationChanged(@NonNull MagnificationController controller,
                     @NonNull Region region, float scale, float centerX, float centerY);
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a7b96a6..38138d8 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7098,6 +7098,7 @@
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
     public final boolean addDumpable(@NonNull Dumpable dumpable) {
         if (mDumpableContainer == null) {
             mDumpableContainer = new DumpableContainerImpl();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index a140983..89dd9ef 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -65,6 +65,8 @@
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PowerExemptionManager;
+import android.os.PowerExemptionManager.ReasonCode;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -937,6 +939,121 @@
     @EnabledSince(targetSdkVersion = VERSION_CODES.S)
     public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L;
 
+    // The background process restriction levels. The definitions here are meant for internal
+    // bookkeeping only.
+
+    /**
+     * Not a valid restriction level.
+     *
+     * @hide
+     */
+    public static final int RESTRICTION_LEVEL_UNKNOWN = 0;
+
+    /**
+     * No background restrictions at all, this should NEVER be used
+     * for any process other than selected system processes, currently it's reserved.
+     *
+     * <p>In the future, apps in {@link #RESTRICTION_LEVEL_EXEMPTED} would receive permissive
+     * background restrictions to protect the system from buggy behaviors; in other words,
+     * the {@link #RESTRICTION_LEVEL_EXEMPTED} would not be the truly "unrestricted" state, while
+     * the {@link #RESTRICTION_LEVEL_UNRESTRICTED} here would be the last resort if there is
+     * a strong reason to grant such a capability to a system app. </p>
+     *
+     * @hide
+     */
+    public static final int RESTRICTION_LEVEL_UNRESTRICTED = 10;
+
+    /**
+     * The default background restriction level for the "unrestricted" apps set by the user,
+     * where it'll have the {@link android.app.AppOpsManager#OP_RUN_ANY_IN_BACKGROUND} set to
+     * ALLOWED, being added into the device idle allow list; however there will be still certain
+     * restrictions to apps in this level.
+     *
+     * @hide
+     */
+    public static final int RESTRICTION_LEVEL_EXEMPTED = 20;
+
+    /**
+     * The default background restriction level for all other apps, they'll be moved between
+     * various standby buckets, including
+     * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_ACTIVE},
+     * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_WORKING_SET},
+     * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_FREQUENT},
+     * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RARE}.
+     *
+     * @hide
+     */
+    public static final int RESTRICTION_LEVEL_ADAPTIVE_BUCKET = 30;
+
+    /**
+     * The background restriction level where the apps will be placed in the restricted bucket
+     * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED}.
+     *
+     * @hide
+     */
+    public static final int RESTRICTION_LEVEL_RESTRICTED_BUCKET = 40;
+
+    /**
+     * The background restricted level, where apps would get more restrictions,
+     * such as not allowed to launch foreground services besides on TOP.
+     *
+     * @hide
+     */
+    public static final int RESTRICTION_LEVEL_BACKGROUND_RESTRICTED = 50;
+
+    /**
+     * The most restricted level where the apps are considered "in-hibernation",
+     * its package visibility to the rest of the system is limited.
+     *
+     * @hide
+     */
+    public static final int RESTRICTION_LEVEL_HIBERNATION = 60;
+
+    /**
+     * Not a valid restriction level, it defines the maximum numerical value of restriction level.
+     *
+     * @hide
+     */
+    public static final int RESTRICTION_LEVEL_MAX = 100;
+
+    /** @hide */
+    @IntDef(prefix = { "RESTRICTION_LEVEL_" }, value = {
+            RESTRICTION_LEVEL_UNKNOWN,
+            RESTRICTION_LEVEL_UNRESTRICTED,
+            RESTRICTION_LEVEL_EXEMPTED,
+            RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
+            RESTRICTION_LEVEL_RESTRICTED_BUCKET,
+            RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
+            RESTRICTION_LEVEL_HIBERNATION,
+            RESTRICTION_LEVEL_MAX,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RestrictionLevel{}
+
+    /** @hide */
+    public static String restrictionLevelToName(@RestrictionLevel int level) {
+        switch (level) {
+            case RESTRICTION_LEVEL_UNKNOWN:
+                return "unknown";
+            case RESTRICTION_LEVEL_UNRESTRICTED:
+                return "unrestricted";
+            case RESTRICTION_LEVEL_EXEMPTED:
+                return "exempted";
+            case RESTRICTION_LEVEL_ADAPTIVE_BUCKET:
+                return "adaptive_bucket";
+            case RESTRICTION_LEVEL_RESTRICTED_BUCKET:
+                return "restricted_bucket";
+            case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
+                return "background_restricted";
+            case RESTRICTION_LEVEL_HIBERNATION:
+                return "hibernation";
+            case RESTRICTION_LEVEL_MAX:
+                return "max";
+            default:
+                return "";
+        }
+    }
+
     /** @hide */
     public int getFrontActivityScreenCompatMode() {
         try {
@@ -4950,6 +5067,27 @@
     }
 
     /**
+     * @return The reason code of whether or not the given UID should be exempted from background
+     * restrictions here.
+     *
+     * <p>
+     * Note: Call it with caution as it'll try to acquire locks in other services.
+     * </p>
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+    @ReasonCode
+    public int getBackgroundRestrictionExemptionReason(int uid) {
+        try {
+            return getService().getBackgroundRestrictionExemptionReason(uid);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return PowerExemptionManager.REASON_DENIED;
+    }
+
+    /**
      * A subset of immutable pending intent information suitable for caching on the client side.
      *
      * @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 96487de..6e072b1 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager.ProcessCapability;
+import android.app.ActivityManager.RestrictionLevel;
 import android.content.ComponentName;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
@@ -712,4 +713,97 @@
          */
         void notifyActivityEventChanged();
     }
+
+    /**
+     * Get the restriction level of the given UID, if it hosts multiple packages,
+     * return least restricted level.
+     */
+    public abstract @RestrictionLevel int getRestrictionLevel(int uid);
+
+    /**
+     * Get the restriction level of the given package for given user id.
+     */
+    public abstract @RestrictionLevel int getRestrictionLevel(String pkg, @UserIdInt int userId);
+
+    /**
+     * Get whether or not apps would be put into restricted standby bucket automatically
+     * when it's background-restricted.
+     */
+    public abstract boolean isBgAutoRestrictedBucketFeatureFlagEnabled();
+
+    /**
+     * A listener interface, which will be notified on background restriction changes.
+     */
+    public interface AppBackgroundRestrictionListener {
+        /**
+         * Called when the background restriction level of given uid/package is changed.
+         */
+        default void onRestrictionLevelChanged(int uid, String packageName,
+                @RestrictionLevel int newLevel) {
+        }
+
+        /**
+         * Called when toggling the feature flag of moving to restricted standby bucket
+         * automatically on background-restricted.
+         */
+        default void onAutoRestrictedBucketFeatureFlagChanged(boolean autoRestrictedBucket) {
+        }
+    }
+
+    /**
+     * Register the background restriction listener callback.
+     */
+    public abstract void addAppBackgroundRestrictionListener(
+            @NonNull AppBackgroundRestrictionListener listener);
+
+    /**
+     * A listener interface, which will be notified on foreground service state changes.
+     */
+    public interface ForegroundServiceStateListener {
+        /**
+         * Call when the given process's foreground service state changes.
+         *
+         * @param packageName The package name of the process.
+         * @param uid The UID of the process.
+         * @param pid The pid of the process.
+         * @param started {@code true} if the process transits from non-FGS state to FGS state.
+         */
+        void onForegroundServiceStateChanged(String packageName, int uid, int pid, boolean started);
+    }
+
+    /**
+     * Register the foreground service state change listener callback.
+     */
+    public abstract void addForegroundServiceStateListener(
+            @NonNull ForegroundServiceStateListener listener);
+
+    /**
+     * A listener interface, which will be notified on the package sends a broadcast.
+     */
+    public interface BroadcastEventListener {
+        /**
+         * Called when the given package/uid is sending a broadcast.
+         */
+        void onSendingBroadcast(String packageName, int uid);
+    }
+
+    /**
+     * Register the broadcast event listener callback.
+     */
+    public abstract void addBroadcastEventListener(@NonNull BroadcastEventListener listener);
+
+    /**
+     * A listener interface, which will be notified on the package binding to a service.
+     */
+    public interface BindServiceEventListener {
+        /**
+         * Called when the given package/uid is binding to a service
+         */
+        void onBindingService(String packageName, int uid);
+    }
+
+    /**
+     * Register the bind service event listener callback.
+     */
+    public abstract void addBindServiceEventListener(@NonNull BindServiceEventListener listener);
 }
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 6261950..877e7d3 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -119,7 +119,7 @@
         for (int i = mExitTransitionCoordinators.size() - 1; i >= 0; i--) {
             WeakReference<ExitTransitionCoordinator> oldRef
                     = mExitTransitionCoordinators.valueAt(i);
-            if (oldRef.get() == null) {
+            if (oldRef.refersTo(null)) {
                 mExitTransitionCoordinators.removeAt(i);
             }
         }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 68c69e5..0081234 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2344,11 +2344,11 @@
             Manifest.permission.USE_BIOMETRIC,
             Manifest.permission.ACTIVITY_RECOGNITION,
             Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
-            null,
+            Manifest.permission.READ_MEDIA_AUDIO,
             null, // no permission for OP_WRITE_MEDIA_AUDIO
-            null,
+            Manifest.permission.READ_MEDIA_VIDEO,
             null, // no permission for OP_WRITE_MEDIA_VIDEO
-            null,
+            Manifest.permission.READ_MEDIA_IMAGE,
             null, // no permission for OP_WRITE_MEDIA_IMAGES
             null, // no permission for OP_LEGACY_STORAGE
             null, // no permission for OP_ACCESS_ACCESSIBILITY
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 7812aba..e31a566 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -52,6 +53,7 @@
     private String[] mRequireNoneOfPermissions;
     private long mRequireCompatChangeId = CHANGE_INVALID;
     private boolean mRequireCompatChangeEnabled = true;
+    private long mIdForResponseEvent;
 
     /**
      * Change ID which is invalid.
@@ -164,6 +166,12 @@
     public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED =
             PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
 
+    /**
+     * Corresponds to {@link #recordResponseEventWhileInBackground(long)}.
+     */
+    private static final String KEY_ID_FOR_RESPONSE_EVENT =
+            "android:broadcast.idForResponseEvent";
+
     public static BroadcastOptions makeBasic() {
         BroadcastOptions opts = new BroadcastOptions();
         return opts;
@@ -198,6 +206,7 @@
         mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS);
         mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID);
         mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
+        mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
     }
 
     /**
@@ -511,6 +520,28 @@
     }
 
     /**
+     * Sets whether events (such as posting a notification) originating from an app after it
+     * receives the broadcast while in background should be recorded as responses to the broadcast.
+     *
+     * @param id ID to be used for the response events corresponding to this broadcast. If the
+     *           value is {@code 0} (default), then response events will not be recorded. Otherwise,
+     *           they will be recorded with the ID provided.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
+    public void recordResponseEventWhileInBackground(@IntRange(from = 0) long id) {
+        mIdForResponseEvent = id;
+    }
+
+    /** @hide */
+    @IntRange(from = 0)
+    public long getIdForResponseEvent() {
+        return mIdForResponseEvent;
+    }
+
+    /**
      * Returns the created options as a Bundle, which can be passed to
      * {@link android.content.Context#sendBroadcast(android.content.Intent)
      * Context.sendBroadcast(Intent)} and related methods.
@@ -549,6 +580,9 @@
             b.putLong(KEY_REQUIRE_COMPAT_CHANGE_ID, mRequireCompatChangeId);
             b.putBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, mRequireCompatChangeEnabled);
         }
+        if (mIdForResponseEvent != 0) {
+            b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
+        }
         return b.isEmpty() ? null : b;
     }
 }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index a2578d6..ead2484 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -743,4 +743,14 @@
 
     /** Blocks until all broadcast queues become idle. */
     void waitForBroadcastIdle();
+
+    /**
+     * @return The reason code of whether or not the given UID should be exempted from background
+     * restrictions here.
+     *
+     * <p>
+     * Note: Call it with caution as it'll try to acquire locks in other services.
+     * </p>
+     */
+    int getBackgroundRestrictionExemptionReason(int uid);
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 779552f1..d57c288 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6712,6 +6712,18 @@
             return;
         }
         boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
+
+        if (mSmallIcon != null
+                // Only bitmap icons can be downscaled.
+                && (mSmallIcon.getType() == Icon.TYPE_BITMAP
+                        || mSmallIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) {
+            Resources resources = context.getResources();
+            int maxSize = resources.getDimensionPixelSize(
+                    isLowRam ? R.dimen.notification_small_icon_size_low_ram
+                            : R.dimen.notification_small_icon_size);
+            mSmallIcon.scaleDownIfNecessary(maxSize, maxSize);
+        }
+
         if (mLargeIcon != null || largeIcon != null) {
             Resources resources = context.getResources();
             Class<? extends Style> style = getNotificationStyle();
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index bf3778d..f360bbed 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -678,8 +678,7 @@
         int refCount = mResourceImpls.size();
         for (int i = 0; i < refCount; i++) {
             WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
-            ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
-            if (resourceImpl == impl) {
+            if (weakImplRef != null && weakImplRef.refersTo(resourceImpl)) {
                 return mResourceImpls.keyAt(i);
             }
         }
@@ -1671,7 +1670,7 @@
                 for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
                     final ResourcesKey key = mResourceImpls.keyAt(i);
                     final WeakReference<ResourcesImpl> impl = mResourceImpls.valueAt(i);
-                    if (impl == null || impl.get() == null
+                    if (impl == null || impl.refersTo(null)
                             || !ArrayUtils.contains(key.mLoaders, loader)) {
                         continue;
                     }
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 64d3a9f..56c301f 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -44,6 +44,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -214,6 +215,34 @@
     public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2;
 
     /**
+     * Session flag for {@link #registerSessionListener} indicating the listener
+     * is interested in sessions on the keygaurd
+     * @hide
+     */
+    public static final int SESSION_KEYGUARD = 1 << 0;
+
+    /**
+     * Session flag for {@link #registerSessionListener} indicating the current session
+     * is interested in session on the biometric prompt.
+     * @hide
+     */
+    public static final int SESSION_BIOMETRIC_PROMPT = 1 << 1;
+
+    /** @hide */
+    public static final Set<Integer> ALL_SESSIONS = Set.of(
+            SESSION_KEYGUARD,
+            SESSION_BIOMETRIC_PROMPT
+    );
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "SESSION_KEYGUARD" }, value = {
+            SESSION_KEYGUARD,
+            SESSION_BIOMETRIC_PROMPT,
+    })
+    public @interface SessionFlags {}
+
+    /**
      * Response indicating that the tile was not added.
      */
     public static final int TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED = 0;
diff --git a/core/java/android/app/admin/DevicePolicyDrawableResource.java b/core/java/android/app/admin/DevicePolicyDrawableResource.java
index d32ff84..61ff11b 100644
--- a/core/java/android/app/admin/DevicePolicyDrawableResource.java
+++ b/core/java/android/app/admin/DevicePolicyDrawableResource.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.admin.DevicePolicyResources.Drawables;
 import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -34,9 +35,9 @@
  */
 @SystemApi
 public final class DevicePolicyDrawableResource implements Parcelable {
-    private final @DevicePolicyResources.UpdatableDrawableId int mDrawableId;
-    private final @DevicePolicyResources.UpdatableDrawableStyle int mDrawableStyle;
-    private final @DevicePolicyResources.UpdatableDrawableSource int mDrawableSource;
+    @NonNull private final @DevicePolicyResources.UpdatableDrawableId String mDrawableId;
+    @NonNull private final @DevicePolicyResources.UpdatableDrawableStyle String mDrawableStyle;
+    @NonNull private final @DevicePolicyResources.UpdatableDrawableSource String mDrawableSource;
     private final @DrawableRes int mCallingPackageResourceId;
     @NonNull private ParcelableResource mResource;
 
@@ -59,9 +60,9 @@
      */
     public DevicePolicyDrawableResource(
             @NonNull Context context,
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
-            @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
             @DrawableRes int callingPackageResourceId) {
         this(drawableId, drawableStyle, drawableSource, callingPackageResourceId,
                 new ParcelableResource(context, callingPackageResourceId,
@@ -69,11 +70,17 @@
     }
 
     private DevicePolicyDrawableResource(
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
-            @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
             @DrawableRes int callingPackageResourceId,
             @NonNull ParcelableResource resource) {
+
+        Objects.requireNonNull(drawableId);
+        Objects.requireNonNull(drawableStyle);
+        Objects.requireNonNull(drawableSource);
+        Objects.requireNonNull(resource);
+
         this.mDrawableId = drawableId;
         this.mDrawableStyle = drawableStyle;
         this.mDrawableSource = drawableSource;
@@ -98,34 +105,37 @@
      */
     public DevicePolicyDrawableResource(
             @NonNull Context context,
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
             @DrawableRes int callingPackageResourceId) {
-       this(context, drawableId, drawableStyle, DevicePolicyResources.Drawable.Source.UNDEFINED,
+       this(context, drawableId, drawableStyle, Drawables.Source.UNDEFINED,
                callingPackageResourceId);
     }
 
     /**
      * Returns the ID of the drawable to update.
      */
+    @NonNull
     @DevicePolicyResources.UpdatableDrawableId
-    public int getDrawableId() {
+    public String getDrawableId() {
         return mDrawableId;
     }
 
     /**
      * Returns the style of the drawable to update
      */
+    @NonNull
     @DevicePolicyResources.UpdatableDrawableStyle
-    public int getDrawableStyle() {
+    public String getDrawableStyle() {
         return mDrawableStyle;
     }
 
     /**
      * Returns the source of the drawable to update.
      */
+    @NonNull
     @DevicePolicyResources.UpdatableDrawableSource
-    public int getDrawableSource() {
+    public String getDrawableSource() {
         return mDrawableSource;
     }
 
@@ -153,9 +163,9 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         DevicePolicyDrawableResource other = (DevicePolicyDrawableResource) o;
-        return mDrawableId == other.mDrawableId
-                && mDrawableStyle == other.mDrawableStyle
-                && mDrawableSource == other.mDrawableSource
+        return mDrawableId.equals(other.mDrawableId)
+                && mDrawableStyle.equals(other.mDrawableStyle)
+                && mDrawableSource.equals(other.mDrawableSource)
                 && mCallingPackageResourceId == other.mCallingPackageResourceId
                 && mResource.equals(other.mResource);
     }
@@ -173,9 +183,9 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mDrawableId);
-        dest.writeInt(mDrawableStyle);
-        dest.writeInt(mDrawableSource);
+        dest.writeString(mDrawableId);
+        dest.writeString(mDrawableStyle);
+        dest.writeString(mDrawableSource);
         dest.writeInt(mCallingPackageResourceId);
         dest.writeTypedObject(mResource, flags);
     }
@@ -184,9 +194,9 @@
             new Creator<DevicePolicyDrawableResource>() {
                 @Override
                 public DevicePolicyDrawableResource createFromParcel(Parcel in) {
-                    int drawableId = in.readInt();
-                    int drawableStyle = in.readInt();
-                    int drawableSource = in.readInt();
+                    String drawableId = in.readString();
+                    String drawableStyle = in.readString();
+                    String drawableSource = in.readString();
                     int callingPackageResourceId = in.readInt();
                     ParcelableResource resource = in.readTypedObject(ParcelableResource.CREATOR);
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index fa0af2d..3960f4e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,9 +16,6 @@
 
 package android.app.admin;
 
-import static android.app.admin.DevicePolicyResources.Drawable.INVALID_ID;
-import static android.app.admin.DevicePolicyResources.Drawable.Source.UNDEFINED;
-
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest.permission;
@@ -42,6 +39,7 @@
 import android.app.Activity;
 import android.app.IServiceConnection;
 import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyResources.Drawables;
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -62,6 +60,7 @@
 import android.net.ProxyInfo;
 import android.net.Uri;
 import android.nfc.NfcAdapter;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -99,6 +98,7 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.net.NetworkUtilsInternal;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
@@ -133,6 +133,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 // TODO(b/172376923) - add CarDevicePolicyManager examples below (or remove reference to it).
 /**
@@ -1795,6 +1796,28 @@
             "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
 
     /**
+     * Broadcast action: sent when there is a location update on a device in lost mode. This
+     * broadcast is explicitly sent to the device policy controller app only.
+     *
+     * @see DevicePolicyManager#sendLostModeLocationUpdate
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_LOST_MODE_LOCATION_UPDATE =
+            "android.app.action.LOST_MODE_LOCATION_UPDATE";
+
+    /**
+     * Extra used with {@link #ACTION_LOST_MODE_LOCATION_UPDATE} to send the location of a device
+     * in lost mode. Value is {@code Location}.
+     *
+     * @see DevicePolicyManager#sendLostModeLocationUpdate
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_LOST_MODE_LOCATION =
+            "android.app.extra.LOST_MODE_LOCATION";
+
+    /**
      * The ComponentName of the administrator component.
      *
      * @see #ACTION_ADD_DEVICE_ADMIN
@@ -5824,6 +5847,56 @@
     }
 
     /**
+     * Send a lost mode location update to the admin. This API is limited to organization-owned
+     * devices, which includes devices with a device owner or devices with a profile owner on an
+     * organization-owned managed profile.
+     *
+     * <p>The caller must hold the
+     * {@link android.Manifest.permission#SEND_LOST_MODE_LOCATION_UPDATES} permission.
+     *
+     * <p> Not for use by third-party applications.
+     *
+     * @param executor The executor through which the callback should be invoked.
+     * @param callback A callback object that will inform the caller whether a lost mode location
+     *                 update was successfully sent
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES)
+    public void sendLostModeLocationUpdate(@NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<Boolean> callback) {
+        throwIfParentInstance("sendLostModeLocationUpdate");
+        if (mService == null) {
+            executeCallback(AndroidFuture.completedFuture(false), executor, callback);
+            return;
+        }
+        try {
+            final AndroidFuture<Boolean> future = new AndroidFuture<>();
+            mService.sendLostModeLocationUpdate(future);
+            executeCallback(future, executor, callback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private void executeCallback(AndroidFuture<Boolean> future,
+            @CallbackExecutor @NonNull Executor executor,
+            Consumer<Boolean> callback) {
+        future.whenComplete((result, error) -> executor.execute(() -> {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (error != null) {
+                    callback.accept(false);
+                } else {
+                    callback.accept(result);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }));
+    }
+
+    /**
      * Called by an application that is administering the device to set the
      * global proxy and exclusion list.
      * <p>
@@ -10018,31 +10091,35 @@
     }
 
     /**
-     * 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.
+     * Same as {@link #logoutUser(ComponentName)}, but called by system (like Settings), not admin.
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
-    public @UserIdInt int getLogoutUserId() {
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS})
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public @UserOperationResult int logoutUser() {
+        // TODO(b/214336184): add CTS test
         try {
-            return mService.getLogoutUserId();
+            return mService.logoutUserInternal();
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
     }
-
     /**
-     * Clears the user that {@link #logoutUser(ComponentName)} would switch to.
-     *
-     * <p>Typically used by system UI after it logout a session.
+     * Gets the user a {@link #logoutUser(ComponentName)} call would switch to,
+     * or {@code null} if the current user is not in a session.
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
-    public void clearLogoutUser() {
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS})
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public @Nullable UserHandle getLogoutUser() {
+        // TODO(b/214336184): add CTS test
         try {
-            mService.clearLogoutUser();
+            int userId = mService.getLogoutUserId();
+            return userId == UserHandle.USER_NULL ? null : UserHandle.of(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -10677,6 +10754,9 @@
      * On fully-managed devices this method is unsupported because all traffic is considered
      * work traffic.
      *
+     * <p> This method enables preferential network service with a default configuration.
+     * To fine-tune the configuration, use {@link #setPreferentialNetworkServiceConfig) instead.
+     *
      * <p>This method can only be called by the profile owner of a managed profile.
      * @param enabled whether preferential network service should be enabled.
      * @throws SecurityException if the caller is not the profile owner.
@@ -10715,6 +10795,56 @@
     }
 
     /**
+     * Sets preferential network configuration on the work profile.
+     * {@see PreferentialNetworkServiceConfig}
+     *
+     * An example of a supported preferential network service is the Enterprise
+     * slice on 5G networks.
+     *
+     * By default, preferential network service is disabled on the work profile on supported
+     * carriers and devices. Admins can explicitly enable it with this API.
+     * On fully-managed devices this method is unsupported because all traffic is considered
+     * work traffic.
+     *
+     * <p>This method can only be called by the profile owner of a managed profile.
+     * @param preferentialNetworkServiceConfig preferential network configuration.
+     * @throws SecurityException if the caller is not the profile owner.
+     **/
+    public void setPreferentialNetworkServiceConfig(
+            @NonNull PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) {
+        throwIfParentInstance("setPreferentialNetworkServiceConfig");
+        if (mService == null) {
+            return;
+        }
+        try {
+            mService.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfig);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get preferential network configuration
+     * {@see PreferentialNetworkServiceConfig}
+     *
+     * <p>This method can be called by the profile owner of a managed profile.
+     *
+     * @return preferential network configuration.
+     * @throws SecurityException if the caller is not the profile owner.
+     */
+    public @NonNull PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig() {
+        throwIfParentInstance("getPreferentialNetworkServiceConfig");
+        if (mService == null) {
+            return PreferentialNetworkServiceConfig.DEFAULT;
+        }
+        try {
+            return mService.getPreferentialNetworkServiceConfig();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * This method is mostly deprecated.
      * Most of the settings that still have an effect have dedicated setter methods or user
      * restrictions. See individual settings for details.
@@ -14733,17 +14863,17 @@
     /**
      * For each {@link DevicePolicyDrawableResource} item in {@code drawables}, if
      * {@link DevicePolicyDrawableResource#getDrawableSource()} is not set or is set to
-     * {@link DevicePolicyResources.Drawable.Source#UNDEFINED}, it updates the drawable resource for
+     * {@link DevicePolicyResources.Drawables.Source#UNDEFINED}, it updates the drawable resource for
      * the combination of {@link DevicePolicyDrawableResource#getDrawableId()} and
      * {@link DevicePolicyDrawableResource#getDrawableStyle()}, (see
-     * {@link DevicePolicyResources.Drawable} and {@link DevicePolicyResources.Drawable.Style}) to
+     * {@link DevicePolicyResources.Drawables} and {@link DevicePolicyResources.Drawables.Style}) to
      * the drawable with ID {@link DevicePolicyDrawableResource#getCallingPackageResourceId()},
      * meaning any system UI surface calling {@link #getDrawable}
      * with {@code drawableId} and {@code drawableStyle} will get the new resource after this API
      * is called.
      *
      * <p>Otherwise, if {@link DevicePolicyDrawableResource#getDrawableSource()} is set (see
-     * {@link DevicePolicyResources.Drawable.Source}, it overrides any drawables that was set for
+     * {@link DevicePolicyResources.Drawables.Source}, it overrides any drawables that was set for
      * the same {@code drawableId} and {@code drawableStyle} for the provided source.
      *
      * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to
@@ -14779,10 +14909,10 @@
 
     /**
      * Removes all updated drawables for the list of {@code drawableIds} (see
-     * {@link DevicePolicyResources.Drawable} that was previously set by calling
+     * {@link DevicePolicyResources.Drawables} that was previously set by calling
      * {@link #setDrawables}, meaning any subsequent calls to {@link #getDrawable} for the provided
-     * IDs with any {@link DevicePolicyResources.Drawable.Style} and any
-     * {@link DevicePolicyResources.Drawable.Source} will return the default drawable from
+     * IDs with any {@link DevicePolicyResources.Drawables.Style} and any
+     * {@link DevicePolicyResources.Drawables.Source} will return the default drawable from
      * {@code defaultDrawableLoader}.
      *
      * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to
@@ -14794,7 +14924,7 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)
-    public void resetDrawables(@NonNull int[] drawableIds) {
+    public void resetDrawables(@NonNull String[] drawableIds) {
         if (mService != null) {
             try {
                 mService.resetDrawables(drawableIds);
@@ -14806,19 +14936,19 @@
 
     /**
      * Returns the appropriate updated drawable for the {@code drawableId}
-     * (see {@link DevicePolicyResources.Drawable}), with style {@code drawableStyle}
-     * (see {@link DevicePolicyResources.Drawable.Style}) if one was set using
+     * (see {@link DevicePolicyResources.Drawables}), with style {@code drawableStyle}
+     * (see {@link DevicePolicyResources.Drawables.Style}) if one was set using
      * {@code setDrawables}, otherwise returns the drawable from {@code defaultDrawableLoader}.
      *
      * <p>Also returns the drawable from {@code defaultDrawableLoader} if
-     * {@link DevicePolicyResources.Drawable#INVALID_ID} was passed.
+     * {@link DevicePolicyResources.Drawables#UNDEFINED} was passed.
      *
      * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
      * {@link NullPointerException} is thrown.
      *
      * <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to
      * set a different value use
-     * {@link #getDrawableForDensity(int, int, int, Callable)}.
+     * {@link #getDrawableForDensity(String, String, int, Callable)}.
      *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
@@ -14833,16 +14963,18 @@
      */
     @NonNull
     public Drawable getDrawable(
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
             @NonNull Callable<Drawable> defaultDrawableLoader) {
-        return getDrawable(drawableId, drawableStyle, UNDEFINED, defaultDrawableLoader);
+        return getDrawable(
+                drawableId, drawableStyle, Drawables.Source.UNDEFINED, defaultDrawableLoader);
     }
 
     /**
-     * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts
-     * a {@code drawableSource} (see {@link DevicePolicyResources.Drawable.Source}) which
-     * could result in returning a different drawable than {@link #getDrawable(int, int, Callable)}
+     * Similar to {@link #getDrawable(String, String, Callable)}, but also accepts
+     * a {@code drawableSource} (see {@link DevicePolicyResources.Drawables.Source}) which
+     * could result in returning a different drawable than
+     * {@link #getDrawable(String, String, Callable)}
      * if an override was set for that specific source.
      *
      * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
@@ -14859,12 +14991,17 @@
      */
     @NonNull
     public Drawable getDrawable(
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
-            @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
             @NonNull Callable<Drawable> defaultDrawableLoader) {
+
+        Objects.requireNonNull(drawableId, "drawableId can't be null");
+        Objects.requireNonNull(drawableStyle, "drawableStyle can't be null");
+        Objects.requireNonNull(drawableSource, "drawableSource can't be null");
         Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
-        if (drawableId == INVALID_ID) {
+
+        if (Drawables.UNDEFINED.equals(drawableId)) {
             return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader);
         }
         if (mService != null) {
@@ -14892,7 +15029,7 @@
     }
 
     /**
-     * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts
+     * Similar to {@link #getDrawable(String, String, Callable)}, but also accepts
      * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
      *
      * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
@@ -14911,20 +15048,20 @@
      */
     @NonNull
     public Drawable getDrawableForDensity(
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
             int density,
             @NonNull Callable<Drawable> defaultDrawableLoader) {
         return getDrawableForDensity(
                 drawableId,
                 drawableStyle,
-                UNDEFINED,
+                Drawables.Source.UNDEFINED,
                 density,
                 defaultDrawableLoader);
     }
 
      /**
-     * Similar to {@link #getDrawable(int, int, int, Callable)}, but also accepts
+     * Similar to {@link #getDrawable(String, String, String, Callable)}, but also accepts
      * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
      *
      * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
@@ -14944,13 +15081,18 @@
      */
     @NonNull
     public Drawable getDrawableForDensity(
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
-            @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
             int density,
             @NonNull Callable<Drawable> defaultDrawableLoader) {
+
+        Objects.requireNonNull(drawableId, "drawableId can't be null");
+        Objects.requireNonNull(drawableStyle, "drawableStyle can't be null");
+        Objects.requireNonNull(drawableSource, "drawableSource can't be null");
         Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
-        if (drawableId == INVALID_ID) {
+
+        if (Drawables.UNDEFINED.equals(drawableId)) {
             return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader);
         }
         if (mService != null) {
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 46e2cce..2ad2010 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -128,7 +128,6 @@
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.STATUS_BAR_WORK_ICON_ACCESSIBILITY;
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.WORK_LOCK_ACCESSIBILITY;
 
-import android.annotation.IntDef;
 import android.annotation.StringDef;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -153,12 +152,12 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {
-            Drawable.INVALID_ID,
-            Drawable.WORK_PROFILE_ICON_BADGE,
-            Drawable.WORK_PROFILE_ICON,
-            Drawable.WORK_PROFILE_OFF_ICON,
-            Drawable.WORK_PROFILE_USER_ICON
+    @StringDef(value = {
+            Drawables.UNDEFINED,
+            Drawables.WORK_PROFILE_ICON_BADGE,
+            Drawables.WORK_PROFILE_ICON,
+            Drawables.WORK_PROFILE_OFF_ICON,
+            Drawables.WORK_PROFILE_USER_ICON
     })
     public @interface UpdatableDrawableId {}
 
@@ -169,10 +168,11 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {
-            Drawable.Style.SOLID_COLORED,
-            Drawable.Style.SOLID_NOT_COLORED,
-            Drawable.Style.OUTLINE,
+    @StringDef(value = {
+            Drawables.Style.SOLID_COLORED,
+            Drawables.Style.SOLID_NOT_COLORED,
+            Drawables.Style.OUTLINE,
+            Drawables.Style.DEFAULT
     })
     public @interface UpdatableDrawableStyle {}
 
@@ -182,14 +182,14 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {
-            Drawable.Source.UNDEFINED,
-            Drawable.Source.NOTIFICATION,
-            Drawable.Source.PROFILE_SWITCH_ANIMATION,
-            Drawable.Source.HOME_WIDGET,
-            Drawable.Source.LAUNCHER_OFF_BUTTON,
-            Drawable.Source.QUICK_SETTINGS,
-            Drawable.Source.STATUS_BAR
+    @StringDef(value = {
+            Drawables.Source.UNDEFINED,
+            Drawables.Source.NOTIFICATION,
+            Drawables.Source.PROFILE_SWITCH_ANIMATION,
+            Drawables.Source.HOME_WIDGET,
+            Drawables.Source.LAUNCHER_OFF_BUTTON,
+            Drawables.Source.QUICK_SETTINGS,
+            Drawables.Source.STATUS_BAR
     })
     public @interface UpdatableDrawableSource {}
 
@@ -265,44 +265,44 @@
     /**
      * Class containing the identifiers used to update device management-related system drawable.
      */
-    public static final class Drawable {
+    public static final class Drawables {
 
-        private Drawable() {
+        private Drawables() {
         }
 
         /**
          * An ID for any drawable that can't be updated.
          */
-        public static final int INVALID_ID = -1;
+        public static final String UNDEFINED = "UNDEFINED";
 
         /**
          * Specifically used to badge work profile app icons.
          */
-        public static final int WORK_PROFILE_ICON_BADGE = 0;
+        public static final String WORK_PROFILE_ICON_BADGE = "WORK_PROFILE_ICON_BADGE";
 
         /**
          * General purpose work profile icon (i.e. generic icon badging). For badging app icons
          * specifically, see {@link #WORK_PROFILE_ICON_BADGE}.
          */
-        public static final int WORK_PROFILE_ICON = 1;
+        public static final String WORK_PROFILE_ICON = "WORK_PROFILE_ICON";
 
         /**
          * General purpose icon representing the work profile off state.
          */
-        public static final int WORK_PROFILE_OFF_ICON = 2;
+        public static final String WORK_PROFILE_OFF_ICON = "WORK_PROFILE_OFF_ICON";
 
         /**
          * General purpose icon for the work profile user avatar.
          */
-        public static final int WORK_PROFILE_USER_ICON = 3;
+        public static final String WORK_PROFILE_USER_ICON = "WORK_PROFILE_USER_ICON";
 
         /**
          * @hide
          */
-        public static final Set<Integer> UPDATABLE_DRAWABLE_IDS = buildDrawablesSet();
+        public static final Set<String> UPDATABLE_DRAWABLE_IDS = buildDrawablesSet();
 
-        private static Set<Integer> buildDrawablesSet() {
-            Set<Integer> drawables = new HashSet<>();
+        private static Set<String> buildDrawablesSet() {
+            Set<String> drawables = new HashSet<>();
             drawables.add(WORK_PROFILE_ICON_BADGE);
             drawables.add(WORK_PROFILE_ICON);
             drawables.add(WORK_PROFILE_OFF_ICON);
@@ -323,48 +323,48 @@
              * A source identifier indicating that the updatable resource is used in a generic
              * undefined location.
              */
-            public static final int UNDEFINED = -1;
+            public static final String UNDEFINED = "UNDEFINED";
 
             /**
              * A source identifier indicating that the updatable drawable is used in notifications.
              */
-            public static final int NOTIFICATION = 0;
+            public static final String NOTIFICATION = "NOTIFICATION";
 
             /**
              * A source identifier indicating that the updatable drawable is used in a cross
              * profile switching animation.
              */
-            public static final int PROFILE_SWITCH_ANIMATION = 1;
+            public static final String PROFILE_SWITCH_ANIMATION = "PROFILE_SWITCH_ANIMATION";
 
             /**
              * A source identifier indicating that the updatable drawable is used in a work
              * profile home screen widget.
              */
-            public static final int HOME_WIDGET = 2;
+            public static final String HOME_WIDGET = "HOME_WIDGET";
 
             /**
              * A source identifier indicating that the updatable drawable is used in the launcher
              * turn off work button.
              */
-            public static final int LAUNCHER_OFF_BUTTON = 3;
+            public static final String LAUNCHER_OFF_BUTTON = "LAUNCHER_OFF_BUTTON";
 
             /**
              * A source identifier indicating that the updatable drawable is used in quick settings.
              */
-            public static final int QUICK_SETTINGS = 4;
+            public static final String QUICK_SETTINGS = "QUICK_SETTINGS";
 
             /**
              * A source identifier indicating that the updatable drawable is used in the status bar.
              */
-            public static final int STATUS_BAR = 5;
+            public static final String STATUS_BAR = "STATUS_BAR";
 
             /**
              * @hide
              */
-            public static final Set<Integer> UPDATABLE_DRAWABLE_SOURCES = buildSourcesSet();
+            public static final Set<String> UPDATABLE_DRAWABLE_SOURCES = buildSourcesSet();
 
-            private static Set<Integer> buildSourcesSet() {
-                Set<Integer> sources = new HashSet<>();
+            private static Set<String> buildSourcesSet() {
+                Set<String> sources = new HashSet<>();
                 sources.add(UNDEFINED);
                 sources.add(NOTIFICATION);
                 sources.add(PROFILE_SWITCH_ANIMATION);
@@ -390,31 +390,31 @@
              * A style identifier indicating that the updatable drawable should use the default
              * style.
              */
-            public static final int DEFAULT = -1;
+            public static final String DEFAULT = "DEFAULT";
 
             /**
              * A style identifier indicating that the updatable drawable has a solid color fill.
              */
-            public static final int SOLID_COLORED = 0;
+            public static final String SOLID_COLORED = "SOLID_COLORED";
 
             /**
              * A style identifier indicating that the updatable drawable has a solid non-colored
              * fill.
              */
-            public static final int SOLID_NOT_COLORED = 1;
+            public static final String SOLID_NOT_COLORED = "SOLID_NOT_COLORED";
 
             /**
              * A style identifier indicating that the updatable drawable is an outline.
              */
-            public static final int OUTLINE = 2;
+            public static final String OUTLINE = "OUTLINE";
 
             /**
              * @hide
              */
-            public static final Set<Integer> UPDATABLE_DRAWABLE_STYLES = buildStylesSet();
+            public static final Set<String> UPDATABLE_DRAWABLE_STYLES = buildStylesSet();
 
-            private static Set<Integer> buildStylesSet() {
-                Set<Integer> styles = new HashSet<>();
+            private static Set<String> buildStylesSet() {
+                Set<String> styles = new HashSet<>();
                 styles.add(DEFAULT);
                 styles.add(SOLID_COLORED);
                 styles.add(SOLID_NOT_COLORED);
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index f663c17..927ee0c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -24,6 +24,7 @@
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.admin.ParcelableGranteeMap;
+import android.app.admin.PreferentialNetworkServiceConfig;
 import android.app.admin.StartInstallingUpdateCallback;
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
@@ -47,6 +48,7 @@
 import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keystore.ParcelableKeyGenParameterSpec;
 import android.telephony.data.ApnSetting;
+import com.android.internal.infra.AndroidFuture;
 
 import java.util.List;
 
@@ -119,6 +121,8 @@
     FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
     boolean isFactoryResetProtectionPolicySupported();
 
+    void sendLostModeLocationUpdate(in AndroidFuture<boolean> future);
+
     ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
     ComponentName getGlobalProxyAdmin(int userHandle);
     void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
@@ -264,8 +268,8 @@
     int startUserInBackground(in ComponentName who, in UserHandle userHandle);
     int stopUser(in ComponentName who, in UserHandle userHandle);
     int logoutUser(in ComponentName who);
+    int logoutUserInternal(); // AIDL doesn't allow overloading name (logoutUser())
     int getLogoutUserId();
-    void clearLogoutUser();
     List<UserHandle> getSecondaryUsers(in ComponentName who);
     void acknowledgeNewUserDisclaimer();
 
@@ -283,6 +287,10 @@
     void setPreferentialNetworkServiceEnabled(in boolean enabled);
     boolean isPreferentialNetworkServiceEnabled(int userHandle);
 
+    void setPreferentialNetworkServiceConfig(
+            in PreferentialNetworkServiceConfig preferentialNetworkServiceConfig);
+    PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig();
+
     void setLockTaskPackages(in ComponentName who, in String[] packages);
     String[] getLockTaskPackages(in ComponentName who);
     boolean isLockTaskPermitted(in String pkg);
@@ -543,8 +551,8 @@
 
     List<UserHandle> listForegroundAffiliatedUsers();
     void setDrawables(in List<DevicePolicyDrawableResource> drawables);
-    void resetDrawables(in int[] drawableIds);
-    ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource);
+    void resetDrawables(in String[] drawableIds);
+    ParcelableResource getDrawable(String drawableId, String drawableStyle, String drawableSource);
 
     void setStrings(in List<DevicePolicyStringResource> strings);
     void resetStrings(in String[] stringIds);
diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.aidl b/core/java/android/app/admin/PreferentialNetworkServiceConfig.aidl
new file mode 100644
index 0000000..6b6ee7d
--- /dev/null
+++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.app.admin;
+
+parcelable PreferentialNetworkServiceConfig;
\ No newline at end of file
diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
new file mode 100644
index 0000000..2849139
--- /dev/null
+++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Network configuration to be set for the user profile
+ * {@see DevicePolicyManager#setPreferentialNetworkServiceConfig}.
+ */
+public final class PreferentialNetworkServiceConfig implements Parcelable {
+    final boolean mIsEnabled;
+    final int mNetworkId;
+    final boolean mAllowFallbackToDefaultConnection;
+    final int[] mIncludedUids;
+    final int[] mExcludedUids;
+
+    /** @hide */
+    public static final PreferentialNetworkServiceConfig DEFAULT =
+            (new PreferentialNetworkServiceConfig.Builder()).build();
+
+    /**
+     * Preferential network identifier 1.
+     */
+    public static final int PREFERENTIAL_NETWORK_ID_1 = 1;
+
+    /**
+     * Preferential network identifier 2.
+     */
+    public static final int PREFERENTIAL_NETWORK_ID_2 = 2;
+
+    /**
+     * Preferential network identifier 3.
+     */
+    public static final int PREFERENTIAL_NETWORK_ID_3 = 3;
+
+    /**
+     * Preferential network identifier 4.
+     */
+    public static final int PREFERENTIAL_NETWORK_ID_4 = 4;
+
+    /**
+     * Preferential network identifier 5.
+     */
+    public static final int PREFERENTIAL_NETWORK_ID_5 = 5;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "PREFERENTIAL_NETWORK_ID_" }, value = {
+            PREFERENTIAL_NETWORK_ID_1,
+            PREFERENTIAL_NETWORK_ID_2,
+            PREFERENTIAL_NETWORK_ID_3,
+            PREFERENTIAL_NETWORK_ID_4,
+            PREFERENTIAL_NETWORK_ID_5,
+    })
+
+    public @interface PreferentialNetworkPreferenceId {
+    }
+
+    private PreferentialNetworkServiceConfig(boolean isEnabled,
+            boolean allowFallbackToDefaultConnection, int[] includedUids,
+            int[] excludedUids, @PreferentialNetworkPreferenceId int networkId) {
+        mIsEnabled = isEnabled;
+        mAllowFallbackToDefaultConnection = allowFallbackToDefaultConnection;
+        mIncludedUids = includedUids;
+        mExcludedUids = excludedUids;
+        mNetworkId = networkId;
+    }
+
+    private PreferentialNetworkServiceConfig(Parcel in) {
+        mIsEnabled = in.readBoolean();
+        mAllowFallbackToDefaultConnection = in.readBoolean();
+        mNetworkId = in.readInt();
+        mIncludedUids = in.createIntArray();
+        mExcludedUids = in.createIntArray();
+    }
+
+    /**
+     * Is the preferential network enabled.
+     * @return true if enabled else false
+     */
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    /**
+     * is fallback to default network allowed. This boolean configures whether default connection
+     * (default internet or wifi) should be used or not if a preferential network service
+     * connection is not available.
+     * @return true if fallback is allowed, else false.
+     */
+    public boolean isFallbackToDefaultConnectionAllowed() {
+        return mAllowFallbackToDefaultConnection;
+    }
+
+    /**
+     * Get the array of uids that are applicable for the profile preference.
+     *
+     * {@see #getExcludedUids()}
+     * Included UIDs and Excluded UIDs can't both be non-empty.
+     * if both are empty, it means this request applies to all uids in the user profile.
+     * if included is not empty, then only included UIDs are applied.
+     * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+     * @return Array of uids applicable for the profile preference.
+     *      Empty array would mean that this request applies to all uids in the profile.
+     */
+    public @NonNull int[] getIncludedUids() {
+        return mIncludedUids;
+    }
+
+    /**
+     * Get the array of uids that are excluded for the profile preference.
+     *
+     * {@see #getIncludedUids()}
+     * Included UIDs and Excluded UIDs can't both be non-empty.
+     * if both are empty, it means this request applies to all uids in the user profile.
+     * if included is not empty, then only included UIDs are applied.
+     * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+     * @return Array of uids that are excluded for the profile preference.
+     *      Empty array would mean that this request applies to all uids in the profile.
+     */
+    public @NonNull int[] getExcludedUids() {
+        return mExcludedUids;
+    }
+
+    /**
+     * @return preference enterprise identifier.
+     * valid values starts from
+     * {@link #PREFERENTIAL_NETWORK_ID_1} to {@link #PREFERENTIAL_NETWORK_ID_5}.
+     * preference identifier is applicable only if preference network service is enabled
+     *
+     */
+    public @PreferentialNetworkPreferenceId int getNetworkId() {
+        return mNetworkId;
+    }
+
+    @Override
+    public String toString() {
+        return "PreferentialNetworkServiceConfig{"
+                + "mIsEnabled=" + isEnabled()
+                + "mAllowFallbackToDefaultConnection=" + isFallbackToDefaultConnectionAllowed()
+                + "mIncludedUids=" + mIncludedUids.toString()
+                + "mExcludedUids=" + mExcludedUids.toString()
+                + "mNetworkId=" + mNetworkId
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        final PreferentialNetworkServiceConfig that = (PreferentialNetworkServiceConfig) o;
+        return mIsEnabled == that.mIsEnabled
+                && mAllowFallbackToDefaultConnection == that.mAllowFallbackToDefaultConnection
+                && mNetworkId == that.mNetworkId
+                && Objects.equals(mIncludedUids, that.mIncludedUids)
+                && Objects.equals(mExcludedUids, that.mExcludedUids);
+    }
+
+    @Override
+    public int hashCode() {
+        return ((Objects.hashCode(mIsEnabled) * 17)
+                + (Objects.hashCode(mAllowFallbackToDefaultConnection) * 19)
+                + (Objects.hashCode(mIncludedUids) * 23)
+                + (Objects.hashCode(mExcludedUids) * 29)
+                + mNetworkId * 31);
+    }
+
+    /**
+     * Builder used to create {@link PreferentialNetworkServiceConfig} objects.
+     * Specify the preferred Network preference
+     */
+    public static final class Builder {
+        boolean mIsEnabled = false;
+        int mNetworkId = 0;
+        boolean mAllowFallbackToDefaultConnection = true;
+        int[] mIncludedUids = new int[0];
+        int[] mExcludedUids = new int[0];
+
+        /**
+         * Constructs an empty Builder with preferential network disabled by default.
+         */
+        public Builder() {}
+
+        /**
+         * Set the preferential network service enabled state.
+         * Default value is false.
+         * @param isEnabled  the desired network preference to use, true to enable else false
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        public PreferentialNetworkServiceConfig.Builder setEnabled(boolean isEnabled) {
+            mIsEnabled = isEnabled;
+            return this;
+        }
+
+        /**
+         * Set whether the default connection should be used as fallback.
+         * This boolean configures whether the default connection (default internet or wifi)
+         * should be used if a preferential network service connection is not available.
+         * Default value is true
+         * @param allowFallbackToDefaultConnection  true if fallback is allowed else false
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public PreferentialNetworkServiceConfig.Builder setFallbackToDefaultConnectionAllowed(
+                boolean allowFallbackToDefaultConnection) {
+            mAllowFallbackToDefaultConnection = allowFallbackToDefaultConnection;
+            return this;
+        }
+
+        /**
+         * Set the array of uids whose network access will go through this preferential
+         * network service.
+         * {@see #setExcludedUids(int[])}
+         * Included UIDs and Excluded UIDs can't both be non-empty.
+         * if both are empty, it means this request applies to all uids in the user profile.
+         * if included is not empty, then only included UIDs are applied.
+         * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+         * @param uids  array of included uids
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        public PreferentialNetworkServiceConfig.Builder setIncludedUids(
+                @NonNull int[] uids) {
+            Objects.requireNonNull(uids);
+            mIncludedUids = uids;
+            return this;
+        }
+
+        /**
+         * Set the array of uids who are not allowed through this preferential
+         * network service.
+         * {@see #setIncludedUids(int[])}
+         * Included UIDs and Excluded UIDs can't both be non-empty.
+         * if both are empty, it means this request applies to all uids in the user profile.
+         * if included is not empty, then only included UIDs are applied.
+         * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+         * @param uids  array of excluded uids
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        public PreferentialNetworkServiceConfig.Builder setExcludedUids(
+                @NonNull int[] uids) {
+            Objects.requireNonNull(uids);
+            mExcludedUids = uids;
+            return this;
+        }
+
+        /**
+         * Returns an instance of {@link PreferentialNetworkServiceConfig} created from the
+         * fields set on this builder.
+         */
+        @NonNull
+        public PreferentialNetworkServiceConfig build() {
+            if (mIncludedUids.length > 0 && mExcludedUids.length > 0) {
+                throw new IllegalStateException("Both includedUids and excludedUids "
+                        + "cannot be nonempty");
+            }
+            return new PreferentialNetworkServiceConfig(mIsEnabled,
+                    mAllowFallbackToDefaultConnection, mIncludedUids, mExcludedUids, mNetworkId);
+        }
+
+        /**
+         * Set the preferential network identifier.
+         * Valid values starts from {@link #PREFERENTIAL_NETWORK_ID_1} to
+         * {@link #PREFERENTIAL_NETWORK_ID_5}.
+         * preference identifier is applicable only if preferential network service is enabled.
+         * @param preferenceId  preference Id
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        public PreferentialNetworkServiceConfig.Builder setNetworkId(
+                @PreferentialNetworkPreferenceId int preferenceId) {
+            if ((preferenceId < PREFERENTIAL_NETWORK_ID_1)
+                    || (preferenceId > PREFERENTIAL_NETWORK_ID_5)) {
+                throw new IllegalArgumentException("Invalid preference identifier");
+            }
+            mNetworkId = preferenceId;
+            return this;
+        }
+    }
+
+    @Override
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        dest.writeBoolean(mIsEnabled);
+        dest.writeBoolean(mAllowFallbackToDefaultConnection);
+        dest.writeInt(mNetworkId);
+        dest.writeIntArray(mIncludedUids);
+        dest.writeIntArray(mExcludedUids);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<PreferentialNetworkServiceConfig> CREATOR =
+            new Creator<PreferentialNetworkServiceConfig>() {
+                @Override
+                public PreferentialNetworkServiceConfig[] newArray(int size) {
+                    return new PreferentialNetworkServiceConfig[size];
+                }
+
+                @Override
+                public PreferentialNetworkServiceConfig createFromParcel(
+                        @NonNull android.os.Parcel in) {
+                    return new PreferentialNetworkServiceConfig(in);
+                }
+            };
+}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index d7e197e..33efa01 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -320,6 +320,17 @@
      * @hide
      */
     public static final int REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY = 1 << 2;
+    /**
+     * The app was moved to restricted bucket due to user interaction, i.e., toggling FAS.
+     *
+     * <p>
+     * Note: This should be coming from the more end-user facing UX, not from developer
+     * options nor adb command.
+     </p>
+     *
+     * @hide
+     */
+    public static final int REASON_SUB_FORCED_USER_FLAG_INTERACTION = 1 << 1;
 
 
     /** @hide */
@@ -336,14 +347,15 @@
     public @interface StandbyBuckets {}
 
     /** @hide */
-    @IntDef(flag = true, prefix = {"REASON_SUB_FORCED_SYSTEM_FLAG_FLAG_"}, value = {
+    @IntDef(flag = true, prefix = {"REASON_SUB_FORCED_"}, value = {
             REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED,
             REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE,
             REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE,
             REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY,
+            REASON_SUB_FORCED_USER_FLAG_INTERACTION,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface SystemForcedReasons {
+    public @interface ForcedReasons {
     }
 
     /**
@@ -1188,11 +1200,6 @@
             case REASON_MAIN_FORCED_BY_USER:
                 sb.append("f");
                 if (subReason > 0) {
-                    // Although not expected and shouldn't happen, this could potentially have a
-                    // sub-reason if the system tries to give a reason when applying the
-                    // FORCED_BY_USER reason. The sub-reason is undefined (though most likely a
-                    // REASON_SUB_FORCED_SYSTEM_FLAG_ sub-reason), but it's better to note it in the
-                    // log than to exclude it altogether.
                     sb.append("-").append(Integer.toBinaryString(subReason));
                 }
                 break;
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 610b7ee..cb96ebe 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -36,11 +36,6 @@
  * See {@link #onDeviceAppeared(AssociationInfo)}/{@link #onDeviceDisappeared(AssociationInfo)}.
  *
  * <p>
- * Additionally, the service will receive a call from the system, if and when the system needs to
- * transfer data to the companion device.
- * See {@link #dispatchMessage(int, int, byte[])}).
- *
- * <p>
  * Companion applications must create a service that {@code extends}
  * {@link CompanionDeviceService}, and declare it in their AndroidManifest.xml with the
  * "android.permission.BIND_COMPANION_DEVICE_SERVICE" permission
@@ -79,8 +74,8 @@
  * <p>
  * It is possible for an application to declare multiple {@link CompanionDeviceService}-s.
  * In such case, the system will bind all declared services, but will deliver
- * {@link #onDeviceAppeared(AssociationInfo)}, {@link #onDeviceDisappeared(AssociationInfo)} and
- * {@link #dispatchMessage(int, int, byte[])} only to one "primary" services.
+ * {@link #onDeviceAppeared(AssociationInfo)} and {@link #onDeviceDisappeared(AssociationInfo)}
+ * only to one "primary" services.
  * Applications that declare multiple {@link CompanionDeviceService}-s should indicate the "primary"
  * service using "android.companion.primary" tag.
  * <pre>{@code
@@ -156,6 +151,8 @@
      * @param messageId system assigned id of the message to be sent
      * @param associationId association id of the associated device
      * @param message message to be sent
+     *
+     * @hide
      */
     @MainThread
     public void onDispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
@@ -172,6 +169,8 @@
      * @param messageId id of the message
      * @param associationId id of the associated device
      * @param message messaged received from the associated device
+     *
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
     public final void dispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
index 53af4c5..a46dc53 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
@@ -23,7 +23,7 @@
  *
  * @hide
  */
-interface IVirtualDeviceActivityListener {
+oneway interface IVirtualDeviceActivityListener {
 
     /**
      * Called when the top activity is changed.
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 64f16ac..bb9bb09 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -164,8 +164,6 @@
          * @param executor The executor to run {@code launchCallback} on.
          * @param launchCallback Callback that is called when the pending intent launching is
          *   complete.
-         *
-         * @hide
          */
         public void launchPendingIntent(
                 int displayId,
@@ -196,9 +194,7 @@
 
         /**
          * Creates a virtual display for this virtual device. All displays created on the same
-         * 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.
+         * device belongs to the same display group.
          *
          * @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.
@@ -369,9 +365,7 @@
          *
          * @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());
         }
@@ -383,9 +377,7 @@
          * @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));
@@ -397,9 +389,7 @@
          *
          * @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);
         }
@@ -407,10 +397,7 @@
 
     /**
      * Callback for launching pending intents on the virtual device.
-     *
-     * @hide
      */
-    // TODO(b/194949534): Unhide this API
     public interface LaunchCallback {
         /**
          * Called when the pending intent launched successfully.
@@ -425,10 +412,7 @@
 
     /**
      * Listener for activity changes in this virtual device.
-     *
-     * @hide
      */
-    // TODO(b/194949534): Unhide this API
     public interface ActivityListener {
 
         /**
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index b4f2302..5584f45 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.os.Process.myUserHandle;
 import static android.os.Trace.TRACE_TAG_DATABASE;
 
 import android.annotation.NonNull;
@@ -738,7 +739,7 @@
     }
 
     boolean checkUser(int pid, int uid, Context context) {
-        int callingUserId = UserHandle.getUserId(uid);
+        final int callingUserId = UserHandle.getUserId(uid);
 
         if (callingUserId == context.getUserId() || mSingleUser) {
             return true;
@@ -765,8 +766,8 @@
                 if (um != null && um.isCloneProfile()) {
                     UserHandle parent = um.getProfileParent(callingUser);
 
-                    if (parent != null && parent.equals(context.getUser())) {
-                        mUsersRedirectedToOwner.put(callingUser.getIdentifier(), true);
+                    if (parent != null && parent.equals(myUserHandle())) {
+                        mUsersRedirectedToOwner.put(callingUserId, true);
                         return true;
                     }
                 }
@@ -774,7 +775,7 @@
                 // ignore
             }
 
-            mUsersRedirectedToOwner.put(UserHandle.getUserId(uid), false);
+            mUsersRedirectedToOwner.put(callingUserId, false);
             return false;
         }
 
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 6ae768a..98ced6d 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -24,6 +24,9 @@
 import android.annotation.UiContext;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -47,12 +50,16 @@
 import android.view.WindowManager.LayoutParams.WindowType;
 import android.view.autofill.AutofillManager.AutofillClient;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -66,6 +73,31 @@
     @UnsupportedAppUsage
     Context mBase;
 
+    /**
+     * After {@link Build.VERSION_CODES#TIRAMISU},
+     * {@link #registerComponentCallbacks(ComponentCallbacks)} will delegate to
+     * {@link #getBaseContext()} instead of {@link #getApplicationContext()}.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    @VisibleForTesting
+    static final long COMPONENT_CALLBACK_ON_WRAPPER = 193247900L;
+
+    /**
+     * A list to store {@link ComponentCallbacks} which
+     * passes to {@link #registerComponentCallbacks(ComponentCallbacks)} before
+     * {@link #attachBaseContext(Context)}.
+     * It is to provide compatibility behavior for Application targeted prior to
+     * {@link Build.VERSION_CODES#TIRAMISU}.
+     *
+     * @hide
+     */
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    public List<ComponentCallbacks> mCallbacksRegisteredToSuper;
+
+    private final Object mLock = new Object();
+
     public ContextWrapper(Context base) {
         mBase = base;
     }
@@ -1301,4 +1333,77 @@
         }
         return mBase.isConfigurationContext();
     }
+
+    /**
+     * Add a new {@link ComponentCallbacks} to the base application of the
+     * Context, which will be called at the same times as the ComponentCallbacks
+     * methods of activities and other components are called. Note that you
+     * <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
+     * appropriate in the future; this will not be removed for you.
+     * <p>
+     * After {@link Build.VERSION_CODES#TIRAMISU}, the {@link ComponentCallbacks} will be registered
+     * to {@link #getBaseContext() the base Context}, and can be only used after
+     * {@link #attachBaseContext(Context)}. Users can still call to
+     * {@code getApplicationContext().registerComponentCallbacks(ComponentCallbacks)} to add
+     * {@link ComponentCallbacks} to the base application.
+     *
+     * @param callback The interface to call.  This can be either a
+     * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+     * @throws IllegalStateException if this method calls before {@link #attachBaseContext(Context)}
+     */
+    @Override
+    public void registerComponentCallbacks(ComponentCallbacks callback) {
+        if (mBase != null) {
+            mBase.registerComponentCallbacks(callback);
+        } else if (!CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
+            super.registerComponentCallbacks(callback);
+            synchronized (mLock) {
+                // Also register ComponentCallbacks to ContextWrapper, so we can find the correct
+                // Context to unregister it for compatibility.
+                if (mCallbacksRegisteredToSuper == null) {
+                    mCallbacksRegisteredToSuper = new ArrayList<>();
+                }
+                mCallbacksRegisteredToSuper.add(callback);
+            }
+        } else {
+            // Throw exception for Application targeting T+
+            throw new IllegalStateException("ComponentCallbacks must be registered after "
+                    + "this ContextWrapper is attached to a base Context.");
+        }
+    }
+
+    /**
+     * Remove a {@link ComponentCallbacks} object that was previously registered
+     * with {@link #registerComponentCallbacks(ComponentCallbacks)}.
+     * <p>
+     * After {@link Build.VERSION_CODES#TIRAMISU}, the {@link ComponentCallbacks} will be
+     * unregistered to {@link #getBaseContext() the base Context}, and can be only used after
+     * {@link #attachBaseContext(Context)}
+     * </p>
+     *
+     * @param callback The interface to call.  This can be either a
+     * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+     * @throws IllegalStateException if this method calls before {@link #attachBaseContext(Context)}
+     */
+    @Override
+    public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+        // It usually means the ComponentCallbacks is registered before this ContextWrapper attaches
+        // to a base Context and Application is targeting prior to S-v2. We should unregister the
+        // ComponentCallbacks to the Application Context instead to prevent leak.
+        synchronized (mLock) {
+            if (mCallbacksRegisteredToSuper != null
+                    && mCallbacksRegisteredToSuper.contains(callback)) {
+                super.unregisterComponentCallbacks(callback);
+                mCallbacksRegisteredToSuper.remove(callback);
+            } else if (mBase != null) {
+                mBase.unregisterComponentCallbacks(callback);
+            } else if (CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
+                // Throw exception for Application that is targeting S-v2+
+                throw new IllegalStateException("ComponentCallbacks must be unregistered after "
+                        + "this ContextWrapper is attached to a base Context.");
+            }
+        }
+        // Do nothing if the callback hasn't been registered to Application Context by
+        // super.unregisterComponentCallbacks() for Application that is targeting prior to T.
+    }
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7f00bcb..1f83207 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2385,6 +2385,10 @@
      * {@link android.Manifest.permission#START_VIEW_APP_FEATURES} permission to ensure that
      * only the system can launch this activity. The system will not launch activities
      * that are not properly protected.
+     *
+     * An optional <meta-data> tag in the activity's manifest with
+     * android:name=app_features_preference_summary and android:resource=@string/<string name> will
+     * be used to add a summary line for the "All Services" preference in settings.
      * </p>
      * @hide
      */
@@ -5053,6 +5057,17 @@
     public static final String ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION =
             "android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION";
 
+    /**
+     * Broadcast Action: Start the foreground service manager.
+     *
+     * <p class="note">
+     * This is a protected intent that can only be sent by the system.
+     * </p>
+     *
+     * @hide
+     */
+    public static final String ACTION_SHOW_FOREGROUND_SERVICE_MANAGER =
+            "android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER";
 
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index 7f1d0d1..5bb845d 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -32,6 +32,9 @@
         },
         {
           "include-filter": "android.content.ComponentCallbacksControllerTest"
+        },
+        {
+          "include-filter": "android.content.ContextWrapperTest"
         }
       ],
       "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java", "(/|^)ComponentCallbacksController.java"]
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 8d9ef853..f20d1e6 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -29,6 +29,7 @@
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -42,43 +43,125 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 /**
+ * A {@link GenericDocument} representation of {@link ShortcutInfo} object.
  * @hide
  */
 public class AppSearchShortcutInfo extends GenericDocument {
 
+    /** The TTL (time-to-live) of the shortcut, in milli-second. */
+    public static final long SHORTCUT_TTL = TimeUnit.DAYS.toMillis(90);
+
     /** The name of the schema type for {@link ShortcutInfo} documents.*/
     public static final String SCHEMA_TYPE = "Shortcut";
-    public static final int SCHEMA_VERSION = 2;
 
+    /** @hide */
+    public static final int SCHEMA_VERSION = 3;
+
+    /**
+     * Property name of the activity this {@link ShortcutInfo} is associated with.
+     * See {@link ShortcutInfo#getActivity()}.
+     */
     public static final String KEY_ACTIVITY = "activity";
+
+    /**
+     * Property name of the short description of this {@link ShortcutInfo}.
+     * See {@link ShortcutInfo#getShortLabel()}.
+     */
     public static final String KEY_SHORT_LABEL = "shortLabel";
-    public static final String KEY_SHORT_LABEL_RES_ID = "shortLabelResId";
-    public static final String KEY_SHORT_LABEL_RES_NAME = "shortLabelResName";
+
+    /**
+     * Property name of the long description of this {@link ShortcutInfo}.
+     * See {@link ShortcutInfo#getLongLabel()}.
+     */
     public static final String KEY_LONG_LABEL = "longLabel";
-    public static final String KEY_LONG_LABEL_RES_ID = "longLabelResId";
-    public static final String KEY_LONG_LABEL_RES_NAME = "longLabelResName";
+
+    /**
+     * @hide
+     */
     public static final String KEY_DISABLED_MESSAGE = "disabledMessage";
-    public static final String KEY_DISABLED_MESSAGE_RES_ID = "disabledMessageResId";
-    public static final String KEY_DISABLED_MESSAGE_RES_NAME = "disabledMessageResName";
+
+    /**
+     * Property name of the categories this {@link ShortcutInfo} is associated with.
+     * See {@link ShortcutInfo#getCategories()}.
+     */
     public static final String KEY_CATEGORIES = "categories";
+
+    /**
+     * Property name of the intents this {@link ShortcutInfo} is associated with.
+     * See {@link ShortcutInfo#getIntents()}.
+     */
     public static final String KEY_INTENTS = "intents";
+
+    /**
+     * @hide
+     */
     public static final String KEY_INTENT_PERSISTABLE_EXTRAS = "intentPersistableExtras";
+
+    /**
+     * Property name of {@link Person} objects this {@link ShortcutInfo} is associated with.
+     * See {@link ShortcutInfo#getPersons()}.
+     */
     public static final String KEY_PERSON = "person";
+
+    /**
+     * Property name of {@link LocusId} this {@link ShortcutInfo} is associated with.
+     * See {@link ShortcutInfo#getLocusId()}.
+     */
     public static final String KEY_LOCUS_ID = "locusId";
-    public static final String KEY_RANK = "rank";
-    public static final String KEY_IMPLICIT_RANK = "implicitRank";
+
+    /**
+     * @hide
+     */
     public static final String KEY_EXTRAS = "extras";
+
+    /**
+     * Property name of the states this {@link ShortcutInfo} is currently in.
+     * Possible values are one or more of the following:
+     *     {@link #IS_DYNAMIC}, {@link #NOT_DYNAMIC}, {@link #IS_MANIFEST}, {@link #NOT_MANIFEST},
+     *     {@link #IS_DISABLED}, {@link #NOT_DISABLED}, {@link #IS_IMMUTABLE},
+     *     {@link #NOT_IMMUTABLE}
+     *
+     */
     public static final String KEY_FLAGS = "flags";
+
+    /**
+     * @hide
+     */
     public static final String KEY_ICON_RES_ID = "iconResId";
+
+    /**
+     * @hide
+     */
     public static final String KEY_ICON_RES_NAME = "iconResName";
+
+    /**
+     * @hide
+     */
     public static final String KEY_ICON_URI = "iconUri";
-    public static final String KEY_BITMAP_PATH = "bitmapPath";
+
+    /**
+     * @hide
+     */
     public static final String KEY_DISABLED_REASON = "disabledReason";
 
+    /**
+     * Property name of capability this {@link ShortcutInfo} is associated with.
+     * See {@link ShortcutInfo#hasCapability(String)}.
+     */
+    public static final String KEY_CAPABILITY = "capability";
+
+    /**
+     * Property name of capability binding this {@link ShortcutInfo} is associated with.
+     * See {@link ShortcutInfo#getCapabilityParameters(String, String)}.
+     */
+    public static final String KEY_CAPABILITY_BINDINGS = "capabilityBindings";
+
     public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE)
             .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_ACTIVITY)
                     .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
@@ -92,50 +175,18 @@
                     .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
                     .build()
 
-            ).addProperty(new AppSearchSchema.LongPropertyConfig.Builder(KEY_SHORT_LABEL_RES_ID)
-                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                    .build()
-
-            ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_SHORT_LABEL_RES_NAME)
-                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                    .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
-                    .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
-                    .build()
-
             ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_LONG_LABEL)
                     .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
                     .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
                     .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
                     .build()
 
-            ).addProperty(new AppSearchSchema.LongPropertyConfig.Builder(KEY_LONG_LABEL_RES_ID)
-                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                    .build()
-
-            ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_LONG_LABEL_RES_NAME)
-                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                    .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
-                    .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
-                    .build()
-
             ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_DISABLED_MESSAGE)
                     .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
                     .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
                     .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
                     .build()
 
-            ).addProperty(new AppSearchSchema.LongPropertyConfig.Builder(
-                    KEY_DISABLED_MESSAGE_RES_ID)
-                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                    .build()
-
-            ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
-                    KEY_DISABLED_MESSAGE_RES_NAME)
-                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                    .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
-                    .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
-                    .build()
-
             ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_CATEGORIES)
                     .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
                     .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
@@ -154,7 +205,7 @@
                     .build()
 
             ).addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder(
-                    KEY_PERSON, AppSearchPerson.SCHEMA_TYPE)
+                    KEY_PERSON, AppSearchShortcutPerson.SCHEMA_TYPE)
                     .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
                     .build()
 
@@ -164,16 +215,6 @@
                     .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
                     .build()
 
-            ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_RANK)
-                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                    .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                    .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
-                    .build()
-
-            ).addProperty(new AppSearchSchema.LongPropertyConfig.Builder(KEY_IMPLICIT_RANK)
-                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                    .build()
-
             ).addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(KEY_EXTRAS)
                     .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
                     .build()
@@ -200,18 +241,24 @@
                     .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
                     .build()
 
-            ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_BITMAP_PATH)
-                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                    .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
-                    .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
-                    .build()
-
             ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_DISABLED_REASON)
                     .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
                     .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
                     .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
                     .build()
 
+            ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_CAPABILITY)
+                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+                    .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                    .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                    .build()
+
+            ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_CAPABILITY_BINDINGS)
+                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+                    .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                    .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                    .build()
+
             ).build();
 
     /**
@@ -219,97 +266,62 @@
      * needs to be camelCase since AppSearch's tokenizer will break the word when it sees
      * underscore.
      */
-    private static final String IS_DYNAMIC = "Dyn";
-    private static final String NOT_DYNAMIC = "nDyn";
-    private static final String IS_PINNED = "Pin";
-    private static final String NOT_PINNED = "nPin";
-    private static final String HAS_ICON_RES = "IcR";
-    private static final String NO_ICON_RES = "nIcR";
-    private static final String HAS_ICON_FILE = "IcF";
-    private static final String NO_ICON_FILE = "nIcF";
-    private static final String IS_KEY_FIELD_ONLY = "Key";
-    private static final String NOT_KEY_FIELD_ONLY = "nKey";
-    private static final String IS_MANIFEST = "Man";
-    private static final String NOT_MANIFEST = "nMan";
-    private static final String IS_DISABLED = "Dis";
-    private static final String NOT_DISABLED = "nDis";
-    private static final String ARE_STRINGS_RESOLVED = "Str";
-    private static final String NOT_STRINGS_RESOLVED = "nStr";
-    private static final String IS_IMMUTABLE = "Im";
-    private static final String NOT_IMMUTABLE = "nIm";
-    private static final String HAS_ADAPTIVE_BITMAP = "IcA";
-    private static final String NO_ADAPTIVE_BITMAP = "nIcA";
-    private static final String IS_RETURNED_BY_SERVICE = "Rets";
-    private static final String NOT_RETURNED_BY_SERVICE = "nRets";
-    private static final String HAS_ICON_FILE_PENDING_SAVE = "Pens";
-    private static final String NO_ICON_FILE_PENDING_SAVE = "nPens";
-    private static final String IS_SHADOW = "Sdw";
-    private static final String NOT_SHADOW = "nSdw";
-    private static final String IS_LONG_LIVED = "Liv";
-    private static final String NOT_LONG_LIVED = "nLiv";
-    private static final String HAS_ICON_URI = "IcU";
-    private static final String NO_ICON_URI = "nIcU";
-    private static final String IS_CACHED_NOTIFICATION = "CaN";
-    private static final String NOT_CACHED_NOTIFICATION = "nCaN";
-    private static final String IS_CACHED_BUBBLE = "CaB";
-    private static final String NOT_CACHED_BUBBLE = "nCaB";
-    private static final String IS_CACHED_PEOPLE_TITLE = "CaPT";
-    private static final String NOT_CACHED_PEOPLE_TITLE = "nCaPT";
 
     /**
-     * Following flags are not store within ShortcutInfo, but book-keeping states to reduce search
-     * space when performing queries against AppSearch.
+     * Indicates the {@link ShortcutInfo} is dynamic shortcut.
+     * See {@link #KEY_FLAGS}
+     * See {@link ShortcutInfo#isDynamic()}.
      */
-    private static final String HAS_BITMAP_PATH = "hBiP";
-    private static final String HAS_STRING_RESOURCE = "hStr";
-    private static final String HAS_NON_ZERO_RANK = "hRan";
+    public static final String IS_DYNAMIC = "Dyn";
 
-    public static final String QUERY_IS_DYNAMIC = KEY_FLAGS + ":" + IS_DYNAMIC;
-    public static final String QUERY_IS_NOT_DYNAMIC = KEY_FLAGS + ":" + NOT_DYNAMIC;
-    public static final String QUERY_IS_PINNED = KEY_FLAGS + ":" + IS_PINNED;
-    public static final String QUERY_IS_NOT_PINNED = KEY_FLAGS + ":" + NOT_PINNED;
-    public static final String QUERY_IS_MANIFEST = KEY_FLAGS + ":" + IS_MANIFEST;
-    public static final String QUERY_IS_NOT_MANIFEST = KEY_FLAGS + ":" + NOT_MANIFEST;
-    public static final String QUERY_IS_PINNED_AND_ENABLED =
-            "(" + KEY_FLAGS + ":" + IS_PINNED + " " + KEY_FLAGS + ":" + NOT_DISABLED + ")";
-    public static final String QUERY_IS_CACHED =
-            "(" + KEY_FLAGS + ":" + IS_CACHED_NOTIFICATION + " OR "
-            + KEY_FLAGS + ":" + IS_CACHED_BUBBLE + " OR "
-            + KEY_FLAGS + ":" + IS_CACHED_PEOPLE_TITLE + ")";
-    public static final String QUERY_IS_NOT_CACHED =
-            "(" + KEY_FLAGS + ":" + NOT_CACHED_NOTIFICATION + " "
-                    + KEY_FLAGS + ":" + NOT_CACHED_BUBBLE + " "
-                    + KEY_FLAGS + ":" + NOT_CACHED_PEOPLE_TITLE + ")";
-    public static final String QUERY_IS_FLOATING =
-            "((" + IS_PINNED + " OR " + QUERY_IS_CACHED + ") "
-                    + QUERY_IS_NOT_DYNAMIC + " " + QUERY_IS_NOT_MANIFEST + ")";
-    public static final String QUERY_IS_NOT_FLOATING =
-            "((" + QUERY_IS_NOT_PINNED + " " + QUERY_IS_NOT_CACHED + ") OR "
-                    + QUERY_IS_DYNAMIC + " OR " + QUERY_IS_MANIFEST + ")";
-    public static final String QUERY_IS_VISIBLE_TO_PUBLISHER =
-            "(" + KEY_DISABLED_REASON + ":" + ShortcutInfo.DISABLED_REASON_NOT_DISABLED
-                    + " OR " + KEY_DISABLED_REASON + ":"
-                    + ShortcutInfo.DISABLED_REASON_BY_APP
-                    + " OR " + KEY_DISABLED_REASON + ":"
-                    + ShortcutInfo.DISABLED_REASON_APP_CHANGED
-                    + " OR " + KEY_DISABLED_REASON + ":"
-                    + ShortcutInfo.DISABLED_REASON_UNKNOWN + ")";
-    public static final String QUERY_DISABLED_REASON_VERSION_LOWER =
-            KEY_DISABLED_REASON + ":" + ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
-    public static final String QUERY_IS_NON_MANIFEST_VISIBLE =
-            "(" + QUERY_IS_NOT_MANIFEST + " " + QUERY_IS_VISIBLE_TO_PUBLISHER + " ("
-                    + QUERY_IS_PINNED + " OR " + QUERY_IS_CACHED + " OR " + QUERY_IS_DYNAMIC + "))";
-    public static final String QUERY_IS_VISIBLE_CACHED_OR_PINNED =
-            "(" + QUERY_IS_VISIBLE_TO_PUBLISHER + " " + QUERY_IS_DYNAMIC
-                    + " (" + QUERY_IS_CACHED + " OR " + QUERY_IS_PINNED + "))";
-    public static final String QUERY_IS_VISIBLE_PINNED_ONLY =
-            "(" + QUERY_IS_VISIBLE_TO_PUBLISHER + " " + QUERY_IS_PINNED + " " + QUERY_IS_NOT_CACHED
-            + " " + QUERY_IS_NOT_DYNAMIC + " " + QUERY_IS_NOT_MANIFEST + ")";
-    public static final String QUERY_HAS_BITMAP_PATH = KEY_FLAGS + ":" + HAS_BITMAP_PATH;
-    public static final String QUERY_HAS_STRING_RESOURCE = KEY_FLAGS + ":" + HAS_STRING_RESOURCE;
-    public static final String QUERY_HAS_NON_ZERO_RANK = KEY_FLAGS + ":" + HAS_NON_ZERO_RANK;
-    public static final String QUERY_IS_FLOATING_AND_HAS_RANK =
-            "(" + QUERY_IS_FLOATING + " " + QUERY_HAS_NON_ZERO_RANK + ")";
+    /**
+     * Indicates the {@link ShortcutInfo} is not a dynamic shortcut.
+     * See {@link #KEY_FLAGS}
+     * See {@link ShortcutInfo#isDynamic()}.
+     */
+    public static final String NOT_DYNAMIC = "nDyn";
+
+    /**
+     * Indicates the {@link ShortcutInfo} is manifest shortcut.
+     * See {@link #KEY_FLAGS}
+     * See {@link ShortcutInfo#isDeclaredInManifest()}.
+     */
+    public static final String IS_MANIFEST = "Man";
+
+    /**
+     * Indicates the {@link ShortcutInfo} is manifest shortcut.
+     * See {@link #KEY_FLAGS}
+     * See {@link ShortcutInfo#isDeclaredInManifest()}.
+     */
+    public static final String NOT_MANIFEST = "nMan";
+
+    /**
+     * Indicates the {@link ShortcutInfo} is disabled.
+     * See {@link #KEY_FLAGS}
+     * See {@link ShortcutInfo#isEnabled()}.
+     */
+    public static final String IS_DISABLED = "Dis";
+
+    /**
+     * Indicates the {@link ShortcutInfo} is enabled.
+     * See {@link #KEY_FLAGS}
+     * See {@link ShortcutInfo#isEnabled()}.
+     */
+    public static final String NOT_DISABLED = "nDis";
+
+    /**
+     * Indicates the {@link ShortcutInfo} was originally from manifest, but currently disabled.
+     * See {@link #KEY_FLAGS}
+     * See {@link ShortcutInfo#isOriginallyFromManifest()}.
+     */
+    public static final String IS_IMMUTABLE = "Im";
+
+    /**
+     * Indicates the {@link ShortcutInfo} was not originally from manifest.
+     * See {@link #KEY_FLAGS}
+     * See {@link ShortcutInfo#isOriginallyFromManifest()}.
+     */
+    public static final String NOT_IMMUTABLE = "nIm";
 
     public AppSearchShortcutInfo(@NonNull GenericDocument document) {
         super(document);
@@ -324,34 +336,27 @@
         return new Builder(shortcutInfo.getPackage(), shortcutInfo.getId())
                 .setActivity(shortcutInfo.getActivity())
                 .setShortLabel(shortcutInfo.getShortLabel())
-                .setShortLabelResId(shortcutInfo.getShortLabelResourceId())
-                .setShortLabelResName(shortcutInfo.getTitleResName())
                 .setLongLabel(shortcutInfo.getLongLabel())
-                .setLongLabelResId(shortcutInfo.getLongLabelResourceId())
-                .setLongLabelResName(shortcutInfo.getTextResName())
                 .setDisabledMessage(shortcutInfo.getDisabledMessage())
-                .setDisabledMessageResId(shortcutInfo.getDisabledMessageResourceId())
-                .setDisabledMessageResName(shortcutInfo.getDisabledMessageResName())
                 .setCategories(shortcutInfo.getCategories())
                 .setIntents(shortcutInfo.getIntents())
-                .setRank(shortcutInfo.getRank())
-                .setImplicitRank(shortcutInfo.getImplicitRank()
-                        | (shortcutInfo.isRankChanged() ? ShortcutInfo.RANK_CHANGED_BIT : 0))
                 .setExtras(shortcutInfo.getExtras())
                 .setCreationTimestampMillis(shortcutInfo.getLastChangedTimestamp())
                 .setFlags(shortcutInfo.getFlags())
                 .setIconResId(shortcutInfo.getIconResourceId())
                 .setIconResName(shortcutInfo.getIconResName())
-                .setBitmapPath(shortcutInfo.getBitmapPath())
                 .setIconUri(shortcutInfo.getIconUri())
                 .setDisabledReason(shortcutInfo.getDisabledReason())
                 .setPersons(shortcutInfo.getPersons())
                 .setLocusId(shortcutInfo.getLocusId())
+                .setCapabilityBindings(shortcutInfo.getCapabilityBindings())
+                .setTtlMillis(SHORTCUT_TTL)
                 .build();
     }
 
     /**
-     * @hide
+     * Converts this {@link GenericDocument} object into {@link ShortcutInfo} to read the
+     * information.
      */
     @NonNull
     public ShortcutInfo toShortcutInfo(@UserIdInt int userId) {
@@ -367,14 +372,8 @@
         // LauncherApps#getShortcutIconDrawable instead.
         final Icon icon = null;
         final String shortLabel = getPropertyString(KEY_SHORT_LABEL);
-        final int shortLabelResId = (int) getPropertyLong(KEY_SHORT_LABEL_RES_ID);
-        final String shortLabelResName = getPropertyString(KEY_SHORT_LABEL_RES_NAME);
         final String longLabel = getPropertyString(KEY_LONG_LABEL);
-        final int longLabelResId = (int) getPropertyLong(KEY_LONG_LABEL_RES_ID);
-        final String longLabelResName = getPropertyString(KEY_LONG_LABEL_RES_NAME);
         final String disabledMessage = getPropertyString(KEY_DISABLED_MESSAGE);
-        final int disabledMessageResId = (int) getPropertyLong(KEY_DISABLED_MESSAGE_RES_ID);
-        final String disabledMessageResName = getPropertyString(KEY_DISABLED_MESSAGE_RES_NAME);
         final String[] categories = getPropertyStringArray(KEY_CATEGORIES);
         final Set<String> categoriesSet = categories == null
                 ? null : new ArraySet<>(Arrays.asList(categories));
@@ -408,27 +407,22 @@
         final Person[] persons = parsePerson(getPropertyDocumentArray(KEY_PERSON));
         final String locusIdString = getPropertyString(KEY_LOCUS_ID);
         final LocusId locusId = locusIdString == null ? null : new LocusId(locusIdString);
-        final int rank = Integer.parseInt(getPropertyString(KEY_RANK));
-        final int implicitRank = (int) getPropertyLong(KEY_IMPLICIT_RANK);
         final byte[] extrasByte = getPropertyBytes(KEY_EXTRAS);
         final PersistableBundle extras = transformToPersistableBundle(extrasByte);
         final int flags = parseFlags(getPropertyStringArray(KEY_FLAGS));
         final int iconResId = (int) getPropertyLong(KEY_ICON_RES_ID);
         final String iconResName = getPropertyString(KEY_ICON_RES_NAME);
         final String iconUri = getPropertyString(KEY_ICON_URI);
-        final String bitmapPath = getPropertyString(KEY_BITMAP_PATH);
         final int disabledReason = Integer.parseInt(getPropertyString(KEY_DISABLED_REASON));
-        final ShortcutInfo si = new ShortcutInfo(
-                userId, getId(), packageName, activity, icon, shortLabel, shortLabelResId,
-                shortLabelResName, longLabel, longLabelResId, longLabelResName, disabledMessage,
-                disabledMessageResId, disabledMessageResName, categoriesSet, intents, rank, extras,
-                getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri,
-                disabledReason, persons, locusId, null, null);
-        si.setImplicitRank(implicitRank);
-        if ((implicitRank & ShortcutInfo.RANK_CHANGED_BIT) != 0) {
-            si.setRankChanged();
-        }
-        return si;
+        final Map<String, Map<String, List<String>>> capabilityBindings =
+                parseCapabilityBindings(getPropertyStringArray(KEY_CAPABILITY_BINDINGS));
+        return new ShortcutInfo(
+                userId, getId(), packageName, activity, icon, shortLabel, 0,
+                null, longLabel, 0, null, disabledMessage,
+                0, null, categoriesSet, intents,
+                ShortcutInfo.RANK_NOT_SET, extras, getCreationTimestampMillis(), flags, iconResId,
+                iconResName, null, iconUri, disabledReason, persons, locusId,
+                null, capabilityBindings);
     }
 
     /**
@@ -449,7 +443,6 @@
     public static class Builder extends GenericDocument.Builder<Builder> {
 
         private List<String> mFlags = new ArrayList<>(1);
-        private boolean mHasStringResource = false;
 
         public Builder(String packageName, String id) {
             super(/*namespace=*/ packageName, id, SCHEMA_TYPE);
@@ -493,28 +486,6 @@
          * @hide
          */
         @NonNull
-        public Builder setShortLabelResId(final int shortLabelResId) {
-            setPropertyLong(KEY_SHORT_LABEL_RES_ID, shortLabelResId);
-            if (shortLabelResId != 0) {
-                mHasStringResource = true;
-            }
-            return this;
-        }
-
-        /**
-         * @hide
-         */
-        public Builder setShortLabelResName(@Nullable final String shortLabelResName) {
-            if (!TextUtils.isEmpty(shortLabelResName)) {
-                setPropertyString(KEY_SHORT_LABEL_RES_NAME, shortLabelResName);
-            }
-            return this;
-        }
-
-        /**
-         * @hide
-         */
-        @NonNull
         public Builder setLongLabel(@Nullable final CharSequence longLabel) {
             if (!TextUtils.isEmpty(longLabel)) {
                 setPropertyString(KEY_LONG_LABEL, Preconditions.checkStringNotEmpty(
@@ -527,28 +498,6 @@
          * @hide
          */
         @NonNull
-        public Builder setLongLabelResId(final int longLabelResId) {
-            setPropertyLong(KEY_LONG_LABEL_RES_ID, longLabelResId);
-            if (longLabelResId != 0) {
-                mHasStringResource = true;
-            }
-            return this;
-        }
-
-        /**
-         * @hide
-         */
-        public Builder setLongLabelResName(@Nullable final String longLabelResName) {
-            if (!TextUtils.isEmpty(longLabelResName)) {
-                setPropertyString(KEY_LONG_LABEL_RES_NAME, longLabelResName);
-            }
-            return this;
-        }
-
-        /**
-         * @hide
-         */
-        @NonNull
         public Builder setDisabledMessage(@Nullable final CharSequence disabledMessage) {
             if (!TextUtils.isEmpty(disabledMessage)) {
                 setPropertyString(KEY_DISABLED_MESSAGE, Preconditions.checkStringNotEmpty(
@@ -561,28 +510,6 @@
          * @hide
          */
         @NonNull
-        public Builder setDisabledMessageResId(final int disabledMessageResId) {
-            setPropertyLong(KEY_DISABLED_MESSAGE_RES_ID, disabledMessageResId);
-            if (disabledMessageResId != 0) {
-                mHasStringResource = true;
-            }
-            return this;
-        }
-
-        /**
-         * @hide
-         */
-        public Builder setDisabledMessageResName(@Nullable final String disabledMessageResName) {
-            if (!TextUtils.isEmpty(disabledMessageResName)) {
-                setPropertyString(KEY_DISABLED_MESSAGE_RES_NAME, disabledMessageResName);
-            }
-            return this;
-        }
-
-        /**
-         * @hide
-         */
-        @NonNull
         public Builder setCategories(@Nullable final Set<String> categories) {
             if (categories != null && !categories.isEmpty()) {
                 setPropertyString(KEY_CATEGORIES, categories.stream().toArray(String[]::new));
@@ -649,8 +576,9 @@
             for (int i = 0; i < persons.length; i++) {
                 final Person person = persons[i];
                 if (person == null) continue;
-                final AppSearchPerson appSearchPerson = AppSearchPerson.instance(person);
-                documents[i] = appSearchPerson;
+                final AppSearchShortcutPerson personEntity =
+                        AppSearchShortcutPerson.instance(person);
+                documents[i] = personEntity;
             }
             setPropertyDocument(KEY_PERSON, documents);
             return this;
@@ -660,28 +588,6 @@
          * @hide
          */
         @NonNull
-        public Builder setRank(final int rank) {
-            Preconditions.checkArgument((0 <= rank), "Rank cannot be negative");
-            setPropertyString(KEY_RANK, String.valueOf(rank));
-            if (rank != 0) {
-                mFlags.add(HAS_NON_ZERO_RANK);
-            }
-            return this;
-        }
-
-        /**
-         * @hide
-         */
-        @NonNull
-        public Builder setImplicitRank(final int rank) {
-            setPropertyLong(KEY_IMPLICIT_RANK, rank);
-            return this;
-        }
-
-        /**
-         * @hide
-         */
-        @NonNull
         public Builder setExtras(@Nullable final PersistableBundle extras) {
             if (extras != null) {
                 setPropertyBytes(KEY_EXTRAS, transformToByteArray(extras));
@@ -722,17 +628,6 @@
         /**
          * @hide
          */
-        public Builder setBitmapPath(@Nullable final String bitmapPath) {
-            if (!TextUtils.isEmpty(bitmapPath)) {
-                setPropertyString(KEY_BITMAP_PATH, bitmapPath);
-                mFlags.add(HAS_BITMAP_PATH);
-            }
-            return this;
-        }
-
-        /**
-         * @hide
-         */
         public Builder setIconUri(@Nullable final String iconUri) {
             if (!TextUtils.isEmpty(iconUri)) {
                 setPropertyString(KEY_ICON_URI, iconUri);
@@ -751,12 +646,33 @@
         /**
          * @hide
          */
+        public Builder setCapabilityBindings(
+                @Nullable final Map<String, Map<String, List<String>>> bindings) {
+            if (bindings != null && !bindings.isEmpty()) {
+                final Set<String> capabilityNames = bindings.keySet();
+                final Set<String> capabilityBindings = new ArraySet<>(1);
+                for (String capabilityName: capabilityNames) {
+                    final Map<String, List<String>> params =
+                            bindings.get(capabilityName);
+                    for (String paramName: params.keySet()) {
+                        params.get(paramName).stream()
+                                .map(v -> capabilityName + "/" + paramName + "/" + v)
+                                .forEach(capabilityBindings::add);
+                    }
+                }
+                setPropertyString(KEY_CAPABILITY, capabilityNames.toArray(new String[0]));
+                setPropertyString(KEY_CAPABILITY_BINDINGS,
+                        capabilityBindings.toArray(new String[0]));
+            }
+            return this;
+        }
+
+        /**
+         * @hide
+         */
         @NonNull
         @Override
         public AppSearchShortcutInfo build() {
-            if (mHasStringResource) {
-                mFlags.add(HAS_STRING_RESOURCE);
-            }
             setPropertyString(KEY_FLAGS, mFlags.toArray(new String[0]));
             return new AppSearchShortcutInfo(super.build());
         }
@@ -827,40 +743,12 @@
         switch (mask) {
             case ShortcutInfo.FLAG_DYNAMIC:
                 return (flags & mask) != 0 ? IS_DYNAMIC : NOT_DYNAMIC;
-            case ShortcutInfo.FLAG_PINNED:
-                return (flags & mask) != 0 ? IS_PINNED : NOT_PINNED;
-            case ShortcutInfo.FLAG_HAS_ICON_RES:
-                return (flags & mask) != 0 ? HAS_ICON_RES : NO_ICON_RES;
-            case ShortcutInfo.FLAG_HAS_ICON_FILE:
-                return (flags & mask) != 0 ? HAS_ICON_FILE : NO_ICON_FILE;
-            case ShortcutInfo.FLAG_KEY_FIELDS_ONLY:
-                return (flags & mask) != 0 ? IS_KEY_FIELD_ONLY : NOT_KEY_FIELD_ONLY;
             case ShortcutInfo.FLAG_MANIFEST:
                 return (flags & mask) != 0 ? IS_MANIFEST : NOT_MANIFEST;
             case ShortcutInfo.FLAG_DISABLED:
                 return (flags & mask) != 0 ? IS_DISABLED : NOT_DISABLED;
-            case ShortcutInfo.FLAG_STRINGS_RESOLVED:
-                return (flags & mask) != 0 ? ARE_STRINGS_RESOLVED : NOT_STRINGS_RESOLVED;
             case ShortcutInfo.FLAG_IMMUTABLE:
                 return (flags & mask) != 0 ? IS_IMMUTABLE : NOT_IMMUTABLE;
-            case ShortcutInfo.FLAG_ADAPTIVE_BITMAP:
-                return (flags & mask) != 0 ? HAS_ADAPTIVE_BITMAP : NO_ADAPTIVE_BITMAP;
-            case ShortcutInfo.FLAG_RETURNED_BY_SERVICE:
-                return (flags & mask) != 0 ? IS_RETURNED_BY_SERVICE : NOT_RETURNED_BY_SERVICE;
-            case ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE:
-                return (flags & mask) != 0 ? HAS_ICON_FILE_PENDING_SAVE : NO_ICON_FILE_PENDING_SAVE;
-            case ShortcutInfo.FLAG_SHADOW:
-                return (flags & mask) != 0 ? IS_SHADOW : NOT_SHADOW;
-            case ShortcutInfo.FLAG_LONG_LIVED:
-                return (flags & mask) != 0 ? IS_LONG_LIVED : NOT_LONG_LIVED;
-            case ShortcutInfo.FLAG_HAS_ICON_URI:
-                return (flags & mask) != 0 ? HAS_ICON_URI : NO_ICON_URI;
-            case ShortcutInfo.FLAG_CACHED_NOTIFICATIONS:
-                return (flags & mask) != 0 ? IS_CACHED_NOTIFICATION : NOT_CACHED_NOTIFICATION;
-            case ShortcutInfo.FLAG_CACHED_BUBBLES:
-                return (flags & mask) != 0 ? IS_CACHED_BUBBLE : NOT_CACHED_BUBBLE;
-            case ShortcutInfo.FLAG_CACHED_PEOPLE_TILE:
-                return (flags & mask) != 0 ? IS_CACHED_PEOPLE_TITLE : NOT_CACHED_PEOPLE_TITLE;
             default:
                 return null;
         }
@@ -881,40 +769,12 @@
         switch (value) {
             case IS_DYNAMIC:
                 return ShortcutInfo.FLAG_DYNAMIC;
-            case IS_PINNED:
-                return ShortcutInfo.FLAG_PINNED;
-            case HAS_ICON_RES:
-                return ShortcutInfo.FLAG_HAS_ICON_RES;
-            case HAS_ICON_FILE:
-                return ShortcutInfo.FLAG_HAS_ICON_FILE;
-            case IS_KEY_FIELD_ONLY:
-                return ShortcutInfo.FLAG_KEY_FIELDS_ONLY;
             case IS_MANIFEST:
                 return ShortcutInfo.FLAG_MANIFEST;
             case IS_DISABLED:
                 return ShortcutInfo.FLAG_DISABLED;
-            case ARE_STRINGS_RESOLVED:
-                return ShortcutInfo.FLAG_STRINGS_RESOLVED;
             case IS_IMMUTABLE:
                 return ShortcutInfo.FLAG_IMMUTABLE;
-            case HAS_ADAPTIVE_BITMAP:
-                return ShortcutInfo.FLAG_ADAPTIVE_BITMAP;
-            case IS_RETURNED_BY_SERVICE:
-                return ShortcutInfo.FLAG_RETURNED_BY_SERVICE;
-            case HAS_ICON_FILE_PENDING_SAVE:
-                return ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE;
-            case IS_SHADOW:
-                return ShortcutInfo.FLAG_SHADOW;
-            case IS_LONG_LIVED:
-                return ShortcutInfo.FLAG_LONG_LIVED;
-            case HAS_ICON_URI:
-                return ShortcutInfo.FLAG_HAS_ICON_URI;
-            case IS_CACHED_NOTIFICATION:
-                return ShortcutInfo.FLAG_CACHED_NOTIFICATIONS;
-            case IS_CACHED_BUBBLE:
-                return ShortcutInfo.FLAG_CACHED_BUBBLES;
-            case IS_CACHED_PEOPLE_TITLE:
-                return ShortcutInfo.FLAG_CACHED_PEOPLE_TILE;
             default:
                 return 0;
         }
@@ -927,9 +787,43 @@
         for (int i = 0; i < persons.length; i++) {
             final GenericDocument document = persons[i];
             if (document == null) continue;
-            final AppSearchPerson person = new AppSearchPerson(document);
+            final AppSearchShortcutPerson person = new AppSearchShortcutPerson(document);
             ret[i] = person.toPerson();
         }
         return ret;
     }
+
+    @Nullable
+    private static Map<String, Map<String, List<String>>> parseCapabilityBindings(
+            @Nullable final String[] capabilityBindings) {
+        if (capabilityBindings == null || capabilityBindings.length == 0) {
+            return null;
+        }
+        final Map<String, Map<String, List<String>>> ret = new ArrayMap<>(1);
+        Arrays.stream(capabilityBindings).forEach(binding -> {
+            if (TextUtils.isEmpty(binding)) {
+                return;
+            }
+            final int capabilityStopIndex = binding.indexOf("/");
+            if (capabilityStopIndex == -1 || capabilityStopIndex == binding.length() - 1) {
+                return;
+            }
+            final String capabilityName = binding.substring(0, capabilityStopIndex);
+            final int paramStopIndex = binding.indexOf("/", capabilityStopIndex + 1);
+            if (paramStopIndex == -1 || paramStopIndex == binding.length() - 1) {
+                return;
+            }
+            final String paramName = binding.substring(capabilityStopIndex + 1, paramStopIndex);
+            final String paramValue = binding.substring(paramStopIndex + 1);
+            if (!ret.containsKey(capabilityName)) {
+                ret.put(capabilityName, new ArrayMap<>(1));
+            }
+            final Map<String, List<String>> params = ret.get(capabilityName);
+            if (!params.containsKey(paramName)) {
+                params.put(paramName, new ArrayList<>(1));
+            }
+            params.get(paramName).add(paramValue);
+        });
+        return ret;
+    }
 }
diff --git a/core/java/android/content/pm/AppSearchPerson.java b/core/java/android/content/pm/AppSearchShortcutPerson.java
similarity index 63%
rename from core/java/android/content/pm/AppSearchPerson.java
rename to core/java/android/content/pm/AppSearchShortcutPerson.java
index 98d150b..ff8a3b6 100644
--- a/core/java/android/content/pm/AppSearchPerson.java
+++ b/core/java/android/content/pm/AppSearchShortcutPerson.java
@@ -21,28 +21,38 @@
 import android.app.Person;
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.GenericDocument;
+import android.graphics.drawable.Icon;
 import android.net.UriCodec;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Objects;
 import java.util.UUID;
 
 /**
+ * A {@link GenericDocument} representation of {@link Person} object.
+ *
  * @hide
  */
-public class AppSearchPerson extends GenericDocument {
+public class AppSearchShortcutPerson extends GenericDocument {
 
-    /** The name of the schema type for {@link Person} documents.*/
-    public static final String SCHEMA_TYPE = "Person";
+    /**
+     * The name of the schema type for {@link Person} documents.
+     * @hide
+     */
+    public static final String SCHEMA_TYPE = "ShortcutPerson";
 
-    public static final String KEY_NAME = "name";
-    public static final String KEY_KEY = "key";
-    public static final String KEY_IS_BOT = "isBot";
-    public static final String KEY_IS_IMPORTANT = "isImportant";
+    private static final String KEY_NAME = "name";
+    private static final String KEY_KEY = "key";
+    private static final String KEY_IS_BOT = "isBot";
+    private static final String KEY_IS_IMPORTANT = "isImportant";
+    private static final String KEY_ICON = "icon";
 
-    public AppSearchPerson(@NonNull GenericDocument document) {
+    public AppSearchShortcutPerson(@NonNull GenericDocument document) {
         super(document);
     }
 
@@ -67,11 +77,15 @@
                     .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
                     .build()
 
+            ).addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(KEY_ICON)
+                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                    .build()
+
             ).build();
 
-    /** hide */
+    /** @hide */
     @NonNull
-    public static AppSearchPerson instance(@NonNull final Person person) {
+    public static AppSearchShortcutPerson instance(@NonNull final Person person) {
         Objects.requireNonNull(person);
         final String id;
         if (person.getUri() != null) {
@@ -82,10 +96,13 @@
         }
         return new Builder(id).setName(person.getName())
                 .setKey(person.getKey()).setIsBot(person.isBot())
-                .setIsImportant(person.isImportant()).build();
+                .setIsImportant(person.isImportant())
+                .setIcon(transformToByteArray(person.getIcon())).build();
     }
 
-    /** hide */
+    /**
+     * Convert this {@link GenericDocument} into {@link Person}.
+     */
     @NonNull
     public Person toPerson() {
         String uri;
@@ -99,7 +116,9 @@
         return new Person.Builder().setName(getPropertyString(KEY_NAME))
                 .setUri(uri).setKey(getPropertyString(KEY_KEY))
                 .setBot(getPropertyBoolean(KEY_IS_BOT))
-                .setImportant(getPropertyBoolean(KEY_IS_IMPORTANT)).build();
+                .setImportant(getPropertyBoolean(KEY_IS_IMPORTANT))
+                .setIcon(transformToIcon(getPropertyBytes(KEY_ICON)))
+                .build();
     }
 
     /** @hide */
@@ -142,10 +161,51 @@
             return this;
         }
 
+        /** @hide */
+        @NonNull
+        public Builder setIcon(@Nullable final byte[] icon) {
+            if (icon != null) {
+                setPropertyBytes(KEY_ICON, icon);
+            }
+            return this;
+        }
+
+        /** @hide */
         @NonNull
         @Override
-        public AppSearchPerson build() {
-            return new AppSearchPerson(super.build());
+        public AppSearchShortcutPerson build() {
+            return new AppSearchShortcutPerson(super.build());
+        }
+    }
+
+    /**
+     * Convert {@link Icon} into byte[].
+     */
+    @Nullable
+    private static byte[] transformToByteArray(@Nullable final Icon icon) {
+        if (icon == null) {
+            return null;
+        }
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            icon.writeToStream(baos);
+            return baos.toByteArray();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Convert byte[] into {@link Icon}.
+     */
+    @Nullable
+    private Icon transformToIcon(@Nullable final byte[] icon) {
+        if (icon == null) {
+            return null;
+        }
+        try (ByteArrayInputStream bais = new ByteArrayInputStream(icon)) {
+            return Icon.createFromStream(bais);
+        } catch (IOException e) {
+            return null;
         }
     }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1021d3e..5b0c275 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -329,7 +329,8 @@
          * @hide
          */
         public Bundle toBundle(Bundle outBundle) {
-            final Bundle b = outBundle == null ? new Bundle() : outBundle;
+            final Bundle b = outBundle == null || outBundle == Bundle.EMPTY
+                    ? new Bundle() : outBundle;
             if (mType == TYPE_BOOLEAN) {
                 b.putBoolean(mName, mBooleanValue);
             } else if (mType == TYPE_FLOAT) {
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 7fc242c..88d7004 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -170,6 +170,15 @@
     public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 1 << 7;
 
     /**
+     * The number of foreground service types, this doesn't include
+     * the {@link #FOREGROUND_SERVICE_TYPE_MANIFEST} and {@link #FOREGROUND_SERVICE_TYPE_NONE}
+     * as they're not real service types.
+     *
+     * @hide
+     */
+    public static final int NUM_OF_FOREGROUND_SERVICE_TYPES = 8;
+
+    /**
      * A special value indicates to use all types set in manifest file.
      */
     public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1;
@@ -239,6 +248,38 @@
             + " " + name + "}";
     }
 
+    /**
+     * @return The label for the given foreground service type.
+     *
+     * @hide
+     */
+    public static String foregroundServiceTypeToLabel(@ForegroundServiceType int type) {
+        switch (type) {
+            case FOREGROUND_SERVICE_TYPE_MANIFEST:
+                return "manifest";
+            case FOREGROUND_SERVICE_TYPE_NONE:
+                return "none";
+            case FOREGROUND_SERVICE_TYPE_DATA_SYNC:
+                return "dataSync";
+            case FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK:
+                return "mediaPlayback";
+            case FOREGROUND_SERVICE_TYPE_PHONE_CALL:
+                return "phoneCall";
+            case FOREGROUND_SERVICE_TYPE_LOCATION:
+                return "location";
+            case FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE:
+                return "connectedDevice";
+            case FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION:
+                return "mediaProjection";
+            case FOREGROUND_SERVICE_TYPE_CAMERA:
+                return "camera";
+            case FOREGROUND_SERVICE_TYPE_MICROPHONE:
+                return "microphone";
+            default:
+                return "unknown";
+        }
+    }
+
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index ab827aa..41dd5bb3 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -24,6 +24,7 @@
 import android.app.Notification;
 import android.app.Person;
 import android.app.TaskStackBuilder;
+import android.app.appsearch.GenericDocument;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -656,6 +657,28 @@
     }
 
     /**
+     * Convert a {@link GenericDocument} into a ShortcutInfo.
+     *
+     * @param context Client context
+     * @param document An instance of {@link GenericDocument} that represents the shortcut.
+     */
+    @NonNull
+    public static ShortcutInfo createFromGenericDocument(@NonNull final Context context,
+            @NonNull final GenericDocument document) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(document);
+        return createFromGenericDocument(context.getUserId(), document);
+    }
+
+    /**
+     * @hide
+     */
+    public static ShortcutInfo createFromGenericDocument(
+            final int userId, @NonNull final GenericDocument document) {
+        return new AppSearchShortcutInfo(document).toShortcutInfo(userId);
+    }
+
+    /**
      * Load a string resource from the publisher app.
      *
      * @param resId resource ID
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index d5498a0..165cae8 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -78,6 +78,7 @@
     public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
     private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
     private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
+    private static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
     private static final String TAG_APPLICATION = "application";
     private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
     private static final String TAG_PROFILEABLE = "profileable";
@@ -101,7 +102,7 @@
     public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
             File packageFile, int flags) {
         if (packageFile.isDirectory()) {
-            return parseClusterPackageLite(input, packageFile, flags);
+            return parseClusterPackageLite(input, packageFile, /* frameworkSplits= */ null, flags);
         } else {
             return parseMonolithicPackageLite(input, packageFile, flags);
         }
@@ -134,21 +135,44 @@
 
     /**
      * Parse lightweight details about a directory of APKs.
+     *
+     * @param packageDirOrApk is the folder that contains split apks for a regular app or the
+     *                        framework-res.apk for framwork-res splits (in which case the
+     *                        splits come in the <code>frameworkSplits</code> parameter)
      */
     public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
-            File packageDir, int flags) {
-        final File[] files = packageDir.listFiles();
-        if (ArrayUtils.isEmpty(files)) {
-            return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
-                    "No packages found in split");
+            File packageDirOrApk, List<File> frameworkSplits, int flags) {
+        final File[] files;
+        final boolean parsingFrameworkSplits = (flags & PARSE_FRAMEWORK_RES_SPLITS) != 0;
+        if (parsingFrameworkSplits) {
+            if (ArrayUtils.isEmpty(frameworkSplits)) {
+                return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+                        "No packages found in split");
+            }
+            files = frameworkSplits.toArray(new File[frameworkSplits.size() + 1]);
+            // we also want to process the base apk so add it to the array
+            files[files.length - 1] = packageDirOrApk;
+        } else {
+            files = packageDirOrApk.listFiles();
+            if (ArrayUtils.isEmpty(files)) {
+                return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+                        "No packages found in split");
+            }
+            // Apk directory is directly nested under the current directory
+            if (files.length == 1 && files[0].isDirectory()) {
+                return parseClusterPackageLite(input, files[0], frameworkSplits, flags);
+            }
         }
-        // Apk directory is directly nested under the current directory
-        if (files.length == 1 && files[0].isDirectory()) {
-            return parseClusterPackageLite(input, files[0], flags);
+
+        if (parsingFrameworkSplits) {
+            // disable the flag for checking the certificates of the splits. We know they
+            // won't match, but we rely on the mainline apex to be safe if it was installed
+            flags = flags & ~PARSE_COLLECT_CERTIFICATES;
         }
 
         String packageName = null;
         int versionCode = 0;
+        ApkLite baseApk = null;
 
         final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
@@ -161,6 +185,10 @@
                     }
 
                     final ApkLite lite = result.getResult();
+                    if (parsingFrameworkSplits && file == files[files.length - 1]) {
+                        baseApk = lite;
+                        break;
+                    }
                     // Assert that all package names and version codes are
                     // consistent with the first one we encounter.
                     if (packageName == null) {
@@ -172,7 +200,8 @@
                                     "Inconsistent package " + lite.getPackageName() + " in " + file
                                             + "; expected " + packageName);
                         }
-                        if (versionCode != lite.getVersionCode()) {
+                        // we allow version codes that do not match for framework splits
+                        if (!parsingFrameworkSplits && versionCode != lite.getVersionCode()) {
                             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                                     "Inconsistent version " + lite.getVersionCode() + " in " + file
                                             + "; expected " + versionCode);
@@ -187,12 +216,15 @@
                     }
                 }
             }
+            // baseApk is set in the last iteration of the for each loop when we are parsing
+            // frameworkRes splits or needs to be done now otherwise
+            if (!parsingFrameworkSplits) {
+                baseApk = apks.remove(null);
+            }
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
-
-        final ApkLite baseApk = apks.remove(null);
-        return composePackageLiteFromApks(input, packageDir, baseApk, apks);
+        return composePackageLiteFromApks(input, packageDirOrApk, baseApk, apks);
     }
 
     /**
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 5b727cc..5031faa 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -198,7 +198,7 @@
             // Prune the cache before adding new items.
             final int N = sCache.size();
             for (int i = N - 1; i >= 0; i--) {
-                if (sCache.valueAt(i).get() == null) {
+                if (sCache.valueAt(i).refersTo(null)) {
                     sCache.removeAt(i);
                 }
             }
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index cb53a2a..1aba961 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2080,7 +2080,7 @@
 
             // Clean up references to garbage collected themes
             if (mThemeRefs.size() > mThemeRefsNextFlushSize) {
-                mThemeRefs.removeIf(ref -> ref.get() == null);
+                mThemeRefs.removeIf(ref -> ref.refersTo(null));
                 mThemeRefsNextFlushSize = Math.max(MIN_THEME_REFS_FLUSH_SIZE,
                         2 * mThemeRefs.size());
             }
diff --git a/core/java/android/content/res/loader/ResourcesLoader.java b/core/java/android/content/res/loader/ResourcesLoader.java
index c308400..cf6e166 100644
--- a/core/java/android/content/res/loader/ResourcesLoader.java
+++ b/core/java/android/content/res/loader/ResourcesLoader.java
@@ -257,7 +257,7 @@
 
         for (int i = mChangeCallbacks.size() - 1; i >= 0; i--) {
             final WeakReference<Object> key = mChangeCallbacks.keyAt(i);
-            if (key.get() == null) {
+            if (key.refersTo(null)) {
                 mChangeCallbacks.removeAt(i);
             } else {
                 uniqueCallbacks.add(mChangeCallbacks.valueAt(i));
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index acceb65..515a009 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -156,7 +156,7 @@
      * 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;
+    public static final long USAGE_FRONT_BUFFER           = 1L << 32;
 
     /**
      * Creates a new <code>HardwareBuffer</code> instance.
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index f866a2e..ad6c12b 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -90,6 +90,12 @@
     public abstract PointF getCursorPosition();
 
     /**
+     * Sets the pointer acceleration.
+     * See {@code frameworks/native/include/input/VelocityControl.h#VelocityControlParameters}.
+     */
+    public abstract void setPointerAcceleration(float acceleration);
+
+    /**
      * Sets the eligibility of windows on a given display for pointer capture. If a display is
      * marked ineligible, requests to enable pointer capture for windows on that display will be
      * ignored.
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 459dab1..b617e05 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -136,6 +136,9 @@
     /* Resets the USB gadget. */
     void resetUsbGadget();
 
+    /* Resets the USB port. */
+    boolean resetUsbPort(in String portId, int operationId, in IUsbOperationInternal callback);
+
     /* Set USB data on or off */
     boolean enableUsbData(in String portId, boolean enable, int operationId, in IUsbOperationInternal callback);
 
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index f0e040e..60f5135 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -1324,6 +1324,43 @@
     }
 
     /**
+     * Should only be called by {@link UsbPort#resetUsbPort}.
+     * <p>
+     * Disable and then re-enable USB data signaling.
+     *
+     * Reset USB first port..
+     * It will force to stop and restart USB data signaling.
+     * Call UsbPort API if the device has more than one UsbPort.
+     * </p>
+     *
+     * @param port reset the USB Port
+     * @return true enable or disable USB data successfully
+     *         false if something wrong
+     *
+     * Should only be called by {@link UsbPort#resetUsbPort}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    boolean resetUsbPort(@NonNull UsbPort port, int operationId,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(port, "resetUsbPort: port must not be null. opId:" + operationId);
+        try {
+            return mService.resetUsbPort(port.getId(), operationId, callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "resetUsbPort: failed. ", e);
+            try {
+                callback.onOperationComplete(UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL);
+            } catch (RemoteException r) {
+                Log.e(TAG, "resetUsbPort: 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.
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index bef4dea..a979725 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -128,6 +128,9 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface EnableUsbDataStatus{}
 
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ResetUsbPortStatus{}
+
     /**
      * The {@link #enableLimitPowerTransfer} request was successfully completed.
      */
@@ -319,6 +322,43 @@
     }
 
     /**
+     * Reset Usb data on the port.
+     *
+     * @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 @ResetUsbPortStatus int resetUsbPort() {
+        // UID is added To minimize operationID overlap between two different packages.
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
+        Log.i(TAG, "resetUsbData opId:" + operationId);
+        UsbOperationInternal opCallback =
+                new UsbOperationInternal(operationId, mId);
+        if (mUsbManager.resetUsbPort(this, 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/Disables Usb data on the port.
      *
      * @param enable When true enables USB data if disabled.
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 75beacf..f16e243 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -300,13 +300,6 @@
         return false;
     }
 
-    // TODO(b/149463653): remove it in T. We missed the API deadline in S.
-    /** @hide */
-    @Override
-    public final boolean isUiContext() {
-        return true;
-    }
-
     /** @hide */
     @Override
     public final int getWindowType() {
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 7295b72..a6e475a 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -16,19 +16,27 @@
 
 package android.inputmethodservice;
 
+import static android.content.Intent.ACTION_OVERLAY_CHANGED;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.inputmethodservice.navigationbar.NavigationBarFrame;
 import android.inputmethodservice.navigationbar.NavigationBarView;
+import android.os.PatternMatcher;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowInsets;
@@ -109,6 +117,9 @@
         @Nullable
         Insets mLastInsets;
 
+        @Nullable
+        private BroadcastReceiver mSystemOverlayChangedReceiver;
+
         Impl(@NonNull InputMethodService inputMethodService) {
             mService = inputMethodService;
         }
@@ -136,6 +147,9 @@
             if (!mRenderGesturalNavButtons) {
                 return;
             }
+            if (mNavigationBarFrame != null) {
+                return;
+            }
             final View rawDecorView = mService.mWindow.getWindow().getDecorView();
             if (!(rawDecorView instanceof ViewGroup)) {
                 return;
@@ -175,6 +189,17 @@
             mNavigationBarFrame.setBackground(null);
         }
 
+        private void uninstallNavigationBarFrameIfNecessary() {
+            if (mNavigationBarFrame == null) {
+                return;
+            }
+            final ViewParent parent = mNavigationBarFrame.getParent();
+            if (parent instanceof ViewGroup) {
+                ((ViewGroup) parent).removeView(mNavigationBarFrame);
+            }
+            mNavigationBarFrame = null;
+        }
+
         @Override
         public void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
                 @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
@@ -241,34 +266,50 @@
                 }
                 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);
-                            }
-                        }
-                    });
+                    scheduleRelayout();
                 }
             }
         }
 
+        private void scheduleRelayout() {
+            // Capture the current frame object in case the object is replaced or cleared later.
+            final NavigationBarFrame frame = mNavigationBarFrame;
+            frame.post(() -> {
+                if (mDestroyed) {
+                    return;
+                }
+                if (!frame.isAttachedToWindow()) {
+                    return;
+                }
+                final Window window = mService.mWindow.getWindow();
+                if (window == null) {
+                    return;
+                }
+                final View decor = window.peekDecorView();
+                if (decor == null) {
+                    return;
+                }
+                if (!(decor instanceof ViewGroup)) {
+                    return;
+                }
+                final ViewGroup decorGroup = (ViewGroup) decor;
+                final Insets currentSystemInsets = getSystemInsets();
+                if (!Objects.equals(currentSystemInsets, mLastInsets)) {
+                    frame.setLayoutParams(new FrameLayout.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            currentSystemInsets.bottom, Gravity.BOTTOM));
+                    mLastInsets = currentSystemInsets;
+                }
+                final View navbarBackgroundView =
+                        window.getNavigationBarBackgroundView();
+                if (navbarBackgroundView != null
+                        && decorGroup.indexOfChild(navbarBackgroundView)
+                        > decorGroup.indexOfChild(frame)) {
+                    decorGroup.bringChildToFront(frame);
+                }
+            });
+        }
+
         private boolean isGesturalNavigationEnabled() {
             final Resources resources = mService.getResources();
             if (resources == null) {
@@ -284,11 +325,38 @@
                 return;
             }
             mRenderGesturalNavButtons = isGesturalNavigationEnabled();
+            if (mSystemOverlayChangedReceiver == null) {
+                final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+                intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+                intentFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
+                mSystemOverlayChangedReceiver = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (mDestroyed) {
+                            return;
+                        }
+                        mRenderGesturalNavButtons = isGesturalNavigationEnabled();
+                        if (mRenderGesturalNavButtons) {
+                            installNavigationBarFrameIfNecessary();
+                        } else {
+                            uninstallNavigationBarFrameIfNecessary();
+                        }
+                    }
+                };
+                mService.registerReceiver(mSystemOverlayChangedReceiver, intentFilter);
+            }
             installNavigationBarFrameIfNecessary();
         }
 
         @Override
         public void onDestroy() {
+            if (mDestroyed) {
+                return;
+            }
+            if (mSystemOverlayChangedReceiver != null) {
+                mService.unregisterReceiver(mSystemOverlayChangedReceiver);
+                mSystemOverlayChangedReceiver = null;
+            }
             mDestroyed = true;
         }
 
@@ -322,7 +390,9 @@
 
         @Override
         public String toDebugString() {
-            return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons + "}";
+            return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons
+                    + " mNavigationBarFrame=" + mNavigationBarFrame
+                    + "}";
         }
     }
 }
diff --git a/core/java/android/net/LocalServerSocket.java b/core/java/android/net/LocalServerSocket.java
index d1f49d2..506cbcb 100644
--- a/core/java/android/net/LocalServerSocket.java
+++ b/core/java/android/net/LocalServerSocket.java
@@ -55,7 +55,9 @@
      * Create a LocalServerSocket from a file descriptor that's already
      * been created and bound. listen() will be called immediately on it.
      * Used for cases where file descriptors are passed in via environment
-     * variables
+     * variables. The passed-in FileDescriptor is not managed by this class
+     * and must be closed by the caller. Calling {@link #close()} on a socket
+     * created by this method has no effect.
      *
      * @param fd bound file descriptor
      * @throws IOException
diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java
index 5b38f78..b69410c 100644
--- a/core/java/android/net/LocalSocket.java
+++ b/core/java/android/net/LocalSocket.java
@@ -16,7 +16,14 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.system.ErrnoException;
+import android.system.Os;
 
 import java.io.Closeable;
 import java.io.FileDescriptor;
@@ -74,32 +81,39 @@
         this.isBound = false;
     }
 
+    private void checkConnected() {
+        try {
+            Os.getpeername(impl.getFileDescriptor());
+        } catch (ErrnoException e) {
+            throw new IllegalArgumentException("Not a connected socket", e);
+        }
+        isConnected = true;
+        isBound = true;
+        implCreated = true;
+    }
+
     /**
-     * Creates a LocalSocket instances using the FileDescriptor for an already-connected
-     * AF_LOCAL/UNIX domain stream socket. Note: the FileDescriptor must be closed by the caller:
-     * closing the LocalSocket will not close it.
+     * Creates a LocalSocket instance using the {@link FileDescriptor} for an already-connected
+     * AF_LOCAL/UNIX domain stream socket. The passed-in FileDescriptor is not managed by this class
+     * and must be closed by the caller. Calling {@link #close()} on a socket created by this
+     * method has no effect.
      *
-     * @hide - used by BluetoothSocket.
+     * @param fd the filedescriptor to adopt
+     *
+     * @hide
      */
-    public static LocalSocket createConnectedLocalSocket(FileDescriptor fd) {
-        return createConnectedLocalSocket(new LocalSocketImpl(fd), SOCKET_UNKNOWN);
+    @SystemApi(client = MODULE_LIBRARIES)
+    public LocalSocket(@NonNull @SuppressLint("UseParcelFileDescriptor") FileDescriptor fd) {
+        this(new LocalSocketImpl(fd), SOCKET_UNKNOWN);
+        checkConnected();
     }
 
     /**
      * for use with LocalServerSocket.accept()
      */
     static LocalSocket createLocalSocketForAccept(LocalSocketImpl impl) {
-        return createConnectedLocalSocket(impl, SOCKET_UNKNOWN);
-    }
-
-    /**
-     * Creates a LocalSocket from an existing LocalSocketImpl that is already connected.
-     */
-    private static LocalSocket createConnectedLocalSocket(LocalSocketImpl impl, int sockType) {
-        LocalSocket socket = new LocalSocket(impl, sockType);
-        socket.isConnected = true;
-        socket.isBound = true;
-        socket.implCreated = true;
+        LocalSocket socket = new LocalSocket(impl, SOCKET_UNKNOWN);
+        socket.checkConnected();
         return socket;
     }
 
diff --git a/core/java/android/nfc/INfcTag.aidl b/core/java/android/nfc/INfcTag.aidl
index 539fd4a..e1ccc4f 100644
--- a/core/java/android/nfc/INfcTag.aidl
+++ b/core/java/android/nfc/INfcTag.aidl
@@ -45,4 +45,7 @@
     boolean canMakeReadOnly(int ndefType);
     int getMaxTransceiveLength(int technology);
     boolean getExtendedLengthApdusSupported();
+
+    void setTagUpToDate(long cookie);
+    boolean isTagUpToDate(long cookie);
 }
diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java
index 398ec63a..0ce9c70 100644
--- a/core/java/android/nfc/Tag.java
+++ b/core/java/android/nfc/Tag.java
@@ -34,6 +34,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.SystemClock;
 
 import java.io.IOException;
 import java.util.Arrays;
@@ -121,6 +122,7 @@
     final INfcTag mTagService; // interface to NFC service, will be null if mock tag
 
     int mConnectedTechnology;
+    long mCookie;
 
     /**
      * Hidden constructor to be used by NFC service and internal classes.
@@ -140,6 +142,13 @@
         mTagService = tagService;
 
         mConnectedTechnology = -1;
+
+        try {
+            mCookie = SystemClock.elapsedRealtime();
+            tagService.setTagUpToDate(mCookie);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
     }
 
     /**
@@ -361,6 +370,18 @@
     /** @hide */
     @UnsupportedAppUsage
     public INfcTag getTagService() {
+        try {
+            if (!mTagService.isTagUpToDate(mCookie)) {
+                String id_str = "";
+                for (int i = 0; i < mId.length; i++) {
+                    id_str = id_str + String.format("%02X ", mId[i]);
+                }
+                String msg = "Permission Denial: Tag ( ID: " + id_str + ") is out of date";
+                throw new SecurityException(msg);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
         return mTagService;
     }
 
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 6d4593a..76f857b 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -187,10 +187,13 @@
     public static final int BATTERY_PLUGGED_USB = OsProtoEnums.BATTERY_PLUGGED_USB; // = 2
     /** Power source is wireless. */
     public static final int BATTERY_PLUGGED_WIRELESS = OsProtoEnums.BATTERY_PLUGGED_WIRELESS; // = 4
+    /** Power source is dock. */
+    public static final int BATTERY_PLUGGED_DOCK = OsProtoEnums.BATTERY_PLUGGED_DOCK; // = 8
 
     /** @hide */
     public static final int BATTERY_PLUGGED_ANY =
-            BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS;
+            BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS
+                    | BATTERY_PLUGGED_DOCK;
 
     /**
      * Sent when the device's battery has started charging (or has reached full charge
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index abe5f81..d41a5fe 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -109,6 +109,7 @@
     static final String XML_ATTR_DISCHARGE_PERCENT = "discharge_pct";
     static final String XML_ATTR_DISCHARGE_LOWER = "discharge_lower";
     static final String XML_ATTR_DISCHARGE_UPPER = "discharge_upper";
+    static final String XML_ATTR_DISCHARGE_DURATION = "discharge_duration";
     static final String XML_ATTR_BATTERY_REMAINING = "battery_remaining";
     static final String XML_ATTR_CHARGE_REMAINING = "charge_remaining";
     static final String XML_ATTR_HIGHEST_DRAIN_PACKAGE = "highest_drain_package";
@@ -127,6 +128,7 @@
     private final long mStatsDurationMs;
     private final double mDischargedPowerLowerBound;
     private final double mDischargedPowerUpperBound;
+    private final long mDischargeDurationMs;
     private final long mBatteryTimeRemainingMs;
     private final long mChargeTimeRemainingMs;
     private final String[] mCustomPowerComponentNames;
@@ -146,6 +148,7 @@
         mDischargePercentage = builder.mDischargePercentage;
         mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah;
         mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah;
+        mDischargeDurationMs = builder.mDischargeDurationMs;
         mBatteryStatsHistory = builder.mBatteryStatsHistory;
         mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs;
         mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
@@ -246,6 +249,13 @@
     }
 
     /**
+     * Returns the total amount of time the battery was discharging.
+     */
+    public long getDischargeDurationMs() {
+        return mDischargeDurationMs;
+    }
+
+    /**
      * Returns an approximation for how much run time (in milliseconds) is remaining on
      * the battery.  Returns -1 if no time can be computed: either there is not
      * enough current data to make a decision, or the battery is currently
@@ -321,6 +331,7 @@
         mDischargePercentage = source.readInt();
         mDischargedPowerLowerBound = source.readDouble();
         mDischargedPowerUpperBound = source.readDouble();
+        mDischargeDurationMs = source.readLong();
         mBatteryTimeRemainingMs = source.readLong();
         mChargeTimeRemainingMs = source.readLong();
         mCustomPowerComponentNames = source.readStringArray();
@@ -378,6 +389,7 @@
         dest.writeInt(mDischargePercentage);
         dest.writeDouble(mDischargedPowerLowerBound);
         dest.writeDouble(mDischargedPowerUpperBound);
+        dest.writeLong(mDischargeDurationMs);
         dest.writeLong(mBatteryTimeRemainingMs);
         dest.writeLong(mChargeTimeRemainingMs);
         dest.writeStringArray(mCustomPowerComponentNames);
@@ -447,6 +459,8 @@
         proto.write(BatteryUsageStatsAtomsProto.SESSION_DURATION_MILLIS, getStatsDuration());
         proto.write(BatteryUsageStatsAtomsProto.SESSION_DISCHARGE_PERCENTAGE,
                 getDischargePercentage());
+        proto.write(BatteryUsageStatsAtomsProto.DISCHARGE_DURATION_MILLIS,
+                getDischargeDurationMs());
         deviceBatteryConsumer.writeStatsProto(proto,
                 BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER);
         writeUidBatteryConsumersProto(proto, maxRawSize);
@@ -638,6 +652,7 @@
         serializer.attributeInt(null, XML_ATTR_DISCHARGE_PERCENT, mDischargePercentage);
         serializer.attributeDouble(null, XML_ATTR_DISCHARGE_LOWER, mDischargedPowerLowerBound);
         serializer.attributeDouble(null, XML_ATTR_DISCHARGE_UPPER, mDischargedPowerUpperBound);
+        serializer.attributeLong(null, XML_ATTR_DISCHARGE_DURATION, mDischargeDurationMs);
         serializer.attributeLong(null, XML_ATTR_BATTERY_REMAINING, mBatteryTimeRemainingMs);
         serializer.attributeLong(null, XML_ATTR_CHARGE_REMAINING, mChargeTimeRemainingMs);
 
@@ -693,6 +708,8 @@
                 builder.setDischargedPowerRange(
                         parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_LOWER),
                         parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_UPPER));
+                builder.setDischargeDurationMs(
+                        parser.getAttributeLong(null, XML_ATTR_DISCHARGE_DURATION));
                 builder.setBatteryTimeRemainingMs(
                         parser.getAttributeLong(null, XML_ATTR_BATTERY_REMAINING));
                 builder.setChargeTimeRemainingMs(
@@ -759,6 +776,7 @@
         private int mDischargePercentage;
         private double mDischargedPowerLowerBoundMah;
         private double mDischargedPowerUpperBoundMah;
+        private long mDischargeDurationMs;
         private long mBatteryTimeRemainingMs = -1;
         private long mChargeTimeRemainingMs = -1;
         private final AggregateBatteryConsumer.Builder[] mAggregateBatteryConsumersBuilders =
@@ -869,6 +887,15 @@
         }
 
         /**
+         * Sets the total battery discharge time, in milliseconds.
+         */
+        @NonNull
+        public Builder setDischargeDurationMs(long durationMs) {
+            mDischargeDurationMs = durationMs;
+            return this;
+        }
+
+        /**
          * Sets an approximation for how much time (in milliseconds) remains until the battery
          * is fully discharged.
          */
@@ -994,6 +1021,7 @@
             mDischargedPowerLowerBoundMah += stats.mDischargedPowerLowerBound;
             mDischargedPowerUpperBoundMah += stats.mDischargedPowerUpperBound;
             mDischargePercentage += stats.mDischargePercentage;
+            mDischargeDurationMs += stats.mDischargeDurationMs;
 
             mStatsDurationMs = getStatsDuration() + stats.getStatsDuration();
 
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 81e49e9..37bd51b 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -75,8 +75,9 @@
     @NonNull
     private final int[] mUserIds;
     private final long mMaxStatsAgeMs;
-    private long mFromTimestamp;
-    private long mToTimestamp;
+    private final long mFromTimestamp;
+    private final long mToTimestamp;
+    private final @BatteryConsumer.PowerComponent int[] mPowerComponents;
 
     private BatteryUsageStatsQuery(@NonNull Builder builder) {
         mFlags = builder.mFlags;
@@ -85,6 +86,7 @@
         mMaxStatsAgeMs = builder.mMaxStatsAgeMs;
         mFromTimestamp = builder.mFromTimestamp;
         mToTimestamp = builder.mToTimestamp;
+        mPowerComponents = builder.mPowerComponents;
     }
 
     @BatteryUsageStatsFlags
@@ -116,6 +118,14 @@
     }
 
     /**
+     * Returns the power components that should be estimated or null if all power components
+     * are being requested.
+     */
+    public int[] getPowerComponents() {
+        return mPowerComponents;
+    }
+
+    /**
      * Returns the client's tolerance for stale battery stats. The data is allowed to be up to
      * this many milliseconds out-of-date.
      */
@@ -147,6 +157,7 @@
         mMaxStatsAgeMs = in.readLong();
         mFromTimestamp = in.readLong();
         mToTimestamp = in.readLong();
+        mPowerComponents = in.createIntArray();
     }
 
     @Override
@@ -157,6 +168,7 @@
         dest.writeLong(mMaxStatsAgeMs);
         dest.writeLong(mFromTimestamp);
         dest.writeLong(mToTimestamp);
+        dest.writeIntArray(mPowerComponents);
     }
 
     @Override
@@ -187,6 +199,7 @@
         private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS;
         private long mFromTimestamp;
         private long mToTimestamp;
+        private @BatteryConsumer.PowerComponent int[] mPowerComponents;
 
         /**
          * Builds a read-only BatteryUsageStatsQuery object.
@@ -248,6 +261,16 @@
         }
 
         /**
+         * Requests to return only statistics for the specified power components.  The default
+         * is all power components.
+         */
+        public Builder includePowerComponents(
+                @BatteryConsumer.PowerComponent int[] powerComponents) {
+            mPowerComponents = powerComponents;
+            return this;
+        }
+
+        /**
          * Requests to aggregate stored snapshots between the two supplied timestamps
          * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
          * @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index e929920..1b7c00c 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -125,7 +125,7 @@
             for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
                 if (a != null) {
                     for (WeakReference<BinderProxy> ref : a) {
-                        if (ref.get() != null) {
+                        if (!ref.refersTo(null)) {
                             ++size;
                         }
                     }
@@ -196,7 +196,7 @@
             // This ensures that ArrayList size is bounded by the maximum occupancy of
             // that bucket.
             for (int i = 0; i < size; ++i) {
-                if (valueArray.get(i).get() == null) {
+                if (valueArray.get(i).refersTo(null)) {
                     valueArray.set(i, newWr);
                     Long[] keyArray = mMainIndexKeys[myHash];
                     keyArray[i] = key;
@@ -204,7 +204,7 @@
                         // "Randomly" check one of the remaining entries in [i+1, size), so that
                         // needlessly long buckets are eventually pruned.
                         int rnd = Math.floorMod(++mRandom, size - (i + 1));
-                        if (valueArray.get(i + 1 + rnd).get() == null) {
+                        if (valueArray.get(i + 1 + rnd).refersTo(null)) {
                             remove(myHash, i + 1 + rnd);
                         }
                     }
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index d310d6e..834867c 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -799,6 +799,10 @@
      * Remove any pending posts of messages with code 'what' and whose obj is
      * 'object' that are in the message queue.  If <var>object</var> is null,
      * all messages will be removed.
+     * <p>
+     * Similar to {@link #removeMessages(int, Object)} but uses object equality
+     * ({@link Object#equals(Object)}) instead of reference equality (==) in
+     * determining whether object is the message's obj'.
      *
      *@hide
      */
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index d4a338b..881fced 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -44,23 +44,12 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.time.Duration;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicLong;
 
 /**
- * This class gives you control of the power state of the device.
- *
- * <p>
- * <b>Device battery life will be significantly affected by the use of this API.</b>
- * Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
- * possible, and be sure to release them as soon as possible. In most cases,
- * you'll want to use
- * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
- *
- * <p>
- * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
- * permission in an {@code <uses-permission>} element of the application's manifest.
- * </p>
+ * This class lets you query and request control of aspects of the device's power state.
  */
 @SystemService(Context.POWER_SERVICE)
 public final class PowerManager {
@@ -612,7 +601,7 @@
     public static final int WAKE_REASON_PLUGGED_IN = 3;
 
     /**
-     * Wake up reason code: Waking up due to a user performed gesture (e.g. douple tapping on the
+     * Wake up reason code: Waking up due to a user performed gesture (e.g. double tapping on the
      * screen).
      * @hide
      */
@@ -703,6 +692,21 @@
         public long wakeTime;
         public @WakeReason int wakeReason;
         public long sleepDuration;
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (o instanceof WakeData) {
+                final WakeData other = (WakeData) o;
+                return wakeTime == other.wakeTime && wakeReason == other.wakeReason
+                        && sleepDuration == other.sleepDuration;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(wakeTime, wakeReason, sleepDuration);
+        }
     }
 
     /**
@@ -1013,7 +1017,7 @@
 
     private static final int MAX_CACHE_ENTRIES = 1;
 
-    private PropertyInvalidatedCache<Void, Boolean> mPowerSaveModeCache =
+    private final PropertyInvalidatedCache<Void, Boolean> mPowerSaveModeCache =
             new PropertyInvalidatedCache<Void, Boolean>(MAX_CACHE_ENTRIES,
                 CACHE_KEY_IS_POWER_SAVE_MODE_PROPERTY) {
                 @Override
@@ -1026,7 +1030,7 @@
                 }
             };
 
-    private PropertyInvalidatedCache<Void, Boolean> mInteractiveCache =
+    private final PropertyInvalidatedCache<Void, Boolean> mInteractiveCache =
             new PropertyInvalidatedCache<Void, Boolean>(MAX_CACHE_ENTRIES,
                 CACHE_KEY_IS_INTERACTIVE_PROPERTY) {
                 @Override
@@ -1047,7 +1051,7 @@
     final IThermalService mThermalService;
 
     /** We lazily initialize it.*/
-    private PowerWhitelistManager mPowerWhitelistManager;
+    private PowerExemptionManager mPowerExemptionManager;
 
     private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener>
             mListenerMap = new ArrayMap<>();
@@ -1063,12 +1067,12 @@
         mHandler = handler;
     }
 
-    private PowerWhitelistManager getPowerWhitelistManager() {
-        if (mPowerWhitelistManager == null) {
+    private PowerExemptionManager getPowerExemptionManager() {
+        if (mPowerExemptionManager == null) {
             // No need for synchronization; getSystemService() will return the same object anyway.
-            mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
+            mPowerExemptionManager = mContext.getSystemService(PowerExemptionManager.class);
         }
-        return mPowerWhitelistManager;
+        return mPowerExemptionManager;
     }
 
     /**
@@ -1180,6 +1184,11 @@
      * Although a wake lock can be created without special permissions,
      * the {@link android.Manifest.permission#WAKE_LOCK} permission is
      * required to actually acquire or release the wake lock that is returned.
+     *
+     * </p><p>
+     * <b>Device battery life will be significantly affected by the use of this API.</b>
+     * Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
+     * possible, and be sure to release them as soon as possible.
      * </p><p class="note">
      * If using this to keep the screen on, you should strongly consider using
      * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
@@ -1339,12 +1348,12 @@
     }
 
     /**
-     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
      * to turn off.
      *
-     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
      * turned on it will be turned off. If all displays are off as a result of this action the
-     * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT
+     * device will be put to sleep. If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP
      * default display group} is already off then nothing will happen.
      *
      * <p>If the device is an Android TV playback device and the current active source on the
@@ -1372,12 +1381,12 @@
     }
 
     /**
-     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
      * to turn off.
      *
-     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
      * turned on it will be turned off. If all displays are off as a result of this action the
-     * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT
+     * device will be put to sleep. If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP
      * default display group} is already off then nothing will happen.
      *
      * <p>
@@ -1409,12 +1418,12 @@
     }
 
     /**
-     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
      * to turn on.
      *
-     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
      * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
-     * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already
+     * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already
      * on then nothing will happen.
      *
      * <p>
@@ -1440,12 +1449,12 @@
     }
 
     /**
-     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
      * to turn on.
      *
-     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
      * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
-     * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already
+     * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already
      * on then nothing will happen.
      *
      * <p>
@@ -2144,7 +2153,7 @@
      * features to the app. Guardrails for extreme cases may still be applied.
      */
     public boolean isIgnoringBatteryOptimizations(String packageName) {
-        return getPowerWhitelistManager().isWhitelisted(packageName, true);
+        return getPowerExemptionManager().isAllowListed(packageName, true);
     }
 
     /**
@@ -2270,8 +2279,8 @@
      * @param listener listener to be added,
      */
     public void addThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
-        Preconditions.checkNotNull(listener, "listener cannot be null");
-        this.addThermalStatusListener(mContext.getMainExecutor(), listener);
+        Objects.requireNonNull(listener, "listener cannot be null");
+        addThermalStatusListener(mContext.getMainExecutor(), listener);
     }
 
     /**
@@ -2282,8 +2291,8 @@
      */
     public void addThermalStatusListener(@NonNull @CallbackExecutor Executor executor,
             @NonNull OnThermalStatusChangedListener listener) {
-        Preconditions.checkNotNull(listener, "listener cannot be null");
-        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Objects.requireNonNull(listener, "listener cannot be null");
+        Objects.requireNonNull(executor, "executor cannot be null");
         Preconditions.checkArgument(!mListenerMap.containsKey(listener),
                 "Listener already registered: %s", listener);
         IThermalStatusListener internalListener = new IThermalStatusListener.Stub() {
@@ -2291,9 +2300,7 @@
             public void onStatusChange(int status) {
                 final long token = Binder.clearCallingIdentity();
                 try {
-                    executor.execute(() -> {
-                        listener.onThermalStatusChanged(status);
-                    });
+                    executor.execute(() -> listener.onThermalStatusChanged(status));
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
@@ -2316,7 +2323,7 @@
      * @param listener listener to be removed
      */
     public void removeThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
-        Preconditions.checkNotNull(listener, "listener cannot be null");
+        Objects.requireNonNull(listener, "listener cannot be null");
         IThermalStatusListener internalListener = mListenerMap.get(listener);
         Preconditions.checkArgument(internalListener != null, "Listener was not added");
         try {
@@ -2945,6 +2952,7 @@
          *
          * @hide
          */
+        @SuppressLint("WakelockTimeout")
         public Runnable wrap(Runnable r) {
             acquire();
             return () -> {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f18c9c9..bd65a41 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4787,33 +4787,6 @@
     }
 
     /**
-     * Immediately removes the user or, if the user cannot be removed, such as when the user is
-     * the current user, then set the user as ephemeral so that it will be removed when it is
-     * stopped.
-     *
-     * @param evenWhenDisallowed when {@code true}, user is removed even if the caller has the
-     * {@link #DISALLOW_REMOVE_USER} or {@link #DISALLOW_REMOVE_MANAGED_PROFILE} restriction
-     *
-     * @return the {@link RemoveResult} code
-     *
-     * @deprecated  TODO(b/199446770): remove this call after converting all calls to
-     * removeUserWhenPossible(UserHandle, boolean)
-     *
-     * @hide
-     */
-    @Deprecated
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS})
-    public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId,
-            boolean evenWhenDisallowed) {
-        try {
-            return mService.removeUserWhenPossible(userId, evenWhenDisallowed);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Updates the user's name.
      *
      * @param userId the user's integer id
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index ae37a71..f490587 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -21,7 +21,6 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
@@ -40,6 +39,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -196,20 +196,20 @@
     /**
      * Create a waveform vibration.
      *
-     * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+     * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
      * each pair, the value in the amplitude array determines the strength of the vibration and the
      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
-     * <p>
-     * The amplitude array of the generated waveform will be the same size as the given
+     *
+     * <p>The amplitude array of the generated waveform will be the same size as the given
      * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
      * starting with 0. Therefore the first timing value will be the period to wait before turning
      * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
      * strength, etc.
-     * </p><p>
-     * To cause the pattern to repeat, pass the index into the timings array at which to start the
-     * repetition, or -1 to disable repeating.
-     * </p>
+     *
+     * <p>To cause the pattern to repeat, pass the index into the timings array at which to start
+     * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
+     * and should be cancelled via {@link Vibrator#cancel()}.
      *
      * @param timings The pattern of alternating on-off timings, starting with off. Timing values
      *                of 0 will cause the timing / amplitude pair to be ignored.
@@ -229,15 +229,15 @@
     /**
      * Create a waveform vibration.
      *
-     * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+     * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
      * each pair, the value in the amplitude array determines the strength of the vibration and the
      * value in the timing array determines how long it vibrates for, in milliseconds. Amplitude
      * values must be between 0 and 255, and an amplitude of 0 implies no vibration (i.e. off). Any
      * pairs with a timing value of 0 will be ignored.
-     * </p><p>
-     * To cause the pattern to repeat, pass the index into the timings array at which to start the
-     * repetition, or -1 to disable repeating.
-     * </p>
+     *
+     * <p>To cause the pattern to repeat, pass the index into the timings array at which to start
+     * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
+     * and should be cancelled via {@link Vibrator#cancel()}.
      *
      * @param timings The timing values, in milliseconds, of the timing / amplitude pairs. Timing
      *                values of 0 will cause the pair to be ignored.
@@ -407,20 +407,59 @@
      * Start building a waveform vibration.
      *
      * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
-     * control over vibration frequency and ramping up or down the vibration amplitude, frequency or
-     * both.
+     * control over vibration amplitude and frequency via smooth transitions between values.
      *
-     * <p>For simpler waveform patterns see {@link #createWaveform} methods.
+     * <p>The waveform will start the first transition from the vibrator off state, with the
+     * resonant frequency by default. To provide an initial state, use
+     * {@link #startWaveform(VibrationParameter)}.
      *
-     * @hide
-     * @see VibrationEffect.WaveformBuilder
+     * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
      */
-    @TestApi
     @NonNull
     public static WaveformBuilder startWaveform() {
         return new WaveformBuilder();
     }
 
+    /**
+     * Start building a waveform vibration with an initial state specified by a
+     * {@link VibrationParameter}.
+     *
+     * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
+     * control over vibration amplitude and frequency via smooth transitions between values.
+     *
+     * @param initialParameter The initial {@link VibrationParameter} value to be applied at the
+     *                         beginning of the vibration.
+     * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+     */
+    @NonNull
+    public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) {
+        WaveformBuilder builder = startWaveform();
+        builder.addTransition(Duration.ZERO, initialParameter);
+        return builder;
+    }
+
+    /**
+     * Start building a waveform vibration with an initial state specified by two
+     * {@link VibrationParameter VibrationParameters}.
+     *
+     * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
+     * control over vibration amplitude and frequency via smooth transitions between values.
+     *
+     * @param initialParameter1 The initial {@link VibrationParameter} value to be applied at the
+     *                          beginning of the vibration.
+     * @param initialParameter2 The initial {@link VibrationParameter} value to be applied at the
+     *                          beginning of the vibration, must be a different type of parameter
+     *                          than the one specified by the first argument.
+     * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+     */
+    @NonNull
+    public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1,
+            @NonNull VibrationParameter initialParameter2) {
+        WaveformBuilder builder = startWaveform();
+        builder.addTransition(Duration.ZERO, initialParameter1, initialParameter2);
+        return builder;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -784,10 +823,23 @@
                 PRIMITIVE_LOW_TICK,
         })
         @Retention(RetentionPolicy.SOURCE)
-        public @interface PrimitiveType {}
+        public @interface PrimitiveType {
+        }
+
+        /**
+         * Exception thrown when adding an element to a {@link Composition} that already ends in an
+         * indefinitely repeating effect.
+         */
+        public static final class UnreachableAfterRepeatingIndefinitelyException
+                extends IllegalStateException {
+            UnreachableAfterRepeatingIndefinitelyException() {
+                super("Compositions ending in an indefinitely repeating effect can't be extended");
+            }
+        }
 
         /**
          * No haptic effect. Used to generate extended delays between primitives.
+         *
          * @hide
          */
         public static final int PRIMITIVE_NOOP = 0;
@@ -837,50 +889,87 @@
         Composition() {}
 
         /**
-         * Add a haptic effect to the end of the current composition.
+         * Adds a time duration to the current composition, during which the vibrator will be
+         * turned off
          *
-         * <p>Similar to {@link #addEffect(VibrationEffect, int)} , but with no delay applied.
+         * @param duration The length of time the vibrator should be off. Value must be non-negative
+         *                 and will be truncated to milliseconds.
+         * @return This {@link Composition} object to enable adding multiple elements in one chain.
          *
-         * @param effect The effect to add to this composition as a primitive
-         * @return The {@link Composition} object to enable adding multiple primitives in one chain.
-         * @hide
+         * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
+         * ending with a repeating effect.
          */
-        @TestApi
         @NonNull
-        public Composition addEffect(@NonNull VibrationEffect effect) {
-            return addEffect(effect, /* delay= */ 0);
+        public Composition addOffDuration(@NonNull Duration duration) {
+            int durationMs = (int) duration.toMillis();
+            Preconditions.checkArgumentNonnegative(durationMs, "Off period must be non-negative");
+            if (durationMs > 0) {
+                // Created a segment sustaining the zero amplitude to represent the delay.
+                addSegment(new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0,
+                        (int) duration.toMillis()));
+            }
+            return this;
         }
 
         /**
          * Add a haptic effect to the end of the current composition.
          *
-         * @param effect The effect to add to this composition as a primitive
-         * @param delay  The amount of time in milliseconds to wait before playing this primitive
-         * @return The {@link Composition} object to enable adding multiple primitives in one chain.
-         * @hide
+         * <p>If this effect is repeating (e.g. created by {@link VibrationEffect#createWaveform}
+         * with a non-negative repeat index, or created by another composition that has effects
+         * repeating indefinitely), then no more effects or primitives will be accepted by this
+         * composition after this method. Such effects should be cancelled via
+         * {@link Vibrator#cancel()}.
+         *
+         * @param effect The effect to add to the end of this composition.
+         * @return This {@link Composition} object to enable adding multiple elements in one chain.
+         *
+         * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
+         * ending with a repeating effect.
          */
-        @TestApi
         @NonNull
-        public Composition addEffect(@NonNull VibrationEffect effect,
-                @IntRange(from = 0) int delay) {
-            Preconditions.checkArgumentNonnegative(delay);
-            if (delay > 0) {
-                // Created a segment sustaining the zero amplitude to represent the delay.
-                addSegment(new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0,
-                        /* duration= */ delay));
-            }
+        public Composition addEffect(@NonNull VibrationEffect effect) {
             return addSegments(effect);
         }
 
         /**
+         * Add a haptic effect to the end of the current composition and play it on repeat,
+         * indefinitely.
+         *
+         * <p>The entire effect will be played on repeat, indefinitely, after all other elements
+         * already added to this composition are played. No more effects or primitives will be
+         * accepted by this composition after this method. Such effects should be cancelled via
+         * {@link Vibrator#cancel()}.
+         *
+         * @param effect The effect to add to the end of this composition, must be finite.
+         * @return This {@link Composition} object to enable adding multiple elements in one chain,
+         * although only {@link #compose()} can follow this call.
+         *
+         * @throws IllegalArgumentException if the given effect is already repeating indefinitely.
+         * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
+         * ending with a repeating effect.
+         */
+        @NonNull
+        public Composition repeatEffectIndefinitely(@NonNull VibrationEffect effect) {
+            Preconditions.checkArgument(effect.getDuration() < Long.MAX_VALUE,
+                    "Can't repeat an indefinitely repeating effect. Consider addEffect instead.");
+            int previousSegmentCount = mSegments.size();
+            addSegments(effect);
+            // Set repeat after segments were added, since addSegments checks this index.
+            mRepeatIndex = previousSegmentCount;
+            return this;
+        }
+
+        /**
          * Add a haptic primitive to the end of the current composition.
          *
          * Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a
          * default scale applied.
          *
          * @param primitiveId The primitive to add
+         * @return This {@link Composition} object to enable adding multiple elements in one chain.
          *
-         * @return The {@link Composition} object to enable adding multiple primitives in one chain.
+         * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
+         * ending with a repeating effect.
          */
         @NonNull
         public Composition addPrimitive(@PrimitiveType int primitiveId) {
@@ -894,8 +983,10 @@
          *
          * @param primitiveId The primitive to add
          * @param scale The scale to apply to the intensity of the primitive.
+         * @return This {@link Composition} object to enable adding multiple elements in one chain.
          *
-         * @return The {@link Composition} object to enable adding multiple primitives in one chain.
+         * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
+         * ending with a repeating effect.
          */
         @NonNull
         public Composition addPrimitive(@PrimitiveType int primitiveId,
@@ -910,7 +1001,10 @@
          * @param scale The scale to apply to the intensity of the primitive.
          * @param delay The amount of time in milliseconds to wait before playing this primitive,
          *              starting at the time the previous element in this composition is finished.
-         * @return The {@link Composition} object to enable adding multiple primitives in one chain.
+         * @return This {@link Composition} object to enable adding multiple elements in one chain.
+         *
+         * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
+         * ending with a repeating effect.
          */
         @NonNull
         public Composition addPrimitive(@PrimitiveType int primitiveId,
@@ -923,9 +1017,7 @@
 
         private Composition addSegment(VibrationEffectSegment segment) {
             if (mRepeatIndex >= 0) {
-                throw new IllegalStateException(
-                        "Composition already have a repeating effect so any new primitive would be"
-                                + " unreachable.");
+                throw new UnreachableAfterRepeatingIndefinitelyException();
             }
             mSegments.add(segment);
             return this;
@@ -933,9 +1025,7 @@
 
         private Composition addSegments(VibrationEffect effect) {
             if (mRepeatIndex >= 0) {
-                throw new IllegalStateException(
-                        "Composition already have a repeating effect so any new primitive would be"
-                                + " unreachable.");
+                throw new UnreachableAfterRepeatingIndefinitelyException();
             }
             Composed composed = (Composed) effect;
             if (composed.getRepeatIndex() >= 0) {
@@ -1001,162 +1091,251 @@
     /**
      * A builder for waveform haptic effects.
      *
-     * <p>Waveform vibrations constitute of one or more timed segments where the vibration
-     * amplitude, frequency or both can linearly ramp to new values.
+     * <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration
+     * parameters. These parameters can be the vibration amplitude or frequency, for example.
      *
-     * <p>Waveform segments may have zero duration, which represent a jump to new vibration
-     * amplitude and/or frequency values.
+     * <p>Note that physical vibration actuators have different reaction times for changing
+     * amplitude and frequency. Durations specified here represent a timeline for the target
+     * parameters, and quality of effects may be improved if the durations allow time for a
+     * transition to be smoothly applied.
      *
-     * <p>Waveform segments may have the same start and end vibration amplitude and frequency,
-     * which represent a step where the amplitude and frequency are maintained for that duration.
+     * <p>Repeating waveforms can be built by constructing the repeating block separately and adding
+     * it to the end of a composition using
+     * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}.
      *
-     * @hide
-     * @see VibrationEffect#startWaveform()
+     * @see VibrationEffect#startWaveform
      */
-    @TestApi
     public static final class WaveformBuilder {
+        // Epsilon used for float comparison of amplitude and frequency values on transitions.
+        private static final float EPSILON = 1e-5f;
+
         private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
+        private float mLastAmplitude = 0f;
+        private float mLastFrequencyHz = 0f;
 
         WaveformBuilder() {}
 
         /**
-         * Vibrate with given amplitude for the given duration, in millis, keeping the previous
-         * frequency the same.
+         * Add a transition to new vibration parameter value to the end of this waveform.
          *
-         * <p>If the duration is zero the vibrator will jump to new amplitude.
+         * <p>The duration represents how long the vibrator should take to smoothly transition to
+         * the new vibration parameter. If the duration is zero then the vibrator will jump to the
+         * new value as fast as possible.
          *
-         * @param amplitude The amplitude for this step
-         * @param duration  The duration of this step in milliseconds
-         * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
+         * <p>Vibration parameter values will be truncated to conform to the device capabilities
+         * according to the {@link android.os.vibrator.VibratorFrequencyProfile}.
+         *
+         * @param duration        The length of time this transition should take. Value must be
+         *                        non-negative and will be truncated to milliseconds.
+         * @param targetParameter The new target {@link VibrationParameter} value to be reached
+         *                        after the given duration.
+         * @return This {@link WaveformBuilder} object to enable adding multiple transitions in
+         * chain.
          */
-        @SuppressLint("MissingGetterMatchingBuilder")
+        @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
         @NonNull
-        public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude,
-                @IntRange(from = 0) int duration) {
-            mSegments.add(new StepSegment(amplitude, getPreviousFrequencyHz(), duration));
+        public WaveformBuilder addTransition(@NonNull Duration duration,
+                @NonNull VibrationParameter targetParameter) {
+            Preconditions.checkNotNull(duration, "Duration is null");
+            checkVibrationParameter(targetParameter, "targetParameter");
+            float amplitude = extractTargetAmplitude(targetParameter, /* target2= */ null);
+            float frequencyHz = extractTargetFrequency(targetParameter, /* target2= */ null);
+            addTransitionSegment(duration, amplitude, frequencyHz);
             return this;
         }
 
         /**
-         * Vibrate with given amplitude and frequency for the given duration, in millis.
+         * Add a transition to new vibration parameters to the end of this waveform.
          *
-         * <p>If the duration is zero the vibrator will jump to new amplitude.
+         * <p>The duration represents how long the vibrator should take to smoothly transition to
+         * the new vibration parameters. If the duration is zero then the vibrator will jump to the
+         * new values as fast as possible.
          *
-         * @param amplitude The amplitude for this step
-         * @param frequencyHz The frequency for this step, in hertz
-         * @param duration  The duration of this step in milliseconds
-         * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
+         * <p>Vibration parameters values will be truncated to conform to the device capabilities
+         * according to the {@link android.os.vibrator.VibratorFrequencyProfile}.
+         *
+         * @param duration         The length of time this transition should take. Value must be
+         *                         non-negative and will be truncated to milliseconds.
+         * @param targetParameter1 The first target {@link VibrationParameter} value to be reached
+         *                         after the given duration.
+         * @param targetParameter2 The second target {@link VibrationParameter} value to be reached
+         *                         after the given duration, must be a different type of parameter
+         *                         than the one specified by the first argument.
+         * @return This {@link WaveformBuilder} object to enable adding multiple transitions in
+         * chain.
          */
-        @SuppressLint("MissingGetterMatchingBuilder")
+        @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
         @NonNull
-        public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude,
-                @FloatRange(from = 1f) float frequencyHz,
-                @IntRange(from = 0) int duration) {
-            Preconditions.checkArgument(frequencyHz >= 1, "Frequency must be >= 1");
-            mSegments.add(new StepSegment(amplitude, frequencyHz, duration));
+        public WaveformBuilder addTransition(@NonNull Duration duration,
+                @NonNull VibrationParameter targetParameter1,
+                @NonNull VibrationParameter targetParameter2) {
+            Preconditions.checkNotNull(duration, "Duration is null");
+            checkVibrationParameter(targetParameter1, "targetParameter1");
+            checkVibrationParameter(targetParameter2, "targetParameter2");
+            Preconditions.checkArgument(
+                    !Objects.equals(targetParameter1.getClass(), targetParameter2.getClass()),
+                    "Parameter arguments must specify different parameter types");
+            float amplitude = extractTargetAmplitude(targetParameter1, targetParameter2);
+            float frequencyHz = extractTargetFrequency(targetParameter1, targetParameter2);
+            addTransitionSegment(duration, amplitude, frequencyHz);
             return this;
         }
 
         /**
-         * Ramp vibration linearly for the given duration, in millis, from previous amplitude value
-         * to the given one, keeping previous frequency.
+         * Add a duration to sustain the last vibration parameters of this waveform.
          *
-         * <p>If the duration is zero the vibrator will jump to new amplitude.
+         * <p>The duration represents how long the vibrator should sustain the last set of
+         * parameters provided to this builder.
          *
-         * @param amplitude The final amplitude this ramp should reach
-         * @param duration  The duration of this ramp in milliseconds
-         * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
+         * @param duration   The length of time the last values should be sustained by the vibrator.
+         *                   Value must be >= 1ms.
+         * @return This {@link WaveformBuilder} object to enable adding multiple transitions in
+         * chain.
          */
-        @SuppressLint("MissingGetterMatchingBuilder")
+        @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
         @NonNull
-        public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude,
-                @IntRange(from = 0) int duration) {
-            mSegments.add(new RampSegment(getPreviousAmplitude(), amplitude,
-                    getPreviousFrequencyHz(), getPreviousFrequencyHz(), duration));
+        public WaveformBuilder addSustain(@NonNull Duration duration) {
+            int durationMs = (int) duration.toMillis();
+            Preconditions.checkArgument(durationMs >= 1, "Sustain duration must be >= 1ms");
+            mSegments.add(new StepSegment(mLastAmplitude, mLastFrequencyHz, durationMs));
             return this;
         }
 
         /**
-         * Ramp vibration linearly for the given duration, in millis, from previous amplitude and
-         * frequency values to the given ones.
-         *
-         * <p>If the duration is zero the vibrator will jump to new amplitude and frequency.
-         *
-         * @param amplitude The final amplitude this ramp should reach
-         * @param frequencyHz The final frequency this ramp should reach, in hertz
-         * @param duration  The duration of this ramp in milliseconds
-         * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
-         */
-        @SuppressLint("MissingGetterMatchingBuilder")
-        @NonNull
-        public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude,
-                @FloatRange(from = 1f) float frequencyHz,
-                @IntRange(from = 0) int duration) {
-            Preconditions.checkArgument(frequencyHz >= 1, "Frequency must be >= 1");
-            mSegments.add(new RampSegment(getPreviousAmplitude(), amplitude,
-                    getPreviousFrequencyHz(), frequencyHz, duration));
-            return this;
-        }
-
-        /**
-         * Compose all the steps together into a single {@link VibrationEffect}.
+         * Build the waveform as a single {@link VibrationEffect}.
          *
          * The {@link WaveformBuilder} object is still valid after this call, so you can
          * continue adding more primitives to it and generating more {@link VibrationEffect}s by
          * calling this method again.
          *
-         * @return The {@link VibrationEffect} resulting from the composition of the steps.
+         * @return The {@link VibrationEffect} resulting from the list of transitions.
          */
         @NonNull
         public VibrationEffect build() {
-            return build(/* repeat= */ -1);
-        }
-
-        /**
-         * Compose all the steps together into a single {@link VibrationEffect}.
-         *
-         * <p>To cause the pattern to repeat, pass the index at which to start the repetition
-         * (starting at 0), or -1 to disable repeating.
-         *
-         * <p>The {@link WaveformBuilder} object is still valid after this call, so you can
-         * continue adding more primitives to it and generating more {@link VibrationEffect}s by
-         * calling this method again.
-         *
-         * @return The {@link VibrationEffect} resulting from the composition of the steps.
-         */
-        @NonNull
-        public VibrationEffect build(int repeat) {
             if (mSegments.isEmpty()) {
                 throw new IllegalStateException(
-                        "WaveformBuilder must have at least one element to build.");
+                        "WaveformBuilder must have at least one transition to build.");
             }
-            VibrationEffect effect = new Composed(mSegments, repeat);
+            VibrationEffect effect = new Composed(mSegments, /* repeatIndex= */ -1);
             effect.validate();
             return effect;
         }
 
-        private float getPreviousFrequencyHz() {
-            if (!mSegments.isEmpty()) {
-                VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1);
-                if (segment instanceof StepSegment) {
-                    return ((StepSegment) segment).getFrequencyHz();
-                } else if (segment instanceof RampSegment) {
-                    return ((RampSegment) segment).getEndFrequencyHz();
-                }
-            }
-            return 0;
+        private void checkVibrationParameter(@NonNull VibrationParameter vibrationParameter,
+                String paramName) {
+            Preconditions.checkNotNull(vibrationParameter, "%s is null", paramName);
+            Preconditions.checkArgument(
+                    (vibrationParameter instanceof AmplitudeVibrationParameter)
+                            || (vibrationParameter instanceof FrequencyVibrationParameter),
+                    "%s is a unknown parameter", paramName);
         }
 
-        private float getPreviousAmplitude() {
-            if (!mSegments.isEmpty()) {
-                VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1);
-                if (segment instanceof StepSegment) {
-                    return ((StepSegment) segment).getAmplitude();
-                } else if (segment instanceof RampSegment) {
-                    return ((RampSegment) segment).getEndAmplitude();
+        private float extractTargetAmplitude(@Nullable VibrationParameter target1,
+                @Nullable VibrationParameter target2) {
+            if (target2 instanceof AmplitudeVibrationParameter) {
+                return ((AmplitudeVibrationParameter) target2).amplitude;
+            }
+            if (target1 instanceof AmplitudeVibrationParameter) {
+                return ((AmplitudeVibrationParameter) target1).amplitude;
+            }
+            return mLastAmplitude;
+        }
+
+        private float extractTargetFrequency(@Nullable VibrationParameter target1,
+                @Nullable VibrationParameter target2) {
+            if (target2 instanceof FrequencyVibrationParameter) {
+                return ((FrequencyVibrationParameter) target2).frequencyHz;
+            }
+            if (target1 instanceof FrequencyVibrationParameter) {
+                return ((FrequencyVibrationParameter) target1).frequencyHz;
+            }
+            return mLastFrequencyHz;
+        }
+
+        private void addTransitionSegment(Duration duration, float targetAmplitude,
+                float targetFrequency) {
+            Preconditions.checkNotNull(duration, "Duration is null");
+            Preconditions.checkArgument(!duration.isNegative(),
+                    "Transition duration must be non-negative");
+            int durationMs = (int) duration.toMillis();
+
+            // Ignore transitions with zero duration, but keep values for next additions.
+            if (durationMs > 0) {
+                if ((Math.abs(mLastAmplitude - targetAmplitude) < EPSILON)
+                        && (Math.abs(mLastFrequencyHz - targetFrequency) < EPSILON)) {
+                    // No value is changing, this can be best represented by a step segment.
+                    mSegments.add(new StepSegment(targetAmplitude, targetFrequency, durationMs));
+                } else {
+                    mSegments.add(new RampSegment(mLastAmplitude, targetAmplitude,
+                            mLastFrequencyHz, targetFrequency, durationMs));
                 }
             }
-            return 0;
+
+            mLastAmplitude = targetAmplitude;
+            mLastFrequencyHz = targetFrequency;
+        }
+    }
+
+    /**
+     * A representation of a single vibration parameter.
+     *
+     * <p>This is to describe a waveform haptic effect, which consists of one or more timed
+     * transitions to a new set of {@link VibrationParameter}s.
+     *
+     * <p>Examples of concrete parameters are the vibration amplitude or frequency.
+     *
+     * @see VibrationEffect.WaveformBuilder
+     */
+    @SuppressWarnings("UserHandleName") // This is not a regular set of parameters, no *Params.
+    public static class VibrationParameter {
+        VibrationParameter() {
+        }
+
+        /**
+         * The target vibration amplitude.
+         *
+         * @param amplitude The amplitude value, between 0 and 1, inclusive, where 0 represents the
+         *                  vibrator turned off and 1 represents the maximum amplitude the vibrator
+         *                  can reach across all supported frequencies.
+         * @return The {@link VibrationParameter} instance that represents given amplitude.
+         */
+        @NonNull
+        public static VibrationParameter targetAmplitude(
+                @FloatRange(from = 0, to = 1) float amplitude) {
+            return new AmplitudeVibrationParameter(amplitude);
+        }
+
+        /**
+         * The target vibration frequency.
+         *
+         * @param frequencyHz The frequency value, in hertz.
+         * @return The {@link VibrationParameter} instance that represents given frequency.
+         */
+        @NonNull
+        public static VibrationParameter targetFrequency(@FloatRange(from = 1) float frequencyHz) {
+            return new FrequencyVibrationParameter(frequencyHz);
+        }
+    }
+
+    /** The vibration amplitude, represented by a value in [0,1]. */
+    private static final class AmplitudeVibrationParameter extends VibrationParameter {
+        public final float amplitude;
+
+        AmplitudeVibrationParameter(float amplitude) {
+            Preconditions.checkArgument((amplitude >= 0) && (amplitude <= 1),
+                    "Amplitude must be within [0,1]");
+            this.amplitude = amplitude;
+        }
+    }
+
+    /** The vibration frequency, in hertz, or zero to represent undefined frequency. */
+    private static final class FrequencyVibrationParameter extends VibrationParameter {
+        public final float frequencyHz;
+
+        FrequencyVibrationParameter(float frequencyHz) {
+            Preconditions.checkArgument(frequencyHz >= 1, "Frequency must be >= 1");
+            Preconditions.checkArgument(Float.isFinite(frequencyHz), "Frequency must be finite");
+            this.frequencyHz = frequencyHz;
         }
     }
 
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index e4aee76..15f13eb 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -33,6 +33,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.ChangeId;
@@ -239,6 +240,41 @@
     }
 
     /**
+     *
+     * Similar to checkPermissionForDataDelivery, except it results in an app op start, rather than
+     * a note. If this method is used, then {@link #finishDataDelivery(String, AttributionSource)}
+     * must be used when access is finished.
+     *
+     * @param permission The permission to check.
+     * @param attributionSource the permission identity
+     * @param message A message describing the reason the permission was checked
+     * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+     *
+     * @see #checkPermissionForDataDelivery(String, AttributionSource, String)
+     */
+    @PermissionCheckerManager.PermissionResult
+    public int checkPermissionForStartDataDelivery(@NonNull String permission,
+            @NonNull AttributionSource attributionSource, @Nullable String message) {
+        return PermissionChecker.checkPermissionForDataDelivery(mContext, permission,
+                // FIXME(b/199526514): PID should be passed inside AttributionSource.
+                PermissionChecker.PID_UNKNOWN, attributionSource, message, true);
+    }
+
+    /**
+     * Indicate that usage has finished for an {@link AttributionSource} started with
+     * {@link #checkPermissionForStartDataDelivery(String, AttributionSource, String)}
+     *
+     * @param permission The permission to check.
+     * @param attributionSource the permission identity to finish
+     */
+    public void finishDataDelivery(@NonNull String permission,
+            @NonNull AttributionSource attributionSource) {
+        PermissionChecker.finishDataDelivery(mContext, AppOpsManager.permissionToOp(permission),
+                attributionSource);
+    }
+
+    /**
      * Checks whether a given data access chain described by the given {@link AttributionSource}
      * has a given permission. Call this method if you are the datasource which would not blame you
      * for access to the data since you are the data. Use this API if you are the datasource of the
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3f54408..2a563ac 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -17210,6 +17210,12 @@
              */
             public static final String CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED =
                     "clockwork_long_press_to_assistant_enabled";
+
+            /*
+             * Whether the device has Wet Mode/ Touch Lock Mode enabled.
+             * @hide
+             */
+            public static final String WET_MODE_ON = "wet_mode_on";
         }
     }
 
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index cb5c19b..f4baedc 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -20,6 +20,7 @@
 import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -271,6 +272,25 @@
     }
 
     /**
+     * Attempts to force stop and relaunch the game associated with the current session. This may
+     * be useful, for example, after applying settings that will not take effect until the game is
+     * restarted.
+     *
+     * @return {@code true} if the game was successfully restarted; otherwise, {@code false}.
+     */
+    @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+    public final boolean restartGame() {
+        try {
+            mGameSessionController.restartGame(mTaskId);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to restart game", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
      * 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.
diff --git a/core/java/android/service/games/IGameSessionController.aidl b/core/java/android/service/games/IGameSessionController.aidl
index fe1d362..84311dc 100644
--- a/core/java/android/service/games/IGameSessionController.aidl
+++ b/core/java/android/service/games/IGameSessionController.aidl
@@ -16,6 +16,7 @@
 
 package android.service.games;
 
+import android.content.Intent;
 import com.android.internal.infra.AndroidFuture;
 
 /**
@@ -23,4 +24,6 @@
  */
 oneway interface IGameSessionController {
     void takeScreenshot(int taskId, in AndroidFuture gameScreenshotResultFuture);
-}
\ No newline at end of file
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)")
+    void restartGame(in int taskId);
+}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
similarity index 91%
rename from services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java
rename to core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
index c26965d..c452e06 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java
+++ b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.server.selectiontoolbar;
+package android.service.selectiontoolbar;
 
-import android.service.selectiontoolbar.SelectionToolbarRenderService;
 import android.util.Log;
 import android.view.selectiontoolbar.ShowInfo;
 
 /**
  * The default implementation of {@link SelectionToolbarRenderService}.
+ *
+ *  @hide
  */
+// TODO(b/214122495): fix class not found then move to system service folder
 public final class DefaultSelectionToolbarRenderService extends SelectionToolbarRenderService {
 
     private static final String TAG = "DefaultSelectionToolbarRenderService";
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
new file mode 100644
index 0000000..95ecc4e
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -0,0 +1,1619 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.selectiontoolbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Size;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.widget.ArrayAdapter;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.internal.widget.floatingtoolbar.FloatingToolbarPopup;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A popup window used by the floating toolbar to render menu items in the local app process.
+ *
+ * This class is responsible for the rendering/animation of the floating toolbar.
+ * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
+ * to transition between panels.
+ */
+
+final class RemoteSelectionToolbar implements FloatingToolbarPopup {
+
+    /* Minimum and maximum number of items allowed in the overflow. */
+    private static final int MIN_OVERFLOW_SIZE = 2;
+    private static final int MAX_OVERFLOW_SIZE = 4;
+
+    private final Context mContext;
+    private final View mParent;  // Parent for the popup window.
+    private final PopupWindow mPopupWindow;
+
+    /* Margins between the popup window and its content. */
+    private final int mMarginHorizontal;
+    private final int mMarginVertical;
+
+    /* View components */
+    private final ViewGroup mContentContainer;  // holds all contents.
+    private final ViewGroup mMainPanel;  // holds menu items that are initially displayed.
+    // holds menu items hidden in the overflow.
+    private final OverflowPanel mOverflowPanel;
+    private final ImageButton mOverflowButton;  // opens/closes the overflow.
+    /* overflow button drawables. */
+    private final Drawable mArrow;
+    private final Drawable mOverflow;
+    private final AnimatedVectorDrawable mToArrow;
+    private final AnimatedVectorDrawable mToOverflow;
+
+    private final OverflowPanelViewHelper mOverflowPanelViewHelper;
+
+    /* Animation interpolators. */
+    private final Interpolator mLogAccelerateInterpolator;
+    private final Interpolator mFastOutSlowInInterpolator;
+    private final Interpolator mLinearOutSlowInInterpolator;
+    private final Interpolator mFastOutLinearInInterpolator;
+
+    /* Animations. */
+    private final AnimatorSet mShowAnimation;
+    private final AnimatorSet mDismissAnimation;
+    private final AnimatorSet mHideAnimation;
+    private final AnimationSet mOpenOverflowAnimation;
+    private final AnimationSet mCloseOverflowAnimation;
+    private final Animation.AnimationListener mOverflowAnimationListener;
+
+    private final Rect mViewPortOnScreen = new Rect();  // portion of screen we can draw in.
+    private final Point mCoordsOnWindow = new Point();  // popup window coordinates.
+    /* Temporary data holders. Reset values before using. */
+    private final int[] mTmpCoords = new int[2];
+
+    private final Region mTouchableRegion = new Region();
+    private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
+            info -> {
+                info.contentInsets.setEmpty();
+                info.visibleInsets.setEmpty();
+                info.touchableRegion.set(mTouchableRegion);
+                info.setTouchableInsets(
+                        ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+            };
+
+    private final int mLineHeight;
+    private final int mIconTextSpacing;
+
+    /**
+     * @see OverflowPanelViewHelper#preparePopupContent().
+     */
+    private final Runnable mPreparePopupContentRTLHelper = new Runnable() {
+        @Override
+        public void run() {
+            setPanelsStatesAtRestingPosition();
+            setContentAreaAsTouchableSurface();
+            mContentContainer.setAlpha(1);
+        }
+    };
+
+    private boolean mDismissed = true; // tracks whether this popup is dismissed or dismissing.
+    private boolean mHidden; // tracks whether this popup is hidden or hiding.
+
+    /* Calculated sizes for panels and overflow button. */
+    private final Size mOverflowButtonSize;
+    private Size mOverflowPanelSize;  // Should be null when there is no overflow.
+    private Size mMainPanelSize;
+
+    /* Menu items and click listeners */
+    private final Map<MenuItemRepr, MenuItem> mMenuItems = new LinkedHashMap<>();
+    private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
+    private final View.OnClickListener mMenuItemButtonOnClickListener =
+            new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (mOnMenuItemClickListener == null) {
+                        return;
+                    }
+                    final Object tag = v.getTag();
+                    if (!(tag instanceof MenuItemRepr)) {
+                        return;
+                    }
+                    final MenuItem menuItem = mMenuItems.get((MenuItemRepr) tag);
+                    if (menuItem == null) {
+                        return;
+                    }
+                    mOnMenuItemClickListener.onMenuItemClick(menuItem);
+                }
+            };
+
+    private boolean mOpenOverflowUpwards;  // Whether the overflow opens upwards or downwards.
+    private boolean mIsOverflowOpen;
+
+    private int mTransitionDurationScale;  // Used to scale the toolbar transition duration.
+
+    private final Rect mPreviousContentRect = new Rect();
+    private int mSuggestedWidth;
+    private boolean mWidthChanged = true;
+
+    /**
+     * Initializes a new floating toolbar popup.
+     *
+     * @param parent  A parent view to get the {@link android.view.View#getWindowToken()} token
+     *      from.
+     */
+    RemoteSelectionToolbar(Context context, View parent) {
+        mParent = Objects.requireNonNull(parent);
+        mContext = applyDefaultTheme(context);
+        mContentContainer = createContentContainer(mContext);
+        mPopupWindow = createPopupWindow(mContentContainer);
+        mMarginHorizontal = parent.getResources()
+                .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
+        mMarginVertical = parent.getResources()
+                .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
+        mLineHeight = context.getResources()
+                .getDimensionPixelSize(R.dimen.floating_toolbar_height);
+        mIconTextSpacing = context.getResources()
+                .getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing);
+
+        // Interpolators
+        mLogAccelerateInterpolator = new LogAccelerateInterpolator();
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+                mContext, android.R.interpolator.fast_out_slow_in);
+        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+                mContext, android.R.interpolator.linear_out_slow_in);
+        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
+                mContext, android.R.interpolator.fast_out_linear_in);
+
+        // Drawables. Needed for views.
+        mArrow = mContext.getResources()
+                .getDrawable(R.drawable.ft_avd_tooverflow, mContext.getTheme());
+        mArrow.setAutoMirrored(true);
+        mOverflow = mContext.getResources()
+                .getDrawable(R.drawable.ft_avd_toarrow, mContext.getTheme());
+        mOverflow.setAutoMirrored(true);
+        mToArrow = (AnimatedVectorDrawable) mContext.getResources()
+                .getDrawable(R.drawable.ft_avd_toarrow_animation, mContext.getTheme());
+        mToArrow.setAutoMirrored(true);
+        mToOverflow = (AnimatedVectorDrawable) mContext.getResources()
+                .getDrawable(R.drawable.ft_avd_tooverflow_animation, mContext.getTheme());
+        mToOverflow.setAutoMirrored(true);
+
+        // Views
+        mOverflowButton = createOverflowButton();
+        mOverflowButtonSize = measure(mOverflowButton);
+        mMainPanel = createMainPanel();
+        mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext, mIconTextSpacing);
+        mOverflowPanel = createOverflowPanel();
+
+        // Animation. Need views.
+        mOverflowAnimationListener = createOverflowAnimationListener();
+        mOpenOverflowAnimation = new AnimationSet(true);
+        mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
+        mCloseOverflowAnimation = new AnimationSet(true);
+        mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
+        mShowAnimation = createEnterAnimation(mContentContainer);
+        mDismissAnimation = createExitAnimation(
+                mContentContainer,
+                150,  // startDelay
+                new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mPopupWindow.dismiss();
+                        mContentContainer.removeAllViews();
+                    }
+                });
+        mHideAnimation = createExitAnimation(
+                mContentContainer,
+                0,  // startDelay
+                new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mPopupWindow.dismiss();
+                    }
+                });
+    }
+
+    @Override
+    public boolean setOutsideTouchable(
+            boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) {
+        boolean ret = false;
+        if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) {
+            mPopupWindow.setOutsideTouchable(outsideTouchable);
+            mPopupWindow.setFocusable(!outsideTouchable);
+            mPopupWindow.update();
+            ret = true;
+        }
+        mPopupWindow.setOnDismissListener(onDismiss);
+        return ret;
+    }
+
+    /**
+     * Lays out buttons for the specified menu items.
+     * Requires a subsequent call to {@link FloatingToolbar#show()} to show the items.
+     */
+    private void layoutMenuItems(
+            List<MenuItem> menuItems,
+            MenuItem.OnMenuItemClickListener menuItemClickListener,
+            int suggestedWidth) {
+        cancelOverflowAnimations();
+        clearPanels();
+        updateMenuItems(menuItems, menuItemClickListener);
+        menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth));
+        if (!menuItems.isEmpty()) {
+            // Add remaining items to the overflow.
+            layoutOverflowPanelItems(menuItems);
+        }
+        updatePopupSize();
+    }
+
+    /**
+     * Updates the popup's menu items without rebuilding the widget.
+     * Use in place of layoutMenuItems() when the popup's views need not be reconstructed.
+     *
+     * @see #isLayoutRequired(List<MenuItem>)
+     */
+    private void updateMenuItems(
+            List<MenuItem> menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener) {
+        mMenuItems.clear();
+        for (MenuItem menuItem : menuItems) {
+            mMenuItems.put(MenuItemRepr.of(menuItem), menuItem);
+        }
+        mOnMenuItemClickListener = menuItemClickListener;
+    }
+
+    /**
+     * Returns true if this popup needs a relayout to properly render the specified menu items.
+     */
+    private boolean isLayoutRequired(List<MenuItem> menuItems) {
+        return !MenuItemRepr.reprEquals(menuItems, mMenuItems.values());
+    }
+
+    @Override
+    public void setWidthChanged(boolean widthChanged) {
+        mWidthChanged = widthChanged;
+    }
+
+    @Override
+    public void setSuggestedWidth(int suggestedWidth) {
+        // Check if there's been a substantial width spec change.
+        int difference = Math.abs(suggestedWidth - mSuggestedWidth);
+        mWidthChanged = difference > (mSuggestedWidth * 0.2);
+        mSuggestedWidth = suggestedWidth;
+    }
+
+    @Override
+    public void show(List<MenuItem> menuItems,
+            MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) {
+        if (isLayoutRequired(menuItems) || mWidthChanged) {
+            dismiss();
+            layoutMenuItems(menuItems, menuItemClickListener, mSuggestedWidth);
+        } else {
+            updateMenuItems(menuItems, menuItemClickListener);
+        }
+        if (!isShowing()) {
+            show(contentRect);
+        } else if (!mPreviousContentRect.equals(contentRect)) {
+            updateCoordinates(contentRect);
+        }
+        mWidthChanged = false;
+        mPreviousContentRect.set(contentRect);
+    }
+
+    private void show(Rect contentRectOnScreen) {
+        Objects.requireNonNull(contentRectOnScreen);
+
+        if (isShowing()) {
+            return;
+        }
+
+        mHidden = false;
+        mDismissed = false;
+        cancelDismissAndHideAnimations();
+        cancelOverflowAnimations();
+
+        refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
+        preparePopupContent();
+        // We need to specify the position in window coordinates.
+        // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can
+        // specify the popup position in screen coordinates.
+        mPopupWindow.showAtLocation(
+                mParent, Gravity.NO_GRAVITY, mCoordsOnWindow.x, mCoordsOnWindow.y);
+        setTouchableSurfaceInsetsComputer();
+        runShowAnimation();
+    }
+
+    @Override
+    public void dismiss() {
+        if (mDismissed) {
+            return;
+        }
+
+        mHidden = false;
+        mDismissed = true;
+        mHideAnimation.cancel();
+
+        runDismissAnimation();
+        setZeroTouchableSurface();
+    }
+
+    @Override
+    public void hide() {
+        if (!isShowing()) {
+            return;
+        }
+
+        mHidden = true;
+        runHideAnimation();
+        setZeroTouchableSurface();
+    }
+
+    @Override
+    public boolean isShowing() {
+        return !mDismissed && !mHidden;
+    }
+
+    @Override
+    public boolean isHidden() {
+        return mHidden;
+    }
+
+    /**
+     * Updates the coordinates of this popup.
+     * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
+     * This is a no-op if this popup is not showing.
+     */
+    private void updateCoordinates(Rect contentRectOnScreen) {
+        Objects.requireNonNull(contentRectOnScreen);
+
+        if (!isShowing() || !mPopupWindow.isShowing()) {
+            return;
+        }
+
+        cancelOverflowAnimations();
+        refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
+        preparePopupContent();
+        // We need to specify the position in window coordinates.
+        // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can
+        // specify the popup position in screen coordinates.
+        mPopupWindow.update(
+                mCoordsOnWindow.x, mCoordsOnWindow.y,
+                mPopupWindow.getWidth(), mPopupWindow.getHeight());
+    }
+
+    private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) {
+        refreshViewPort();
+
+        // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in
+        // landscape.
+        final int x = Math.min(
+                contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
+                mViewPortOnScreen.right - mPopupWindow.getWidth());
+
+        final int y;
+
+        final int availableHeightAboveContent =
+                contentRectOnScreen.top - mViewPortOnScreen.top;
+        final int availableHeightBelowContent =
+                mViewPortOnScreen.bottom - contentRectOnScreen.bottom;
+
+        final int margin = 2 * mMarginVertical;
+        final int toolbarHeightWithVerticalMargin = mLineHeight + margin;
+
+        if (!hasOverflow()) {
+            if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin) {
+                // There is enough space at the top of the content.
+                y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
+            } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin) {
+                // There is enough space at the bottom of the content.
+                y = contentRectOnScreen.bottom;
+            } else if (availableHeightBelowContent >= mLineHeight) {
+                // Just enough space to fit the toolbar with no vertical margins.
+                y = contentRectOnScreen.bottom - mMarginVertical;
+            } else {
+                // Not enough space. Prefer to position as high as possible.
+                y = Math.max(
+                        mViewPortOnScreen.top,
+                        contentRectOnScreen.top - toolbarHeightWithVerticalMargin);
+            }
+        } else {
+            // Has an overflow.
+            final int minimumOverflowHeightWithMargin =
+                    calculateOverflowHeight(MIN_OVERFLOW_SIZE) + margin;
+            final int availableHeightThroughContentDown =
+                    mViewPortOnScreen.bottom - contentRectOnScreen.top
+                            + toolbarHeightWithVerticalMargin;
+            final int availableHeightThroughContentUp =
+                    contentRectOnScreen.bottom - mViewPortOnScreen.top
+                            + toolbarHeightWithVerticalMargin;
+
+            if (availableHeightAboveContent >= minimumOverflowHeightWithMargin) {
+                // There is enough space at the top of the content rect for the overflow.
+                // Position above and open upwards.
+                updateOverflowHeight(availableHeightAboveContent - margin);
+                y = contentRectOnScreen.top - mPopupWindow.getHeight();
+                mOpenOverflowUpwards = true;
+            } else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin
+                    && availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) {
+                // There is enough space at the top of the content rect for the main panel
+                // but not the overflow.
+                // Position above but open downwards.
+                updateOverflowHeight(availableHeightThroughContentDown - margin);
+                y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
+                mOpenOverflowUpwards = false;
+            } else if (availableHeightBelowContent >= minimumOverflowHeightWithMargin) {
+                // There is enough space at the bottom of the content rect for the overflow.
+                // Position below and open downwards.
+                updateOverflowHeight(availableHeightBelowContent - margin);
+                y = contentRectOnScreen.bottom;
+                mOpenOverflowUpwards = false;
+            } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin
+                    && mViewPortOnScreen.height() >= minimumOverflowHeightWithMargin) {
+                // There is enough space at the bottom of the content rect for the main panel
+                // but not the overflow.
+                // Position below but open upwards.
+                updateOverflowHeight(availableHeightThroughContentUp - margin);
+                y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin
+                        - mPopupWindow.getHeight();
+                mOpenOverflowUpwards = true;
+            } else {
+                // Not enough space.
+                // Position at the top of the view port and open downwards.
+                updateOverflowHeight(mViewPortOnScreen.height() - margin);
+                y = mViewPortOnScreen.top;
+                mOpenOverflowUpwards = false;
+            }
+        }
+
+        // We later specify the location of PopupWindow relative to the attached window.
+        // The idea here is that 1) we can get the location of a View in both window coordinates
+        // and screen coordinates, where the offset between them should be equal to the window
+        // origin, and 2) we can use an arbitrary for this calculation while calculating the
+        // location of the rootview is supposed to be least expensive.
+        // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can avoid
+        // the following calculation.
+        mParent.getRootView().getLocationOnScreen(mTmpCoords);
+        int rootViewLeftOnScreen = mTmpCoords[0];
+        int rootViewTopOnScreen = mTmpCoords[1];
+        mParent.getRootView().getLocationInWindow(mTmpCoords);
+        int rootViewLeftOnWindow = mTmpCoords[0];
+        int rootViewTopOnWindow = mTmpCoords[1];
+        int windowLeftOnScreen = rootViewLeftOnScreen - rootViewLeftOnWindow;
+        int windowTopOnScreen = rootViewTopOnScreen - rootViewTopOnWindow;
+        mCoordsOnWindow.set(
+                Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen));
+    }
+
+    /**
+     * Performs the "show" animation on the floating popup.
+     */
+    private void runShowAnimation() {
+        mShowAnimation.start();
+    }
+
+    /**
+     * Performs the "dismiss" animation on the floating popup.
+     */
+    private void runDismissAnimation() {
+        mDismissAnimation.start();
+    }
+
+    /**
+     * Performs the "hide" animation on the floating popup.
+     */
+    private void runHideAnimation() {
+        mHideAnimation.start();
+    }
+
+    private void cancelDismissAndHideAnimations() {
+        mDismissAnimation.cancel();
+        mHideAnimation.cancel();
+    }
+
+    private void cancelOverflowAnimations() {
+        mContentContainer.clearAnimation();
+        mMainPanel.animate().cancel();
+        mOverflowPanel.animate().cancel();
+        mToArrow.stop();
+        mToOverflow.stop();
+    }
+
+    private void openOverflow() {
+        final int targetWidth = mOverflowPanelSize.getWidth();
+        final int targetHeight = mOverflowPanelSize.getHeight();
+        final int startWidth = mContentContainer.getWidth();
+        final int startHeight = mContentContainer.getHeight();
+        final float startY = mContentContainer.getY();
+        final float left = mContentContainer.getX();
+        final float right = left + mContentContainer.getWidth();
+        Animation widthAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
+                setWidth(mContentContainer, startWidth + deltaWidth);
+                if (isInRTLMode()) {
+                    mContentContainer.setX(left);
+
+                    // Lock the panels in place.
+                    mMainPanel.setX(0);
+                    mOverflowPanel.setX(0);
+                } else {
+                    mContentContainer.setX(right - mContentContainer.getWidth());
+
+                    // Offset the panels' positions so they look like they're locked in place
+                    // on the screen.
+                    mMainPanel.setX(mContentContainer.getWidth() - startWidth);
+                    mOverflowPanel.setX(mContentContainer.getWidth() - targetWidth);
+                }
+            }
+        };
+        Animation heightAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
+                setHeight(mContentContainer, startHeight + deltaHeight);
+                if (mOpenOverflowUpwards) {
+                    mContentContainer.setY(
+                            startY - (mContentContainer.getHeight() - startHeight));
+                    positionContentYCoordinatesIfOpeningOverflowUpwards();
+                }
+            }
+        };
+        final float overflowButtonStartX = mOverflowButton.getX();
+        final float overflowButtonTargetX =
+                isInRTLMode() ? overflowButtonStartX + targetWidth - mOverflowButton.getWidth()
+                        : overflowButtonStartX - targetWidth + mOverflowButton.getWidth();
+        Animation overflowButtonAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                float overflowButtonX = overflowButtonStartX
+                        + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
+                float deltaContainerWidth =
+                        isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
+                float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
+                mOverflowButton.setX(actualOverflowButtonX);
+            }
+        };
+        widthAnimation.setInterpolator(mLogAccelerateInterpolator);
+        widthAnimation.setDuration(getAdjustedDuration(250));
+        heightAnimation.setInterpolator(mFastOutSlowInInterpolator);
+        heightAnimation.setDuration(getAdjustedDuration(250));
+        overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
+        overflowButtonAnimation.setDuration(getAdjustedDuration(250));
+        mOpenOverflowAnimation.getAnimations().clear();
+        mOpenOverflowAnimation.getAnimations().clear();
+        mOpenOverflowAnimation.addAnimation(widthAnimation);
+        mOpenOverflowAnimation.addAnimation(heightAnimation);
+        mOpenOverflowAnimation.addAnimation(overflowButtonAnimation);
+        mContentContainer.startAnimation(mOpenOverflowAnimation);
+        mIsOverflowOpen = true;
+        mMainPanel.animate()
+                .alpha(0).withLayer()
+                .setInterpolator(mLinearOutSlowInInterpolator)
+                .setDuration(250)
+                .start();
+        mOverflowPanel.setAlpha(1); // fadeIn in 0ms.
+    }
+
+    private void closeOverflow() {
+        final int targetWidth = mMainPanelSize.getWidth();
+        final int startWidth = mContentContainer.getWidth();
+        final float left = mContentContainer.getX();
+        final float right = left + mContentContainer.getWidth();
+        Animation widthAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
+                setWidth(mContentContainer, startWidth + deltaWidth);
+                if (isInRTLMode()) {
+                    mContentContainer.setX(left);
+
+                    // Lock the panels in place.
+                    mMainPanel.setX(0);
+                    mOverflowPanel.setX(0);
+                } else {
+                    mContentContainer.setX(right - mContentContainer.getWidth());
+
+                    // Offset the panels' positions so they look like they're locked in place
+                    // on the screen.
+                    mMainPanel.setX(mContentContainer.getWidth() - targetWidth);
+                    mOverflowPanel.setX(mContentContainer.getWidth() - startWidth);
+                }
+            }
+        };
+        final int targetHeight = mMainPanelSize.getHeight();
+        final int startHeight = mContentContainer.getHeight();
+        final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
+        Animation heightAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
+                setHeight(mContentContainer, startHeight + deltaHeight);
+                if (mOpenOverflowUpwards) {
+                    mContentContainer.setY(bottom - mContentContainer.getHeight());
+                    positionContentYCoordinatesIfOpeningOverflowUpwards();
+                }
+            }
+        };
+        final float overflowButtonStartX = mOverflowButton.getX();
+        final float overflowButtonTargetX =
+                isInRTLMode() ? overflowButtonStartX - startWidth + mOverflowButton.getWidth()
+                        : overflowButtonStartX + startWidth - mOverflowButton.getWidth();
+        Animation overflowButtonAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                float overflowButtonX = overflowButtonStartX
+                        + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
+                float deltaContainerWidth =
+                        isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
+                float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
+                mOverflowButton.setX(actualOverflowButtonX);
+            }
+        };
+        widthAnimation.setInterpolator(mFastOutSlowInInterpolator);
+        widthAnimation.setDuration(getAdjustedDuration(250));
+        heightAnimation.setInterpolator(mLogAccelerateInterpolator);
+        heightAnimation.setDuration(getAdjustedDuration(250));
+        overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
+        overflowButtonAnimation.setDuration(getAdjustedDuration(250));
+        mCloseOverflowAnimation.getAnimations().clear();
+        mCloseOverflowAnimation.addAnimation(widthAnimation);
+        mCloseOverflowAnimation.addAnimation(heightAnimation);
+        mCloseOverflowAnimation.addAnimation(overflowButtonAnimation);
+        mContentContainer.startAnimation(mCloseOverflowAnimation);
+        mIsOverflowOpen = false;
+        mMainPanel.animate()
+                .alpha(1).withLayer()
+                .setInterpolator(mFastOutLinearInInterpolator)
+                .setDuration(100)
+                .start();
+        mOverflowPanel.animate()
+                .alpha(0).withLayer()
+                .setInterpolator(mLinearOutSlowInInterpolator)
+                .setDuration(150)
+                .start();
+    }
+
+    /**
+     * Defines the position of the floating toolbar popup panels when transition animation has
+     * stopped.
+     */
+    private void setPanelsStatesAtRestingPosition() {
+        mOverflowButton.setEnabled(true);
+        mOverflowPanel.awakenScrollBars();
+
+        if (mIsOverflowOpen) {
+            // Set open state.
+            final Size containerSize = mOverflowPanelSize;
+            setSize(mContentContainer, containerSize);
+            mMainPanel.setAlpha(0);
+            mMainPanel.setVisibility(View.INVISIBLE);
+            mOverflowPanel.setAlpha(1);
+            mOverflowPanel.setVisibility(View.VISIBLE);
+            mOverflowButton.setImageDrawable(mArrow);
+            mOverflowButton.setContentDescription(mContext.getString(
+                    R.string.floating_toolbar_close_overflow_description));
+
+            // Update x-coordinates depending on RTL state.
+            if (isInRTLMode()) {
+                mContentContainer.setX(mMarginHorizontal);  // align left
+                mMainPanel.setX(0);  // align left
+                mOverflowButton.setX(// align right
+                        containerSize.getWidth() - mOverflowButtonSize.getWidth());
+                mOverflowPanel.setX(0);  // align left
+            } else {
+                mContentContainer.setX(// align right
+                        mPopupWindow.getWidth() - containerSize.getWidth() - mMarginHorizontal);
+                mMainPanel.setX(-mContentContainer.getX());  // align right
+                mOverflowButton.setX(0);  // align left
+                mOverflowPanel.setX(0);  // align left
+            }
+
+            // Update y-coordinates depending on overflow's open direction.
+            if (mOpenOverflowUpwards) {
+                mContentContainer.setY(mMarginVertical);  // align top
+                mMainPanel.setY(// align bottom
+                        containerSize.getHeight() - mContentContainer.getHeight());
+                mOverflowButton.setY(// align bottom
+                        containerSize.getHeight() - mOverflowButtonSize.getHeight());
+                mOverflowPanel.setY(0);  // align top
+            } else {
+                // opens downwards.
+                mContentContainer.setY(mMarginVertical);  // align top
+                mMainPanel.setY(0);  // align top
+                mOverflowButton.setY(0);  // align top
+                mOverflowPanel.setY(mOverflowButtonSize.getHeight());  // align bottom
+            }
+        } else {
+            // Overflow not open. Set closed state.
+            final Size containerSize = mMainPanelSize;
+            setSize(mContentContainer, containerSize);
+            mMainPanel.setAlpha(1);
+            mMainPanel.setVisibility(View.VISIBLE);
+            mOverflowPanel.setAlpha(0);
+            mOverflowPanel.setVisibility(View.INVISIBLE);
+            mOverflowButton.setImageDrawable(mOverflow);
+            mOverflowButton.setContentDescription(mContext.getString(
+                    R.string.floating_toolbar_open_overflow_description));
+
+            if (hasOverflow()) {
+                // Update x-coordinates depending on RTL state.
+                if (isInRTLMode()) {
+                    mContentContainer.setX(mMarginHorizontal);  // align left
+                    mMainPanel.setX(0);  // align left
+                    mOverflowButton.setX(0);  // align left
+                    mOverflowPanel.setX(0);  // align left
+                } else {
+                    mContentContainer.setX(// align right
+                            mPopupWindow.getWidth() - containerSize.getWidth() - mMarginHorizontal);
+                    mMainPanel.setX(0);  // align left
+                    mOverflowButton.setX(// align right
+                            containerSize.getWidth() - mOverflowButtonSize.getWidth());
+                    mOverflowPanel.setX(// align right
+                            containerSize.getWidth() - mOverflowPanelSize.getWidth());
+                }
+
+                // Update y-coordinates depending on overflow's open direction.
+                if (mOpenOverflowUpwards) {
+                    mContentContainer.setY(// align bottom
+                            mMarginVertical + mOverflowPanelSize.getHeight()
+                                    - containerSize.getHeight());
+                    mMainPanel.setY(0);  // align top
+                    mOverflowButton.setY(0);  // align top
+                    mOverflowPanel.setY(// align bottom
+                            containerSize.getHeight() - mOverflowPanelSize.getHeight());
+                } else {
+                    // opens downwards.
+                    mContentContainer.setY(mMarginVertical);  // align top
+                    mMainPanel.setY(0);  // align top
+                    mOverflowButton.setY(0);  // align top
+                    mOverflowPanel.setY(mOverflowButtonSize.getHeight());  // align bottom
+                }
+            } else {
+                // No overflow.
+                mContentContainer.setX(mMarginHorizontal);  // align left
+                mContentContainer.setY(mMarginVertical);  // align top
+                mMainPanel.setX(0);  // align left
+                mMainPanel.setY(0);  // align top
+            }
+        }
+    }
+
+    private void updateOverflowHeight(int suggestedHeight) {
+        if (hasOverflow()) {
+            final int maxItemSize =
+                    (suggestedHeight - mOverflowButtonSize.getHeight()) / mLineHeight;
+            final int newHeight = calculateOverflowHeight(maxItemSize);
+            if (mOverflowPanelSize.getHeight() != newHeight) {
+                mOverflowPanelSize = new Size(mOverflowPanelSize.getWidth(), newHeight);
+            }
+            setSize(mOverflowPanel, mOverflowPanelSize);
+            if (mIsOverflowOpen) {
+                setSize(mContentContainer, mOverflowPanelSize);
+                if (mOpenOverflowUpwards) {
+                    final int deltaHeight = mOverflowPanelSize.getHeight() - newHeight;
+                    mContentContainer.setY(mContentContainer.getY() + deltaHeight);
+                    mOverflowButton.setY(mOverflowButton.getY() - deltaHeight);
+                }
+            } else {
+                setSize(mContentContainer, mMainPanelSize);
+            }
+            updatePopupSize();
+        }
+    }
+
+    private void updatePopupSize() {
+        int width = 0;
+        int height = 0;
+        if (mMainPanelSize != null) {
+            width = Math.max(width, mMainPanelSize.getWidth());
+            height = Math.max(height, mMainPanelSize.getHeight());
+        }
+        if (mOverflowPanelSize != null) {
+            width = Math.max(width, mOverflowPanelSize.getWidth());
+            height = Math.max(height, mOverflowPanelSize.getHeight());
+        }
+        mPopupWindow.setWidth(width + mMarginHorizontal * 2);
+        mPopupWindow.setHeight(height + mMarginVertical * 2);
+        maybeComputeTransitionDurationScale();
+    }
+
+    private void refreshViewPort() {
+        mParent.getWindowVisibleDisplayFrame(mViewPortOnScreen);
+    }
+
+    private int getAdjustedToolbarWidth(int suggestedWidth) {
+        int width = suggestedWidth;
+        refreshViewPort();
+        int maximumWidth = mViewPortOnScreen.width() - 2 * mParent.getResources()
+                .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
+        if (width <= 0) {
+            width = mParent.getResources()
+                    .getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width);
+        }
+        return Math.min(width, maximumWidth);
+    }
+
+    /**
+     * Sets the touchable region of this popup to be zero. This means that all touch events on
+     * this popup will go through to the surface behind it.
+     */
+    private void setZeroTouchableSurface() {
+        mTouchableRegion.setEmpty();
+    }
+
+    /**
+     * Sets the touchable region of this popup to be the area occupied by its content.
+     */
+    private void setContentAreaAsTouchableSurface() {
+        Objects.requireNonNull(mMainPanelSize);
+        final int width;
+        final int height;
+        if (mIsOverflowOpen) {
+            Objects.requireNonNull(mOverflowPanelSize);
+            width = mOverflowPanelSize.getWidth();
+            height = mOverflowPanelSize.getHeight();
+        } else {
+            width = mMainPanelSize.getWidth();
+            height = mMainPanelSize.getHeight();
+        }
+        mTouchableRegion.set(
+                (int) mContentContainer.getX(),
+                (int) mContentContainer.getY(),
+                (int) mContentContainer.getX() + width,
+                (int) mContentContainer.getY() + height);
+    }
+
+    /**
+     * Make the touchable area of this popup be the area specified by mTouchableRegion.
+     * This should be called after the popup window has been dismissed (dismiss/hide)
+     * and is probably being re-shown with a new content root view.
+     */
+    private void setTouchableSurfaceInsetsComputer() {
+        ViewTreeObserver viewTreeObserver = mPopupWindow.getContentView()
+                .getRootView()
+                .getViewTreeObserver();
+        viewTreeObserver.removeOnComputeInternalInsetsListener(mInsetsComputer);
+        viewTreeObserver.addOnComputeInternalInsetsListener(mInsetsComputer);
+    }
+
+    private boolean isInRTLMode() {
+        return mContext.getApplicationInfo().hasRtlSupport()
+                && mContext.getResources().getConfiguration().getLayoutDirection()
+                == View.LAYOUT_DIRECTION_RTL;
+    }
+
+    private boolean hasOverflow() {
+        return mOverflowPanelSize != null;
+    }
+
+    /**
+     * Fits as many menu items in the main panel and returns a list of the menu items that
+     * were not fit in.
+     *
+     * @return The menu items that are not included in this main panel.
+     */
+    public List<MenuItem> layoutMainPanelItems(
+            List<MenuItem> menuItems, final int toolbarWidth) {
+        Objects.requireNonNull(menuItems);
+
+        int availableWidth = toolbarWidth;
+
+        final LinkedList<MenuItem> remainingMenuItems = new LinkedList<>();
+        // add the overflow menu items to the end of the remainingMenuItems list.
+        final LinkedList<MenuItem> overflowMenuItems = new LinkedList();
+        for (MenuItem menuItem : menuItems) {
+            if (menuItem.getItemId() != android.R.id.textAssist
+                    && menuItem.requiresOverflow()) {
+                overflowMenuItems.add(menuItem);
+            } else {
+                remainingMenuItems.add(menuItem);
+            }
+        }
+        remainingMenuItems.addAll(overflowMenuItems);
+
+        mMainPanel.removeAllViews();
+        mMainPanel.setPaddingRelative(0, 0, 0, 0);
+
+        int lastGroupId = -1;
+        boolean isFirstItem = true;
+        while (!remainingMenuItems.isEmpty()) {
+            final MenuItem menuItem = remainingMenuItems.peek();
+
+            // if this is the first item, regardless of requiresOverflow(), it should be
+            // displayed on the main panel. Otherwise all items including this one will be
+            // overflow items, and should be displayed in overflow panel.
+            if (!isFirstItem && menuItem.requiresOverflow()) {
+                break;
+            }
+
+            final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist;
+            final View menuItemButton = createMenuItemButton(
+                    mContext, menuItem, mIconTextSpacing, showIcon);
+            if (!showIcon && menuItemButton instanceof LinearLayout) {
+                ((LinearLayout) menuItemButton).setGravity(Gravity.CENTER);
+            }
+
+            // Adding additional start padding for the first button to even out button spacing.
+            if (isFirstItem) {
+                menuItemButton.setPaddingRelative(
+                        (int) (1.5 * menuItemButton.getPaddingStart()),
+                        menuItemButton.getPaddingTop(),
+                        menuItemButton.getPaddingEnd(),
+                        menuItemButton.getPaddingBottom());
+            }
+
+            // Adding additional end padding for the last button to even out button spacing.
+            boolean isLastItem = remainingMenuItems.size() == 1;
+            if (isLastItem) {
+                menuItemButton.setPaddingRelative(
+                        menuItemButton.getPaddingStart(),
+                        menuItemButton.getPaddingTop(),
+                        (int) (1.5 * menuItemButton.getPaddingEnd()),
+                        menuItemButton.getPaddingBottom());
+            }
+
+            menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+            final int menuItemButtonWidth = Math.min(
+                    menuItemButton.getMeasuredWidth(), toolbarWidth);
+
+            // Check if we can fit an item while reserving space for the overflowButton.
+            final boolean canFitWithOverflow =
+                    menuItemButtonWidth <= availableWidth - mOverflowButtonSize.getWidth();
+            final boolean canFitNoOverflow =
+                    isLastItem && menuItemButtonWidth <= availableWidth;
+            if (canFitWithOverflow || canFitNoOverflow) {
+                setButtonTagAndClickListener(menuItemButton, menuItem);
+                // Set tooltips for main panel items, but not overflow items (b/35726766).
+                menuItemButton.setTooltipText(menuItem.getTooltipText());
+                mMainPanel.addView(menuItemButton);
+                final ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
+                params.width = menuItemButtonWidth;
+                menuItemButton.setLayoutParams(params);
+                availableWidth -= menuItemButtonWidth;
+                remainingMenuItems.pop();
+            } else {
+                break;
+            }
+            lastGroupId = menuItem.getGroupId();
+            isFirstItem = false;
+        }
+
+        if (!remainingMenuItems.isEmpty()) {
+            // Reserve space for overflowButton.
+            mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0);
+        }
+
+        mMainPanelSize = measure(mMainPanel);
+        return remainingMenuItems;
+    }
+
+    private void layoutOverflowPanelItems(List<MenuItem> menuItems) {
+        ArrayAdapter<MenuItem> overflowPanelAdapter =
+                (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+        overflowPanelAdapter.clear();
+        final int size = menuItems.size();
+        for (int i = 0; i < size; i++) {
+            overflowPanelAdapter.add(menuItems.get(i));
+        }
+        mOverflowPanel.setAdapter(overflowPanelAdapter);
+        if (mOpenOverflowUpwards) {
+            mOverflowPanel.setY(0);
+        } else {
+            mOverflowPanel.setY(mOverflowButtonSize.getHeight());
+        }
+
+        int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth());
+        int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE);
+        mOverflowPanelSize = new Size(width, height);
+        setSize(mOverflowPanel, mOverflowPanelSize);
+    }
+
+    /**
+     * Resets the content container and appropriately position it's panels.
+     */
+    private void preparePopupContent() {
+        mContentContainer.removeAllViews();
+
+        // Add views in the specified order so they stack up as expected.
+        // Order: overflowPanel, mainPanel, overflowButton.
+        if (hasOverflow()) {
+            mContentContainer.addView(mOverflowPanel);
+        }
+        mContentContainer.addView(mMainPanel);
+        if (hasOverflow()) {
+            mContentContainer.addView(mOverflowButton);
+        }
+        setPanelsStatesAtRestingPosition();
+        setContentAreaAsTouchableSurface();
+
+        // The positioning of contents in RTL is wrong when the view is first rendered.
+        // Hide the view and post a runnable to recalculate positions and render the view.
+        // TODO: Investigate why this happens and fix.
+        if (isInRTLMode()) {
+            mContentContainer.setAlpha(0);
+            mContentContainer.post(mPreparePopupContentRTLHelper);
+        }
+    }
+
+    /**
+     * Clears out the panels and their container. Resets their calculated sizes.
+     */
+    private void clearPanels() {
+        mOverflowPanelSize = null;
+        mMainPanelSize = null;
+        mIsOverflowOpen = false;
+        mMainPanel.removeAllViews();
+        ArrayAdapter<MenuItem> overflowPanelAdapter =
+                (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+        overflowPanelAdapter.clear();
+        mOverflowPanel.setAdapter(overflowPanelAdapter);
+        mContentContainer.removeAllViews();
+    }
+
+    private void positionContentYCoordinatesIfOpeningOverflowUpwards() {
+        if (mOpenOverflowUpwards) {
+            mMainPanel.setY(mContentContainer.getHeight() - mMainPanelSize.getHeight());
+            mOverflowButton.setY(mContentContainer.getHeight() - mOverflowButton.getHeight());
+            mOverflowPanel.setY(mContentContainer.getHeight() - mOverflowPanelSize.getHeight());
+        }
+    }
+
+    private int getOverflowWidth() {
+        int overflowWidth = 0;
+        final int count = mOverflowPanel.getAdapter().getCount();
+        for (int i = 0; i < count; i++) {
+            MenuItem menuItem = (MenuItem) mOverflowPanel.getAdapter().getItem(i);
+            overflowWidth =
+                    Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth);
+        }
+        return overflowWidth;
+    }
+
+    private int calculateOverflowHeight(int maxItemSize) {
+        // Maximum of 4 items, minimum of 2 if the overflow has to scroll.
+        int actualSize = Math.min(
+                MAX_OVERFLOW_SIZE,
+                Math.min(
+                        Math.max(MIN_OVERFLOW_SIZE, maxItemSize),
+                        mOverflowPanel.getCount()));
+        int extension = 0;
+        if (actualSize < mOverflowPanel.getCount()) {
+            // The overflow will require scrolling to get to all the items.
+            // Extend the height so that part of the hidden items is displayed.
+            extension = (int) (mLineHeight * 0.5f);
+        }
+        return actualSize * mLineHeight
+                + mOverflowButtonSize.getHeight()
+                + extension;
+    }
+
+    private void setButtonTagAndClickListener(View menuItemButton, MenuItem menuItem) {
+        menuItemButton.setTag(MenuItemRepr.of(menuItem));
+        menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
+    }
+
+    /**
+     * NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.*
+     * animations. See comment about this in the code.
+     */
+    private int getAdjustedDuration(int originalDuration) {
+        if (mTransitionDurationScale < 150) {
+            // For smaller transition, decrease the time.
+            return Math.max(originalDuration - 50, 0);
+        } else if (mTransitionDurationScale > 300) {
+            // For bigger transition, increase the time.
+            return originalDuration + 50;
+        }
+
+        // Scale the animation duration with getDurationScale(). This allows
+        // android.view.animation.* animations to scale just like android.animation.* animations
+        // when  animator duration scale is adjusted in "Developer Options".
+        // For this reason, do not use this method for android.animation.* animations.
+        return (int) (originalDuration * ValueAnimator.getDurationScale());
+    }
+
+    private void maybeComputeTransitionDurationScale() {
+        if (mMainPanelSize != null && mOverflowPanelSize != null) {
+            int w = mMainPanelSize.getWidth() - mOverflowPanelSize.getWidth();
+            int h = mOverflowPanelSize.getHeight() - mMainPanelSize.getHeight();
+            mTransitionDurationScale = (int) (Math.sqrt(w * w + h * h)
+                    / mContentContainer.getContext().getResources().getDisplayMetrics().density);
+        }
+    }
+
+    private ViewGroup createMainPanel() {
+        ViewGroup mainPanel = new LinearLayout(mContext) {
+            @Override
+            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+                if (isOverflowAnimating()) {
+                    // Update widthMeasureSpec to make sure that this view is not clipped
+                    // as we offset its coordinates with respect to its parent.
+                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                            mMainPanelSize.getWidth(),
+                            MeasureSpec.EXACTLY);
+                }
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            }
+
+            @Override
+            public boolean onInterceptTouchEvent(MotionEvent ev) {
+                // Intercept the touch event while the overflow is animating.
+                return isOverflowAnimating();
+            }
+        };
+        return mainPanel;
+    }
+
+    private ImageButton createOverflowButton() {
+        final ImageButton overflowButton = (ImageButton) LayoutInflater.from(mContext)
+                .inflate(R.layout.floating_popup_overflow_button, null);
+        overflowButton.setImageDrawable(mOverflow);
+        overflowButton.setOnClickListener(v -> {
+            if (mIsOverflowOpen) {
+                overflowButton.setImageDrawable(mToOverflow);
+                mToOverflow.start();
+                closeOverflow();
+            } else {
+                overflowButton.setImageDrawable(mToArrow);
+                mToArrow.start();
+                openOverflow();
+            }
+        });
+        return overflowButton;
+    }
+
+    private OverflowPanel createOverflowPanel() {
+        final OverflowPanel overflowPanel = new OverflowPanel(this);
+        overflowPanel.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+        overflowPanel.setDivider(null);
+        overflowPanel.setDividerHeight(0);
+
+        final ArrayAdapter adapter =
+                new ArrayAdapter<MenuItem>(mContext, 0) {
+                    @Override
+                    public View getView(int position, View convertView, ViewGroup parent) {
+                        return mOverflowPanelViewHelper.getView(
+                                getItem(position), mOverflowPanelSize.getWidth(), convertView);
+                    }
+                };
+        overflowPanel.setAdapter(adapter);
+
+        overflowPanel.setOnItemClickListener((parent, view, position, id) -> {
+            MenuItem menuItem = (MenuItem) overflowPanel.getAdapter().getItem(position);
+            if (mOnMenuItemClickListener != null) {
+                mOnMenuItemClickListener.onMenuItemClick(menuItem);
+            }
+        });
+
+        return overflowPanel;
+    }
+
+    private boolean isOverflowAnimating() {
+        final boolean overflowOpening = mOpenOverflowAnimation.hasStarted()
+                && !mOpenOverflowAnimation.hasEnded();
+        final boolean overflowClosing = mCloseOverflowAnimation.hasStarted()
+                && !mCloseOverflowAnimation.hasEnded();
+        return overflowOpening || overflowClosing;
+    }
+
+    private Animation.AnimationListener createOverflowAnimationListener() {
+        Animation.AnimationListener listener = new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+                // Disable the overflow button while it's animating.
+                // It will be re-enabled when the animation stops.
+                mOverflowButton.setEnabled(false);
+                // Ensure both panels have visibility turned on when the overflow animation
+                // starts.
+                mMainPanel.setVisibility(View.VISIBLE);
+                mOverflowPanel.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                // Posting this because it seems like this is called before the animation
+                // actually ends.
+                mContentContainer.post(() -> {
+                    setPanelsStatesAtRestingPosition();
+                    setContentAreaAsTouchableSurface();
+                });
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+        };
+        return listener;
+    }
+
+    private static Size measure(View view) {
+        Preconditions.checkState(view.getParent() == null);
+        view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        return new Size(view.getMeasuredWidth(), view.getMeasuredHeight());
+    }
+
+    private static void setSize(View view, int width, int height) {
+        view.setMinimumWidth(width);
+        view.setMinimumHeight(height);
+        ViewGroup.LayoutParams params = view.getLayoutParams();
+        params = (params == null) ? new ViewGroup.LayoutParams(0, 0) : params;
+        params.width = width;
+        params.height = height;
+        view.setLayoutParams(params);
+    }
+
+    private static void setSize(View view, Size size) {
+        setSize(view, size.getWidth(), size.getHeight());
+    }
+
+    private static void setWidth(View view, int width) {
+        ViewGroup.LayoutParams params = view.getLayoutParams();
+        setSize(view, width, params.height);
+    }
+
+    private static void setHeight(View view, int height) {
+        ViewGroup.LayoutParams params = view.getLayoutParams();
+        setSize(view, params.width, height);
+    }
+
+    /**
+     * A custom ListView for the overflow panel.
+     */
+    private static final class OverflowPanel extends ListView {
+
+        private final RemoteSelectionToolbar mPopup;
+
+        OverflowPanel(RemoteSelectionToolbar popup) {
+            super(Objects.requireNonNull(popup).mContext);
+            this.mPopup = popup;
+            setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3);
+            setScrollIndicators(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            // Update heightMeasureSpec to make sure that this view is not clipped
+            // as we offset it's coordinates with respect to its parent.
+            int height = mPopup.mOverflowPanelSize.getHeight()
+                    - mPopup.mOverflowButtonSize.getHeight();
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        @Override
+        public boolean dispatchTouchEvent(MotionEvent ev) {
+            if (mPopup.isOverflowAnimating()) {
+                // Eat the touch event.
+                return true;
+            }
+            return super.dispatchTouchEvent(ev);
+        }
+
+        @Override
+        protected boolean awakenScrollBars() {
+            return super.awakenScrollBars();
+        }
+    }
+
+    /**
+     * A custom interpolator used for various floating toolbar animations.
+     */
+    private static final class LogAccelerateInterpolator implements Interpolator {
+
+        private static final int BASE = 100;
+        private static final float LOGS_SCALE = 1f / computeLog(1, BASE);
+
+        private static float computeLog(float t, int base) {
+            return (float) (1 - Math.pow(base, -t));
+        }
+
+        @Override
+        public float getInterpolation(float t) {
+            return 1 - computeLog(1 - t, BASE) * LOGS_SCALE;
+        }
+    }
+
+    /**
+     * A helper for generating views for the overflow panel.
+     */
+    private static final class OverflowPanelViewHelper {
+
+        private final View mCalculator;
+        private final int mIconTextSpacing;
+        private final int mSidePadding;
+
+        private final Context mContext;
+
+        OverflowPanelViewHelper(Context context, int iconTextSpacing) {
+            mContext = Objects.requireNonNull(context);
+            mIconTextSpacing = iconTextSpacing;
+            mSidePadding = context.getResources()
+                    .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_side_padding);
+            mCalculator = createMenuButton(null);
+        }
+
+        public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
+            Objects.requireNonNull(menuItem);
+            if (convertView != null) {
+                updateMenuItemButton(
+                        convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
+            } else {
+                convertView = createMenuButton(menuItem);
+            }
+            convertView.setMinimumWidth(minimumWidth);
+            return convertView;
+        }
+
+        public int calculateWidth(MenuItem menuItem) {
+            updateMenuItemButton(
+                    mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
+            mCalculator.measure(
+                    View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+            return mCalculator.getMeasuredWidth();
+        }
+
+        private View createMenuButton(MenuItem menuItem) {
+            View button = createMenuItemButton(
+                    mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
+            button.setPadding(mSidePadding, 0, mSidePadding, 0);
+            return button;
+        }
+
+        private boolean shouldShowIcon(MenuItem menuItem) {
+            if (menuItem != null) {
+                return menuItem.getGroupId() == android.R.id.textAssist;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Creates and returns a menu button for the specified menu item.
+     */
+    private static View createMenuItemButton(
+            Context context, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+        final View menuItemButton = LayoutInflater.from(context)
+                .inflate(R.layout.floating_popup_menu_button, null);
+        if (menuItem != null) {
+            updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon);
+        }
+        return menuItemButton;
+    }
+
+    /**
+     * Updates the specified menu item button with the specified menu item data.
+     */
+    private static void updateMenuItemButton(
+            View menuItemButton, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+        final TextView buttonText = menuItemButton.findViewById(
+                R.id.floating_toolbar_menu_item_text);
+        buttonText.setEllipsize(null);
+        if (TextUtils.isEmpty(menuItem.getTitle())) {
+            buttonText.setVisibility(View.GONE);
+        } else {
+            buttonText.setVisibility(View.VISIBLE);
+            buttonText.setText(menuItem.getTitle());
+        }
+        final ImageView buttonIcon = menuItemButton.findViewById(
+                R.id.floating_toolbar_menu_item_image);
+        if (menuItem.getIcon() == null || !showIcon) {
+            buttonIcon.setVisibility(View.GONE);
+            if (buttonText != null) {
+                buttonText.setPaddingRelative(0, 0, 0, 0);
+            }
+        } else {
+            buttonIcon.setVisibility(View.VISIBLE);
+            buttonIcon.setImageDrawable(menuItem.getIcon());
+            if (buttonText != null) {
+                buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0);
+            }
+        }
+        final CharSequence contentDescription = menuItem.getContentDescription();
+        if (TextUtils.isEmpty(contentDescription)) {
+            menuItemButton.setContentDescription(menuItem.getTitle());
+        } else {
+            menuItemButton.setContentDescription(contentDescription);
+        }
+    }
+
+    private static ViewGroup createContentContainer(Context context) {
+        ViewGroup contentContainer = (ViewGroup) LayoutInflater.from(context)
+                .inflate(R.layout.floating_popup_container, null);
+        contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+        contentContainer.setTag("floating_toolbar");
+        contentContainer.setClipToOutline(true);
+        return contentContainer;
+    }
+
+    private static PopupWindow createPopupWindow(ViewGroup content) {
+        ViewGroup popupContentHolder = new LinearLayout(content.getContext());
+        PopupWindow popupWindow = new PopupWindow(popupContentHolder);
+        // TODO: Use .setIsLaidOutInScreen(true) instead of .setClippingEnabled(false)
+        // unless FLAG_LAYOUT_IN_SCREEN has any unintentional side-effects.
+        popupWindow.setClippingEnabled(false);
+        popupWindow.setWindowLayoutType(
+                WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
+        popupWindow.setAnimationStyle(0);
+        popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+        content.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+        popupContentHolder.addView(content);
+        return popupWindow;
+    }
+
+    /**
+     * Creates an "appear" animation for the specified view.
+     *
+     * @param view  The view to animate
+     */
+    private static AnimatorSet createEnterAnimation(View view) {
+        AnimatorSet animation = new AnimatorSet();
+        animation.playTogether(
+                ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150));
+        return animation;
+    }
+
+    /**
+     * Creates a "disappear" animation for the specified view.
+     *
+     * @param view  The view to animate
+     * @param startDelay  The start delay of the animation
+     * @param listener  The animation listener
+     */
+    private static AnimatorSet createExitAnimation(
+            View view, int startDelay, Animator.AnimatorListener listener) {
+        AnimatorSet animation =  new AnimatorSet();
+        animation.playTogether(
+                ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(100));
+        animation.setStartDelay(startDelay);
+        animation.addListener(listener);
+        return animation;
+    }
+
+    /**
+     * Returns a re-themed context with controlled look and feel for views.
+     */
+    private static Context applyDefaultTheme(Context originalContext) {
+        TypedArray a = originalContext.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
+        boolean isLightTheme = a.getBoolean(0, true);
+        int themeId =
+                isLightTheme ? R.style.Theme_DeviceDefault_Light : R.style.Theme_DeviceDefault;
+        a.recycle();
+        return new ContextThemeWrapper(originalContext, themeId);
+    }
+
+    /**
+     * Represents the identity of a MenuItem that is rendered in a FloatingToolbarPopup.
+     */
+    @VisibleForTesting
+    public static final class MenuItemRepr {
+
+        public final int itemId;
+        public final int groupId;
+        @Nullable public final String title;
+        @Nullable private final Drawable mIcon;
+
+        private MenuItemRepr(
+                int itemId, int groupId, @Nullable CharSequence title, @Nullable Drawable icon) {
+            this.itemId = itemId;
+            this.groupId = groupId;
+            this.title = (title == null) ? null : title.toString();
+            mIcon = icon;
+        }
+
+        /**
+         * Creates an instance of MenuItemRepr for the specified menu item.
+         */
+        public static MenuItemRepr of(MenuItem menuItem) {
+            return new MenuItemRepr(
+                    menuItem.getItemId(),
+                    menuItem.getGroupId(),
+                    menuItem.getTitle(),
+                    menuItem.getIcon());
+        }
+
+        /**
+         * Returns this object's hashcode.
+         */
+        @Override
+        public int hashCode() {
+            return Objects.hash(itemId, groupId, title, mIcon);
+        }
+
+        /**
+         * Returns true if this object is the same as the specified object.
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (!(o instanceof MenuItemRepr)) {
+                return false;
+            }
+            final MenuItemRepr other = (MenuItemRepr) o;
+            return itemId == other.itemId
+                    && groupId == other.groupId
+                    && TextUtils.equals(title, other.title)
+                    // Many Drawables (icons) do not implement equals(). Using equals() here instead
+                    // of reference comparisons in case a Drawable subclass implements equals().
+                    && Objects.equals(mIcon, other.mIcon);
+        }
+
+        /**
+         * Returns true if the two menu item collections are the same based on MenuItemRepr.
+         */
+        public static boolean reprEquals(
+                Collection<MenuItem> menuItems1, Collection<MenuItem> menuItems2) {
+            if (menuItems1.size() != menuItems2.size()) {
+                return false;
+            }
+
+            final Iterator<MenuItem> menuItems2Iter = menuItems2.iterator();
+            for (MenuItem menuItem1 : menuItems1) {
+                final MenuItem menuItem2 = menuItems2Iter.next();
+                if (!MenuItemRepr.of(menuItem1).equals(MenuItemRepr.of(menuItem2))) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index dd4355d..f2a0355 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -87,6 +87,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
+import android.view.WindowLayout;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.window.ClientWindowFrames;
@@ -389,8 +390,9 @@
             public void resized(ClientWindowFrames frames, boolean reportDraw,
                     MergedConfiguration mergedConfiguration, boolean forceLayout,
                     boolean alwaysConsumeSystemBars, int displayId) {
-                Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
-                        reportDraw ? 1 : 0);
+                Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
+                        reportDraw ? 1 : 0,
+                        mergedConfiguration);
                 mCaller.sendMessage(msg);
             }
 
@@ -1028,6 +1030,10 @@
             }
         }
 
+        private void updateConfiguration(MergedConfiguration mergedConfiguration) {
+            mMergedConfiguration.setTo(mergedConfiguration);
+        }
+
         void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
             if (mDestroyed) {
                 Log.w(TAG, "Ignoring updateSurface due to destroyed");
@@ -1066,8 +1072,6 @@
                     mLayout.x = 0;
                     mLayout.y = 0;
 
-                    mLayout.width = myWidth;
-                    mLayout.height = myHeight;
                     mLayout.format = mFormat;
 
                     mCurWindowFlags = mWindowFlags;
@@ -1076,6 +1080,23 @@
                             | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
                             | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                             | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+
+                    final Configuration config = mMergedConfiguration.getMergedConfiguration();
+                    final Rect maxBounds = config.windowConfiguration.getMaxBounds();
+                    if (myWidth == ViewGroup.LayoutParams.MATCH_PARENT
+                            && myHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+                        mLayout.width = myWidth;
+                        mLayout.height = myHeight;
+                        mLayout.flags &= ~WindowManager.LayoutParams.FLAG_SCALED;
+                    } else {
+                        final float layoutScale = Math.max(
+                                maxBounds.width() / (float) myWidth,
+                                maxBounds.height() / (float) myHeight);
+                        mLayout.width = (int) (myWidth * layoutScale + .5f);
+                        mLayout.height = (int) (myHeight * layoutScale + .5f);
+                        mLayout.flags |= WindowManager.LayoutParams.FLAG_SCALED;
+                    }
+
                     mCurWindowPrivateFlags = mWindowPrivateFlags;
                     mLayout.privateFlags = mWindowPrivateFlags;
 
@@ -1121,12 +1142,14 @@
 
                     final int relayoutResult = mSession.relayout(
                             mWindow, mLayout, mWidth, mHeight,
-                            View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl,
-                            mInsetsState, mTempControls, mSurfaceSize);
+                            View.VISIBLE, 0, mWinFrames, mMergedConfiguration, mSurfaceControl,
+                            mInsetsState, mTempControls);
 
                     final int transformHint = SurfaceControl.rotationToBufferTransform(
                             (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
                     mSurfaceControl.setTransformHint(transformHint);
+                    WindowLayout.computeSurfaceSize(mLayout, maxBounds, mWidth, mHeight,
+                            mWinFrames.frame, false /* dragResizing */, mSurfaceSize);
 
                     if (mSurfaceControl.isValid()) {
                         if (mBbqSurfaceControl == null) {
@@ -1164,7 +1187,6 @@
                     int h = mWinFrames.frame.height();
 
                     final DisplayCutout rawCutout = mInsetsState.getDisplayCutout();
-                    final Configuration config = getResources().getConfiguration();
                     final Rect visibleFrame = new Rect(mWinFrames.frame);
                     visibleFrame.intersect(mInsetsState.getDisplayFrame());
                     WindowInsets windowInsets = mInsetsState.calculateInsets(visibleFrame,
@@ -2321,6 +2343,7 @@
                 } break;
                 case MSG_WINDOW_RESIZED: {
                     final boolean reportDraw = message.arg1 != 0;
+                    mEngine.updateConfiguration(((MergedConfiguration) message.obj));
                     mEngine.updateSurface(true, false, reportDraw);
                     mEngine.doOffsetsChanged(true);
                     mEngine.scaleAndCropScreenshot();
diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java
index 9cbda9c..2eb917b 100644
--- a/core/java/android/text/method/TextKeyListener.java
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -306,7 +306,7 @@
 
     /* package */ int getPrefs(Context context) {
         synchronized (this) {
-            if (!mPrefsInited || mResolver.get() == null) {
+            if (!mPrefsInited || mResolver.refersTo(null)) {
                 initPrefs(context);
             }
         }
diff --git a/core/java/android/util/SparseArrayMap.java b/core/java/android/util/SparseArrayMap.java
index cd592a7..e5bb9f45 100644
--- a/core/java/android/util/SparseArrayMap.java
+++ b/core/java/android/util/SparseArrayMap.java
@@ -62,6 +62,14 @@
     }
 
     /**
+     * Removes all the data for the keyIndex, if there was any.
+     * @hide
+     */
+    public void deleteAt(int keyIndex) {
+        mData.removeAt(keyIndex);
+    }
+
+    /**
      * Removes the data for the key and mapKey, if there was any.
      *
      * @return Returns the value that was stored under the keys, or null if there was none.
@@ -142,6 +150,15 @@
         return data == null ? 0 : data.size();
     }
 
+    /**
+     * Returns the number of elements in the map of the given keyIndex.
+     * @hide
+     */
+    public int numElementsForKeyAt(int keyIndex) {
+        ArrayMap<K, V> data = mData.valueAt(keyIndex);
+        return data == null ? 0 : data.size();
+    }
+
     /** Returns the value V at the given key and map index. */
     @Nullable
     public V valueAt(int keyIndex, int mapIndex) {
diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java
index e8d96d8..ee2e3ce 100644
--- a/core/java/android/util/SparseDoubleArray.java
+++ b/core/java/android/util/SparseDoubleArray.java
@@ -124,6 +124,15 @@
     }
 
     /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(int key) {
+        return mValues.indexOfKey(key);
+    }
+
+    /**
      * Given an index in the range <code>0...size()-1</code>, returns
      * the key from the <code>index</code>th key-value mapping that this
      * SparseDoubleArray stores.
@@ -146,6 +155,34 @@
     }
 
     /**
+     * Given an index in the range <code>0...size()-1</code>, sets a new
+     * value for the <code>index</code>th key-value mapping that this
+     * SparseDoubleArray stores.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public void setValueAt(int index, double value) {
+        mValues.setValueAt(index, Double.doubleToRawLongBits(value));
+    }
+
+    /**
+     * Removes the mapping at the given index.
+     */
+    public void removeAt(int index) {
+        mValues.removeAt(index);
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(int key) {
+        mValues.delete(key);
+    }
+
+    /**
      * Removes all key-value mappings from this SparseDoubleArray.
      */
     public void clear() {
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index 7185972..b739e37 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -245,6 +245,28 @@
     }
 
     /**
+     * Given an index in the range <code>0...size()-1</code>, sets a new
+     * value for the <code>index</code>th key-value mapping that this
+     * SparseLongArray stores.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     *
+     * @hide
+     */
+    public void setValueAt(int index, long value) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+
+        mValues[index] = value;
+    }
+
+    /**
      * Returns the index for which {@link #keyAt} would return the
      * specified key, or a negative number if the specified
      * key is not mapped.
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index c7d9b9c..c8c1fd4 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -407,20 +407,6 @@
         }
     }
 
-    static byte[] generateApkVerityRootHash(String apkPath)
-            throws IOException, SignatureNotFoundException, DigestException,
-                   NoSuchAlgorithmException {
-        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
-            SignatureInfo signatureInfo = findSignature(apk);
-            VerifiedSigner vSigner = verify(apk, false);
-            if (vSigner.verityRootHash == null) {
-                return null;
-            }
-            return VerityBuilder.generateApkVerityRootHash(
-                    apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
-        }
-    }
-
     /**
      * Verified APK Signature Scheme v2 signer.
      *
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
index 7e65d61..3287ce8 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -545,20 +545,6 @@
         }
     }
 
-    static byte[] generateApkVerityRootHash(String apkPath)
-            throws NoSuchAlgorithmException, DigestException, IOException,
-            SignatureNotFoundException {
-        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
-            SignatureInfo signatureInfo = findSignature(apk);
-            VerifiedSigner vSigner = verify(apk, false);
-            if (vSigner.verityRootHash == null) {
-                return null;
-            }
-            return VerityBuilder.generateApkVerityRootHash(
-                    apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
-        }
-    }
-
     /**
      * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
      *
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index bff5426..d2a18dd 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -569,27 +569,6 @@
     }
 
     /**
-     * Generates the FSVerity root hash from FSVerity header, extensions and Merkle tree root hash
-     * in Signing Block.
-     *
-     * @return FSverity root hash
-     */
-    public static byte[] generateApkVerityRootHash(String apkPath)
-            throws NoSuchAlgorithmException, DigestException, IOException {
-        // first try v3
-        try {
-            return ApkSignatureSchemeV3Verifier.generateApkVerityRootHash(apkPath);
-        } catch (SignatureNotFoundException e) {
-            // try older version
-        }
-        try {
-            return ApkSignatureSchemeV2Verifier.generateApkVerityRootHash(apkPath);
-        } catch (SignatureNotFoundException e) {
-            return null;
-        }
-    }
-
-    /**
      * Extended signing details.
      * @hide for internal use only.
      */
diff --git a/core/java/android/util/apk/VerityBuilder.java b/core/java/android/util/apk/VerityBuilder.java
index c7c465d..adf53c2 100644
--- a/core/java/android/util/apk/VerityBuilder.java
+++ b/core/java/android/util/apk/VerityBuilder.java
@@ -143,25 +143,6 @@
             return generateFsVerityTreeInternal(apk, salt, levelOffset, tree);
         }
     }
-    /**
-     * Calculates the apk-verity root hash for integrity measurement.  This needs to be consistent
-     * to what kernel returns.
-     */
-    @NonNull
-    static byte[] generateApkVerityRootHash(@NonNull RandomAccessFile apk,
-            @NonNull ByteBuffer apkDigest, @NonNull SignatureInfo signatureInfo)
-            throws NoSuchAlgorithmException, DigestException, IOException {
-        assertSigningBlockAlignedAndHasFullPages(signatureInfo);
-
-        ByteBuffer footer = ByteBuffer.allocate(CHUNK_SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN);
-        generateApkVerityFooter(apk, signatureInfo, footer);
-        footer.flip();
-
-        MessageDigest md = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM);
-        md.update(footer);
-        md.update(apkDigest);
-        return md.digest();
-    }
 
     /**
      * Generates the apk-verity header and hash tree to be used by kernel for the given apk. This
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 69af2a5..bd468d9 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -17,7 +17,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.annotation.UiThread;
+import android.graphics.Region;
 import android.hardware.HardwareBuffer;
 
 /**
@@ -124,4 +126,16 @@
     default void removeOnBufferTransformHintChangedListener(
             @NonNull OnBufferTransformHintChangedListener listener) {
     }
+
+    /**
+     * Sets the touchable region for this SurfaceControl, expressed in surface local
+     * coordinates. By default the touchable region is the entire Layer, indicating
+     * that if the layer is otherwise eligible to receive touch it receives touch
+     * on the entire surface. Setting the touchable region allows the SurfaceControl
+     * to receive touch in some regions, while allowing for pass-through in others.
+     *
+     * @param r The region to use or null to use the entire Layer bounds
+     */
+    default void setTouchableRegion(@Nullable Region r) {
+    }
 }
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 9889eaa..9b36b9b 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -53,6 +53,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -386,6 +387,33 @@
      * @param boundBottom the bottom bounding rect of the display cutout in pixels.  If null is
      *                   passed, it's treated as an empty rectangle (0,0)-(0,0).
      * @param waterfallInsets the insets for the curved areas in waterfall display.
+     * @param info the cutout path parser info.
+     * @hide
+     */
+    public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
+            @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
+            @NonNull Insets waterfallInsets, @Nullable CutoutPathParserInfo info) {
+        this(safeInsets.toRect(), waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
+                info, true);
+    }
+
+    /**
+     * Creates a DisplayCutout instance.
+     *
+     * <p>Note that this is only useful for tests. For production code, developers should always
+     * use a {@link DisplayCutout} obtained from the system.</p>
+     *
+     * @param safeInsets the insets from each edge which avoid the display cutout as returned by
+     *                   {@link #getSafeInsetTop()} etc.
+     * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundTop the top bounding rect of the display cutout in pixels.  If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundRight the right bounding rect of the display cutout in pixels.  If null is
+     *                  passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundBottom the bottom bounding rect of the display cutout in pixels.  If null is
+     *                   passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param waterfallInsets the insets for the curved areas in waterfall display.
      */
     public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
             @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
@@ -1098,6 +1126,85 @@
     }
 
     /**
+     * @return a copy of this cutout that has been rotated for a display in toRotation.
+     * @hide
+     */
+    public DisplayCutout getRotated(int startWidth, int startHeight,
+            int fromRotation, int toRotation) {
+        if (this == DisplayCutout.NO_CUTOUT) {
+            return DisplayCutout.NO_CUTOUT;
+        }
+        final int rotation = RotationUtils.deltaRotation(fromRotation, toRotation);
+        if (rotation == ROTATION_0) {
+            return this;
+        }
+        final Insets waterfallInsets = RotationUtils.rotateInsets(getWaterfallInsets(), rotation);
+        // returns a copy
+        final Rect[] newBounds = getBoundingRectsAll();
+        final Rect displayBounds = new Rect(0, 0, startWidth, startHeight);
+        for (int i = 0; i < newBounds.length; ++i) {
+            if (newBounds[i].isEmpty()) continue;
+            RotationUtils.rotateBounds(newBounds[i], displayBounds, rotation);
+        }
+        Collections.rotate(Arrays.asList(newBounds), -rotation);
+        final CutoutPathParserInfo info = getCutoutPathParserInfo();
+        final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
+                info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
+                info.getCutoutSpec(), toRotation, info.getScale());
+        final boolean swapAspect = (rotation % 2) != 0;
+        final int endWidth = swapAspect ? startHeight : startWidth;
+        final int endHeight = swapAspect ? startWidth : startHeight;
+        final DisplayCutout tmp =
+                DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo);
+        final Rect safeInsets = DisplayCutout.computeSafeInsets(endWidth, endHeight, tmp);
+        return tmp.replaceSafeInsets(safeInsets);
+    }
+
+    /**
+     * Compute the insets derived from a cutout. This is usually used to populate the safe-insets
+     * of the cutout via {@link #replaceSafeInsets}.
+     * @hide
+     */
+    public static Rect computeSafeInsets(int displayW, int displayH, DisplayCutout cutout) {
+        if (displayW == displayH) {
+            throw new UnsupportedOperationException("not implemented: display=" + displayW + "x"
+                    + displayH + " cutout=" + cutout);
+        }
+
+        int leftInset = Math.max(cutout.getWaterfallInsets().left, findCutoutInsetForSide(
+                displayW, displayH, cutout.getBoundingRectLeft(), Gravity.LEFT));
+        int topInset = Math.max(cutout.getWaterfallInsets().top, findCutoutInsetForSide(
+                displayW, displayH, cutout.getBoundingRectTop(), Gravity.TOP));
+        int rightInset = Math.max(cutout.getWaterfallInsets().right, findCutoutInsetForSide(
+                displayW, displayH, cutout.getBoundingRectRight(), Gravity.RIGHT));
+        int bottomInset = Math.max(cutout.getWaterfallInsets().bottom, findCutoutInsetForSide(
+                displayW, displayH, cutout.getBoundingRectBottom(), Gravity.BOTTOM));
+
+        return new Rect(leftInset, topInset, rightInset, bottomInset);
+    }
+
+    private static int findCutoutInsetForSide(int displayW, int displayH, Rect boundingRect,
+            int gravity) {
+        if (boundingRect.isEmpty()) {
+            return 0;
+        }
+
+        int inset = 0;
+        switch (gravity) {
+            case Gravity.TOP:
+                return Math.max(inset, boundingRect.bottom);
+            case Gravity.BOTTOM:
+                return Math.max(inset, displayH - boundingRect.top);
+            case Gravity.LEFT:
+                return Math.max(inset, boundingRect.right);
+            case Gravity.RIGHT:
+                return Math.max(inset, displayW - boundingRect.left);
+            default:
+                throw new IllegalArgumentException("unknown gravity: " + gravity);
+        }
+    }
+
+    /**
      * Helper class for passing {@link DisplayCutout} through binder.
      *
      * Needed, because {@code readFromParcel} cannot be used with immutable classes.
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 097d1d0..c87c13d 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -161,6 +161,11 @@
         return mConnectedView.get();
     }
 
+    private void clearConnectedView() {
+        mConnectedView = null;
+        mConnectionCount = 0;
+    }
+
     /**
      * Notify HandwritingInitiator that a new InputConnection is created.
      * The caller of this method should guarantee that each onInputConnectionCreated call
@@ -169,6 +174,10 @@
      * @see  #onInputConnectionClosed(View)
      */
     public void onInputConnectionCreated(@NonNull View view) {
+        if (!view.isAutoHandwritingEnabled()) {
+            clearConnectedView();
+            return;
+        }
         final View connectedView = getConnectedView();
         if (connectedView == view) {
             ++mConnectionCount;
@@ -187,15 +196,15 @@
      */
     public void onInputConnectionClosed(@NonNull View view) {
         final View connectedView = getConnectedView();
+        if (connectedView == null) return;
         if (connectedView == view) {
             --mConnectionCount;
             if (mConnectionCount == 0) {
-                mConnectedView = null;
+                clearConnectedView();
             }
         } else {
             // Unexpected branch, set mConnectedView to null to avoid further problem.
-            mConnectedView = null;
-            mConnectionCount = 0;
+            clearConnectedView();
         }
     }
 
@@ -218,6 +227,12 @@
         if (connectedView == null) {
             return;
         }
+
+        if (!connectedView.isAutoHandwritingEnabled()) {
+            clearConnectedView();
+            return;
+        }
+
         final ViewParent viewParent = connectedView.getParent();
         // Do a final check before startHandwriting.
         if (viewParent != null && connectedView.isAttachedToWindow()) {
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 6226566..ccf1e44 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -74,7 +74,6 @@
      * @param viewVisibility Window root view's visibility.
      * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING},
      * {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
-     * @param frameNumber A frame number in which changes requested in this layout will be rendered.
      * @param outFrame Rect in which is placed the new position/size on
      * screen.
      * @param outContentInsets Rect in which is placed the offsets from
@@ -97,17 +96,15 @@
      * since it was last displayed.
      * @param outSurface Object in which is placed the new display surface.
      * @param insetsState The current insets state in the system.
-     * @param outSurfaceSize The width and height of the surface control
      *
      * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS},
      * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
      */
     int relayout(IWindow window, in WindowManager.LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewVisibility,
-            int flags, long frameNumber, out ClientWindowFrames outFrames,
+            int flags, out ClientWindowFrames outFrames,
             out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
-            out InsetsState insetsState, out InsetsSourceControl[] activeControls,
-            out Point outSurfaceSize);
+            out InsetsState insetsState, out InsetsSourceControl[] activeControls);
 
     /*
      * Notify the window manager that an application is relaunching and
@@ -342,4 +339,9 @@
      * @param callback The {@link IOnBackInvokedCallback} to set.
      */
     oneway void setOnBackInvokedCallback(IWindow window, IOnBackInvokedCallback callback);
+
+    /**
+     * Clears a touchable region set by {@link #setInsets}.
+     */
+    void clearTouchableRegion(IWindow window);
 }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index e751720b..904d7c8 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -254,6 +254,7 @@
     private static native int nativeGetLayerId(long nativeObject);
     private static native void nativeAddTransactionCommittedListener(long nativeObject,
             TransactionCommittedListener listener);
+    private static native void nativeSanitize(long transactionObject);
 
     /**
      * Transforms that can be applied to buffers as they are displayed to a window.
@@ -3700,6 +3701,13 @@
         }
 
         /**
+         * @hide
+         */
+        public void sanitize() {
+            nativeSanitize(mNativeObject);
+        }
+
+        /**
          * Merge the other transaction into this transaction, clearing the
          * other transaction as if it had been applied.
          *
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 93fdee0..7559273 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3517,6 +3517,7 @@
      *                   1              PFLAG4_DETACHED
      *                  1               PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE
      *                 1                PFLAG4_DRAG_A11Y_STARTED
+     *                1                 PFLAG4_AUTO_HANDWRITING_INITIATION_ENABLED
      * |-------|-------|-------|-------|
      */
 
@@ -3593,6 +3594,10 @@
      */
     private static final int PFLAG4_DRAG_A11Y_STARTED = 0x000008000;
 
+    /**
+     * Indicates that the view enables auto handwriting initiation.
+     */
+    private static final int PFLAG4_AUTO_HANDWRITING_ENABLED = 0x000010000;
     /* End of masks for mPrivateFlags4 */
 
     /** @hide */
@@ -5321,6 +5326,7 @@
                 (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
                 (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
                 (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
+        mPrivateFlags4 = PFLAG4_AUTO_HANDWRITING_ENABLED;
 
         final ViewConfiguration configuration = ViewConfiguration.get(context);
         mTouchSlop = configuration.getScaledTouchSlop();
@@ -6034,6 +6040,9 @@
                 case R.styleable.View_preferKeepClear:
                     setPreferKeepClear(a.getBoolean(attr, false));
                     break;
+                case R.styleable.View_autoHandwritingEnabled:
+                    setAutoHandwritingEnabled(a.getBoolean(attr, true));
+                    break;
             }
         }
 
@@ -31118,6 +31127,42 @@
     }
 
     /**
+     * Set whether this view enables automatic handwriting initiation.
+     *
+     * For a view with an active {@link InputConnection}, if auto handwriting is enabled then
+     * stylus movement within its view boundary will automatically trigger the handwriting mode.
+     * Check {@link android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)} for
+     * more details about handwriting mode.
+     *
+     * If the View wants to initiate handwriting mode by itself, it can set this field to
+     * {@code false} and call
+     * {@link android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)} when there
+     * is stylus movement detected.
+     *
+     * @see #onCreateInputConnection(EditorInfo)
+     * @see android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)
+     * @param enabled whether auto handwriting initiation is enabled for this view.
+     * @attr ref android.R.styleable#View_autoHandwritingEnabled
+     */
+    public void setAutoHandwritingEnabled(boolean enabled) {
+        if (enabled) {
+            mPrivateFlags4 |= PFLAG4_AUTO_HANDWRITING_ENABLED;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_AUTO_HANDWRITING_ENABLED;
+        }
+    }
+
+    /**
+     * Return whether the View allows automatic handwriting initiation. Returns true if automatic
+     * handwriting initiation is enabled, and verse visa.
+     * @see #setAutoHandwritingEnabled(boolean)
+     */
+    public boolean isAutoHandwritingEnabled() {
+        return (mPrivateFlags4 & PFLAG4_AUTO_HANDWRITING_ENABLED)
+                == PFLAG4_AUTO_HANDWRITING_ENABLED;
+    }
+
+    /**
      * Collects a {@link ViewTranslationRequest} which represents the content to be translated in
      * the view.
      *
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 97b5a31..1496a4a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -52,6 +52,7 @@
 import static android.view.ViewRootImplProto.WIDTH;
 import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
 import static android.view.ViewRootImplProto.WIN_FRAME;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
 import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -83,6 +84,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
@@ -471,6 +474,9 @@
     final Region mTransparentRegion;
     final Region mPreviousTransparentRegion;
 
+    Region mTouchableRegion;
+    Region mPreviousTouchableRegion;
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     int mWidth;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2868,9 +2874,9 @@
                 }
                 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
                 final boolean freeformResizing = (relayoutResult
-                        & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
+                        & RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
                 final boolean dockedResizing = (relayoutResult
-                        & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
+                        & RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
                 final boolean dragResizing = freeformResizing || dockedResizing;
                 if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC) != 0) {
                     if (DEBUG_BLAST) {
@@ -3249,9 +3255,15 @@
             mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
         }
 
+        Rect contentInsets = null;
+        Rect visibleInsets = null;
+        Region touchableRegion = null;
+        int touchableInsetMode = TOUCHABLE_INSETS_REGION;
+        boolean computedInternalInsets = false;
         if (computesInternalInsets) {
-            // Clear the original insets.
             final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
+
+            // Clear the original insets.
             insets.reset();
 
             // Compute new insets in place.
@@ -3263,9 +3275,6 @@
                 mLastGivenInsets.set(insets);
 
                 // Translate insets to screen coordinates if needed.
-                final Rect contentInsets;
-                final Rect visibleInsets;
-                final Region touchableRegion;
                 if (mTranslator != null) {
                     contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
                     visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
@@ -3275,12 +3284,46 @@
                     visibleInsets = insets.visibleInsets;
                     touchableRegion = insets.touchableRegion;
                 }
-
-                try {
-                    mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
-                            contentInsets, visibleInsets, touchableRegion);
-                } catch (RemoteException e) {
+                computedInternalInsets = true;
+            }
+            touchableInsetMode = insets.mTouchableInsets;
+        }
+        boolean needsSetInsets = computedInternalInsets;
+        needsSetInsets |= !Objects.equals(mPreviousTouchableRegion, mTouchableRegion) &&
+            (mTouchableRegion != null);
+        if (needsSetInsets) {
+            if (mTouchableRegion != null) {
+                if (mPreviousTouchableRegion == null) {
+                    mPreviousTouchableRegion = new Region();
                 }
+                mPreviousTouchableRegion.set(mTouchableRegion);
+                if (touchableInsetMode != TOUCHABLE_INSETS_REGION) {
+                    Log.e(mTag, "Setting touchableInsetMode to non TOUCHABLE_INSETS_REGION" +
+                          " from OnComputeInternalInsets, while also using setTouchableRegion" +
+                          " causes setTouchableRegion to be ignored");
+                }
+            } else {
+                mPreviousTouchableRegion = null;
+            }
+            if (contentInsets == null) contentInsets = new Rect(0,0,0,0);
+            if (visibleInsets == null) visibleInsets = new Rect(0,0,0,0);
+            if (touchableRegion == null) {
+                touchableRegion = mTouchableRegion;
+            } else if (touchableRegion != null && mTouchableRegion != null) {
+                touchableRegion.op(touchableRegion, mTouchableRegion, Region.Op.UNION);
+            }
+            try {
+                mWindowSession.setInsets(mWindow, touchableInsetMode,
+                                         contentInsets, visibleInsets, touchableRegion);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else if (mTouchableRegion == null && mPreviousTouchableRegion != null) {
+            mPreviousTouchableRegion = null;
+            try {
+                mWindowSession.clearTouchableRegion(mWindow);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
         }
 
@@ -7928,22 +7971,25 @@
             }
         }
 
-        long frameNumber = -1;
-        if (mSurface.isValid()) {
-            frameNumber = mSurface.getNextFrameNumber();
-        }
+        final int requestedWidth = (int) (mView.getMeasuredWidth() * appScale + 0.5f);
+        final int requestedHeight = (int) (mView.getMeasuredHeight() * appScale + 0.5f);
 
         int relayoutResult = mWindowSession.relayout(mWindow, params,
-                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
-                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
-                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
+                requestedWidth, requestedHeight, viewVisibility,
+                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                 mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
-                mTempControls, mSurfaceSize);
+                mTempControls);
 
         final int transformHint = SurfaceControl.rotationToBufferTransform(
                 (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
         mSurfaceControl.setTransformHint(transformHint);
 
+        final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
+        final boolean dragResizing = (relayoutResult
+                & (RELAYOUT_RES_DRAG_RESIZING_DOCKED | RELAYOUT_RES_DRAG_RESIZING_FREEFORM)) != 0;
+        WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
+                requestedHeight, mTmpFrames.frame, dragResizing, mSurfaceSize);
+
         if (mAttachInfo.mContentCaptureManager != null) {
             MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
                     .getMainContentCaptureSession();
@@ -10701,4 +10747,15 @@
         }
         return mFallbackOnBackInvokedDispatcher;
     }
+
+    @Override
+    public void setTouchableRegion(Region r) {
+        if (r != null) {
+            mTouchableRegion = new Region(r);
+        } else {
+            mTouchableRegion = null;
+        }
+        mLastGivenInsets.reset();
+        requestLayout();
+    }
 }
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index e5c7d6d..27f89ec 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -36,6 +36,7 @@
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
 import android.graphics.Insets;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Log;
 
@@ -258,6 +259,7 @@
                 + " outFrame=" + outFrame.toShortString()
                 + " outParentFrame=" + outParentFrame.toShortString()
                 + " outDisplayFrame=" + outDisplayFrame.toShortString()
+                + " windowBounds=" + windowBounds.toShortString()
                 + " attachedWindowFrame=" + (attachedWindowFrame != null
                         ? attachedWindowFrame.toShortString()
                         : "null")
@@ -272,4 +274,45 @@
 
         return clippedByDisplayCutout;
     }
+
+    public static void computeSurfaceSize(WindowManager.LayoutParams attrs, Rect maxBounds,
+            int requestedWidth, int requestedHeight, Rect winFrame, boolean dragResizing,
+            Point outSurfaceSize) {
+        int width;
+        int height;
+        if ((attrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0) {
+            // For a scaled surface, we always want the requested size.
+            width = requestedWidth;
+            height = requestedHeight;
+        } else {
+            // When we're doing a drag-resizing, request a surface that's fullscreen size,
+            // so that we don't need to reallocate during the process. This also prevents
+            // buffer drops due to size mismatch.
+            if (dragResizing) {
+                // The maxBounds should match the display size which applies fixed-rotation
+                // transformation if there is any.
+                width = maxBounds.width();
+                height = maxBounds.height();
+            } else {
+                width = winFrame.width();
+                height = winFrame.height();
+            }
+        }
+
+        // This doesn't necessarily mean that there is an error in the system. The sizes might be
+        // incorrect, because it is before the first layout or draw.
+        if (width < 1) {
+            width = 1;
+        }
+        if (height < 1) {
+            height = 1;
+        }
+
+        // Adjust for surface insets.
+        final Rect surfaceInsets = attrs.surfaceInsets;
+        width += surfaceInsets.left + surfaceInsets.right;
+        height += surfaceInsets.top + surfaceInsets.bottom;
+
+        outSurfaceSize.set(width, height);
+    }
 }
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 998498b..3392edc 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.IBinder;
@@ -264,10 +263,10 @@
 
     @Override
     public int relayout(IWindow window, WindowManager.LayoutParams inAttrs,
-            int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
+            int requestedWidth, int requestedHeight, int viewFlags, int flags,
             ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
             SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
+            InsetsSourceControl[] outActiveControls) {
         final State state;
         synchronized (this) {
             state = mStateForWindow.get(window.asBinder());
@@ -286,7 +285,6 @@
         WindowManager.LayoutParams attrs = state.mParams;
 
         if (viewFlags == View.VISIBLE) {
-            outSurfaceSize.set(getSurfaceWidth(attrs), getSurfaceHeight(attrs));
             t.setOpaque(sc, isOpaque(attrs)).show(sc).apply();
             outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
         } else {
@@ -335,6 +333,11 @@
     }
 
     @Override
+    public void clearTouchableRegion(android.view.IWindow window) {
+        setTouchRegion(window.asBinder(), null);
+    }
+
+    @Override
     public void finishDrawing(android.view.IWindow window,
             android.view.SurfaceControl.Transaction postDrawTransaction) {
         synchronized (this) {
@@ -480,17 +483,6 @@
         return null;
     }
 
-    private int getSurfaceWidth(WindowManager.LayoutParams attrs) {
-      final Rect surfaceInsets = attrs.surfaceInsets;
-      return surfaceInsets != null
-          ? attrs.width + surfaceInsets.left + surfaceInsets.right : attrs.width;
-    }
-    private int getSurfaceHeight(WindowManager.LayoutParams attrs) {
-      final Rect surfaceInsets = attrs.surfaceInsets;
-      return surfaceInsets != null
-          ? attrs.height + surfaceInsets.top + surfaceInsets.bottom : attrs.height;
-    }
-
     @Override
     public void grantEmbeddedWindowFocus(IWindow callingWindow, IBinder targetInputToken,
                                          boolean grantFocus) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 6a22023..849f3c9 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
+import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.EDITOR_INFO;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_INSETS_SOURCE_CONSUMER;
@@ -1793,6 +1795,14 @@
                 Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus.");
                 return;
             }
+            if (mServedInputConnection != null && getDelegate().hasActiveConnection(view)) {
+                // TODO (b/210039666): optimize CURSOR_UPDATE_IMMEDIATE.
+                // TODO (b/215533103): Introduce new modes in requestCursorUpdates().
+                // TODO (b/210039666): Pipe IME displayId from InputBindResult and use it here.
+                //  instead of mDisplayId.
+                mServedInputConnection.requestCursorUpdatesFromImm(
+                        CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR, mDisplayId);
+            }
 
             try {
                 mService.startStylusHandwriting(mClient);
@@ -2419,9 +2429,9 @@
     public boolean isCursorAnchorInfoEnabled() {
         synchronized (mH) {
             final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
-                    InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
+                    CURSOR_UPDATE_IMMEDIATE) != 0;
             final boolean isMonitoring = (mRequestUpdateCursorAnchorInfoMonitorMode &
-                    InputConnection.CURSOR_UPDATE_MONITOR) != 0;
+                    CURSOR_UPDATE_MONITOR) != 0;
             return isImmediate || isMonitoring;
         }
     }
@@ -2493,7 +2503,7 @@
             // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
             // not been changed from the previous call.
             final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
-                    InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
+                    CURSOR_UPDATE_IMMEDIATE) != 0;
             if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
                 // TODO: Consider always emitting this message once we have addressed redundant
                 // calls of this method from android.widget.Editor.
@@ -2507,7 +2517,7 @@
             mCurrentInputMethodSession.updateCursorAnchorInfo(cursorAnchorInfo);
             mCursorAnchorInfo = cursorAnchorInfo;
             // Clear immediate bit (if any).
-            mRequestUpdateCursorAnchorInfoMonitorMode &= ~InputConnection.CURSOR_UPDATE_IMMEDIATE;
+            mRequestUpdateCursorAnchorInfoMonitorMode &= ~CURSOR_UPDATE_IMMEDIATE;
         }
     }
 
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 364ba50..f14c251 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -19,6 +19,8 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 
@@ -135,6 +137,20 @@
     public @interface CacheMode {}
 
     /**
+     * Enable web content to apply light or dark style according to the app's theme
+     * and WebView to attempt to darken web content by algorithmic darkening when
+     * appropriate.
+     *
+     * Refer to {@link #setAllowAlgorithmicDarkening} for detail.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+    @SystemApi
+    public static final long ENABLE_SIMPLIFIED_DARK_MODE = 214741472L;
+
+    /**
      * Default cache usage mode. If the navigation type doesn't impose any
      * specific behavior, use cached resources when they are available
      * and not expired, otherwise load resources from the network.
@@ -239,6 +255,7 @@
      * automatically darkened.
      *
      * @see #setForceDark
+     * @deprecated refer to {@link #setForceDark}
      */
     public static final int FORCE_DARK_OFF = 0;
 
@@ -250,6 +267,7 @@
      * be inverted.
      *
      * @see #setForceDark
+     * @deprecated refer to {@link #setForceDark}
      */
     public static final int FORCE_DARK_AUTO = 1;
 
@@ -258,6 +276,7 @@
      * as to emulate a dark theme.
      *
      * @see #setForceDark
+     * @deprecated refer to {@link #setForceDark}
      */
     public static final int FORCE_DARK_ON = 2;
 
@@ -1533,6 +1552,13 @@
      *
      * @param forceDark the force dark mode to set.
      * @see #getForceDark
+     * @deprecated The "force dark" model previously implemented by WebView was complex
+     * and didn't interoperate well with current Web standards for
+     * prefers-color-scheme and color-scheme. In apps with
+     * {@code targetSdkVersion} &ge; {@link android.os.Build.VERSION_CODES#TIRAMISU}
+     * this API is a no-op and WebView will always use the dark style defined by web content
+     * authors if the app's theme is dark. To customize the behavior, refer to
+     * {@link #setAllowAlgorithmicDarkening}.
      */
     public void setForceDark(@ForceDark int forceDark) {
         // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
@@ -1544,6 +1570,7 @@
      *
      * @return the currently set force dark mode.
      * @see #setForceDark
+     * @deprecated refer to {@link #setForceDark}.
      */
     public @ForceDark int getForceDark() {
         // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
@@ -1551,6 +1578,49 @@
     }
 
     /**
+     * Control whether algorithmic darkening is allowed.
+     *
+     * <p class="note">
+     * <b>Note:</b> This API and the behaviour described only apply to apps with
+     * {@code targetSdkVersion} &ge; {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+     *
+     * <p>
+     * WebView always sets the media query {@code prefers-color-scheme} according to the app's
+     * theme attribute {@link android.R.styleable#Theme_isLightTheme isLightTheme}, i.e.
+     * {@code prefers-color-scheme} is {@code light} if isLightTheme is true or not specified,
+     * otherwise it is {@code dark}. This means that the web content's light or dark style will
+     * be applied automatically to match the app's theme if the content supports it.
+     *
+     * <p>
+     * Algorithmic darkening is disallowed by default.
+     * <p>
+     * If the app's theme is dark and it allows algorithmic darkening, WebView will attempt to
+     * darken web content using an algorithm, if the content doesn't define its own dark styles
+     * and doesn't explicitly disable darkening.
+     *
+     * <p>
+     * If Android is applying Force Dark to WebView then WebView will ignore the value of
+     * this setting and behave as if it were set to true.
+     *
+     * @param allow allow algorithmic darkening or not.
+     */
+    public void setAllowAlgorithmicDarkening(boolean allow) {
+        // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
+    }
+
+    /**
+     * Get if algorithmic darkening is allowed or not for this WebView.
+     * The default is false.
+     *
+     * @return if the algorithmic darkening is allowed or not.
+     * @see #setAllowAlgorithmicDarkening
+     */
+    public boolean getAllowAlgorithmicDarkening() {
+        // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
+        return false;
+    }
+
+    /**
      * @hide
      */
     @IntDef(flag = true, prefix = { "MENU_ITEM_" }, value = {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index b21d08c..c6f64f4 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1517,6 +1517,12 @@
         }
     }
 
+    /**
+     * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call
+     * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change
+     * unexpectedly.
+     */
+    @Deprecated
     private final class ViewContentNavigation extends Action {
         final boolean mNext;
 
@@ -4121,7 +4127,11 @@
      * Equivalent to calling {@link AdapterViewAnimator#showNext()}
      *
      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
+     * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call
+     * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change
+     * unexpectedly.
      */
+    @Deprecated
     public void showNext(@IdRes int viewId) {
         addAction(new ViewContentNavigation(viewId, true /* next */));
     }
@@ -4130,7 +4140,11 @@
      * Equivalent to calling {@link AdapterViewAnimator#showPrevious()}
      *
      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
+     * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call
+     * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change
+     * unexpectedly.
      */
+    @Deprecated
     public void showPrevious(@IdRes int viewId) {
         addAction(new ViewContentNavigation(viewId, false /* next */));
     }
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index f04155d..b90e628 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -151,6 +151,7 @@
         private Instant mIconAnimationStart;
         private Duration mIconAnimationDuration;
         private Consumer<Runnable> mUiThreadInitTask;
+        private boolean mAllowHandleEmpty = true;
 
         public Builder(@NonNull Context context) {
             mContext = context;
@@ -258,6 +259,15 @@
         }
 
         /**
+         * Sets whether this view can be copied and transferred to the client if the view is
+         * empty style splash screen.
+         */
+        public Builder setAllowHandleEmpty(boolean allowHandleEmpty) {
+            mAllowHandleEmpty = allowHandleEmpty;
+            return this;
+        }
+
+        /**
          * Create SplashScreenWindowView object from materials.
          */
         public SplashScreenView build() {
@@ -303,7 +313,7 @@
                 }
                 view.mIconView = imageView;
             }
-            if (mOverlayDrawable != null || mIconDrawable == null) {
+            if (mOverlayDrawable != null || (view.mIconView == null && !mAllowHandleEmpty)) {
                 view.setNotCopyable();
             }
 
@@ -720,13 +730,15 @@
         private RemoteCallback mClientCallback;
 
         public SplashScreenViewParcelable(SplashScreenView view) {
-            mIconSize = view.mIconView.getWidth();
+            final View iconView = view.getIconView();
+            mIconSize = iconView != null ? iconView.getWidth() : 0;
             mBackgroundColor = view.getInitBackgroundColor();
-            mIconBackground = copyDrawable(view.getIconView().getBackground());
+            mIconBackground = iconView != null ? copyDrawable(iconView.getBackground()) : null;
             mSurfacePackage = view.mSurfacePackageCopy;
             if (mSurfacePackage == null) {
                 // We only need to copy the drawable if we are not using a SurfaceView
-                mIconBitmap = copyDrawable(((ImageView) view.getIconView()).getDrawable());
+                mIconBitmap = iconView != null
+                        ? copyDrawable(((ImageView) view.getIconView()).getDrawable()) : null;
             }
             mBrandingBitmap = copyDrawable(view.getBrandingView().getBackground());
 
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index aec910b..24899a4 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -116,6 +116,7 @@
             TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT,
             TYPE_PARAMETER_ACTIVITY_CREATED,
             TYPE_PARAMETER_USE_EMPTY_SPLASH_SCREEN,
+            TYPE_PARAMETER_ALLOW_HANDLE_EMPTY_SCREEN,
             TYPE_PARAMETER_LEGACY_SPLASH_SCREEN
     })
     public @interface StartingTypeParams {}
@@ -141,6 +142,11 @@
      */
     public static final int TYPE_PARAMETER_ACTIVITY_DRAWN = 0x00000040;
     /**
+     * Application is allowed to handle empty splash screen.
+     * @hide
+     */
+    public static final int TYPE_PARAMETER_ALLOW_HANDLE_EMPTY_SCREEN = 0x00000080;
+    /**
      * Application is allowed to use the legacy splash screen
      * @hide
      */
@@ -185,6 +191,13 @@
         readFromParcel(source);
     }
 
+    /**
+     * Return whether the application allow to handle the empty style splash screen.
+     */
+    public boolean allowHandleEmptySplashScreen() {
+        return (startingWindowTypeParameter & TYPE_PARAMETER_ALLOW_HANDLE_EMPTY_SCREEN) != 0;
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index cfccb71..fdaab66 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -17,6 +17,8 @@
 
 import static android.view.WindowManagerImpl.createWindowContextWindowManager;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiContext;
@@ -135,7 +137,8 @@
     }
 
     /** Dispatch {@link Configuration} to each {@link ComponentCallbacks}. */
-    void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+    @VisibleForTesting(visibility = PACKAGE)
+    public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
         mCallbacksController.dispatchConfigurationChanged(newConfig);
     }
 
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index 0470444..d1942ac 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -932,6 +932,24 @@
         });
     }
 
+    /**
+     * Dispatches {@link InputConnection#requestCursorUpdates(int)}.
+     *
+     * <p>This method is intended to be called only from {@link InputMethodManager}.</p>
+     * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int)}
+     * @param imeDisplayId displayId on which IME is displayed.
+     */
+    @Dispatching(cancellable = true)
+    public void requestCursorUpdatesFromImm(int cursorUpdateMode, int imeDisplayId) {
+        final int currentSessionId = mCurrentSessionId.get();
+        dispatchWithTracing("requestCursorUpdatesFromImm", () -> {
+            if (currentSessionId != mCurrentSessionId.get()) {
+                return;  // cancelled
+            }
+            requestCursorUpdatesInternal(cursorUpdateMode, imeDisplayId);
+        });
+    }
+
     @Dispatching(cancellable = true)
     @Override
     public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
@@ -940,24 +958,28 @@
             if (header.mSessionId != mCurrentSessionId.get()) {
                 return false;  // cancelled
             }
-            final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
-                Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
-                return false;
-            }
-            if (mParentInputMethodManager.getDisplayId() != imeDisplayId) {
-                // requestCursorUpdates() is not currently supported across displays.
-                return false;
-            }
-            try {
-                return ic.requestCursorUpdates(cursorUpdateMode);
-            } catch (AbstractMethodError ignored) {
-                // TODO(b/199934664): See if we can remove this by providing a default impl.
-                return false;
-            }
+            return requestCursorUpdatesInternal(cursorUpdateMode, imeDisplayId);
         });
     }
 
+    private boolean requestCursorUpdatesInternal(int cursorUpdateMode, int imeDisplayId) {
+        final InputConnection ic = getInputConnection();
+        if (ic == null || !isActive()) {
+            Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+            return false;
+        }
+        if (mParentInputMethodManager.getDisplayId() != imeDisplayId) {
+            // requestCursorUpdates() is not currently supported across displays.
+            return false;
+        }
+        try {
+            return ic.requestCursorUpdates(cursorUpdateMode);
+        } catch (AbstractMethodError ignored) {
+            // TODO(b/199934664): See if we can remove this by providing a default impl.
+            return false;
+        }
+    }
+
     @Dispatching(cancellable = true)
     @Override
     public void commitContent(InputConnectionCommandHeader header,
diff --git a/core/java/com/android/internal/logging/InstanceId.aidl b/core/java/com/android/internal/logging/InstanceId.aidl
new file mode 100644
index 0000000..19a6177
--- /dev/null
+++ b/core/java/com/android/internal/logging/InstanceId.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 com.android.internal.logging;
+
+parcelable InstanceId;
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 3b6f8f6..b79c0be 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -62,6 +62,7 @@
     public static String DO_NOT_DISTURB = "DO_NOT_DISTURB";
     public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
     public static String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
+    public static String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
 
     public static void createAll(Context context) {
         final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -209,6 +210,12 @@
                 NotificationManager.IMPORTANCE_LOW);
         channelsList.add(accessibilitySecurityPolicyChannel);
 
+        final NotificationChannel abusiveBackgroundAppsChannel = new NotificationChannel(
+                ABUSIVE_BACKGROUND_APPS,
+                context.getString(R.string.notification_channel_abusive_bg_apps),
+                NotificationManager.IMPORTANCE_LOW);
+        channelsList.add(abusiveBackgroundAppsChannel);
+
         nm.createNotificationChannels(channelsList);
     }
 
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 9443070..d8e89b4 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -33,6 +33,11 @@
 public class AmbientDisplayPowerCalculator extends PowerCalculator {
     private final UsageBasedPowerEstimator[] mPowerEstimators;
 
+    @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY;
+    }
+
     public AmbientDisplayPowerCalculator(PowerProfile powerProfile) {
         final int numDisplays = powerProfile.getNumDisplays();
         mPowerEstimators = new UsageBasedPowerEstimator[numDisplays];
diff --git a/core/java/com/android/internal/os/AudioPowerCalculator.java b/core/java/com/android/internal/os/AudioPowerCalculator.java
index 2eab506..f9310b0 100644
--- a/core/java/com/android/internal/os/AudioPowerCalculator.java
+++ b/core/java/com/android/internal/os/AudioPowerCalculator.java
@@ -44,6 +44,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_AUDIO;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final PowerAndDuration total = new PowerAndDuration();
diff --git a/core/java/com/android/internal/os/BatteryChargeCalculator.java b/core/java/com/android/internal/os/BatteryChargeCalculator.java
index 8178529..71a1463 100644
--- a/core/java/com/android/internal/os/BatteryChargeCalculator.java
+++ b/core/java/com/android/internal/os/BatteryChargeCalculator.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.os;
 
+import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
@@ -30,6 +31,12 @@
 public class BatteryChargeCalculator extends PowerCalculator {
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        // Always apply this power calculator, no matter what power components were requested
+        return true;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         builder.setDischargePercentage(
@@ -51,7 +58,8 @@
         builder.setDischargePercentage(
                 batteryStats.getDischargeAmount(BatteryStats.STATS_SINCE_CHARGED))
                 .setDischargedPowerRange(dischargedPowerLowerBoundMah,
-                        dischargedPowerUpperBoundMah);
+                        dischargedPowerUpperBoundMah)
+                .setDischargeDurationMs(batteryStats.getBatteryRealtime(rawRealtimeUs) / 1000);
 
         final long batteryTimeRemainingMs = batteryStats.computeBatteryTimeRemaining(rawRealtimeUs);
         if (batteryTimeRemainingMs != -1) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 8213c86..25ee2d0 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -13223,59 +13223,57 @@
             long totalRxPackets = 0;
             long totalTxPackets = 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);
-                    if (entry.rxPackets == 0 && entry.txPackets == 0) {
+                for (NetworkStats.Entry entry : delta) {
+                    if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
                         continue;
                     }
 
                     if (DEBUG_ENERGY) {
-                        Slog.d(TAG, "Mobile uid " + entry.uid + ": delta rx=" + entry.rxBytes
-                                + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
-                                + " txPackets=" + entry.txPackets);
+                        Slog.d(TAG, "Mobile uid " + entry.getUid() + ": delta rx="
+                                + entry.getRxBytes() + " tx=" + entry.getTxBytes()
+                                + " rxPackets=" + entry.getRxPackets()
+                                + " txPackets=" + entry.getTxPackets());
                     }
 
-                    totalRxPackets += entry.rxPackets;
-                    totalTxPackets += entry.txPackets;
+                    totalRxPackets += entry.getRxPackets();
+                    totalTxPackets += entry.getTxPackets();
 
-                    final Uid u = getUidStatsLocked(mapUid(entry.uid), elapsedRealtimeMs, uptimeMs);
-                    u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes,
-                            entry.rxPackets);
-                    u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes,
-                            entry.txPackets);
-                    if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
+                    final Uid u = getUidStatsLocked(
+                            mapUid(entry.getUid()), elapsedRealtimeMs, uptimeMs);
+                    u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.getRxBytes(),
+                            entry.getRxPackets());
+                    u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.getTxBytes(),
+                            entry.getTxPackets());
+                    if (entry.getSet() == NetworkStats.SET_DEFAULT) { // Background transfers
                         u.noteNetworkActivityLocked(NETWORK_MOBILE_BG_RX_DATA,
-                                entry.rxBytes, entry.rxPackets);
+                                entry.getRxBytes(), entry.getRxPackets());
                         u.noteNetworkActivityLocked(NETWORK_MOBILE_BG_TX_DATA,
-                                entry.txBytes, entry.txPackets);
+                                entry.getTxBytes(), entry.getTxPackets());
                     }
 
                     mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
-                            entry.rxBytes);
+                            entry.getRxBytes());
                     mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
-                            entry.txBytes);
+                            entry.getTxBytes());
                     mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
-                            entry.rxPackets);
+                            entry.getRxPackets());
                     mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
-                            entry.txPackets);
+                            entry.getTxPackets());
                 }
 
                 // Now distribute proportional blame to the apps that did networking.
                 long totalPackets = totalRxPackets + totalTxPackets;
                 if (totalPackets > 0) {
-                    for (int i = 0; i < size; i++) {
-                        entry = delta.getValues(i, entry);
-                        if (entry.rxPackets == 0 && entry.txPackets == 0) {
+                    for (NetworkStats.Entry entry : delta) {
+                        if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
                             continue;
                         }
 
-                        final Uid u = getUidStatsLocked(mapUid(entry.uid),
+                        final Uid u = getUidStatsLocked(mapUid(entry.getUid()),
                                 elapsedRealtimeMs, uptimeMs);
 
                         // Distribute total radio active time in to this app.
-                        final long appPackets = entry.rxPackets + entry.txPackets;
+                        final long appPackets = entry.getRxPackets() + entry.getTxPackets();
                         final long appRadioTimeUs =
                                 (totalAppRadioTimeUs * appPackets) / totalPackets;
                         u.noteMobileRadioActiveTimeLocked(appRadioTimeUs, elapsedRealtimeMs);
@@ -13296,17 +13294,17 @@
                         if (deltaInfo != null) {
                             ControllerActivityCounterImpl activityCounter =
                                     u.getOrCreateModemControllerActivityLocked();
-                            if (totalRxPackets > 0 && entry.rxPackets > 0) {
-                                final long rxMs = (entry.rxPackets
+                            if (totalRxPackets > 0 && entry.getRxPackets() > 0) {
+                                final long rxMs = (entry.getRxPackets()
                                     * deltaInfo.getReceiveTimeMillis()) / totalRxPackets;
                                 activityCounter.getOrCreateRxTimeCounter()
                                         .increment(rxMs, elapsedRealtimeMs);
                             }
 
-                            if (totalTxPackets > 0 && entry.txPackets > 0) {
+                            if (totalTxPackets > 0 && entry.getTxPackets() > 0) {
                                 for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels();
                                         lvl++) {
-                                    long txMs = entry.txPackets
+                                    long txMs = entry.getTxPackets()
                                             * deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl);
                                     txMs /= totalTxPackets;
                                     activityCounter.getOrCreateTxTimeCounters()[lvl]
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 69b7b4e..e4d5fb7 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -181,9 +181,22 @@
                             getProcessForegroundTimeMs(uid, realtimeUs));
         }
 
+        final int[] powerComponents = query.getPowerComponents();
         final List<PowerCalculator> powerCalculators = getPowerCalculators();
         for (int i = 0, count = powerCalculators.size(); i < count; i++) {
             PowerCalculator powerCalculator = powerCalculators.get(i);
+            if (powerComponents != null) {
+                boolean include = false;
+                for (int j = 0; j < powerComponents.length; j++) {
+                    if (powerCalculator.isPowerComponentSupported(powerComponents[j])) {
+                        include = true;
+                        break;
+                    }
+                }
+                if (!include) {
+                    continue;
+                }
+            }
             powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs,
                     query);
         }
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index 20535d2..066ee84 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -64,6 +64,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_BLUETOOTH;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         if (!batteryStats.hasBluetoothActivityReporting()) {
diff --git a/core/java/com/android/internal/os/CameraPowerCalculator.java b/core/java/com/android/internal/os/CameraPowerCalculator.java
index ddcabe8..7bccab5 100644
--- a/core/java/com/android/internal/os/CameraPowerCalculator.java
+++ b/core/java/com/android/internal/os/CameraPowerCalculator.java
@@ -37,6 +37,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_CAMERA;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index ee614cd..6a96cfe 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -93,6 +93,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_CPU;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         double totalPowerMah = 0;
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
index bb307a0..4cb7ef1 100644
--- a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -37,6 +37,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(int powerComponent) {
+        return false;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         double[] totalAppPowerMah = null;
diff --git a/core/java/com/android/internal/os/FlashlightPowerCalculator.java b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
index 32df17c..7d3f962 100644
--- a/core/java/com/android/internal/os/FlashlightPowerCalculator.java
+++ b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
@@ -35,6 +35,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_FLASHLIGHT;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java
index a508e03..a836ddb 100644
--- a/core/java/com/android/internal/os/GnssPowerCalculator.java
+++ b/core/java/com/android/internal/os/GnssPowerCalculator.java
@@ -44,6 +44,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_GNSS;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         double appsPowerMah = 0;
diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java
index d33a88d..46808f9 100644
--- a/core/java/com/android/internal/os/IdlePowerCalculator.java
+++ b/core/java/com/android/internal/os/IdlePowerCalculator.java
@@ -47,6 +47,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_IDLE;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         calculatePowerAndDuration(batteryStats, rawRealtimeUs, rawUptimeUs,
diff --git a/core/java/com/android/internal/os/MediaPowerCalculator.java b/core/java/com/android/internal/os/MediaPowerCalculator.java
index e93d93c..fff96da 100644
--- a/core/java/com/android/internal/os/MediaPowerCalculator.java
+++ b/core/java/com/android/internal/os/MediaPowerCalculator.java
@@ -15,6 +15,7 @@
  */
 package com.android.internal.os;
 
+import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 
 /**
@@ -33,6 +34,12 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_VIDEO
+                || powerComponent == BatteryConsumer.POWER_COMPONENT_AUDIO;
+    }
+
+    @Override
     protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
             long rawUptimeUs, int statsType) {
         // Calculate audio power usage, an estimate based on the average power routed to different
diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java
index 09fd85e..0440a58 100644
--- a/core/java/com/android/internal/os/MemoryPowerCalculator.java
+++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java
@@ -24,6 +24,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_MEMORY;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index 28cc836..a1d5fc9 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -86,6 +86,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
 
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index 8dd463c..7310314 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -37,6 +37,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_PHONE;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs,
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
index 93d562c..d0a83e7 100644
--- a/core/java/com/android/internal/os/PowerCalculator.java
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -36,6 +36,14 @@
     protected static final double MILLIAMPHOUR_PER_MICROCOULOMB = 1.0 / 1000.0 / 60.0 / 60.0;
 
     /**
+     * Returns true if this power calculator computes power/duration for the specified
+     * power component.
+     */
+    public abstract boolean isPowerComponentSupported(
+            @BatteryConsumer.PowerComponent int powerComponent);
+
+
+    /**
      * Attributes the total amount of power used by this subsystem to various consumers such
      * as apps.
      *
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/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index 2b63459..d989e2a 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -66,6 +66,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_SCREEN;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
diff --git a/core/java/com/android/internal/os/SensorPowerCalculator.java b/core/java/com/android/internal/os/SensorPowerCalculator.java
index 83e5b57..495a6d9 100644
--- a/core/java/com/android/internal/os/SensorPowerCalculator.java
+++ b/core/java/com/android/internal/os/SensorPowerCalculator.java
@@ -39,6 +39,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_SENSORS;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         double appsPowerMah = 0;
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index c527c06..d7872ba 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -62,6 +62,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final BatteryStats.Uid systemUid = batteryStats.getUidStats().get(Process.SYSTEM_UID);
diff --git a/core/java/com/android/internal/os/UserPowerCalculator.java b/core/java/com/android/internal/os/UserPowerCalculator.java
index 8e80286..b590bf7 100644
--- a/core/java/com/android/internal/os/UserPowerCalculator.java
+++ b/core/java/com/android/internal/os/UserPowerCalculator.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.os;
 
+import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
@@ -34,6 +35,11 @@
 public class UserPowerCalculator extends PowerCalculator {
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return true;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final int[] userIds = query.getUserIds();
diff --git a/core/java/com/android/internal/os/VideoPowerCalculator.java b/core/java/com/android/internal/os/VideoPowerCalculator.java
index 47916a6..a222bcb 100644
--- a/core/java/com/android/internal/os/VideoPowerCalculator.java
+++ b/core/java/com/android/internal/os/VideoPowerCalculator.java
@@ -41,6 +41,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_VIDEO;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final PowerAndDuration total = new PowerAndDuration();
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
index e0ef129..aa6a4f8 100644
--- a/core/java/com/android/internal/os/WakelockPowerCalculator.java
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -44,6 +44,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_WAKELOCK;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final PowerAndDuration result = new PowerAndDuration();
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index 2a71ac6..77f15f1 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -82,6 +82,11 @@
     }
 
     @Override
+    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_WIFI;
+    }
+
+    @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index 8770267..76f7b21 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -18,28 +18,15 @@
 
 import android.annotation.NonNull;
 import android.os.Build;
-import android.os.SharedMemory;
 import android.os.SystemProperties;
-import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.util.Pair;
 import android.util.Slog;
-import android.util.apk.ApkSignatureVerifier;
-import android.util.apk.ByteBufferFactory;
-import android.util.apk.SignatureNotFoundException;
-
-import libcore.util.HexEncoding;
 
 import java.io.File;
-import java.io.FileDescriptor;
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.nio.file.Files;
 import java.nio.file.Paths;
-import java.security.DigestException;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
 
 /** Provides fsverity related operations. */
 public abstract class VerityUtils {
@@ -57,8 +44,6 @@
     /** SHA256 hash size. */
     private static final int HASH_SIZE_BYTES = 32;
 
-    private static final boolean DEBUG = false;
-
     public static boolean isFsVeritySupported() {
         return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R
                 || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
@@ -123,204 +108,4 @@
     private static native int measureFsverityNative(@NonNull String filePath,
             @NonNull byte[] digest);
     private static native int statxForFsverityNative(@NonNull String filePath);
-
-    /**
-     * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped.
-     *
-     * @deprecated This is only used for previous fs-verity implementation, and should never be used
-     *             on new devices.
-     * @return {@code SetupResult} that contains the result code, and when success, the
-     *         {@code FileDescriptor} to read all the data from.
-     */
-    @Deprecated
-    public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
-        if (DEBUG) {
-            Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath);
-        }
-        SharedMemory shm = null;
-        try {
-            final byte[] signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
-            if (signedVerityHash == null) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Skip verity tree generation since there is no signed root hash");
-                }
-                return SetupResult.skipped();
-            }
-
-            Pair<SharedMemory, Integer> result =
-                    generateFsVerityIntoSharedMemory(apkPath, signedVerityHash);
-            shm = result.first;
-            int contentSize = result.second;
-            FileDescriptor rfd = shm.getFileDescriptor();
-            if (rfd == null || !rfd.valid()) {
-                return SetupResult.failed();
-            }
-            return SetupResult.ok(Os.dup(rfd), contentSize);
-        } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException
-                | SignatureNotFoundException | ErrnoException e) {
-            Slog.e(TAG, "Failed to set up apk verity: ", e);
-            return SetupResult.failed();
-        } finally {
-            if (shm != null) {
-                shm.close();
-            }
-        }
-    }
-
-    /**
-     * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}.
-     * @deprecated This is only used for previous fs-verity implementation, and should never be used
-     *             on new devices.
-     */
-    @Deprecated
-    public static byte[] generateApkVerityRootHash(@NonNull String apkPath)
-            throws NoSuchAlgorithmException, DigestException, IOException {
-        return ApkSignatureVerifier.generateApkVerityRootHash(apkPath);
-    }
-
-    /**
-     * {@see ApkSignatureVerifier#getVerityRootHash(String)}.
-     * @deprecated This is only used for previous fs-verity implementation, and should never be used
-     *             on new devices.
-     */
-    @Deprecated
-    public static byte[] getVerityRootHash(@NonNull String apkPath)
-            throws IOException, SignatureNotFoundException {
-        return ApkSignatureVerifier.getVerityRootHash(apkPath);
-    }
-
-    /**
-     * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains
-     * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used
-     * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has
-     * length equals to the returned {@code Integer}.
-     */
-    private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory(String apkPath,
-            @NonNull byte[] expectedRootHash)
-            throws IOException, DigestException, NoSuchAlgorithmException,
-                   SignatureNotFoundException {
-        TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory();
-        byte[] generatedRootHash =
-                ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory);
-        // We only generate Merkle tree once here, so it's important to make sure the root hash
-        // matches the signed one in the apk.
-        if (!Arrays.equals(expectedRootHash, generatedRootHash)) {
-            throw new SecurityException("verity hash mismatch: "
-                    + bytesToString(generatedRootHash) + " != " + bytesToString(expectedRootHash));
-        }
-
-        int contentSize = shmBufferFactory.getBufferLimit();
-        SharedMemory shm = shmBufferFactory.releaseSharedMemory();
-        if (shm == null) {
-            throw new IllegalStateException("Failed to generate verity tree into shared memory");
-        }
-        if (!shm.setProtect(OsConstants.PROT_READ)) {
-            throw new SecurityException("Failed to set up shared memory correctly");
-        }
-        return Pair.create(shm, contentSize);
-    }
-
-    private static String bytesToString(byte[] bytes) {
-        return HexEncoding.encodeToString(bytes);
-    }
-
-    /**
-     * @deprecated This is only used for previous fs-verity implementation, and should never be used
-     *             on new devices.
-     */
-    @Deprecated
-    public static class SetupResult {
-        /** Result code if verity is set up correctly. */
-        private static final int RESULT_OK = 1;
-
-        /** Result code if signature is not provided. */
-        private static final int RESULT_SKIPPED = 2;
-
-        /** Result code if the setup failed. */
-        private static final int RESULT_FAILED = 3;
-
-        private final int mCode;
-        private final FileDescriptor mFileDescriptor;
-        private final int mContentSize;
-
-        /** @deprecated */
-        @Deprecated
-        public static SetupResult ok(@NonNull FileDescriptor fileDescriptor, int contentSize) {
-            return new SetupResult(RESULT_OK, fileDescriptor, contentSize);
-        }
-
-        /** @deprecated */
-        @Deprecated
-        public static SetupResult skipped() {
-            return new SetupResult(RESULT_SKIPPED, null, -1);
-        }
-
-        /** @deprecated */
-        @Deprecated
-        public static SetupResult failed() {
-            return new SetupResult(RESULT_FAILED, null, -1);
-        }
-
-        private SetupResult(int code, FileDescriptor fileDescriptor, int contentSize) {
-            this.mCode = code;
-            this.mFileDescriptor = fileDescriptor;
-            this.mContentSize = contentSize;
-        }
-
-        public boolean isFailed() {
-            return mCode == RESULT_FAILED;
-        }
-
-        public boolean isOk() {
-            return mCode == RESULT_OK;
-        }
-
-        public @NonNull FileDescriptor getUnownedFileDescriptor() {
-            return mFileDescriptor;
-        }
-
-        public int getContentSize() {
-            return mContentSize;
-        }
-    }
-
-    /** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */
-    private static class TrackedShmBufferFactory implements ByteBufferFactory {
-        private SharedMemory mShm;
-        private ByteBuffer mBuffer;
-
-        @Override
-        public ByteBuffer create(int capacity) {
-            try {
-                if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity");
-                // NB: This method is supposed to be called once according to the contract with
-                // ApkSignatureSchemeV2Verifier.
-                if (mBuffer != null) {
-                    throw new IllegalStateException("Multiple instantiation from this factory");
-                }
-                mShm = SharedMemory.create("apkverity", capacity);
-                if (!mShm.setProtect(OsConstants.PROT_READ | OsConstants.PROT_WRITE)) {
-                    throw new SecurityException("Failed to set protection");
-                }
-                mBuffer = mShm.mapReadWrite();
-                return mBuffer;
-            } catch (ErrnoException e) {
-                throw new SecurityException("Failed to set protection", e);
-            }
-        }
-
-        public SharedMemory releaseSharedMemory() {
-            if (mBuffer != null) {
-                SharedMemory.unmap(mBuffer);
-                mBuffer = null;
-            }
-            SharedMemory tmp = mShm;
-            mShm = null;
-            return tmp;
-        }
-
-        public int getBufferLimit() {
-            return mBuffer == null ? -1 : mBuffer.limit();
-        }
-    }
 }
diff --git a/core/java/com/android/internal/statusbar/ISessionListener.aidl b/core/java/com/android/internal/statusbar/ISessionListener.aidl
new file mode 100644
index 0000000..101a2d2
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/ISessionListener.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissons and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+import com.android.internal.logging.InstanceId;
+
+/** {@hide} */
+oneway interface ISessionListener {
+    void onSessionStarted(int sessionType, in InstanceId instance);
+    void onSessionEnded(int sessionType, in InstanceId instance);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 3c6b7ff..accb986 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -28,7 +28,9 @@
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 
+import com.android.internal.logging.InstanceId;
 import com.android.internal.statusbar.IAddTileResultCallback;
+import com.android.internal.statusbar.ISessionListener;
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.internal.statusbar.StatusBarIcon;
@@ -178,4 +180,17 @@
     * @hide
     */
     int getNavBarModeOverride();
+
+    /**
+    * Register a listener for certain sessions. Each session may be guarded by its own permission.
+    */
+    void registerSessionListener(int sessionFlags, in ISessionListener listener);
+    void unregisterSessionListener(int sessionFlags, in ISessionListener listener);
+
+    /**
+    * Informs all registered listeners that a session has begun and has the following instanceId.
+    * Can only be set by callers with certain permission based on the session type being updated.
+    */
+    void onSessionStarted(int sessionType, in InstanceId instanceId);
+    void onSessionEnded(int sessionType, in InstanceId instanceId);
 }
diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp
index 845d65c..a022842 100644
--- a/core/jni/android_hardware_UsbDeviceConnection.cpp
+++ b/core/jni/android_hardware_UsbDeviceConnection.cpp
@@ -16,20 +16,19 @@
 
 #define LOG_TAG "UsbDeviceConnectionJNI"
 
-#include "utils/Log.h"
-
-#include "jni.h"
+#include <fcntl.h>
 #include <nativehelper/JNIPlatformHelp.h>
-#include "core_jni_helpers.h"
-
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <usbhost/usbhost.h>
+#include <usbhost/usbhost_jni.h>
 
 #include <chrono>
 
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
+#include "core_jni_helpers.h"
+#include "jni.h"
+#include "utils/Log.h"
 
 using namespace android;
 using namespace std::chrono;
@@ -91,22 +90,8 @@
 static jbyteArray
 android_hardware_UsbDeviceConnection_get_desc(JNIEnv *env, jobject thiz)
 {
-    char buffer[16384];
     int fd = android_hardware_UsbDeviceConnection_get_fd(env, thiz);
-    if (fd < 0) return NULL;
-    lseek(fd, 0, SEEK_SET);
-    int length = read(fd, buffer, sizeof(buffer));
-    if (length < 0) return NULL;
-
-    jbyteArray ret = env->NewByteArray(length);
-    if (ret) {
-        jbyte* bytes = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);
-        if (bytes) {
-            memcpy(bytes, buffer, length);
-            env->ReleasePrimitiveArrayCritical(ret, bytes, 0);
-        }
-    }
-    return ret;
+    return usb_jni_read_descriptors(env, fd);
 }
 
 static jboolean
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 3e2b258..e13b788 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -1236,6 +1236,65 @@
     return false;
 }
 
+static jint convertAudioProfileFromNative(JNIEnv *env, jobject *jAudioProfile,
+                                          const audio_profile *nAudioProfile, bool useInMask) {
+    size_t numPositionMasks = 0;
+    size_t numIndexMasks = 0;
+
+    // count up how many masks are positional and indexed
+    for (size_t index = 0; index < nAudioProfile->num_channel_masks; index++) {
+        const audio_channel_mask_t mask = nAudioProfile->channel_masks[index];
+        if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
+            numIndexMasks++;
+        } else {
+            numPositionMasks++;
+        }
+    }
+
+    ScopedLocalRef<jintArray> jSamplingRates(env,
+                                             env->NewIntArray(nAudioProfile->num_sample_rates));
+    ScopedLocalRef<jintArray> jChannelMasks(env, env->NewIntArray(numPositionMasks));
+    ScopedLocalRef<jintArray> jChannelIndexMasks(env, env->NewIntArray(numIndexMasks));
+    if (!jSamplingRates.get() || !jChannelMasks.get() || !jChannelIndexMasks.get()) {
+        return AUDIO_JAVA_ERROR;
+    }
+
+    if (nAudioProfile->num_sample_rates) {
+        env->SetIntArrayRegion(jSamplingRates.get(), 0 /*start*/, nAudioProfile->num_sample_rates,
+                               (jint *)nAudioProfile->sample_rates);
+    }
+
+    // put the masks in the output arrays
+    for (size_t maskIndex = 0, posMaskIndex = 0, indexedMaskIndex = 0;
+         maskIndex < nAudioProfile->num_channel_masks; maskIndex++) {
+        const audio_channel_mask_t mask = nAudioProfile->channel_masks[maskIndex];
+        if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
+            jint jMask = audio_channel_mask_get_bits(mask);
+            env->SetIntArrayRegion(jChannelIndexMasks.get(), indexedMaskIndex++, 1, &jMask);
+        } else {
+            jint jMask = useInMask ? inChannelMaskFromNative(mask) : outChannelMaskFromNative(mask);
+            env->SetIntArrayRegion(jChannelMasks.get(), posMaskIndex++, 1, &jMask);
+        }
+    }
+
+    int encapsulationType;
+    if (audioEncapsulationTypeFromNative(nAudioProfile->encapsulation_type, &encapsulationType) !=
+        NO_ERROR) {
+        ALOGW("Unknown encapsulation type for JAVA API: %u", nAudioProfile->encapsulation_type);
+    }
+
+    *jAudioProfile =
+            env->NewObject(gAudioProfileClass, gAudioProfileCstor,
+                           audioFormatFromNative(nAudioProfile->format), jSamplingRates.get(),
+                           jChannelMasks.get(), jChannelIndexMasks.get(), encapsulationType);
+
+    if (jAudioProfile == nullptr) {
+        return AUDIO_JAVA_ERROR;
+    }
+
+    return AUDIO_JAVA_SUCCESS;
+}
+
 static jint convertAudioPortFromNative(JNIEnv *env, jobject *jAudioPort,
                                        const struct audio_port_v7 *nAudioPort) {
     jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
@@ -2479,6 +2538,10 @@
     return AudioSystem::isHapticPlaybackSupported();
 }
 
+static jboolean android_media_AudioSystem_isUltrasoundSupported(JNIEnv *env, jobject thiz) {
+    return AudioSystem::isUltrasoundSupported();
+}
+
 static jint android_media_AudioSystem_setSupportedSystemUsages(JNIEnv *env, jobject thiz,
                                                                jintArray systemUsages) {
     std::vector<audio_usage_t> nativeSystemUsagesVector;
@@ -2814,6 +2877,50 @@
     return convertAudioDirectModeFromNative(directMode);
 }
 
+static jint android_media_AudioSystem_getDirectProfilesForAttributes(JNIEnv *env, jobject thiz,
+                                                                     jobject jAudioAttributes,
+                                                                     jobject jAudioProfilesList) {
+    ALOGV("getDirectProfilesForAttributes");
+
+    if (jAudioAttributes == nullptr) {
+        ALOGE("jAudioAttributes is NULL");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    if (jAudioProfilesList == nullptr) {
+        ALOGE("jAudioProfilesList is NULL");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    if (!env->IsInstanceOf(jAudioProfilesList, gArrayListClass)) {
+        ALOGE("jAudioProfilesList not an ArrayList");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
+    jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
+    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+        return jStatus;
+    }
+
+    std::vector<audio_profile> audioProfiles;
+    status_t status = AudioSystem::getDirectProfilesForAttributes(paa.get(), &audioProfiles);
+    if (status != NO_ERROR) {
+        ALOGE("AudioSystem::getDirectProfilesForAttributes error %d", status);
+        jStatus = nativeToJavaStatus(status);
+        return jStatus;
+    }
+
+    for (const auto &audioProfile : audioProfiles) {
+        jobject jAudioProfile;
+        jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &audioProfile, false);
+        if (jStatus != AUDIO_JAVA_SUCCESS) {
+            return jStatus;
+        }
+        env->CallBooleanMethod(jAudioProfilesList, gArrayListMethods.add, jAudioProfile);
+        env->DeleteLocalRef(jAudioProfile);
+    }
+    return jStatus;
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] =
@@ -2915,6 +3022,7 @@
          {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids},
          {"isHapticPlaybackSupported", "()Z",
           (void *)android_media_AudioSystem_isHapticPlaybackSupported},
+         {"isUltrasoundSupported", "()Z", (void *)android_media_AudioSystem_isUltrasoundSupported},
          {"getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I",
           (void *)android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia},
          {"setSupportedSystemUsages", "([I)I",
@@ -2960,7 +3068,10 @@
           (void *)android_media_AudioSystem_canBeSpatialized},
          {"getDirectPlaybackSupport",
           "(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I",
-          (void *)android_media_AudioSystem_getDirectPlaybackSupport}};
+          (void *)android_media_AudioSystem_getDirectPlaybackSupport},
+         {"getDirectProfilesForAttributes",
+          "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I",
+          (void *)android_media_AudioSystem_getDirectProfilesForAttributes}};
 
 static const JNINativeMethod gEventHandlerMethods[] = {
     {"native_setup",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index d5470cc..a8cf253 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -953,6 +953,11 @@
     transaction->setDropInputMode(ctrl, static_cast<gui::DropInputMode>(mode));
 }
 
+static void nativeSanitize(JNIEnv* env, jclass clazz, jlong transactionObj) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    transaction->sanitize();
+}
+
 static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) {
     const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
     jlongArray array = env->NewLongArray(displayIds.size());
@@ -2134,6 +2139,8 @@
              (void*)nativeSetDropInputMode },
     {"nativeAddTransactionCommittedListener", "(JLandroid/view/TransactionCommittedListener;)V",
             (void*) nativeAddTransactionCommittedListener },
+    {"nativeSanitize", "(J)V",
+            (void*) nativeSanitize }
         // clang-format on
 };
 
diff --git a/core/proto/android/os/batteryusagestats.proto b/core/proto/android/os/batteryusagestats.proto
index c0a9f03..cc90e05 100644
--- a/core/proto/android/os/batteryusagestats.proto
+++ b/core/proto/android/os/batteryusagestats.proto
@@ -98,4 +98,7 @@
 
     // Sum of all discharge percentage point drops during the reported session.
     optional int32 session_discharge_percentage = 6;
-}
\ No newline at end of file
+
+    // Total amount of time battery was discharging during the reported session
+    optional int64 discharge_duration_millis = 7;
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 998ec96..51e150e 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -249,7 +249,8 @@
 
     optional android.service.NetworkStatsServiceDumpProto netstats = 3001 [
         (section).type = SECTION_DUMPSYS,
-        (section).args = "netstats --proto"
+        (section).args = "netstats --proto",
+        (section).userdebug_and_eng_only = true
     ];
 
     optional android.providers.settings.SettingsServiceDumpProto settings = 3002 [
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 11560a5c..c33b7c9 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -69,7 +69,7 @@
     // know what activity types to check for when invoking splitscreen multi-window.
     optional bool is_home_recents_component = 6;
     repeated IdentifierProto pending_activities = 7 [deprecated=true];
-    optional int32 default_min_size_resizable_task = 8;
+    optional int32 default_min_size_resizable_task = 8 [deprecated=true];
 }
 
 message BarControllerProto {
@@ -226,7 +226,7 @@
     optional bool is_sleeping = 36;
     repeated string sleep_tokens = 37;
     repeated .android.graphics.RectProto keep_clear_areas = 38;
-
+    optional int32 min_size_of_resizeable_task_dp = 39;
 }
 
 /* represents DisplayArea object */
@@ -439,7 +439,7 @@
     optional bool is_on_screen = 37;
     optional bool is_visible = 38;
     optional bool pending_seamless_rotation = 39;
-    optional int64 finished_seamless_rotation_frame = 40;
+    optional int64 finished_seamless_rotation_frame = 40 [deprecated=true];
     optional WindowFramesProto window_frames = 41;
     optional bool force_seamless_rotation = 42;
     optional bool has_compat_scale = 43;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3a842ee..17de7ca 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -717,6 +717,7 @@
     <!-- Added in T -->
     <protected-broadcast android:name="android.intent.action.REFRESH_SAFETY_SOURCES" />
     <protected-broadcast android:name="android.app.action.DEVICE_POLICY_RESOURCE_UPDATED" />
+    <protected-broadcast android:name="android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -974,6 +975,61 @@
         android:permissionFlags="softRestricted|immutablyRestricted"
         android:protectionLevel="dangerous" />
 
+    <!-- Required to be able to read audio files from shared storage.
+     <p>Protection level: dangerous -->
+    <permission-group android:name="android.permission-group.READ_MEDIA_AURAL"
+                      android:icon="@drawable/perm_group_read_media_aural"
+                      android:label="@string/permgrouplab_readMediaAural"
+                      android:description="@string/permgroupdesc_readMediaAural"
+                      android:priority="950" />
+
+    <!-- Allows an application to read audio files from external storage.
+      <p>This permission is enforced starting in API level
+      {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+      For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+      targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+      must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+     <p>Protection level: dangerous -->
+    <permission android:name="android.permission.READ_MEDIA_AUDIO"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_readMediaAudio"
+                android:description="@string/permdesc_readMediaAudio"
+                android:protectionLevel="dangerous" />
+
+    <!-- Required to be able to read image and video files from shared storage.
+     <p>Protection level: dangerous -->
+    <permission-group android:name="android.permission-group.READ_MEDIA_VISUAL"
+                      android:icon="@drawable/perm_group_read_media_visual"
+                      android:label="@string/permgrouplab_readMediaVisual"
+                      android:description="@string/permgroupdesc_readMediaVisual"
+                      android:priority="1000" />
+
+    <!-- Allows an application to read audio files from external storage.
+    <p>This permission is enforced starting in API level
+    {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+    For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+    targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+    must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+   <p>Protection level: dangerous -->
+    <permission android:name="android.permission.READ_MEDIA_VIDEO"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_readMediaVideo"
+                android:description="@string/permdesc_readMediaVideo"
+                android:protectionLevel="dangerous" />
+
+    <!-- Allows an application to read image files from external storage.
+      <p>This permission is enforced starting in API level
+      {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+      For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+      targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+      must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+     <p>Protection level: dangerous -->
+    <permission android:name="android.permission.READ_MEDIA_IMAGE"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_readMediaImage"
+                android:description="@string/permdesc_readMediaImage"
+                android:protectionLevel="dangerous" />
+
     <!-- Allows an application to write to external storage.
          <p class="note"><strong>Note:</strong> If <em>both</em> your <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
@@ -2306,6 +2362,26 @@
     <permission android:name="android.permission.MANAGE_FACTORY_RESET_PROTECTION"
         android:protectionLevel="signature|privileged"/>
 
+    <!-- ======================================== -->
+    <!-- Permissions for lost mode -->
+    <!-- ======================================== -->
+    <eat-comment />
+
+    <!-- @SystemApi Allows an application to trigger lost mode on an organization-owned device.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.TRIGGER_LOST_MODE"
+        android:protectionLevel="signature|role"/>
+
+    <!-- @SystemApi Allows an application to instruct the framework to send location to the device
+         admin when an organization-owned device is in lost mode.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES"
+        android:protectionLevel="signature|privileged"/>
+
     <!-- ================================== -->
     <!-- Permissions for accessing hardware -->
     <!-- ================================== -->
@@ -4130,6 +4206,13 @@
     <permission android:name="android.permission.BIND_TV_INPUT"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Must be required by a {@link android.media.tv.interactive.TvInteractiveAppService}
+         to ensure that only the system can bind to it.
+         <p>Protection level: signature|privileged
+    -->
+    <permission android:name="android.permission.BIND_TV_INTERACTIVE_APP"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi
          Must be required by a {@link com.android.media.tv.remoteprovider.TvRemoteProvider}
          to ensure that only the system can bind to it.
@@ -6200,6 +6283,9 @@
          <p>Not for use by third-party applications.</p> -->
     <attribution android:tag="MusicRecognitionManagerService"
         android:label="@string/music_recognition_manager_service"/>
+    <!-- Attribution for Device Policy Manager service. -->
+    <attribution android:tag="DevicePolicyManagerService"
+        android:label="@string/device_policy_manager_service"/>
 
     <application android:process="system"
                  android:persistent="true"
@@ -6691,7 +6777,7 @@
         </service>
 
         <!-- TODO: Move to ExtServices or relevant component. -->
-        <service android:name="com.android.server.selectiontoolbar.DefaultSelectionToolbarRenderService"
+        <service android:name="android.service.selectiontoolbar.DefaultSelectionToolbarRenderService"
                  android:permission="android.permission.BIND_SELECTION_TOOLBAR_RENDER_SERVICE"
                  android:process=":ui"
                  android:exported="false">
diff --git a/core/res/res/drawable/perm_group_read_media_aural.xml b/core/res/res/drawable/perm_group_read_media_aural.xml
new file mode 100644
index 0000000..6fc9c69
--- /dev/null
+++ b/core/res/res/drawable/perm_group_read_media_aural.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M10,21q-1.65,0 -2.825,-1.175Q6,18.65 6,17q0,-1.65 1.175,-2.825Q8.35,13 10,13q0.575,0 1.063,0.137 0.487,0.138 0.937,0.413V3h6v4h-4v10q0,1.65 -1.175,2.825Q11.65,21 10,21z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/perm_group_read_media_visual.xml b/core/res/res/drawable/perm_group_read_media_visual.xml
new file mode 100644
index 0000000..a5db271
--- /dev/null
+++ b/core/res/res/drawable/perm_group_read_media_visual.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2zM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18zM8,16h12L20,4L8,4v12zM4,22q-0.825,0 -1.413,-0.587Q2,20.825 2,20L2,6h2v14h14v2zM8,4v12L8,4z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8696f5a..7916ef4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3355,6 +3355,14 @@
             <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" />
+
+        <!-- <p>Whether or not the auto handwriting initiation is enabled in this View.
+             <p>For a view with active {@link android.view.inputmethod.InputConnection},
+             if auto handwriting initiation is enabled stylus movement within its view boundary
+             will automatically trigger the handwriting mode.
+             <p>This is true by default.
+             See {@link android.view.View#setAutoHandwritingEnabled}. -->
+        <attr name="autoHandwritingEnabled" format="boolean" />
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c0c8618..5fee1fa 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -134,9 +134,6 @@
          be sent during a change to the audio output device. -->
     <bool name="config_sendAudioBecomingNoisy">true</bool>
 
-    <!-- Whether Hearing Aid profile is supported -->
-    <bool name="config_hearing_aid_profile_supported">false</bool>
-
     <!-- Flag to disable all transition animations -->
     <bool name="config_disableTransitionAnimation">false</bool>
 
@@ -404,10 +401,6 @@
     <string-array translatable="false" name="config_tether_bluetooth_regexs">
     </string-array>
 
-    <!-- Max number of Bluetooth tethering connections allowed. If this is
-         updated config_tether_dhcp_range has to be updated appropriately. -->
-    <integer translatable="false" name="config_max_pan_devices">5</integer>
-
     <!-- This setting is deprecated, please use
          com.android.networkstack.tethering.R.array.config_dhcp_range instead. -->
     <string-array translatable="false" name="config_tether_dhcp_range">
@@ -1944,53 +1937,38 @@
     <!-- Integer to set a max latency the accelerometer will batch sensor requests with. -->
     <integer name="config_flipToScreenOffMaxLatencyMicros">2000000</integer>
 
-    <!-- Boolean indicating if current platform supports bluetooth SCO for off call
-    use cases -->
+    <!-- Note: This config is deprecated
+          Boolean indicating if current platform supports bluetooth SCO for off call
+          use cases
+    -->
     <bool name="config_bluetooth_sco_off_call">true</bool>
 
-    <!-- Boolean indicating if current platform supports bluetooth wide band
-         speech -->
-    <bool name="config_bluetooth_wide_band_speech">true</bool>
-
-    <!-- Boolean indicating if current platform need do one-time bluetooth address
-         re-validation -->
+    <!-- Note: This config is deprecated
+          Boolean indicating if current platform need do one-time bluetooth address
+          re-validation
+    -->
     <bool name="config_bluetooth_address_validation">false</bool>
 
-    <!-- Boolean indicating if current platform supports BLE peripheral mode -->
-    <bool name="config_bluetooth_le_peripheral_mode_supported">false</bool>
-
-    <!-- Boolean indicating if current platform supports HFP inband ringing -->
-    <bool name="config_bluetooth_hfp_inband_ringing_support">false</bool>
-
-    <!-- Max number of scan filters supported by blutooth controller. 0 if the
-         device does not support hardware scan filters-->
-    <integer translatable="false" name="config_bluetooth_max_scan_filters">0</integer>
-
-    <!-- Max number of advertisers supported by bluetooth controller. 0 if the
-         device does not support multiple advertisement-->
-    <integer translatable="false" name="config_bluetooth_max_advertisers">0</integer>
-
-    <!-- Idle current for bluetooth controller. 0 by default-->
+    <!-- Note: This config is deprecated, use BluetoothProperties instead.
+         Idle current for bluetooth controller. 0 by default
+    -->
     <integer translatable="false" name="config_bluetooth_idle_cur_ma">0</integer>
 
-    <!-- Rx current for bluetooth controller. 0 by default-->
+    <!-- Note: This config is deprecated, use BluetoothProperties instead.
+         Rx current for bluetooth controller. 0 by default
+    -->
     <integer translatable="false" name="config_bluetooth_rx_cur_ma">0</integer>
 
-    <!-- Tx current for bluetooth controller. 0 by default-->
+    <!-- Note: This config is deprecated, use BluetoothProperties instead.
+         Tx current for bluetooth controller. 0 by default
+    -->
     <integer translatable="false" name="config_bluetooth_tx_cur_ma">0</integer>
 
-    <!-- Operating volatage for bluetooth controller. 0 by default-->
+    <!-- Note: This config is deprecated, use BluetoothProperties instead.
+         Operating volatage for bluetooth controller. 0 by default
+    -->
     <integer translatable="false" name="config_bluetooth_operating_voltage_mv">0</integer>
 
-    <!-- Max number of connected audio devices supported by Bluetooth stack -->
-    <integer name="config_bluetooth_max_connected_audio_devices">5</integer>
-
-    <!-- Whether supported profiles should be reloaded upon enabling bluetooth -->
-    <bool name="config_bluetooth_reload_supported_profiles_when_enabled">false</bool>
-
-    <!-- Enabling autoconnect over pan -->
-    <bool name="config_bluetooth_pan_enable_autoconnect">false</bool>
-
     <!-- The default data-use polling period. -->
     <integer name="config_datause_polling_period_sec">600</integer>
 
@@ -2144,10 +2122,6 @@
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string>
 
-    <!-- Enable/disable default bluetooth profiles:
-        HSP_AG, ObexObjectPush, Audio, NAP -->
-    <bool name="config_bluetooth_default_profiles">true</bool>
-
     <!-- IP address of the dns server to use if nobody else suggests one -->
     <string name="config_default_dns_server" translatable="false">8.8.8.8</string>
 
@@ -4325,6 +4299,10 @@
          or empty if the default should be used. -->
     <string translatable="false" name="config_deviceSpecificDisplayAreaPolicyProvider"></string>
 
+    <!-- Class name of the device specific implementation of DeviceStatePolicy.Provider
+        or empty if the default should be used. -->
+    <string translatable="false" name="config_deviceSpecificDeviceStatePolicyProvider"></string>
+
     <!-- Component name of media projection permission dialog -->
     <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
 
@@ -4353,8 +4331,6 @@
     <!-- Component name that should be granted Notification Assistant access -->
     <string name="config_defaultAssistantAccessComponent" translatable="false">android.ext.services/android.ext.services.notification.Assistant</string>
 
-    <bool name="config_supportBluetoothPersistedState">true</bool>
-
     <bool name="config_keepRestrictedProfilesInBackground">true</bool>
 
     <!-- Cellular network service package name to bind to by default. -->
@@ -4624,6 +4600,18 @@
     <array name="config_displayWhiteBalanceDisplayColorTemperatures">
     </array>
 
+    <!-- See DisplayWhiteBalanceController.
+         The same as config_displayWhiteBalanceAmbientColorTemperatures, but with a stronger
+         visual adjustment. -->
+    <array name="config_displayWhiteBalanceStrongAmbientColorTemperatures">
+    </array>
+
+    <!-- See DisplayWhiteBalanceController.
+         The same as config_displayWhiteBalanceDisplayColorTemperatures, but with a stronger
+         visual adjustment. -->
+    <array name="config_displayWhiteBalanceStrongDisplayColorTemperatures">
+    </array>
+
     <!-- All of the paths defined for the batterymeter are defined on a 12x20 canvas, and must
      be parsable by android.utill.PathParser -->
     <string name="config_batterymeterPerimeterPath" translatable="false">
@@ -4968,6 +4956,9 @@
          Used by ChooserActivity. -->
     <string translatable="false" name="config_defaultNearbySharingComponent"></string>
 
+    <!-- URI used for Nearby Share SliceProvider scanning. -->
+    <string translatable="false" name="config_defaultNearbySharingSliceUri"></string>
+
     <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
          check after reboot or airplane mode toggling -->
     <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">false</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 3f08e4b..d374b74 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -755,6 +755,8 @@
     <dimen name="item_touch_helper_swipe_escape_velocity">120dp</dimen>
     <dimen name="item_touch_helper_swipe_escape_max_velocity">800dp</dimen>
 
+    <!-- The maximum size of the small notification icon. -->
+    <dimen name="notification_small_icon_size">48dp</dimen>
     <!-- The maximum height of any image in a remote view. This is applied to all images in custom remoteviews. This value is determined by the maximum notification height -->
     <dimen name="notification_custom_view_max_image_height">284dp</dimen>
     <!-- The maximum height of any image in a remote view. This is applied to all images in custom remoteviews. This value is determined a maximum notification width -->
@@ -779,6 +781,8 @@
     <!-- The alpha of a disabled notification button -->
     <item type="dimen" format="float" name="notification_action_disabled_alpha">0.5</item>
 
+    <!-- The maximum size of the small notification icon on low memory devices. -->
+    <dimen name="notification_small_icon_size_low_ram">@dimen/notification_small_icon_size</dimen>
     <!-- The maximum height of any image in a remote view. This is applied to all images in custom remoteviews. -->
     <dimen name="notification_custom_view_max_image_height_low_ram">208dp</dimen>
     <!-- The maximum height of any image in a remote view. This is applied to all images in custom remoteviews. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 505fe59..655deac 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3259,6 +3259,7 @@
     <public name="showBackground" />
     <public name="inheritKeyStoreKeys" />
     <public name="preferKeepClear" />
+    <public name="autoHandwritingEnabled" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01de0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1a5d8b7..99451b0 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -197,7 +197,7 @@
     <string name="ThreeWCMmi">Three way calling</string>
     <string name="RuacMmi">Rejection of undesired annoying calls</string>
     <string name="CndMmi">Calling number delivery</string>
-    <string name="DndMmi">Do not disturb</string>
+    <string name="DndMmi" translatable="false">Priority mode</string>
 
     <!-- Displayed to confirm to the user that caller ID will be restricted on the next call as usual. -->
     <string name="CLIRDefaultOnNextCallOn">Caller ID defaults to restricted. Next call: Restricted</string>
@@ -521,6 +521,8 @@
     <string name="twilight_service">Twilight Service</string>
     <!-- Attribution for Gnss Time Update service. [CHAR LIMIT=NONE]-->
     <string name="gnss_time_update_service">GNSS Time Update Service</string>
+    <!-- Attribution for Device Policy Manager service. [CHAR LIMIT=NONE]-->
+    <string name="device_policy_manager_service">Device Policy Manager Service</string>
 
     <!-- Attribution for MusicRecognitionManagerService. [CHAR LIMIT=NONE]-->
     <string name="music_recognition_manager_service">Music Recognition Manager Service</string>
@@ -876,6 +878,16 @@
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgroupdesc_storage">access photos, media, and files on your device</string>
 
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
+    <string name="permgrouplab_readMediaAural">Music &amp; other audio</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
+    <string name="permgroupdesc_readMediaAural">access audio files on your device</string>
+
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
+    <string name="permgrouplab_readMediaVisual">Photos &amp; videos</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
+    <string name="permgroupdesc_readMediaVisual">access images and video files on your device</string>
+
     <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgrouplab_microphone">Microphone</string>
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1889,6 +1901,21 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
     <string name="permdesc_sdcardRead">Allows the app to read the contents of your shared storage.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permlab_readMediaAudio">read audio files from shared storage</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permdesc_readMediaAudio">Allows the app to read audio files from your shared storage.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permlab_readMediaVideo">read video files from shared storage</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permdesc_readMediaVideo">Allows the app to read video files from your shared storage.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permlab_readMediaImage">read image files from shared storage</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permdesc_readMediaImage">Allows the app to read image files from your shared storage.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
     <string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
@@ -2002,9 +2029,9 @@
     <string name="permdesc_bindCarrierServices">Allows the holder to bind to carrier services. Should never be needed for normal apps.</string>
 
     <!-- Title of an application permission, for applications that wish to access notification policy. -->
-    <string name="permlab_access_notification_policy">access Do Not Disturb</string>
+    <string name="permlab_access_notification_policy" translatable="false">access Priority mode</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_access_notification_policy">Allows the app to read and write Do Not Disturb configuration.</string>
+    <string name="permdesc_access_notification_policy" translatable="false">Allows the app to read and write Priority mode configuration.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_startViewPermissionUsage">start view permission usage</string>
@@ -5267,7 +5294,7 @@
     <string name="zen_mode_forever">Until you turn off</string>
 
     <!-- Zen mode condition: no exit criteria, includes the name of the feature for emphasis. [CHAR LIMIT=NONE] -->
-    <string name="zen_mode_forever_dnd">Until you turn off Do Not Disturb</string>
+    <string name="zen_mode_forever_dnd" translatable="false">Until you turn off Priority mode</string>
 
     <!-- Zen mode active automatic rule name separator. [CHAR LIMIT=NONE] -->
     <string name="zen_mode_rule_name_combination"><xliff:g id="first" example="Weeknights">%1$s</xliff:g> / <xliff:g id="rest" example="Meetings">%2$s</xliff:g></string>
@@ -5276,7 +5303,7 @@
     <string name="toolbar_collapse_description">Collapse</string>
 
     <!-- Zen mode - feature name. [CHAR LIMIT=40] -->
-    <string name="zen_mode_feature_name">Do not disturb</string>
+    <string name="zen_mode_feature_name" translatable="false">Priority mode</string>
 
     <!-- Zen mode - downtime legacy feature name. [CHAR LIMIT=40] -->
     <string name="zen_mode_downtime_feature_name">Downtime</string>
@@ -5692,14 +5719,14 @@
 
     <!-- Title for the notification channel notifying user of settings system changes. [CHAR LIMIT=NONE] -->
     <string name="notification_channel_system_changes">System changes</string>
-    <!-- Title for the notification channel notifying user of do not disturb system changes (i.e. Do Not Disturb has changed). [CHAR LIMIT=NONE] -->
-    <string name="notification_channel_do_not_disturb">Do Not Disturb</string>
-    <!-- Title of notification indicating do not disturb visual interruption settings have changed when upgrading to P -->
-    <string name="zen_upgrade_notification_visd_title">New: Do Not Disturb is hiding notifications</string>
+    <!-- Title for the notification channel notifying user of priority mode system changes (i.e. Priority mode has changed). [CHAR LIMIT=NONE] -->
+    <string name="notification_channel_do_not_disturb" translatable="false">Priority mode</string>
+    <!-- Title of notification indicating Priority mode visual interruption settings have changed when upgrading to P -->
+    <string name="zen_upgrade_notification_visd_title" translatable="false">New: Priority mode is hiding notifications</string>
     <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
     <string name="zen_upgrade_notification_visd_content">Tap to learn more and change.</string>
-    <!-- Title of notification indicating do not disturb settings have changed when upgrading to P -->
-    <string name="zen_upgrade_notification_title">Do Not Disturb has changed</string>
+    <!-- Title of notification indicating priority mode settings have changed when upgrading to P -->
+    <string name="zen_upgrade_notification_title" translatable="false">Priority mode has changed</string>
     <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
     <string name="zen_upgrade_notification_content">Tap to check what\'s blocked.</string>
 
@@ -5740,7 +5767,7 @@
     <!-- Label of notification action button to learn more about the enhanced notifications [CHAR LIMIT=20] -->
     <string name="nas_upgrade_notification_learn_more_action">Learn more</string>
     <!-- Content of notification learn more dialog about the enhanced notifications [CHAR LIMIT=NONE] -->
-    <string name="nas_upgrade_notification_learn_more_content">Enhanced notifications replaced Android Adaptive Notifications in Android 12. This feature shows suggested actions and replies, and organizes your notifications.\n\nEnhanced notifications can access notification content, including personal information like contact names and messages. This feature can also dismiss or respond to notifications, such as answering phone calls, and control Do Not Disturb.</string>
+    <string name="nas_upgrade_notification_learn_more_content" translatable="false">Enhanced notifications replaced Android Adaptive Notifications in Android 12. This feature shows suggested actions and replies, and organizes your notifications.\n\nEnhanced notifications can access notification content, including personal information like contact names and messages. This feature can also dismiss or respond to notifications, such as answering phone calls, and control Priority mode.</string>
 
 
     <!-- Dynamic mode battery saver strings -->
@@ -6199,4 +6226,19 @@
     <string name="ui_translation_accessibility_translated_text"><xliff:g id="message" example="Hello">%1$s</xliff:g> Translated.</string>
     <!-- Accessibility message announced to notify the user when the system has finished translating the content displayed on the screen to a different language after the user requested translation. [CHAR LIMIT=NONE] -->
     <string name="ui_translation_accessibility_translation_finished">Message translated from <xliff:g id="from_language" example="English">%1$s</xliff:g> to <xliff:g id="to_language" example="French">%2$s</xliff:g>.</string>
+
+    <!-- Title for the notification channel notifying user of abusive background apps. [CHAR LIMIT=NONE] -->
+    <string name="notification_channel_abusive_bg_apps">Background Activity</string>
+    <!-- Title of notification indicating abusive background apps. [CHAR LIMIT=NONE] -->
+    <string name="notification_title_abusive_bg_apps">Background Activity</string>
+    <!-- Content of notification indicating abusive background apps. [CHAR LIMIT=NONE] -->
+    <string name="notification_content_abusive_bg_apps">
+        <xliff:g id="app" example="Gmail">%1$s</xliff:g> is running in the background and draining battery. Tap to review.
+    </string>
+    <!-- Content of notification indicating long running foreground service. [CHAR LIMIT=NONE] -->
+    <string name="notification_content_long_running_fgs">
+        <xliff:g id="app" example="Gmail">%1$s</xliff:g> is running in the background for a long time. Tap to review.
+    </string>
+    <!-- Action label of notification for user to check background apps. [CHAR LIMIT=NONE]  -->
+    <string name="notification_action_check_bg_apps">Check active apps</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8c8ef12..c22d1ed 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -279,8 +279,6 @@
   <java-symbol type="bool" name="config_flipToScreenOffEnabled" />
   <java-symbol type="integer" name="config_flipToScreenOffMaxLatencyMicros" />
   <java-symbol type="bool" name="config_bluetooth_sco_off_call" />
-  <java-symbol type="bool" name="config_bluetooth_le_peripheral_mode_supported" />
-  <java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" />
   <java-symbol type="bool" name="config_cellBroadcastAppLinks" />
   <java-symbol type="bool" name="config_duplicate_port_omadm_wappush" />
   <java-symbol type="bool" name="config_disableTransitionAnimation" />
@@ -344,7 +342,6 @@
   <java-symbol type="integer" name="config_timeZoneRulesCheckRetryCount" />
   <java-symbol type="bool" name="config_sendAudioBecomingNoisy" />
   <java-symbol type="bool" name="config_enableScreenshotChord" />
-  <java-symbol type="bool" name="config_bluetooth_default_profiles" />
   <java-symbol type="bool" name="config_enableWifiDisplay" />
   <java-symbol type="bool" name="config_allowAnimationsInLowPowerMode" />
   <java-symbol type="bool" name="config_useDevInputEventForAudioJack" />
@@ -418,9 +415,6 @@
   <java-symbol type="dimen" name="config_pictureInPictureMaxAspectRatio" />
   <java-symbol type="integer" name="config_pictureInPictureMaxNumberOfActions" />
   <java-symbol type="dimen" name="config_closeToSquareDisplayMaxAspectRatio" />
-  <java-symbol type="integer" name="config_bluetooth_max_advertisers" />
-  <java-symbol type="integer" name="config_bluetooth_max_scan_filters" />
-  <java-symbol type="integer" name="config_bluetooth_max_connected_audio_devices" />
   <java-symbol type="integer" name="config_burnInProtectionMinHorizontalOffset" />
   <java-symbol type="integer" name="config_burnInProtectionMaxHorizontalOffset" />
   <java-symbol type="integer" name="config_burnInProtectionMinVerticalOffset" />
@@ -430,9 +424,6 @@
   <java-symbol type="integer" name="config_bluetooth_rx_cur_ma" />
   <java-symbol type="integer" name="config_bluetooth_tx_cur_ma" />
   <java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" />
-  <java-symbol type="bool" name="config_bluetooth_pan_enable_autoconnect" />
-  <java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" />
-  <java-symbol type="bool" name="config_hearing_aid_profile_supported" />
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_drawLockTimeoutMillis" />
   <java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
@@ -451,7 +442,6 @@
   <java-symbol type="integer" name="config_wakeUpToLastStateTimeoutMillis" />
   <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" />
   <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAbsolute" />
-  <java-symbol type="integer" name="config_max_pan_devices" />
   <java-symbol type="integer" name="config_ntpPollingInterval" />
   <java-symbol type="integer" name="config_ntpPollingIntervalShorter" />
   <java-symbol type="integer" name="config_ntpRetry" />
@@ -3571,6 +3561,7 @@
   <java-symbol type="style" name="Theme.DeviceDefault.Autofill.Save" />
   <java-symbol type="style" name="Theme.DeviceDefault.Light.Autofill.Save" />
 
+  <java-symbol type="dimen" name="notification_small_icon_size"/>
   <java-symbol type="dimen" name="notification_big_picture_max_height"/>
   <java-symbol type="dimen" name="notification_big_picture_max_width"/>
   <java-symbol type="dimen" name="notification_right_icon_size"/>
@@ -3579,6 +3570,7 @@
   <java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
 
+  <java-symbol type="dimen" name="notification_small_icon_size_low_ram"/>
   <java-symbol type="dimen" name="notification_big_picture_max_height_low_ram"/>
   <java-symbol type="dimen" name="notification_big_picture_max_width_low_ram"/>
   <java-symbol type="dimen" name="notification_right_icon_size_low_ram"/>
@@ -3842,8 +3834,6 @@
 
   <java-symbol type="string" name="config_defaultAssistantAccessComponent" />
 
-  <java-symbol type="bool" name="config_supportBluetoothPersistedState" />
-
   <java-symbol type="string" name="slices_permission_request" />
 
   <java-symbol type="string" name="screenshot_edit" />
@@ -3992,6 +3982,8 @@
   <java-symbol type="dimen" name="config_displayWhiteBalanceHighLightAmbientColorTemperature" />
   <java-symbol type="array" name="config_displayWhiteBalanceAmbientColorTemperatures" />
   <java-symbol type="array" name="config_displayWhiteBalanceDisplayColorTemperatures" />
+  <java-symbol type="array" name="config_displayWhiteBalanceStrongAmbientColorTemperatures" />
+  <java-symbol type="array" name="config_displayWhiteBalanceStrongDisplayColorTemperatures" />
   <java-symbol type="drawable" name="ic_action_open" />
   <java-symbol type="drawable" name="ic_menu_copy_material" />
 
@@ -4667,4 +4659,12 @@
   <java-symbol type="bool" name="config_enableSafetyCenter" />
 
   <java-symbol type="string" name="config_deviceManagerUpdater" />
+
+  <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
+
+  <java-symbol type="string" name="notification_channel_abusive_bg_apps"/>
+  <java-symbol type="string" name="notification_title_abusive_bg_apps"/>
+  <java-symbol type="string" name="notification_content_abusive_bg_apps"/>
+  <java-symbol type="string" name="notification_content_long_running_fgs"/>
+  <java-symbol type="string" name="notification_action_check_bg_apps"/>
 </resources>
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
index e230a54..23b12cf 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
+++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
@@ -68,6 +68,7 @@
                 bus.getStatsEndTimestamp() - bus.getStatsStartTimestamp(),
                 proto.sessionDurationMillis);
         assertEquals(bus.getDischargePercentage(), proto.sessionDischargePercentage);
+        assertEquals(bus.getDischargeDurationMs(), proto.dischargeDurationMillis);
 
         assertEquals(3, proto.deviceBatteryConsumer.powerComponents.length); // Only 3 are non-empty
         assertSameBatteryConsumer("For deviceBatteryConsumer",
@@ -215,6 +216,7 @@
                         /* includeProcessStats */true)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
+                        .setDischargeDurationMs(1234)
                         .setStatsStartTimestamp(1000);
         final UidBatteryConsumer.Builder uidBuilder = builder.getOrCreateUidBatteryConsumerBuilder(
                 batteryStatsUid0)
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 37cf514..e6d2364 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -39,6 +39,7 @@
 import android.content.LocusId;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
+import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
@@ -504,6 +505,22 @@
     }
 
     @Test
+    public void testBuild_ensureSmallIconIsNotTooBig_resizesIcon() {
+        Icon hugeIcon = Icon.createWithBitmap(
+                Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
+        Notification notification = new Notification.Builder(mContext, "Channel").setSmallIcon(
+                hugeIcon).build();
+
+        Bitmap smallNotificationIcon = notification.getSmallIcon().getBitmap();
+        assertThat(smallNotificationIcon.getWidth()).isEqualTo(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_small_icon_size));
+        assertThat(smallNotificationIcon.getHeight()).isEqualTo(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_small_icon_size));
+    }
+
+    @Test
     public void testColors_ensureColors_dayMode_producesValidPalette() {
         Notification.Colors c = new Notification.Colors();
         boolean colorized = false;
diff --git a/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
index 09985a8..aa188f2 100644
--- a/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
+++ b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
@@ -116,25 +116,4 @@
         @Override
         public void onLowMemory() {}
     }
-
-    private static class TestComponentCallbacks2 implements ComponentCallbacks2 {
-        private Configuration mConfiguration;
-        private boolean mLowMemoryCalled;
-        private int mLevel;
-
-        @Override
-        public void onConfigurationChanged(@NonNull Configuration newConfig) {
-            mConfiguration = newConfig;
-        }
-
-        @Override
-        public void onLowMemory() {
-            mLowMemoryCalled = true;
-        }
-
-        @Override
-        public void onTrimMemory(int level) {
-            mLevel = level;
-        }
-    }
 }
diff --git a/core/tests/coretests/src/android/content/ContextWrapperTest.java b/core/tests/coretests/src/android/content/ContextWrapperTest.java
new file mode 100644
index 0000000..ecaf1f4
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ContextWrapperTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import static android.content.ContextWrapper.COMPONENT_CALLBACK_ON_WRAPPER;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.WindowConfiguration;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.window.WindowContext;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+
+/**
+ *  Build/Install/Run:
+ *   atest FrameworksCoreTests:ContextWrapperTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ContextWrapperTest {
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    /**
+     * Before {@link android.os.Build.VERSION_CODES#TIRAMISU}, {@link ContextWrapper} must
+     * register {@link ComponentCallbacks} to {@link ContextWrapper#getApplicationContext} before
+     * {@link ContextWrapper#attachBaseContext(Context)}.
+     */
+    @DisableCompatChanges(COMPONENT_CALLBACK_ON_WRAPPER)
+    @Test
+    public void testRegisterComponentCallbacksWithoutBaseContextBeforeT() {
+        final ContextWrapper wrapper = new TestContextWrapper(null /* base */);
+        final ComponentCallbacks callbacks = new TestComponentCallbacks2();
+
+        // It should be no-op if unregister a ComponentCallbacks without registration.
+        wrapper.unregisterComponentCallbacks(callbacks);
+
+        wrapper.registerComponentCallbacks(callbacks);
+
+        assertThat(wrapper.mCallbacksRegisteredToSuper.size()).isEqualTo(1);
+        assertThat(wrapper.mCallbacksRegisteredToSuper.get(0)).isEqualTo(callbacks);
+
+        wrapper.unregisterComponentCallbacks(callbacks);
+
+        assertThat(wrapper.mCallbacksRegisteredToSuper.isEmpty()).isTrue();
+    }
+
+    /**
+     * After {@link android.os.Build.VERSION_CODES#TIRAMISU}, {@link ContextWrapper} must
+     * throw {@link IllegalStateException} before {@link ContextWrapper#attachBaseContext(Context)}.
+     */
+    @Test
+    public void testRegisterComponentCallbacksWithoutBaseContextAfterT() {
+        final ContextWrapper wrapper = new TestContextWrapper(null /* base */);
+        final ComponentCallbacks callbacks = new TestComponentCallbacks2();
+
+        try {
+            wrapper.unregisterComponentCallbacks(callbacks);
+            fail("ContextWrapper#unregisterComponentCallbacks must throw Exception before"
+                    + " ContextWrapper#attachToBaseContext.");
+        } catch (IllegalStateException ignored) {
+            // It is expected to throw IllegalStateException.
+        }
+
+        try {
+            wrapper.registerComponentCallbacks(callbacks);
+            fail("ContextWrapper#registerComponentCallbacks must throw Exception before"
+                    + " ContextWrapper#attachToBaseContext.");
+        } catch (IllegalStateException ignored) {
+            // It is expected to throw IllegalStateException.
+        }
+    }
+
+    /**
+     * {@link ContextWrapper#registerComponentCallbacks(ComponentCallbacks)} must delegate to its
+     * {@link ContextWrapper#getBaseContext()}, so does
+     * {@link ContextWrapper#unregisterComponentCallbacks(ComponentCallbacks)}.
+     */
+    @Test
+    public void testRegisterComponentCallbacks() {
+        final Context appContext = ApplicationProvider.getApplicationContext();
+        final Display display = appContext.getSystemService(DisplayManager.class)
+                .getDisplay(DEFAULT_DISPLAY);
+        final WindowContext windowContext = (WindowContext) appContext.createWindowContext(display,
+                TYPE_APPLICATION_OVERLAY, null /* options */);
+        final ContextWrapper wrapper = new ContextWrapper(windowContext);
+        final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
+
+        wrapper.registerComponentCallbacks(callbacks);
+
+        assertThat(wrapper.mCallbacksRegisteredToSuper).isNull();
+
+        final Configuration dispatchedConfig = new Configuration();
+        dispatchedConfig.fontScale = 1.2f;
+        dispatchedConfig.windowConfiguration.setWindowingMode(
+                WindowConfiguration.WINDOWING_MODE_FREEFORM);
+        dispatchedConfig.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+        windowContext.dispatchConfigurationChanged(dispatchedConfig);
+
+        assertThat(callbacks.mConfiguration).isEqualTo(dispatchedConfig);
+    }
+
+    private static class TestContextWrapper extends ContextWrapper {
+        TestContextWrapper(Context base) {
+            super(base);
+        }
+
+        @Override
+        public Context getApplicationContext() {
+            // The default implementation of ContextWrapper#getApplicationContext is to delegate
+            // to the base Context, and it leads to NPE if #registerComponentCallbacks is called
+            // directly before attach to base Context.
+            // We call to ApplicationProvider#getApplicationContext to prevent NPE because
+            // developers may have its implementation to prevent NPE without attaching base Context.
+            final Context baseContext = getBaseContext();
+            if (baseContext == null) {
+                return ApplicationProvider.getApplicationContext();
+            } else {
+                return super.getApplicationContext();
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
new file mode 100644
index 0000000..6ae7fc4
--- /dev/null
+++ b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.res.Configuration;
+
+import androidx.annotation.NonNull;
+
+class TestComponentCallbacks2 implements ComponentCallbacks2 {
+    android.content.res.Configuration mConfiguration;
+    boolean mLowMemoryCalled;
+    int mLevel;
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        mConfiguration = newConfig;
+    }
+
+    @Override
+    public void onLowMemory() {
+        mLowMemoryCalled = true;
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        mLevel = level;
+    }
+}
diff --git a/core/tests/coretests/src/android/content/pm/AppSearchPersonTest.java b/core/tests/coretests/src/android/content/pm/AppSearchShortcutPersonTest.java
similarity index 92%
rename from core/tests/coretests/src/android/content/pm/AppSearchPersonTest.java
rename to core/tests/coretests/src/android/content/pm/AppSearchShortcutPersonTest.java
index 5ba9059..3c49481 100644
--- a/core/tests/coretests/src/android/content/pm/AppSearchPersonTest.java
+++ b/core/tests/coretests/src/android/content/pm/AppSearchShortcutPersonTest.java
@@ -24,7 +24,7 @@
 import org.junit.Test;
 
 @Presubmit
-public class AppSearchPersonTest {
+public class AppSearchShortcutPersonTest {
 
     @Test
     public void testBuildPersonAndGetValue() {
@@ -32,7 +32,7 @@
         final String key = "key";
         final String uri = "name:name";
 
-        final Person person = new AppSearchPerson.Builder(uri)
+        final Person person = new AppSearchShortcutPerson.Builder(uri)
                 .setName(name)
                 .setKey(key)
                 .setIsBot(true)
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index 10cec82..104f077 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -16,6 +16,9 @@
 
 package android.os;
 
+import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
+import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -32,6 +35,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.Uri;
+import android.os.VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.StepSegment;
@@ -43,6 +47,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.time.Duration;
+
 @Presubmit
 @RunWith(MockitoJUnitRunner.class)
 public class VibrationEffectTest {
@@ -122,16 +128,7 @@
         VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1).validate();
         VibrationEffect.createWaveform(new long[]{10, 10}, new int[] {0, 0}, -1).validate();
         VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0).validate();
-        VibrationEffect.startWaveform()
-                .addStep(/* amplitude= */ 1, /* duration= */ 10)
-                .addRamp(/* amplitude= */ 0, /* duration= */ 20)
-                .addStep(/* amplitude= */ 1, /* frequencyHz= */ 1, /* duration= */ 100)
-                .addRamp(/* amplitude= */ 0.5f, /* frequencyHz= */ 100, /* duration= */ 50)
-                .build()
-                .validate();
 
-        assertThrows(IllegalStateException.class,
-                () -> VibrationEffect.startWaveform().build().validate());
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.createWaveform(new long[0], new int[0], -1).validate());
         assertThrows(IllegalArgumentException.class,
@@ -145,27 +142,31 @@
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.createWaveform(
                         TEST_TIMINGS, TEST_AMPLITUDES, TEST_TIMINGS.length).validate());
+    }
+
+    @Test
+    public void testValidateWaveformBuilder() {
+        VibrationEffect.startWaveform(targetAmplitude(1))
+                .addTransition(Duration.ofSeconds(1), targetAmplitude(0.5f), targetFrequency(100))
+                .addTransition(Duration.ZERO, targetAmplitude(0f), targetFrequency(200))
+                .addSustain(Duration.ofMinutes(2))
+                .addTransition(Duration.ofMillis(10), targetAmplitude(1f), targetFrequency(50))
+                .addSustain(Duration.ofMillis(1))
+                .addTransition(Duration.ZERO, targetFrequency(150))
+                .addSustain(Duration.ofMillis(2))
+                .addTransition(Duration.ofSeconds(15), targetAmplitude(1))
+                .build()
+                .validate();
+
+        assertThrows(IllegalStateException.class,
+                () -> VibrationEffect.startWaveform().build().validate());
+        assertThrows(IllegalArgumentException.class, () -> targetAmplitude(-2));
+        assertThrows(IllegalArgumentException.class, () -> targetFrequency(0));
         assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveform()
-                        .addStep(/* amplitude= */ -2, 10).build().validate());
+                () -> VibrationEffect.startWaveform().addTransition(
+                        Duration.ofMillis(-10), targetAmplitude(1)).build().validate());
         assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveform()
-                        .addStep(1, /* frequencyHz= */ -1f, 10).build().validate());
-        assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveform()
-                        .addStep(1, /* duration= */ -1).build().validate());
-        assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveform()
-                        .addStep(1, 100f, /* duration= */ -1).build().validate());
-        assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveform()
-                        .addRamp(/* amplitude= */ -3, 10).build().validate());
-        assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveform()
-                        .addRamp(1, /* frequencyHz= */ 0, 10).build().validate());
-        assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.startWaveform()
-                        .addRamp(1, 10f, /* duration= */ -3).build().validate());
+                () -> VibrationEffect.startWaveform().addSustain(Duration.ZERO).build().validate());
     }
 
     @Test
@@ -174,14 +175,24 @@
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
                 .addEffect(TEST_ONE_SHOT)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addEffect(TEST_WAVEFORM, 100)
+                .addOffDuration(Duration.ofMillis(100))
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .compose()
                 .validate();
 
+        VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(TEST_ONE_SHOT)
+                .compose()
+                .validate();
+
         assertThrows(IllegalStateException.class,
                 () -> VibrationEffect.startComposition().compose().validate());
+        assertThrows(IllegalStateException.class,
+                () -> VibrationEffect.startComposition()
+                        .addOffDuration(Duration.ofSeconds(0))
+                        .compose()
+                        .validate());
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.startComposition().addPrimitive(-1).compose().validate());
         assertThrows(IllegalArgumentException.class,
@@ -196,12 +207,27 @@
                         .validate());
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.startComposition()
-                        .addEffect(TEST_ONE_SHOT, /* delay= */ -10)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, -1)
                         .compose()
                         .validate());
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, -1)
+                        .repeatEffectIndefinitely(
+                                // Repeating waveform.
+                                VibrationEffect.createWaveform(
+                                        new long[] { 10 }, new int[] { 100}, 0))
+                        .compose()
+                        .validate());
+        assertThrows(UnreachableAfterRepeatingIndefinitelyException.class,
+                () -> VibrationEffect.startComposition()
+                        .repeatEffectIndefinitely(TEST_WAVEFORM)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose()
+                        .validate());
+        assertThrows(UnreachableAfterRepeatingIndefinitelyException.class,
+                () -> VibrationEffect.startComposition()
+                        .repeatEffectIndefinitely(TEST_WAVEFORM)
+                        .addEffect(TEST_ONE_SHOT)
                         .compose()
                         .validate());
     }
@@ -354,9 +380,9 @@
         assertFalse(VibrationEffect.createWaveform(
                 new long[]{200, 200, 700}, new int[]{1, 2, 3}, -1).isHapticFeedbackCandidate());
         assertFalse(VibrationEffect.startWaveform()
-                .addRamp(1, 500)
-                .addStep(1, 200)
-                .addRamp(0, 500)
+                .addTransition(Duration.ofMillis(500), targetAmplitude(1))
+                .addTransition(Duration.ofMillis(200), targetAmplitude(0.5f))
+                .addTransition(Duration.ofMillis(500), targetAmplitude(0))
                 .build()
                 .isHapticFeedbackCandidate());
     }
@@ -367,9 +393,9 @@
         assertTrue(VibrationEffect.createWaveform(
                 new long[]{100, 200, 300}, new int[]{1, 2, 3}, -1).isHapticFeedbackCandidate());
         assertTrue(VibrationEffect.startWaveform()
-                .addRamp(1, 300)
-                .addStep(1, 200)
-                .addRamp(0, 300)
+                .addTransition(Duration.ofMillis(300), targetAmplitude(1))
+                .addTransition(Duration.ofMillis(200), targetAmplitude(0.5f))
+                .addTransition(Duration.ofMillis(300), targetAmplitude(0))
                 .build()
                 .isHapticFeedbackCandidate());
     }
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index a5261ae..4319b97 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -19,6 +19,10 @@
 import static android.view.DisplayCutout.NO_CUTOUT;
 import static android.view.DisplayCutout.extractBoundsFromList;
 import static android.view.DisplayCutout.fromSpec;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.not;
@@ -497,6 +501,74 @@
                 new ParcelableWrapper(mCutoutNumbers));
     }
 
+    @Test
+    public void testGetRotatedBounds_top_rot0() {
+        int displayW = 500, displayH = 1000;
+        DisplayCutout expected = new DisplayCutout(Insets.of(20, 100, 20, 0),
+                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+                Insets.of(20, 0, 20, 0));
+        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+                Insets.of(20, 0, 20, 0));
+        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_0);
+        assertEquals(expected, rotated);
+    }
+
+    @Test
+    public void testGetRotatedBounds_top_rot90() {
+        int displayW = 500, displayH = 1000;
+        DisplayCutout expected = new DisplayCutout(Insets.of(100, 20, 0, 20),
+                new Rect(0, displayW - 75, 100, displayW - 50), ZERO_RECT, ZERO_RECT, ZERO_RECT,
+                Insets.of(0, 20, 0, 20), createParserInfo(ROTATION_90));
+        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+                Insets.of(20, 0, 20, 0));
+        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_90);
+        assertEquals(expected, rotated);
+    }
+
+    @Test
+    public void testGetRotatedBounds_top_rot180() {
+        int displayW = 500, displayH = 1000;
+        DisplayCutout expected = new DisplayCutout(Insets.of(20, 0, 20, 100),
+                ZERO_RECT, ZERO_RECT, ZERO_RECT,
+                new Rect(displayW - 75, displayH - 100, displayW - 50, displayH - 0),
+                Insets.of(20, 0, 20, 0), createParserInfo(ROTATION_180));
+        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+                Insets.of(20, 0, 20, 0));
+        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_180);
+        assertEquals(expected, rotated);
+    }
+
+    @Test
+    public void testGetRotatedBounds_top_rot270() {
+        int displayW = 500, displayH = 1000;
+        DisplayCutout expected = new DisplayCutout(Insets.of(0, 20, 100, 20),
+                ZERO_RECT, ZERO_RECT, new Rect(displayH - 100, 50, displayH - 0, 75), ZERO_RECT,
+                Insets.of(0, 20, 0, 20), createParserInfo(ROTATION_270));
+        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+                Insets.of(20, 0, 20, 0));
+        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_270);
+        assertEquals(expected, rotated);
+    }
+
+    @Test
+    public void testGetRotatedBounds_top_rot90to180() {
+        int displayW = 500, displayH = 1000;
+        DisplayCutout expected = new DisplayCutout(Insets.of(20, 0, 20, 100),
+                ZERO_RECT, ZERO_RECT, ZERO_RECT,
+                new Rect(displayW - 75, displayH - 100, displayW - 50, displayH - 0),
+                Insets.of(20, 0, 20, 0), createParserInfo(ROTATION_180));
+        DisplayCutout cutout = new DisplayCutout(Insets.of(100, 20, 0, 20),
+                new Rect(0, displayW - 75, 100, displayW - 50), ZERO_RECT, ZERO_RECT, ZERO_RECT,
+                Insets.of(0, 20, 0, 20));
+        // starting from 90, so the start displayW/H are swapped:
+        DisplayCutout rotated = cutout.getRotated(displayH, displayW, ROTATION_90, ROTATION_180);
+        assertEquals(expected, rotated);
+    }
+
     private static DisplayCutout createCutoutTop() {
         return createCutoutWithInsets(0, 100, 0, 0);
     }
@@ -533,4 +605,11 @@
                 ZERO_RECT,
                 waterfallInsets);
     }
+
+    private static DisplayCutout.CutoutPathParserInfo createParserInfo(
+            @Surface.Rotation int rotation) {
+        return new DisplayCutout.CutoutPathParserInfo(
+                0 /* displayWidth */, 0 /* displayHeight */, 0f /* density */, "" /* cutoutSpec */,
+                rotation, 0f /* scale */);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
index 5ea9199..632a1a9 100644
--- a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
@@ -59,34 +59,20 @@
 
     private HandwritingInitiator mHandwritingInitiator;
     private View mTestView;
+    private  Context mContext;
 
     @Before
     public void setup() {
         final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        Context context = mInstrumentation.getTargetContext();
+        mContext = mInstrumentation.getTargetContext();
         ViewConfiguration viewConfiguration = mock(ViewConfiguration.class);
         when(viewConfiguration.getScaledTouchSlop()).thenReturn(TOUCH_SLOP);
 
-        InputMethodManager inputMethodManager = context.getSystemService(InputMethodManager.class);
+        InputMethodManager inputMethodManager = mContext.getSystemService(InputMethodManager.class);
         mHandwritingInitiator =
                 spy(new HandwritingInitiator(viewConfiguration, inputMethodManager));
 
-        // mock a parent so that HandwritingInitiator can get
-        ViewGroup parent = new ViewGroup(context) {
-            @Override
-            protected void onLayout(boolean changed, int l, int t, int r, int b) {
-                // We don't layout this view.
-            }
-            @Override
-            public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
-                r.set(sHwArea);
-                return true;
-            }
-        };
-
-        mTestView = mock(View.class);
-        when(mTestView.isAttachedToWindow()).thenReturn(true);
-        parent.addView(mTestView);
+        mTestView = createMockView(sHwArea, true);
     }
 
     @Test
@@ -203,14 +189,42 @@
     }
 
     @Test
-    public void onInputConnectionCreated_inputConnectionCreated() {
+    public void autoHandwriting_whenDisabled_wontStartHW() {
+        View mockView = createMockView(sHwArea, false);
+        mHandwritingInitiator.onInputConnectionCreated(mockView);
+        final int x1 = (sHwArea.left + sHwArea.right) / 2;
+        final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+        final int x2 = x1 + TOUCH_SLOP * 2;
+        final int y2 = y1;
+
+        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+        verify(mHandwritingInitiator, never()).startHandwriting(mTestView);
+    }
+
+    @Test
+    public void onInputConnectionCreated() {
         mHandwritingInitiator.onInputConnectionCreated(mTestView);
         assertThat(mHandwritingInitiator.mConnectedView).isNotNull();
         assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView);
     }
 
     @Test
-    public void onInputConnectionCreated_inputConnectionClosed() {
+    public void onInputConnectionCreated_whenAutoHandwritingIsDisabled() {
+        View view = new View(mContext);
+        view.setAutoHandwritingEnabled(false);
+        assertThat(view.isAutoHandwritingEnabled()).isFalse();
+        mHandwritingInitiator.onInputConnectionCreated(view);
+
+        assertThat(mHandwritingInitiator.mConnectedView).isNull();
+    }
+
+    @Test
+    public void onInputConnectionClosed() {
         mHandwritingInitiator.onInputConnectionCreated(mTestView);
         mHandwritingInitiator.onInputConnectionClosed(mTestView);
 
@@ -218,6 +232,16 @@
     }
 
     @Test
+    public void onInputConnectionClosed_whenAutoHandwritingIsDisabled() {
+        View view = new View(mContext);
+        view.setAutoHandwritingEnabled(false);
+        mHandwritingInitiator.onInputConnectionCreated(view);
+        mHandwritingInitiator.onInputConnectionClosed(view);
+
+        assertThat(mHandwritingInitiator.mConnectedView).isNull();
+    }
+
+    @Test
     public void onInputConnectionCreated_inputConnectionRestarted() {
         // When IMM restarts input connection, View#onInputConnectionCreatedInternal might be
         // called before View#onInputConnectionClosedInternal. As a result, we need to handle the
@@ -243,4 +267,25 @@
                 1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */,
                 InputDevice.SOURCE_TOUCHSCREEN, 0 /* flags */);
     }
+
+    private View createMockView(Rect viewBound, boolean autoHandwritingEnabled) {
+        // mock a parent so that HandwritingInitiator can get
+        ViewGroup parent = new ViewGroup(mContext) {
+            @Override
+            protected void onLayout(boolean changed, int l, int t, int r, int b) {
+                // We don't layout this view.
+            }
+            @Override
+            public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+                r.set(viewBound);
+                return true;
+            }
+        };
+
+        View mockView = mock(View.class);
+        when(mockView.isAttachedToWindow()).thenReturn(true);
+        when(mockView.isAutoHandwritingEnabled()).thenReturn(autoHandwritingEnabled);
+        parent.addView(mockView);
+        return mockView;
+    }
 }
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index 656e756..b2a4044 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -21,16 +21,25 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.Activity;
 import android.app.EmptyActivity;
 import android.app.Instrumentation;
+import android.app.WindowConfiguration;
+import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.IBinder;
@@ -43,6 +52,7 @@
 import android.view.WindowManagerGlobal;
 import android.view.WindowManagerImpl;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
@@ -79,6 +89,8 @@
     private final WindowContext mWindowContext = createWindowContext();
     private final IWindowManager mWms = WindowManagerGlobal.getWindowManagerService();
 
+    private static final int TIMEOUT_IN_SECONDS = 4;
+
     @Test
     public void testCreateWindowContextWindowManagerAttachClientToken() {
         final WindowManager windowContextWm = WindowManagerImpl
@@ -131,7 +143,7 @@
         });
 
 
-        assertTrue(latch.await(4, TimeUnit.SECONDS));
+        assertTrue(latch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS));
 
 
         // Verify that the window token of the window context is created after first addView().
@@ -234,7 +246,7 @@
         // Add the parent window
         mInstrumentation.runOnMainSync(() -> wm.addView(parentWindow, params));
 
-        assertTrue(listener.mLatch.await(4, TimeUnit.SECONDS));
+        assertTrue(listener.mLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS));
 
         final WindowManager.LayoutParams subWindowAttrs =
                 new WindowManager.LayoutParams(TYPE_APPLICATION_ATTACHED_DIALOG);
@@ -251,6 +263,63 @@
                 null /* theme */));
     }
 
+    @Test
+    public void testRegisterComponentCallbacks() {
+        final WindowContext windowContext = createWindowContext();
+        final ConfigurationListener listener = new ConfigurationListener();
+
+        windowContext.registerComponentCallbacks(listener);
+
+        try {
+            final Configuration config = new Configuration();
+            config.windowConfiguration.setWindowingMode(
+                    WindowConfiguration.WINDOWING_MODE_FREEFORM);
+            config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+
+            windowContext.dispatchConfigurationChanged(config);
+
+            try {
+                assertWithMessage("Waiting for onConfigurationChanged timeout.")
+                        .that(listener.mLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)).isTrue();
+            } catch (InterruptedException e) {
+                fail("Waiting for configuration changed failed because of " + e);
+            }
+
+            assertThat(listener.mConfiguration).isEqualTo(config);
+        } finally {
+            windowContext.unregisterComponentCallbacks(listener);
+        }
+    }
+
+    @Test
+    public void testRegisterComponentCallbacksOnWindowContextWrapper() {
+        final WindowContext windowContext = createWindowContext();
+        final Context wrapper = new ContextWrapper(windowContext);
+        final ConfigurationListener listener = new ConfigurationListener();
+
+        wrapper.registerComponentCallbacks(listener);
+
+        try {
+            final Configuration config = new Configuration();
+            config.windowConfiguration.setWindowingMode(
+                    WindowConfiguration.WINDOWING_MODE_FREEFORM);
+            config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+
+            windowContext.dispatchConfigurationChanged(config);
+
+            try {
+                assertWithMessage("Waiting for onConfigurationChanged timeout.")
+                        .that(listener.mLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)).isTrue();
+            } catch (InterruptedException e) {
+                fail("Waiting for configuration changed failed because of " + e);
+            }
+
+            assertThat(listener.mConfiguration).isEqualTo(config);
+        } finally {
+            wrapper.unregisterComponentCallbacks(listener);
+        }
+    }
+
     private WindowContext createWindowContext() {
         return createWindowContext(TYPE_APPLICATION_OVERLAY);
     }
@@ -262,6 +331,20 @@
         return (WindowContext) instContext.createWindowContext(display, type,  null /* options */);
     }
 
+    private static class ConfigurationListener implements ComponentCallbacks {
+        private Configuration mConfiguration;
+        private CountDownLatch mLatch = new CountDownLatch(1);
+
+        @Override
+        public void onConfigurationChanged(@NonNull Configuration newConfig) {
+            mConfiguration = newConfig;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onLowMemory() {}
+    }
+
     private static class AttachStateListener implements View.OnAttachStateChangeListener {
         final CountDownLatch mLatch = new CountDownLatch(1);
 
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 69ff7c6..cd42a34 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -81,6 +81,7 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.service.chooser.ChooserTarget;
+import android.util.Log;
 import android.view.View;
 
 import androidx.annotation.CallSuper;
@@ -187,7 +188,7 @@
      * TODO: remove when we no longer want to test the system's on-the-fly evaluation.
      */
     protected boolean shouldTestTogglingAppPredictionServiceAvailabilityAtRuntime() {
-        return true;
+        return false;
     }
 
     /* --------
@@ -762,6 +763,7 @@
 
 
     @Test
+    @Ignore
     public void testNearbyShareLogging() throws Exception {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -1327,16 +1329,17 @@
         final ChooserActivity activity =
                 mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
         waitForIdle();
-
         if (activity.getPackageManager().getAppPredictionServicePackageName() == null) {
             assertThat(activity.isAppPredictionServiceAvailable(), is(false));
         } else {
-            assertThat(activity.isAppPredictionServiceAvailable(), is(true));
-
             if (!shouldTestTogglingAppPredictionServiceAvailabilityAtRuntime()) {
                 return;
             }
 
+            // This isn't a toggle per-se, but isAppPredictionServiceAvailable only works in
+            // system (see comment in the method).
+            assertThat(activity.isAppPredictionServiceAvailable(), is(true));
+
             ChooserActivityOverrideData.getInstance().resources =
                     Mockito.spy(activity.getResources());
             when(
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index d4f08ba..7f85982 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -51,6 +51,13 @@
     static final ChooserActivityOverrideData sOverrides = ChooserActivityOverrideData.getInstance();
     private UsageStatsManager mUsm;
 
+    // ResolverActivity (the base class of ChooserActivity) inspects the launched-from UID at
+    // onCreate and needs to see some non-negative value in the test.
+    @Override
+    public int getLaunchedFromUid() {
+        return 1234;
+    }
+
     @Override
     protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
             Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
index 6457e3f..96d6a7e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
@@ -54,6 +54,7 @@
                 /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0,
                 2_000_000, 2_000_000, 2_000_000);
 
+        mStatsRule.setTime(5_000_000, 5_000_000);
         BatteryChargeCalculator calculator = new BatteryChargeCalculator();
         BatteryUsageStats batteryUsageStats = mStatsRule.apply(calculator);
 
@@ -64,6 +65,8 @@
                 .isWithin(PRECISION).of(360.0);
         assertThat(batteryUsageStats.getDischargedPowerRange().getUpper())
                 .isWithin(PRECISION).of(400.0);
+        // 5_000_000 (current time) - 1_000_000 (started discharging)
+        assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(4_000_000);
         assertThat(batteryUsageStats.getBatteryTimeRemainingMs()).isEqualTo(8_000_000);
         assertThat(batteryUsageStats.getChargeTimeRemainingMs()).isEqualTo(-1);
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 9b3876f..354b937 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
@@ -50,19 +50,75 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
 public class BatteryUsageStatsProviderTest {
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
     private static final long MINUTE_IN_MS = 60 * 1000;
+    private static final double PRECISION = 0.00001;
 
     private final File mHistoryDir =
             TestIoUtils.createTemporaryDirectory(getClass().getSimpleName());
     @Rule
     public final BatteryUsageStatsRule mStatsRule =
             new BatteryUsageStatsRule(12345, mHistoryDir)
-                    .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0);
+                    .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)
+                    .setAveragePower(PowerProfile.POWER_AUDIO, 720.0);
 
     @Test
     public void test_getBatteryUsageStats() {
+        BatteryStatsImpl batteryStats = prepareBatteryStats();
+
+        Context context = InstrumentationRegistry.getContext();
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats);
+
+        final BatteryUsageStats batteryUsageStats =
+                provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT);
+
+        final List<UidBatteryConsumer> uidBatteryConsumers =
+                batteryUsageStats.getUidBatteryConsumers();
+        final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
+        assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND))
+                .isEqualTo(60 * MINUTE_IN_MS);
+        assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND))
+                .isEqualTo(10 * MINUTE_IN_MS);
+        assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
+                .isWithin(PRECISION).of(2.0);
+        assertThat(
+                uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+                .isWithin(PRECISION).of(0.4);
+
+        assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(12345);
+        assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(54321);
+    }
+
+    @Test
+    public void test_selectPowerComponents() {
+        BatteryStatsImpl batteryStats = prepareBatteryStats();
+
+        Context context = InstrumentationRegistry.getContext();
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats);
+
+        final BatteryUsageStats batteryUsageStats =
+                provider.getBatteryUsageStats(
+                        new BatteryUsageStatsQuery.Builder()
+                                .includePowerComponents(
+                                        new int[]{BatteryConsumer.POWER_COMPONENT_AUDIO})
+                                .build()
+                );
+
+        final List<UidBatteryConsumer> uidBatteryConsumers =
+                batteryUsageStats.getUidBatteryConsumers();
+        final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
+        assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
+                .isWithin(PRECISION).of(2.0);
+
+        // FLASHLIGHT power estimation not requested, so the returned value is 0
+        assertThat(
+                uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+                .isEqualTo(0);
+    }
+
+    private BatteryStatsImpl prepareBatteryStats() {
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
         batteryStats.noteActivityResumedLocked(APP_UID,
@@ -82,24 +138,14 @@
         batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_CACHED_EMPTY,
                 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
 
+        batteryStats.noteFlashlightOnLocked(APP_UID, 1000, 1000);
+        batteryStats.noteFlashlightOffLocked(APP_UID, 5000, 5000);
+
+        batteryStats.noteAudioOnLocked(APP_UID, 10000, 10000);
+        batteryStats.noteAudioOffLocked(APP_UID, 20000, 20000);
+
         mStatsRule.setCurrentTime(54321);
-
-        Context context = InstrumentationRegistry.getContext();
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats);
-
-        final BatteryUsageStats batteryUsageStats =
-                provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT);
-
-        final List<UidBatteryConsumer> uidBatteryConsumers =
-                batteryUsageStats.getUidBatteryConsumers();
-        final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
-        assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND))
-                .isEqualTo(60 * MINUTE_IN_MS);
-        assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND))
-                .isEqualTo(10 * MINUTE_IN_MS);
-
-        assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(12345);
-        assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(54321);
+        return batteryStats;
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index b3056e2..4f29863 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -212,8 +212,8 @@
         }
 
         for (PowerCalculator calculator : calculators) {
-            calculator.calculate(builder, mBatteryStats, mMockClock.realtime, mMockClock.uptime,
-                    query);
+            calculator.calculate(builder, mBatteryStats, mMockClock.realtime * 1000,
+                    mMockClock.uptime * 1000, query);
         }
 
         mBatteryUsageStats = builder.build();
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java
index 51f20f3..21f6e7c 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java
@@ -44,6 +44,7 @@
 import java.util.Arrays;
 
 @RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
 public class BatteryUsageStatsStoreTest {
     private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
 
@@ -76,8 +77,13 @@
     @Test
     public void testStoreSnapshot() {
         mMockClock.currentTime = 1_600_000;
+        mMockClock.realtime = 1000;
+        mMockClock.uptime = 1000;
 
         prepareBatteryStats();
+
+        mMockClock.realtime = 1_000_000;
+        mMockClock.uptime = 1_000_000;
         mBatteryStats.resetAllStatsCmdLocked();
 
         final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
@@ -90,6 +96,7 @@
         assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(1_600_000);
         assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000);
         assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(5);
+        assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(1_000_000 - 1_000);
         assertThat(batteryUsageStats.getAggregateBatteryConsumer(
                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE).getConsumedPower())
                 .isEqualTo(600);  // (3_600_000 - 3_000_000) / 1000
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 8cc4c34..5adc9bd 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -183,7 +183,7 @@
                         .add(stats2)
                         .build();
 
-        assertBatteryUsageStats(sum, 42345, 50, 2234, 4345, 1000, 5000, 5000);
+        assertBatteryUsageStats(sum, 42345, 50, 2234, 4345, 1234, 1000, 5000, 5000);
 
         final List<UidBatteryConsumer> uidBatteryConsumers =
                 sum.getUidBatteryConsumers();
@@ -259,6 +259,7 @@
                         .setBatteryCapacity(4000)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
+                        .setDischargeDurationMs(1234)
                         .setStatsStartTimestamp(1000)
                         .setStatsEndTimestamp(3000);
 
@@ -420,7 +421,7 @@
 
     public void assertBatteryUsageStats1(BatteryUsageStats batteryUsageStats,
             boolean includesUserBatteryConsumers) {
-        assertBatteryUsageStats(batteryUsageStats, 30000, 20, 1000, 2000, 1000, 3000, 2000);
+        assertBatteryUsageStats(batteryUsageStats, 30000, 20, 1000, 2000, 1234, 1000, 3000, 2000);
 
         final List<UidBatteryConsumer> uidBatteryConsumers =
                 batteryUsageStats.getUidBatteryConsumers();
@@ -463,13 +464,15 @@
 
     private void assertBatteryUsageStats(BatteryUsageStats batteryUsageStats, int consumedPower,
             int dischargePercentage, int dischagePowerLower, int dischargePowerUpper,
-            int statsStartTimestamp, int statsEndTimestamp, int statsDuration) {
+            int dischargeDuration, int statsStartTimestamp, int statsEndTimestamp,
+            int statsDuration) {
         assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(consumedPower);
         assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(dischargePercentage);
         assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(
                 dischagePowerLower);
         assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(
                 dischargePowerUpper);
+        assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(dischargeDuration);
         assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(statsStartTimestamp);
         assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(statsEndTimestamp);
         assertThat(batteryUsageStats.getStatsDuration()).isEqualTo(statsDuration);
diff --git a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
index 67b1e51..2b28031 100644
--- a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
@@ -39,7 +39,7 @@
 
     @Test
     public void testTimerBasedModel() {
-        mStatsRule.setTime(3_000_000, 2_000_000);
+        mStatsRule.setTime(3_000, 2_000);
 
         IdlePowerCalculator calculator = new IdlePowerCalculator(mStatsRule.getPowerProfile());
 
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
index ce2f764..c20293b 100644
--- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -102,7 +102,7 @@
         stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
                 mNetworkStatsManager);
 
-        mStatsRule.setTime(12_000_000, 12_000_000);
+        mStatsRule.setTime(12_000, 12_000);
 
         MobileRadioPowerCalculator calculator =
                 new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
@@ -248,7 +248,7 @@
                 new int[]{100, 200, 300, 400, 500}, 600);
         stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000, mNetworkStatsManager);
 
-        mStatsRule.setTime(12_000_000, 12_000_000);
+        mStatsRule.setTime(12_000, 12_000);
 
         MobileRadioPowerCalculator calculator =
                 new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
diff --git a/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
index aae69d7..aec4f52 100644
--- a/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
@@ -126,6 +126,13 @@
     }
 
     private static class FakeAudioPowerCalculator extends PowerCalculator {
+
+        @Override
+        public boolean isPowerComponentSupported(
+                @BatteryConsumer.PowerComponent int powerComponent) {
+            return powerComponent == BatteryConsumer.POWER_COMPONENT_AUDIO;
+        }
+
         @Override
         protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
                 long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
@@ -135,6 +142,13 @@
     }
 
     private static class FakeVideoPowerCalculator extends PowerCalculator {
+
+        @Override
+        public boolean isPowerComponentSupported(
+                @BatteryConsumer.PowerComponent int powerComponent) {
+            return powerComponent == BatteryConsumer.POWER_COMPONENT_VIDEO;
+        }
+
         @Override
         protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
                 long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
diff --git a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
index a7f4fb3..f3456af 100644
--- a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
@@ -54,7 +54,7 @@
         batteryStats.noteStopWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "",
                 BatteryStats.WAKE_TYPE_PARTIAL, 2000, 2000);
 
-        mStatsRule.setTime(10_000_000, 6_000_000);
+        mStatsRule.setTime(10_000, 6_000);
 
         WakelockPowerCalculator calculator =
                 new WakelockPowerCalculator(mStatsRule.getPowerProfile());
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 92fca36..88920c8 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -231,6 +231,18 @@
                       targetSdk="29">
         <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
     </split-permission>
+    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+                      targetSdk="33">
+        <new-permission name="android.permission.READ_MEDIA_AUDIO" />
+    </split-permission>
+    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+                      targetSdk="33">
+        <new-permission name="android.permission.READ_MEDIA_VIDEO" />
+    </split-permission>
+    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+                      targetSdk="33">
+        <new-permission name="android.permission.READ_MEDIA_IMAGE" />
+    </split-permission>
     <split-permission name="android.permission.BLUETOOTH"
                       targetSdk="31">
         <new-permission name="android.permission.BLUETOOTH_SCAN" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index de086df..056f871 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -391,6 +391,7 @@
         <permission name="android.permission.STATUS_BAR_SERVICE"/>
         <permission name="android.permission.REQUEST_INCIDENT_REPORT_APPROVAL"/>
         <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
+        <permission name="android.permission.MANAGE_WEAK_ESCROW_TOKEN"/>
         <permission name="android.permission.SET_WALLPAPER" />
         <permission name="android.permission.SET_WALLPAPER_COMPONENT" />
         <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT" />
@@ -471,6 +472,7 @@
         <!-- Permission needed for CTS test - WifiManagerTest -->
         <permission name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" />
         <permission name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" />
+        <permission name="android.permission.OVERRIDE_WIFI_CONFIG" />
         <!-- Permission required for CTS test CarrierMessagingServiceWrapperTest -->
         <permission name="android.permission.BIND_CARRIER_SERVICES"/>
         <!-- Permission required for CTS test - MusicRecognitionManagerTest -->
@@ -528,6 +530,7 @@
         <!-- Permissions required for CTS test - CtsSafetyCenterTestCases -->
         <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
+        <permission name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0752329..1567233 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -319,6 +319,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
+    "-1764792832": {
+      "message": "Start collecting in Transition: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "-1750384749": {
       "message": "Launch on display check: allow launch on public display",
       "level": "DEBUG",
@@ -349,6 +355,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "-1728919185": {
+      "message": "        unrelated invisible sibling %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-1715268616": {
       "message": "Last window, removing starting window %s",
       "level": "VERBOSE",
@@ -445,12 +457,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "-1587921395": {
-      "message": "  Top targets: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "-1585311008": {
       "message": "Bring to front target: %s from %s",
       "level": "DEBUG",
@@ -703,12 +709,6 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
-    "-1375751630": {
-      "message": "  --- Start combine pass ---",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "-1364754753": {
       "message": "Task vanished taskId=%d",
       "level": "VERBOSE",
@@ -1171,12 +1171,6 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
     },
-    "-855366859": {
-      "message": "        merging children in from %s: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "-853404763": {
       "message": "\twallpaper=%s",
       "level": "DEBUG",
@@ -1237,6 +1231,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-779095785": {
+      "message": "        sibling is a participant with mode %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-775004869": {
       "message": "Not a match: %s",
       "level": "DEBUG",
@@ -1549,12 +1549,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
-    "-446752714": {
-      "message": "        SKIP: sibling contains top target %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "-445944810": {
       "message": "finish(%b): mCanceled=%b",
       "level": "DEBUG",
@@ -1723,12 +1717,6 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
-    "-302335479": {
-      "message": "        remove from topTargets %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "-292790591": {
       "message": "Attempted to set IME policy to a display that does not exist: %d",
       "level": "WARN",
@@ -2071,6 +2059,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "112145970": {
+      "message": "      SKIP: its sibling was rejected",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "114070759": {
       "message": "New wallpaper target: %s prevTarget: %s caller=%s",
       "level": "VERBOSE",
@@ -2407,6 +2401,12 @@
       "group": "WM_SHOW_SURFACE_ALLOC",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "405146734": {
+      "message": "  Final targets: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "416924848": {
       "message": "InsetsSource Control %s for target %s",
       "level": "DEBUG",
@@ -2431,12 +2431,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "430260320": {
-      "message": "        sibling is a top target with mode %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "431715812": {
       "message": "Launch on display check: allow launch any on display",
       "level": "DEBUG",
@@ -2755,12 +2749,6 @@
       "group": "WM_SHOW_SURFACE_ALLOC",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
-    "751854538": {
-      "message": "DisplayArea keep clear rects changed name =%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_ORGANIZER",
-      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
-    },
     "765395228": {
       "message": "onAnimationFinished(): controller=%s reorderMode=%d",
       "level": "DEBUG",
@@ -2791,6 +2779,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "800698875": {
+      "message": "SyncGroup %d: Started when there is other active SyncGroup",
+      "level": "WARN",
+      "group": "WM_DEBUG_SYNC_ENGINE",
+      "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+    },
     "806891543": {
       "message": "Setting mOrientationChangeComplete=true because wtoken %s numInteresting=%d numDrawn=%d",
       "level": "INFO",
@@ -3085,12 +3079,6 @@
       "group": "WM_DEBUG_WALLPAPER",
       "at": "com\/android\/server\/wm\/WallpaperController.java"
     },
-    "1186730970": {
-      "message": "          no common mode yet, so set it",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "1191587912": {
       "message": "Moved rootTask=%s behind rootTask=%s",
       "level": "DEBUG",
@@ -3205,6 +3193,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1333520287": {
+      "message": "Creating PendingTransition: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "1337596507": {
       "message": "Sending to proc %s new compat %s",
       "level": "VERBOSE",
@@ -3823,6 +3817,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "2034988903": {
+      "message": "PendingStartTransaction found",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+    },
     "2039056415": {
       "message": "Found matching affinity candidate!",
       "level": "DEBUG",
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index eefad8d..afd320d 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2184,9 +2184,13 @@
                     contextCount, isRtl, outMetrics);
         } else {
             char[] buf = TemporaryBuffer.obtain(contextCount);
-            TextUtils.getChars(text, contextStart, contextStart + contextCount, buf, 0);
-            nGetFontMetricsIntForText(mNativePaint, buf, start - contextStart, count, 0,
-                    contextCount, isRtl, outMetrics);
+            try {
+                TextUtils.getChars(text, contextStart, contextStart + contextCount, buf, 0);
+                nGetFontMetricsIntForText(mNativePaint, buf, start - contextStart, count, 0,
+                        contextCount, isRtl, outMetrics);
+            } finally {
+                TemporaryBuffer.recycle(buf);
+            }
         }
 
     }
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background.xml
new file mode 100644
index 0000000..723963f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="@android:color/system_accent1_100"/>
+    <corners android:radius="12dp"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background_ripple.xml
new file mode 100644
index 0000000..0a3a813
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background_ripple.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<!-- DO NOT SUBMIT - find right color!! -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@android:color/system_accent1_10">
+    <item android:drawable="@drawable/letterbox_education_dismiss_background"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
new file mode 100644
index 0000000..ff57406
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/system_neutral2_400"
+        android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
new file mode 100644
index 0000000..16dea48
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@android:color/system_neutral2_200">
+    <item android:drawable="@drawable/letterbox_education_ic_expand_more"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml
new file mode 100644
index 0000000..cbfcfd0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/letterbox_education_dialog_icon_size"
+        android:height="@dimen/letterbox_education_dialog_icon_size"
+        android:viewportWidth="48"
+        android:viewportHeight="48">
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:fillType="evenOdd"
+        android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" />
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:pathData="M 14 22 H 30 V 26 H 14 V 22 Z" />
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:pathData="M26 16L34 24L26 32V16Z" />
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml
new file mode 100644
index 0000000..469eb1e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/letterbox_education_dialog_icon_size"
+        android:height="@dimen/letterbox_education_dialog_icon_size"
+        android:viewportWidth="48"
+        android:viewportHeight="48">
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:fillType="evenOdd"
+        android:pathData="M22.56 2H26C37.02 2 46 10.98 46 22H42C42 14.44 36.74 8.1 29.7 6.42L31.74 10L28.26 12L22.56 2ZM22 46H25.44L19.74 36L16.26 38L18.3 41.58C11.26 39.9 6 33.56 6 26H2C2 37.02 10.98 46 22 46ZM20.46 12L36 27.52L27.54 36L12 20.48L20.46 12ZM17.64 9.18C18.42 8.4 19.44 8 20.46 8C21.5 8 22.52 8.4 23.3 9.16L38.84 24.7C40.4 26.26 40.4 28.78 38.84 30.34L30.36 38.82C29.58 39.6 28.56 40 27.54 40C26.52 40 25.5 39.6 24.72 38.82L9.18 23.28C7.62 21.72 7.62 19.2 9.18 17.64L17.64 9.18Z" />
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml
new file mode 100644
index 0000000..dcb8aed
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/letterbox_education_dialog_icon_size"
+        android:height="@dimen/letterbox_education_dialog_icon_size"
+        android:viewportWidth="48"
+        android:viewportHeight="48">
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:fillType="evenOdd"
+        android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" />
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:pathData="M6 16C6 14.8954 6.89543 14 8 14H21C22.1046 14 23 14.8954 23 16V32C23 33.1046 22.1046 34 21 34H8C6.89543 34 6 33.1046 6 32V16Z" />
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:pathData="M25 16C25 14.8954 25.8954 14 27 14H40C41.1046 14 42 14.8954 42 16V32C42 33.1046 41.1046 34 40 34H27C25.8954 34 25 33.1046 25 32V16Z" />
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
new file mode 100644
index 0000000..cd1d99a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
@@ -0,0 +1,40 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/letterbox_education_dialog_action_width"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/letterbox_education_dialog_action_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginBottom="12dp"/>
+
+    <TextView
+        android:id="@+id/letterbox_education_dialog_action_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:lineSpacingExtra="4sp"
+        android:textAlignment="center"
+        android:textColor="@color/compat_controls_text"
+        android:textSize="14sp"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
new file mode 100644
index 0000000..edf737f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
@@ -0,0 +1,98 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.wm.shell.compatui.LetterboxEduDialogLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@color/compat_controls_background"
+    android:gravity="center"
+    android:paddingTop="24dp"
+    android:paddingBottom="32dp"
+    android:paddingHorizontal="32dp">
+
+    <!-- Adding an extra layer to animate the alpha of the background and content separately. -->
+    <LinearLayout
+        android:id="@+id/letterbox_education_content"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:orientation="vertical">
+
+        <ImageView
+            android:id="@+id/letterbox_education_icon"
+            android:layout_width="@dimen/letterbox_education_dialog_icon_size"
+            android:layout_height="@dimen/letterbox_education_dialog_icon_size"
+            android:layout_marginBottom="20dp" />
+
+        <TextView
+            android:id="@+id/letterbox_education_dialog_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxWidth="@dimen/letterbox_education_dialog_title_max_width"
+            android:lineSpacingExtra="4sp"
+            android:textAlignment="center"
+            android:textColor="@color/compat_controls_text"
+            android:textSize="24sp"/>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="top"
+            android:orientation="horizontal"
+            android:paddingTop="43dp">
+
+            <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+                android:id="@+id/letterbox_education_dialog_screen_rotation_action"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                app:icon="@drawable/letterbox_education_ic_screen_rotation"/>
+
+            <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+                android:id="@+id/letterbox_education_dialog_split_screen_action"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/letterbox_education_dialog_space_between_actions"
+                app:icon="@drawable/letterbox_education_ic_split_screen"
+                app:text="@string/letterbox_education_split_screen_text"/>
+
+            <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+                android:id="@+id/letterbox_education_dialog_reposition_action"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="gone"
+                android:layout_marginStart="@dimen/letterbox_education_dialog_space_between_actions"
+                app:icon="@drawable/letterbox_education_ic_reposition"
+                app:text="@string/letterbox_education_reposition_text"/>
+
+        </LinearLayout>
+
+        <Button
+            android:id="@+id/letterbox_education_dialog_dismiss"
+            android:layout_width="match_parent"
+            android:layout_height="56dp"
+            android:layout_marginTop="43dp"
+            android:layout_marginHorizontal="24dp"
+            android:background="@drawable/letterbox_education_dismiss_background_ripple"
+            android:gravity="center"
+            android:text="@string/letterbox_education_got_it"
+            android:textColor="@android:color/system_neutral1_900"
+            android:textAlignment="center"
+            android:contentDescription="@string/letterbox_education_got_it"/>
+
+    </LinearLayout>
+
+</com.android.wm.shell.compatui.LetterboxEduDialogLayout>
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_overlay_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_overlay_layout.xml
new file mode 100644
index 0000000..f4c6d65
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_overlay_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:alpha="0"
+    android:background="@android:color/system_neutral1_900"
+    android:clickable="true">
+</FrameLayout>
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml
new file mode 100644
index 0000000..a309d48
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml
@@ -0,0 +1,61 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.wm.shell.compatui.LetterboxEduToastLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@color/compat_controls_background"
+    android:gravity="center"
+    android:paddingVertical="14dp"
+    android:paddingHorizontal="16dp">
+
+    <!-- Adding an extra layer to animate the alpha of the background and content separately. -->
+    <LinearLayout
+        android:id="@+id/letterbox_education_content"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/letterbox_education_icon"
+            android:layout_width="@dimen/letterbox_education_toast_icon_size"
+            android:layout_height="@dimen/letterbox_education_toast_icon_size"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxWidth="@dimen/letterbox_education_toast_text_max_width"
+            android:paddingHorizontal="16dp"
+            android:lineSpacingExtra="5sp"
+            android:text="@string/letterbox_education_toast_title"
+            android:textAlignment="viewStart"
+            android:textColor="@color/compat_controls_text"
+            android:textSize="16sp"
+            android:maxLines="1"
+            android:ellipsize="end"/>
+
+        <ImageButton
+            android:id="@+id/letterbox_education_toast_expand"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/letterbox_education_ic_expand_more_ripple"
+            android:background="@android:color/transparent"
+            android:contentDescription="@string/letterbox_education_expand_button_description"/>
+
+    </LinearLayout>
+
+</com.android.wm.shell.compatui.LetterboxEduToastLayout>
diff --git a/libs/WindowManager/Shell/res/values-af/strings_tv.xml b/libs/WindowManager/Shell/res/values-af/strings_tv.xml
index 3edb8e9..1bfe128 100644
--- a/libs/WindowManager/Shell/res/values-af/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Titellose program)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Maak PIP toe"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Volskerm"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Skuif PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
index b1c6542..456b4b8 100644
--- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ርዕስ የሌለው ፕሮግራም)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIPን ዝጋ"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገጽ"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"ፒአይፒ ውሰድ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
index dfc5053..2546fe9 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ليس هناك عنوان للبرنامج)"</string>
     <string name="pip_close" msgid="9135220303720555525">"‏إغلاق PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"ملء الشاشة"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"‏نقل نافذة داخل النافذة (PIP)"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings_tv.xml b/libs/WindowManager/Shell/res/values-as/strings_tv.xml
index 352bde5..d17c1f3 100644
--- a/libs/WindowManager/Shell/res/values-as/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিৰোনামবিহীন কাৰ্যক্ৰম)"</string>
     <string name="pip_close" msgid="9135220303720555525">"পিপ বন্ধ কৰক"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"সম্পূৰ্ণ স্ক্ৰীন"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"পিপ স্থানান্তৰ কৰক"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings_tv.xml b/libs/WindowManager/Shell/res/values-az/strings_tv.xml
index 9b46d5f..a5c4792 100644
--- a/libs/WindowManager/Shell/res/values-az/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıqsız proqram)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP bağlayın"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP tətbiq edin"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml
index 790a6d47..b4d9bd1 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Ceo ekran"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Premesti sliku u slici"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings_tv.xml b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
index c4df7fc..514d06b 100644
--- a/libs/WindowManager/Shell/res/values-be/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Праграма без назвы)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Закрыць PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Поўнаэкранны рэжым"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Перамясціць PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
index cbb00ae..19f83e7 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без заглавие)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Затваряне на PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Цял екран"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"„Картина в картина“: Преместв."</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
index f24c92a..5f90eeb 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিরোনামহীন প্রোগ্রাম)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP বন্ধ করুন"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"পূর্ণ স্ক্রিন"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP সরান"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
index 80bac2a..3f2adf3 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Zatvori sliku u slici"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli ekran"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Pokreni sliku u slici"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
index 66cd93a..db750c4 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sense títol)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Tanca PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Mou pantalla en pantalla"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
index 500050b..cef0b99 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Bez názvu)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Ukončit obraz v obraze (PIP)"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Přesunout PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings_tv.xml b/libs/WindowManager/Shell/res/values-da/strings_tv.xml
index 896895b..2330530 100644
--- a/libs/WindowManager/Shell/res/values-da/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uden titel)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Luk integreret billede"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Fuld skærm"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Flyt PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings_tv.xml b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
index 7809efe..8da9110 100644
--- a/libs/WindowManager/Shell/res/values-de/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Kein Sendungsname gefunden)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP schließen"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Vollbild"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"BiB verschieben"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings_tv.xml b/libs/WindowManager/Shell/res/values-el/strings_tv.xml
index 088bcc3..df35113 100644
--- a/libs/WindowManager/Shell/res/values-el/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Δεν υπάρχει τίτλος προγράμματος)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Κλείσιμο PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Πλήρης οθόνη"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Μετακίνηση PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
index 7900fdc..1fb3191 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
index 7900fdc..1fb3191 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
index 7900fdc..1fb3191 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
index 7900fdc..1fb3191 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
index 3be850a..1beb0b5 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sin título de programa)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Mover PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings_tv.xml b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
index 7eba361..d042b43 100644
--- a/libs/WindowManager/Shell/res/values-es/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sin título)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Mover imagen en imagen"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings_tv.xml b/libs/WindowManager/Shell/res/values-et/strings_tv.xml
index ca6e669..3da16db 100644
--- a/libs/WindowManager/Shell/res/values-et/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programmi pealkiri puudub)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Sule PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Täisekraan"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Teisalda PIP-režiimi"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
index 3f47e95..e4b57ba 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa izengabea)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Itxi PIPa"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Pantaila osoa"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Mugitu pantaila txiki gainjarria"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
index cc5cf64..aaab34f 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(برنامه بدون عنوان)"</string>
     <string name="pip_close" msgid="9135220303720555525">"‏بستن PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"تمام صفحه"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"‏انتقال PIP (تصویر در تصویر)"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
index b779886..21c6463 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nimetön)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Sulje PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Koko näyttö"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Siirrä PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
index 1798c7d..f4baaad 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Aucun programme de titre)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Fermer mode IDI"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Déplacer l\'image incrustée"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
index b039934..6ad8174 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programme sans titre)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Fermer mode PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Déplacer le PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
index 0d91eba..dcb8709 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sen título)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Pechar PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Mover pantalla superposta"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
index a748df3..ed815ca 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(કોઈ ટાઇટલ પ્રોગ્રામ નથી)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP બંધ કરો"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"પૂર્ણ સ્ક્રીન"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP ખસેડો"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
index 040072b..8bcc631 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई शीर्षक कार्यक्रम नहीं)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP बंद करें"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"फ़ुल स्‍क्रीन"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"पीआईपी को दूसरी जगह लेकर जाएं"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
index 20081e4..49b7ae0 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli zaslon"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Premjesti PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
index c78146d..484db0c 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Cím nélküli program)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP bezárása"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Teljes képernyő"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP áthelyezése"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
index 55d5bd7..e447ffc 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Առանց վերնագրի ծրագիր)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Փակել PIP-ն"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Լիէկրան"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Տեղափոխել PIP-ը"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings_tv.xml b/libs/WindowManager/Shell/res/values-in/strings_tv.xml
index 6401852..b631705 100644
--- a/libs/WindowManager/Shell/res/values-in/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tanpa judul)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Layar penuh"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Pindahkan PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings_tv.xml b/libs/WindowManager/Shell/res/values-is/strings_tv.xml
index fa36829..119ecf0 100644
--- a/libs/WindowManager/Shell/res/values-is/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Efni án titils)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Loka mynd í mynd"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Allur skjárinn"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Færa innfellda mynd"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings_tv.xml b/libs/WindowManager/Shell/res/values-it/strings_tv.xml
index f6e91be..92f015c 100644
--- a/libs/WindowManager/Shell/res/values-it/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma senza titolo)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Chiudi PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Schermo intero"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Sposta PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
index 356e8d5..d09b850 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(תוכנית ללא כותרת)"</string>
     <string name="pip_close" msgid="9135220303720555525">"‏סגירת PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"מסך מלא"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"‏העברת תמונה בתוך תמונה (PIP)"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
index 07684d3..d6399e5 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無題の番組)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP を閉じる"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"全画面表示"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP を移動"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
index 043e5eb..8d7bee8 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(პროგრამის სათაურის გარეშე)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP-ის დახურვა"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"სრულ ეკრანზე"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP გადატანა"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
index 7943797..05bdcc7 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Атаусыз бағдарлама)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP жабу"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Толық экран"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP клипін жылжыту"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings_tv.xml b/libs/WindowManager/Shell/res/values-km/strings_tv.xml
index 2e56254..e831516 100644
--- a/libs/WindowManager/Shell/res/values-km/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(កម្មវិធី​គ្មានចំណងជើង)"</string>
     <string name="pip_close" msgid="9135220303720555525">"បិទ PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"ពេញអេក្រង់"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"ផ្លាស់ទី PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
index 6c8880d..305ef66 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ಶೀರ್ಷಿಕೆ ರಹಿತ ಕಾರ್ಯಕ್ರಮ)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP ಮುಚ್ಚಿ"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"ಪೂರ್ಣ ಪರದೆ"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP ಅನ್ನು ಸರಿಸಿ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
index 35b1b19..76b0adf 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(제목 없는 프로그램)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP 닫기"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"전체화면"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP 이동"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
index 72d70f0..57b955a 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Аталышы жок программа)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP\'ти жабуу"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Толук экран"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP\'ти жылдыруу"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
index 3604726..cbea84e 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ໂປຣແກຣມບໍ່ມີຊື່)"</string>
     <string name="pip_close" msgid="9135220303720555525">"ປິດ PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"ເຕັມໜ້າຈໍ"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"ຍ້າຍ PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
index fa5a4c4..81716a6 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa be pavadinimo)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Uždaryti PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Visas ekranas"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Perkelti PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
index cafd43a..5295cd2 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma bez nosaukuma)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Aizvērt PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Pilnekrāna režīms"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Pārvietot attēlu attēlā"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
index b927b56..fa48a6c 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без наслов)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Цел екран"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Премести PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
index aef059f..5333757 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(പേരില്ലാത്ത പ്രോഗ്രാം)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP അടയ്ക്കുക"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"പൂര്‍ണ്ണ സ്ക്രീന്‍"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP നീക്കുക"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
index 7dfec68..ca1d27f 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Гарчиггүй хөтөлбөр)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP-г хаах"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Бүтэн дэлгэц"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP-г зөөх"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
index 447cb7d..212bd21 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षक नसलेला कार्यक्रम)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP बंद करा"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रीन"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP हलवा"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
index 3a5584d..ce29126 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tiada tajuk)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Skrin penuh"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Alihkan PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings_tv.xml b/libs/WindowManager/Shell/res/values-my/strings_tv.xml
index 84ec0e5..4847742 100644
--- a/libs/WindowManager/Shell/res/values-my/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ခေါင်းစဉ်မဲ့ အစီအစဉ်)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP ကိုပိတ်ပါ"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"မျက်နှာပြင် အပြည့်"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP ရွှေ့ရန်"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
index 78ec6db..7cef11c 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uten tittel)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Lukk PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Fullskjerm"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Flytt BIB"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
index 4458a14..684d114 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षकविहीन कार्यक्रम)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP लाई बन्द गर्नुहोस्"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रिन"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP सार्नुहोस्"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
index d21515d..8562517 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Naamloos programma)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP sluiten"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Volledig scherm"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"SIS verplaatsen"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings_tv.xml b/libs/WindowManager/Shell/res/values-or/strings_tv.xml
index a679350..f8bc016 100644
--- a/libs/WindowManager/Shell/res/values-or/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(କୌଣସି ଟାଇଟଲ୍‍ ପ୍ରୋଗ୍ରାମ୍‍ ନାହିଁ)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍‍"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIPକୁ ମୁଭ କରନ୍ତୁ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
index a0ff4f3..1667e5f 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ਸਿਰਲੇਖ-ਰਹਿਤ ਪ੍ਰੋਗਰਾਮ)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP ਬੰਦ ਕਰੋ"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP ਨੂੰ ਲਿਜਾਓ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
index 6320893..28bf66a 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez tytułu)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Zamknij PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Pełny ekran"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Przenieś PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
index fef9d47..27626b8 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Mover picture-in-picture"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
index 461571f..a2010ce 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sem título do programa)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Ecrã inteiro"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Mover Ecrã no ecrã"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
index fef9d47..27626b8 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Mover picture-in-picture"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
index 80bf151..18e29a6 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program fără titlu)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Închideți PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Ecran complet"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Mutați fereastra PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
index de5348a..d119240 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Без названия)"</string>
     <string name="pip_close" msgid="9135220303720555525">"\"Кадр в кадре\" – выйти"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Во весь экран"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Переместить PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings_tv.xml b/libs/WindowManager/Shell/res/values-si/strings_tv.xml
index 0470040..86769b6 100644
--- a/libs/WindowManager/Shell/res/values-si/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(මාතෘකාවක් නැති වැඩසටහන)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP වසන්න"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"සම්පූර්ණ තිරය"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP ගෙන යන්න"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
index 41a432c..6f6ccb7 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez názvu)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Zavrieť režim PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Presunúť obraz v obraze"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
index de5605f..837794a 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program brez naslova)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Zapri način PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Celozaslonsko"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Premakni sliko v sliki"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
index 08a6409..107870d0 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program pa titull)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Mbyll PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Ekrani i plotë"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Zhvendos PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
index f932928..ee5690b 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програм без наслова)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Цео екран"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Премести слику у слици"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
index 1428fdb..7355adf 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Namnlöst program)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Stäng PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Helskärm"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Flytta BIB"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
index 615209f..0ee2841 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programu isiyo na jina)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Funga PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Skrini nzima"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Kuhamisha PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
index 71c242c..8bcc43b 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(தலைப்பு இல்லை)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIPஐ மூடு"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"முழுத்திரை"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIPபை நகர்த்து"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings_tv.xml b/libs/WindowManager/Shell/res/values-te/strings_tv.xml
index f2dfb39..6e80bd7 100644
--- a/libs/WindowManager/Shell/res/values-te/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings_tv.xml
@@ -20,7 +20,6 @@
     <string name="notification_channel_tv_pip" msgid="2576686079160402435">"పిక్చర్-ఇన్-పిక్చర్"</string>
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(శీర్షిక లేని ప్రోగ్రామ్)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIPని మూసివేయి"</string>
-    <string name="pip_fullscreen" msgid="7278047353591302554">"పూర్తి స్క్రీన్"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_fullscreen" msgid="7278047353591302554">"ఫుల్-స్క్రీన్‌"</string>
+    <string name="pip_move" msgid="1544227837964635439">"PIPను తరలించండి"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings_tv.xml b/libs/WindowManager/Shell/res/values-th/strings_tv.xml
index e810c88..b6f6369 100644
--- a/libs/WindowManager/Shell/res/values-th/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ไม่มีชื่อรายการ)"</string>
     <string name="pip_close" msgid="9135220303720555525">"ปิด PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"เต็มหน้าจอ"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"ย้าย PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
index 11d2953..71ca230 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Walang pamagat na programa)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Isara ang PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Ilipat ang PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
index 6ed6e9f..e6ae7f1 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıksız program)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP\'yi kapat"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIP\'yi taşı"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
index 482f59a..97e1f09 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без назви)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Закрити PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"На весь екран"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Перемістити картинку в картинці"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
index c1954c7..1418570 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(بلا عنوان پروگرام)"</string>
     <string name="pip_close" msgid="9135220303720555525">"‏PIP بند کریں"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"فُل اسکرین"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"‏PIP کو منتقل کریں"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
index 5140552..31c762e 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nomsiz)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Kadr ichida kadr – chiqish"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Butun ekran"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"PIPni siljitish"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
index e54d866..b46cd49 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Không có chương trình tiêu đề)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Đóng PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Toàn màn hình"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Di chuyển PIP (Ảnh trong ảnh)"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
index 9ce1e6c..b6fec63 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(节目没有标题)"</string>
     <string name="pip_close" msgid="9135220303720555525">"关闭画中画"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"全屏"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"移动画中画窗口"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
index 9846772..b5d54cb 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(沒有標題的節目)"</string>
     <string name="pip_close" msgid="9135220303720555525">"關閉 PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"移動畫中畫"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
index 7314d48..57db7a8 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無標題的節目)"</string>
     <string name="pip_close" msgid="9135220303720555525">"關閉子母畫面"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"移動子母畫面"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
index 63d9dd5..646a488 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
@@ -21,6 +21,5 @@
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Alukho uhlelo lwesihloko)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Vala i-PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Iskrini esigcwele"</string>
-    <!-- no translation found for pip_move (1544227837964635439) -->
-    <skip />
+    <string name="pip_move" msgid="1544227837964635439">"Hambisa i-PIP"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/attrs.xml b/libs/WindowManager/Shell/res/values/attrs.xml
new file mode 100644
index 0000000..4aaeef8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values/attrs.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <declare-styleable name="LetterboxEduDialogActionLayout">
+        <attr name="icon" format="reference" />
+        <attr name="text" format="string" />
+    </declare-styleable>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index cf596f7..84aec64 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -34,6 +34,9 @@
     <color name="compat_controls_background">@android:color/system_neutral1_800</color>
     <color name="compat_controls_text">@android:color/system_neutral1_50</color>
 
+    <!-- Letterbox Education -->
+    <color name="letterbox_education_text_secondary">@android:color/system_neutral2_200</color>
+
     <!-- GM2 colors -->
     <color name="GM2_grey_200">#E8EAED</color>
     <color name="GM2_grey_700">#5F6368</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 1c19a10..ab2c9b1 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -219,6 +219,37 @@
     <!-- The width of the camera compat hint. -->
     <dimen name="camera_compat_hint_width">143dp</dimen>
 
+    <!-- The corner radius of the letterbox education toast. -->
+    <dimen name="letterbox_education_toast_corner_radius">100dp</dimen>
+
+    <!-- The corner radius of the letterbox education dialog. -->
+    <dimen name="letterbox_education_dialog_corner_radius">28dp</dimen>
+
+    <!-- The margin between the letterbox education toast/dialog and the bottom of the task. -->
+    <dimen name="letterbox_education_margin_bottom">16dp</dimen>
+
+    <!-- The size of the icon in the letterbox education toast. -->
+    <dimen name="letterbox_education_toast_icon_size">24dp</dimen>
+
+    <!-- The size of an icon in the letterbox education dialog. -->
+    <dimen name="letterbox_education_dialog_icon_size">48dp</dimen>
+
+    <!-- The width of each action container in the letterbox education dialog -->
+    <dimen name="letterbox_education_dialog_action_width">136dp</dimen>
+
+    <!-- The space between two actions in the letterbox education dialog -->
+    <dimen name="letterbox_education_dialog_space_between_actions">18dp</dimen>
+
+    <!-- The maximum width of the title and subtitle in the letterbox education dialog. -->
+    <dimen name="letterbox_education_dialog_title_max_width">444dp</dimen>
+
+    <!-- The maximum width of the text in the letterbox education toast. -->
+    <dimen name="letterbox_education_toast_text_max_width">398dp</dimen>
+
+    <!-- The distance that the letterbox education dialog will move up during appear/dismiss
+         animation.  -->
+    <dimen name="letterbox_education_dialog_animation_elevation">20dp</dimen>
+
     <!-- The width of the brand image on staring surface. -->
     <dimen name="starting_surface_brand_image_width">200dp</dimen>
 
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index ab0013a..a8a9ed7 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -171,4 +171,28 @@
          compatibility control. [CHAR LIMIT=NONE] -->
     <string name="camera_compat_dismiss_button_description">No camera issues? Tap to dismiss.</string>
 
+    <!-- The title of the letterbox education dialog. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_dialog_title">Get the most out of <xliff:g id="app_name" example="YouTube">%s</xliff:g></string>
+
+    <!-- The title of the letterbox education toast. [CHAR LIMIT=60] -->
+    <string name="letterbox_education_toast_title">Rotate your device for a full-screen view</string>
+
+    <!-- Description of the rotate screen into portrait action. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_screen_rotation_portrait_text">Rotate your screen to portrait</string>
+
+    <!-- Description of the rotate screen into landscape action. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_screen_rotation_landscape_text">Rotate your screen to landscape</string>
+
+    <!-- Description of the put app in split-screen action. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_split_screen_text">Drag in another app to use split screen</string>
+
+    <!-- Description of the reposition app action. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_reposition_text">Double tap to reposition</string>
+
+    <!-- Button text for dismissing the letterbox education dialog. [CHAR LIMIT=20] -->
+    <string name="letterbox_education_got_it">Got it</string>
+
+    <!-- Accessibility description of the letterbox education toast expand to dialog button. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_expand_button_description">Expand for more information.</string>
+
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
new file mode 100644
index 0000000..762a037
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterboxedu;
+
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Custom layout for Letterbox Education dialog action.
+ */
+// TODO(b/215316431): Add tests
+class LetterboxEduDialogActionLayout extends FrameLayout {
+    private final ImageView mIcon;
+    private final TextView mText;
+
+    LetterboxEduDialogActionLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray styledAttributes =
+                context.getTheme().obtainStyledAttributes(
+                        attrs,
+                        R.styleable.LetterboxEduDialogActionLayout,
+                        /* defStyleAttr= */ 0,
+                        /* defStyleRes= */ 0);
+        int iconId = styledAttributes.getResourceId(
+                R.styleable.LetterboxEduDialogActionLayout_icon, 0);
+        String optionalText = styledAttributes.getString(
+                R.styleable.LetterboxEduDialogActionLayout_text);
+        styledAttributes.recycle();
+
+        View rootView = inflate(getContext(), R.layout.letterbox_education_dialog_action_layout,
+                this);
+
+        mIcon = rootView.findViewById(R.id.letterbox_education_dialog_action_icon);
+        mIcon.setImageResource(iconId);
+        mText = rootView.findViewById(R.id.letterbox_education_dialog_action_text);
+        if (optionalText != null) {
+            mText.setText(optionalText);
+        }
+    }
+
+    void setText(@StringRes int id) {
+        mText.setText(getResources().getString(id));
+    }
+
+    void setIconRotation(float rotation) {
+        mIcon.setRotation(rotation);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
new file mode 100644
index 0000000..662862a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterboxedu;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Configuration.Orientation;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Container for Letterbox Education Dialog.
+ */
+// TODO(b/215316431): Add tests
+public class LetterboxEduDialogLayout extends FrameLayout {
+
+    public LetterboxEduDialogLayout(Context context) {
+        this(context, null);
+    }
+
+    public LetterboxEduDialogLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LetterboxEduDialogLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public LetterboxEduDialogLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /**
+     * Register a callback for the dismiss button.
+     * @param callback The callback to register
+     */
+    void setDismissOnClickListener(Runnable callback) {
+        findViewById(R.id.letterbox_education_dialog_dismiss).setOnClickListener(
+                view -> callback.run());
+    }
+
+    /**
+     * Updates the layout with the given app info.
+     * @param appIcon The name of the app
+     * @param appIcon The icon of the app
+     */
+    void updateAppInfo(String appName, Drawable appIcon) {
+        ((ImageView) findViewById(R.id.letterbox_education_icon)).setImageDrawable(appIcon);
+        ((TextView) findViewById(R.id.letterbox_education_dialog_title)).setText(
+                getResources().getString(R.string.letterbox_education_dialog_title, appName));
+    }
+
+    /**
+     * Updates the layout according to the given orientation.
+     * @param orientation The orientation of the display
+     */
+    void updateDisplayOrientation(@Orientation int orientation) {
+        boolean isOrientationPortrait = orientation == Configuration.ORIENTATION_PORTRAIT;
+        ((LetterboxEduDialogActionLayout) findViewById(
+                R.id.letterbox_education_dialog_screen_rotation_action)).setText(
+                isOrientationPortrait
+                        ? R.string.letterbox_education_screen_rotation_landscape_text
+                        : R.string.letterbox_education_screen_rotation_portrait_text);
+
+        if (isOrientationPortrait) {
+            ((LetterboxEduDialogActionLayout) findViewById(
+                    R.id.letterbox_education_dialog_split_screen_action)).setIconRotation(90f);
+        }
+
+        findViewById(R.id.letterbox_education_dialog_reposition_action).setVisibility(
+                isOrientationPortrait ? View.GONE : View.VISIBLE);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java
new file mode 100644
index 0000000..e7f592d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterboxedu;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Container for the Letterbox Education Toast.
+ */
+// TODO(b/215316431): Add tests
+public class LetterboxEduToastLayout extends FrameLayout {
+
+    public LetterboxEduToastLayout(Context context) {
+        this(context, null);
+    }
+
+    public LetterboxEduToastLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LetterboxEduToastLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public LetterboxEduToastLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /**
+     * Register a callback for the dismiss button.
+     * @param callback The callback to register
+     */
+    void setExpandOnClickListener(Runnable callback) {
+        findViewById(R.id.letterbox_education_toast_expand).setOnClickListener(
+                view -> callback.run());
+    }
+
+    /**
+     * Updates the layout with the given app info.
+     * @param appName The name of the app
+     * @param appIcon The icon of the app
+     */
+    void updateAppInfo(String appName, Drawable appIcon) {
+        ImageView icon = findViewById(R.id.letterbox_education_icon);
+        icon.setContentDescription(appName);
+        icon.setImageDrawable(appIcon);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 6921448..3e6dc82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -137,6 +137,9 @@
     private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
 
     private StageCoordinator mStageCoordinator;
+    // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
+    // outside the bounds of the roots by being reparented into a higher level fullscreen container
+    private SurfaceControl mSplitTasksContainerLayer;
 
     public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue, Context context,
@@ -378,20 +381,24 @@
     RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
         if (ENABLE_SHELL_TRANSITIONS || apps.length < 2) return null;
         // TODO(b/206487881): Integrate this with shell transition.
+        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+        if (mSplitTasksContainerLayer != null) {
+            // Remove the previous layer before recreating
+            transaction.remove(mSplitTasksContainerLayer);
+        }
         final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
                 .setContainerLayer()
                 .setName("RecentsAnimationSplitTasks")
                 .setHidden(false)
                 .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
         mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
-        SurfaceControl sc = builder.build();
-        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+        mSplitTasksContainerLayer = builder.build();
 
         // Ensure that we order these in the parent in the right z-order as their previous order
         Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
         int layer = 1;
         for (RemoteAnimationTarget appTarget : apps) {
-            transaction.reparent(appTarget.leash, sc);
+            transaction.reparent(appTarget.leash, mSplitTasksContainerLayer);
             transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
                     appTarget.screenSpaceBounds.top);
             transaction.setLayer(appTarget.leash, layer++);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 413627d..e255e44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -54,6 +54,7 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.window.SplashScreenView;
+import android.window.StartingWindowInfo;
 import android.window.StartingWindowInfo.StartingWindowType;
 
 import com.android.internal.R;
@@ -138,8 +139,8 @@
      *                                 executed on splash screen thread. Note that the view can be
      *                                 null if failed.
      */
-    void createContentView(Context context, @StartingWindowType int suggestType, ActivityInfo info,
-            int taskId, Consumer<SplashScreenView> splashScreenViewConsumer,
+    void createContentView(Context context, @StartingWindowType int suggestType,
+            StartingWindowInfo info, Consumer<SplashScreenView> splashScreenViewConsumer,
             Consumer<Runnable> uiThreadInitConsumer) {
         mSplashscreenWorkerHandler.post(() -> {
             SplashScreenView contentView;
@@ -150,7 +151,7 @@
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             } catch (RuntimeException e) {
                 Slog.w(TAG, "failed creating starting window content at taskId: "
-                        + taskId, e);
+                        + info.taskInfo.taskId, e);
                 contentView = null;
             }
             splashScreenViewConsumer.accept(contentView);
@@ -241,7 +242,7 @@
         return null;
     }
 
-    private SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai,
+    private SplashScreenView makeSplashScreenContentView(Context context, StartingWindowInfo info,
             @StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) {
         updateDensity();
 
@@ -250,6 +251,9 @@
 
         final Drawable legacyDrawable = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
                 ? peekLegacySplashscreenContent(context, mTmpAttrs) : null;
+        final ActivityInfo ai = info.targetActivityInfo != null
+                ? info.targetActivityInfo
+                : info.taskInfo.topActivityInfo;
         final int themeBGColor = legacyDrawable != null
                 ? getBGColorFromCache(ai, () -> estimateWindowBGColor(legacyDrawable))
                 : getBGColorFromCache(ai, () -> peekWindowBGColor(context, mTmpAttrs));
@@ -258,6 +262,7 @@
                 .overlayDrawable(legacyDrawable)
                 .chooseStyle(suggestType)
                 .setUiThreadInitConsumer(uiThreadInitConsumer)
+                .setAllowHandleEmpty(info.allowHandleEmptySplashScreen())
                 .build();
     }
 
@@ -327,6 +332,7 @@
         private Drawable[] mFinalIconDrawables;
         private int mFinalIconSize = mIconSize;
         private Consumer<Runnable> mUiThreadInitTask;
+        private boolean mAllowHandleEmpty;
 
         StartingWindowViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
             mContext = context;
@@ -353,6 +359,11 @@
             return this;
         }
 
+        StartingWindowViewBuilder setAllowHandleEmpty(boolean allowHandleEmpty) {
+            mAllowHandleEmpty = allowHandleEmpty;
+            return this;
+        }
+
         SplashScreenView build() {
             Drawable iconDrawable;
             final int animationDuration;
@@ -491,7 +502,8 @@
                     .setIconBackground(background)
                     .setCenterViewDrawable(foreground)
                     .setAnimationDurationMillis(animationDuration)
-                    .setUiThreadInitConsumer(uiThreadInitTask);
+                    .setUiThreadInitConsumer(uiThreadInitTask)
+                    .setAllowHandleEmpty(mAllowHandleEmpty);
 
             if (mSuggestType == STARTING_WINDOW_TYPE_SPLASH_SCREEN
                     && mTmpAttrs.mBrandingImage != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index f8902c6..9a966b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -328,7 +328,7 @@
         if (mSysuiProxy != null) {
             mSysuiProxy.requestTopUi(true, TAG);
         }
-        mSplashscreenContentDrawer.createContentView(context, suggestType, activityInfo, taskId,
+        mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
                 viewSupplier::setView, viewSupplier::setUiThreadInitTask);
         try {
             if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 4ecc0b6..1bef552e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -126,9 +126,6 @@
      */
     private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
 
-    //tmp vars for unused relayout params
-    private static final Point TMP_SURFACE_SIZE = new Point();
-
     private final Window mWindow;
     private final Runnable mClearWindowHandler;
     private final ShellExecutor mSplashScreenExecutor;
@@ -244,9 +241,9 @@
         window.setOuter(snapshotSurface);
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
-            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1,
+            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
                     tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
-                    tmpControls, TMP_SURFACE_SIZE);
+                    tmpControls);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         } catch (RemoteException e) {
             snapshotSurface.clearWindowSynced();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/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/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
index f8d14c6..af629cc 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,9 +24,6 @@
 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
@@ -62,12 +59,6 @@
             }
         }
 
-    @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 c93c5ad..add11c1 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,9 +25,6 @@
 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
@@ -70,12 +67,6 @@
             }
         }
 
-    @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/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index dee13c1..afe64e3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -204,7 +204,6 @@
     @Presubmit
     @Test
     fun testAppPlusPipLayerCoversFullScreenOnEnd() {
-        // This test doesn't work in shell transitions because of b/206669574
         testSpec.assertLayersEnd {
             val pipRegion = visibleRegion(pipApp.component).region
             visibleRegion(testApp.component)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index c36dfda..1d61ab4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -31,6 +31,7 @@
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.wm.shell.flicker.helpers.ImeAppHelper
 import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -82,6 +83,13 @@
         super.statusBarLayerRotatesScales()
     }
 
+    @FlakyTest(bugId = 214452854)
+    @Test
+    fun statusBarLayerRotatesScales_shellTransit() {
+        assumeTrue(isShellTransitionsEnabled)
+        super.statusBarLayerRotatesScales()
+    }
+
     /**
      * Ensure the pip window remains visible throughout any keyboard interactions
      */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
index df58194..21175a0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
@@ -92,11 +92,7 @@
     /** {@inheritDoc}  */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(com.android.server.wm.flicker.helpers.isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     @FlakyTest(bugId = 161435597)
     @Test
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 6989ac0..5db0783 100755
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -686,16 +686,14 @@
                                           }
                                           return data->ptr != nullptr;
                                       }));
-        inPlaceCallback(std::move(data.ptr), data.size);
-        return STATUS_OK;
+        return inPlaceCallback(std::move(data.ptr), data.size);
     } else if (type == BlobType::ASHMEM) {
         int rawFd = -1;
         int32_t size = 0;
         ON_ERROR_RETURN(AParcel_readInt32(parcel, &size));
         ON_ERROR_RETURN(AParcel_readParcelFileDescriptor(parcel, &rawFd));
         android::base::unique_fd fd(rawFd);
-        ashmemCallback(std::move(fd), size);
-        return STATUS_OK;
+        return ashmemCallback(std::move(fd), size);
     } else {
         // Although the above if/else was "exhaustive" guard against unknown types
         return STATUS_UNKNOWN_ERROR;
@@ -768,7 +766,7 @@
 // framework, we may need to update this maximum size.
 static constexpr size_t kMaxColorSpaceSerializedBytes = 80;
 
-static constexpr auto RuntimeException = "java/lang/RuntimeException";
+static constexpr auto BadParcelableException = "android/os/BadParcelableException";
 
 static bool validateImageInfo(const SkImageInfo& info, int32_t rowBytes) {
     // TODO: Can we avoid making a SkBitmap for this?
@@ -809,7 +807,7 @@
             kRGB_565_SkColorType != colorType &&
             kARGB_4444_SkColorType != colorType &&
             kAlpha_8_SkColorType != colorType) {
-        jniThrowExceptionFmt(env, RuntimeException,
+        jniThrowExceptionFmt(env, BadParcelableException,
                              "Bitmap_createFromParcel unknown colortype: %d\n", colorType);
         return NULL;
     }
@@ -821,7 +819,7 @@
         return NULL;
     }
     if (!Bitmap::computeAllocationSize(rowBytes, height, &allocationSize)) {
-        jniThrowExceptionFmt(env, RuntimeException,
+        jniThrowExceptionFmt(env, BadParcelableException,
                              "Received bad bitmap size: width=%d, height=%d, rowBytes=%d", width,
                              height, rowBytes);
         return NULL;
@@ -831,13 +829,23 @@
             p.get(),
             // In place callback
             [&](std::unique_ptr<int8_t[]> buffer, int32_t size) {
+                if (allocationSize > size) {
+                    android_errorWriteLog(0x534e4554, "213169612");
+                    return STATUS_BAD_VALUE;
+                }
                 nativeBitmap = Bitmap::allocateHeapBitmap(allocationSize, imageInfo, rowBytes);
                 if (nativeBitmap) {
-                    memcpy(nativeBitmap->pixels(), buffer.get(), size);
+                    memcpy(nativeBitmap->pixels(), buffer.get(), allocationSize);
+                    return STATUS_OK;
                 }
+                return STATUS_NO_MEMORY;
             },
             // Ashmem callback
             [&](android::base::unique_fd fd, int32_t size) {
+                if (allocationSize > size) {
+                    android_errorWriteLog(0x534e4554, "213169612");
+                    return STATUS_BAD_VALUE;
+                }
                 int flags = PROT_READ;
                 if (isMutable) {
                     flags |= PROT_WRITE;
@@ -846,18 +854,21 @@
                 if (addr == MAP_FAILED) {
                     const int err = errno;
                     ALOGW("mmap failed, error %d (%s)", err, strerror(err));
-                    return;
+                    return STATUS_NO_MEMORY;
                 }
                 nativeBitmap =
                         Bitmap::createFrom(imageInfo, rowBytes, fd.release(), addr, size, !isMutable);
+                return STATUS_OK;
             });
-    if (error != STATUS_OK) {
+
+    if (error != STATUS_OK && error != STATUS_NO_MEMORY) {
         // TODO: Stringify the error, see signalExceptionForError in android_util_Binder.cpp
-        jniThrowExceptionFmt(env, RuntimeException, "Failed to read from Parcel, error=%d", error);
+        jniThrowExceptionFmt(env, BadParcelableException, "Failed to read from Parcel, error=%d",
+                             error);
         return nullptr;
     }
-    if (!nativeBitmap) {
-        jniThrowRuntimeException(env, "Could not allocate java pixel ref.");
+    if (error == STATUS_NO_MEMORY || !nativeBitmap) {
+        jniThrowRuntimeException(env, "Could not allocate bitmap data.");
         return nullptr;
     }
 
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 553b08f..7c57bd5 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -78,7 +78,9 @@
 
 static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader,
                                                 const shaders::LinearEffect& linearEffect,
-                                                float maxDisplayLuminance, float maxLuminance) {
+                                                float maxDisplayLuminance,
+                                                float currentDisplayLuminanceNits,
+                                                float maxLuminance) {
     auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
     auto [runtimeEffect, error] = SkRuntimeEffect::MakeForShader(std::move(shaderString));
     if (!runtimeEffect) {
@@ -89,8 +91,8 @@
 
     effectBuilder.child("child") = std::move(shader);
 
-    const auto uniforms = shaders::buildLinearEffectUniforms(linearEffect, mat4(),
-                                                             maxDisplayLuminance, maxLuminance);
+    const auto uniforms = shaders::buildLinearEffectUniforms(
+            linearEffect, mat4(), maxDisplayLuminance, currentDisplayLuminanceNits, maxLuminance);
 
     for (const auto& uniform : uniforms) {
         effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
@@ -201,7 +203,9 @@
             auto shader = layerImage->makeShader(sampling,
                                                  SkMatrix::RectToRect(skiaSrcRect, skiaDestRect));
             constexpr float kMaxDisplayBrightess = 1000.f;
+            constexpr float kCurrentDisplayBrightness = 500.f;
             shader = createLinearEffectShader(std::move(shader), effect, kMaxDisplayBrightess,
+                                              kCurrentDisplayBrightness,
                                               layer->getMaxLuminanceNits());
             paint.setShader(shader);
             canvas->drawRect(skiaDestRect, paint);
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index d6e203c..587222a 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -29,7 +29,6 @@
 import android.annotation.SystemApi;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -180,13 +179,9 @@
     private static final long IMPLICIT_MIN_UPDATE_INTERVAL = -1;
     private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D;
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
-            + "LocationManager} methods to provide the provider explicitly.")
-    @Nullable private String mProvider;
+    private @Nullable String mProvider;
     private @Quality int mQuality;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
-            + "LocationRequest} instead.")
-    private long mInterval;
+    private long mIntervalMillis;
     private long mMinUpdateIntervalMillis;
     private long mExpireAtRealtimeMillis;
     private long mDurationMillis;
@@ -195,7 +190,7 @@
     private final long mMaxUpdateDelayMillis;
     private boolean mHideFromAppOps;
     private final boolean mAdasGnssBypass;
-    private boolean mLocationSettingsIgnored;
+    private boolean mBypass;
     private boolean mLowPower;
     private @Nullable WorkSource mWorkSource;
 
@@ -208,9 +203,7 @@
     @NonNull
     public static LocationRequest create() {
         // 60 minutes is the default legacy interval
-        return new LocationRequest.Builder(60 * 60 * 1000)
-                .setQuality(QUALITY_LOW_POWER)
-                .build();
+        return new LocationRequest.Builder(60 * 60 * 1000).build();
     }
 
     /**
@@ -239,7 +232,7 @@
         } else if (LocationManager.GPS_PROVIDER.equals(provider)) {
             quality = QUALITY_HIGH_ACCURACY;
         } else {
-            quality = POWER_LOW;
+            quality = QUALITY_BALANCED_POWER_ACCURACY;
         }
 
         return new LocationRequest.Builder(intervalMillis)
@@ -291,11 +284,11 @@
             long maxUpdateDelayMillis,
             boolean hiddenFromAppOps,
             boolean adasGnssBypass,
-            boolean locationSettingsIgnored,
+            boolean bypass,
             boolean lowPower,
             WorkSource workSource) {
         mProvider = provider;
-        mInterval = intervalMillis;
+        mIntervalMillis = intervalMillis;
         mQuality = quality;
         mMinUpdateIntervalMillis = minUpdateIntervalMillis;
         mExpireAtRealtimeMillis = expireAtRealtimeMillis;
@@ -305,7 +298,7 @@
         mMaxUpdateDelayMillis = maxUpdateDelayMillis;
         mHideFromAppOps = hiddenFromAppOps;
         mAdasGnssBypass = adasGnssBypass;
-        mLocationSettingsIgnored = locationSettingsIgnored;
+        mBypass = bypass;
         mLowPower = lowPower;
         mWorkSource = Objects.requireNonNull(workSource);
     }
@@ -354,7 +347,7 @@
                 mQuality = QUALITY_LOW_POWER;
                 break;
             case POWER_NONE:
-                mInterval = PASSIVE_INTERVAL;
+                mIntervalMillis = PASSIVE_INTERVAL;
                 break;
             default:
                 throw new IllegalArgumentException("invalid quality: " + quality);
@@ -388,9 +381,9 @@
             millis = Long.MAX_VALUE - 1;
         }
 
-        mInterval = millis;
-        if (mMinUpdateIntervalMillis > mInterval) {
-            mMinUpdateIntervalMillis = mInterval;
+        mIntervalMillis = millis;
+        if (mMinUpdateIntervalMillis > mIntervalMillis) {
+            mMinUpdateIntervalMillis = mIntervalMillis;
         }
         return this;
     }
@@ -418,7 +411,7 @@
      * @return the desired interval of location updates
      */
     public @IntRange(from = 0) long getIntervalMillis() {
-        return mInterval;
+        return mIntervalMillis;
     }
 
     /**
@@ -556,11 +549,11 @@
      */
     public @IntRange(from = 0) long getMinUpdateIntervalMillis() {
         if (mMinUpdateIntervalMillis == IMPLICIT_MIN_UPDATE_INTERVAL) {
-            return (long) (mInterval * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
+            return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
         } else {
             // the min is only necessary in case someone use a deprecated function to mess with the
             // interval or min update interval
-            return min(mMinUpdateIntervalMillis, mInterval);
+            return min(mMinUpdateIntervalMillis, mIntervalMillis);
         }
     }
 
@@ -673,7 +666,7 @@
     @Deprecated
     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
     public @NonNull LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) {
-        mLocationSettingsIgnored = locationSettingsIgnored;
+        mBypass = locationSettingsIgnored;
         return this;
     }
 
@@ -687,7 +680,7 @@
      */
     @SystemApi
     public boolean isLocationSettingsIgnored() {
-        return mLocationSettingsIgnored;
+        return mBypass;
     }
 
     /**
@@ -696,7 +689,7 @@
      * @hide
      */
     public boolean isBypass() {
-        return mAdasGnssBypass || mLocationSettingsIgnored;
+        return mAdasGnssBypass || mBypass;
     }
 
     /**
@@ -796,7 +789,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
         parcel.writeString(mProvider);
-        parcel.writeLong(mInterval);
+        parcel.writeLong(mIntervalMillis);
         parcel.writeInt(mQuality);
         parcel.writeLong(mExpireAtRealtimeMillis);
         parcel.writeLong(mDurationMillis);
@@ -806,7 +799,7 @@
         parcel.writeLong(mMaxUpdateDelayMillis);
         parcel.writeBoolean(mHideFromAppOps);
         parcel.writeBoolean(mAdasGnssBypass);
-        parcel.writeBoolean(mLocationSettingsIgnored);
+        parcel.writeBoolean(mBypass);
         parcel.writeBoolean(mLowPower);
         parcel.writeTypedObject(mWorkSource, 0);
     }
@@ -821,7 +814,7 @@
         }
 
         LocationRequest that = (LocationRequest) o;
-        return mInterval == that.mInterval
+        return mIntervalMillis == that.mIntervalMillis
                 && mQuality == that.mQuality
                 && mExpireAtRealtimeMillis == that.mExpireAtRealtimeMillis
                 && mDurationMillis == that.mDurationMillis
@@ -831,7 +824,7 @@
                 && mMaxUpdateDelayMillis == that.mMaxUpdateDelayMillis
                 && mHideFromAppOps == that.mHideFromAppOps
                 && mAdasGnssBypass == that.mAdasGnssBypass
-                && mLocationSettingsIgnored == that.mLocationSettingsIgnored
+                && mBypass == that.mBypass
                 && mLowPower == that.mLowPower
                 && Objects.equals(mProvider, that.mProvider)
                 && Objects.equals(mWorkSource, that.mWorkSource);
@@ -839,7 +832,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mProvider, mInterval, mWorkSource);
+        return Objects.hash(mProvider, mIntervalMillis, mWorkSource);
     }
 
     @NonNull
@@ -850,9 +843,9 @@
         if (mProvider != null) {
             s.append(mProvider).append(" ");
         }
-        if (mInterval != PASSIVE_INTERVAL) {
+        if (mIntervalMillis != PASSIVE_INTERVAL) {
             s.append("@");
-            TimeUtils.formatDuration(mInterval, s);
+            TimeUtils.formatDuration(mIntervalMillis, s);
 
             switch (mQuality) {
                 case QUALITY_HIGH_ACCURACY:
@@ -879,14 +872,14 @@
             s.append(", maxUpdates=").append(mMaxUpdates);
         }
         if (mMinUpdateIntervalMillis != IMPLICIT_MIN_UPDATE_INTERVAL
-                && mMinUpdateIntervalMillis < mInterval) {
+                && mMinUpdateIntervalMillis < mIntervalMillis) {
             s.append(", minUpdateInterval=");
             TimeUtils.formatDuration(mMinUpdateIntervalMillis, s);
         }
         if (mMinUpdateDistanceMeters > 0.0) {
             s.append(", minUpdateDistance=").append(mMinUpdateDistanceMeters);
         }
-        if (mMaxUpdateDelayMillis / 2 > mInterval) {
+        if (mMaxUpdateDelayMillis / 2 > mIntervalMillis) {
             s.append(", maxUpdateDelay=");
             TimeUtils.formatDuration(mMaxUpdateDelayMillis, s);
         }
@@ -899,8 +892,8 @@
         if (mAdasGnssBypass) {
             s.append(", adasGnssBypass");
         }
-        if (mLocationSettingsIgnored) {
-            s.append(", settingsBypass");
+        if (mBypass) {
+            s.append(", bypass");
         }
         if (mWorkSource != null && !mWorkSource.isEmpty()) {
             s.append(", ").append(mWorkSource);
@@ -923,7 +916,7 @@
         private long mMaxUpdateDelayMillis;
         private boolean mHiddenFromAppOps;
         private boolean mAdasGnssBypass;
-        private boolean mLocationSettingsIgnored;
+        private boolean mBypass;
         private boolean mLowPower;
         @Nullable private WorkSource mWorkSource;
 
@@ -943,7 +936,7 @@
             mMaxUpdateDelayMillis = 0;
             mHiddenFromAppOps = false;
             mAdasGnssBypass = false;
-            mLocationSettingsIgnored = false;
+            mBypass = false;
             mLowPower = false;
             mWorkSource = null;
         }
@@ -952,7 +945,7 @@
          * Creates a new Builder with all parameters copied from the given location request.
          */
         public Builder(@NonNull LocationRequest locationRequest) {
-            mIntervalMillis = locationRequest.mInterval;
+            mIntervalMillis = locationRequest.mIntervalMillis;
             mQuality = locationRequest.mQuality;
             mDurationMillis = locationRequest.mDurationMillis;
             mMaxUpdates = locationRequest.mMaxUpdates;
@@ -961,7 +954,7 @@
             mMaxUpdateDelayMillis = locationRequest.mMaxUpdateDelayMillis;
             mHiddenFromAppOps = locationRequest.mHideFromAppOps;
             mAdasGnssBypass = locationRequest.mAdasGnssBypass;
-            mLocationSettingsIgnored = locationRequest.mLocationSettingsIgnored;
+            mBypass = locationRequest.mBypass;
             mLowPower = locationRequest.mLowPower;
             mWorkSource = locationRequest.mWorkSource;
 
@@ -1160,14 +1153,15 @@
         @SystemApi
         @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
         public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) {
-            mLocationSettingsIgnored = locationSettingsIgnored;
+            mBypass = locationSettingsIgnored;
             return this;
         }
 
         /**
          * It set to true, indicates that extreme trade-offs should be made if possible to save
          * power for this request. This usually involves specialized hardware modes which can
-         * greatly affect the quality of locations. Defaults to false.
+         * greatly affect the quality of locations. Not all devices may support this. Defaults to
+         * false.
          *
          * <p>Permissions enforcement occurs when resulting location request is actually used, not
          * when this method is invoked.
@@ -1227,7 +1221,7 @@
                     mMaxUpdateDelayMillis,
                     mHiddenFromAppOps,
                     mAdasGnssBypass,
-                    mLocationSettingsIgnored,
+                    mBypass,
                     mLowPower,
                     new WorkSource(mWorkSource));
         }
diff --git a/location/java/android/location/SatellitePvt.java b/location/java/android/location/SatellitePvt.java
index aa43cfd..29888e1 100644
--- a/location/java/android/location/SatellitePvt.java
+++ b/location/java/android/location/SatellitePvt.java
@@ -17,12 +17,19 @@
 package android.location;
 
 import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A class that contains GNSS satellite position, velocity and time information at the
  * same signal transmission time {@link GnssMeasurement#getReceivedSvTimeNanos()}.
@@ -64,6 +71,60 @@
     private static final int HAS_TROPO = 1 << 2;
 
     /**
+     * Bit mask for {@link #mFlags} indicating a valid Issue of Data, Clock field is stored in the
+     * SatellitePvt.
+     */
+    private static final int HAS_ISSUE_OF_DATA_CLOCK = 1 << 3;
+
+    /**
+     * Bit mask for {@link #mFlags} indicating a valid Issue of Data, Ephemeris field is stored in
+     * the SatellitePvt.
+     */
+    private static final int HAS_ISSUE_OF_DATA_EPHEMERIS = 1 << 4;
+
+    /**
+     * Bit mask for {@link #mFlags} indicating a valid Time of Clock field is stored in the
+     * SatellitePvt.
+     */
+    private static final int HAS_TIME_OF_CLOCK = 1 << 5;
+
+    /**
+     * Bit mask for {@link #mFlags} indicating a valid Time of Ephemeris field is stored in
+     * the SatellitePvt.
+     */
+    private static final int HAS_TIME_OF_EPHEMERIS = 1 << 6;
+
+
+    /** Ephemeris demodulated from broadcast signals */
+    public static final int EPHEMERIS_SOURCE_DEMODULATED = 0;
+
+    /**
+     * Server provided Normal type ephemeris data, which is similar to broadcast ephemeris in
+     * longevity (e.g. SUPL) - lasting for few hours and providing satellite orbit and clock
+     * with accuracy of 1 - 2 meters.
+     */
+    public static final int EPHEMERIS_SOURCE_SERVER_NORMAL = 1;
+
+    /**
+     * Server provided Long-Term type ephemeris data, which lasts for many hours to several days
+     * and often provides satellite orbit and clock accuracy of 2 - 20 meters.
+     */
+    public static final int EPHEMERIS_SOURCE_SERVER_LONG_TERM = 2;
+
+    /** Other ephemeris source */
+    public static final int EPHEMERIS_SOURCE_OTHER = 3;
+
+    /**
+     * Satellite ephemeris source
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({EPHEMERIS_SOURCE_DEMODULATED, EPHEMERIS_SOURCE_SERVER_NORMAL,
+            EPHEMERIS_SOURCE_SERVER_LONG_TERM, EPHEMERIS_SOURCE_OTHER})
+    public @interface EphemerisSource {
+    }
+
+    /**
      * A bitfield of flags indicating the validity of the fields in this SatellitePvt.
      * The bit masks are defined in the constants with prefix HAS_*
      *
@@ -83,6 +144,12 @@
     private final ClockInfo mClockInfo;
     private final double mIonoDelayMeters;
     private final double mTropoDelayMeters;
+    private final int mTimeOfClock;
+    private final int mTimeOfEphemeris;
+    private final int mIssueOfDataClock;
+    private final int mIssueOfDataEphemeris;
+    @EphemerisSource
+    private final int mEphemerisSource;
 
     /**
      * Class containing estimates of the satellite position fields in ECEF coordinate frame.
@@ -389,13 +456,23 @@
             @Nullable VelocityEcef velocityEcef,
             @Nullable ClockInfo clockInfo,
             double ionoDelayMeters,
-            double tropoDelayMeters) {
+            double tropoDelayMeters,
+            int timeOfClock,
+            int timeOfEphemeris,
+            int issueOfDataClock,
+            int issueOfDataEphemeris,
+            @EphemerisSource int ephemerisSource) {
         mFlags = flags;
         mPositionEcef = positionEcef;
         mVelocityEcef = velocityEcef;
         mClockInfo = clockInfo;
         mIonoDelayMeters = ionoDelayMeters;
         mTropoDelayMeters = tropoDelayMeters;
+        mTimeOfClock = timeOfClock;
+        mTimeOfEphemeris = timeOfEphemeris;
+        mIssueOfDataClock = issueOfDataClock;
+        mIssueOfDataEphemeris = issueOfDataEphemeris;
+        mEphemerisSource = ephemerisSource;
     }
 
     /**
@@ -441,6 +518,66 @@
         return mTropoDelayMeters;
     }
 
+    /**
+     * Issue of Data, Clock.
+     *
+     * <p>This is defined in GPS ICD200 documentation (e.g.,
+     * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
+     *
+     * <p>This field is valid if {@link #hasIssueOfDataClock()} is true.
+     */
+    @IntRange(from = 0, to = 1023)
+    public int getIssueOfDataClock() {
+        return mIssueOfDataClock;
+    }
+
+    /**
+     * Issue of Data, Ephemeris.
+     *
+     * <p>This is defined in GPS ICD200 documentation (e.g.,
+     * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
+     *
+     * <p>This field is valid if {@link #hasIssueOfDataEphemeris()} is true.
+     */
+    @IntRange(from = 0, to = 255)
+    public int getIssueOfDataEphemeris() {
+        return mIssueOfDataEphemeris;
+    }
+
+    /**
+     * Time of Clock.
+     *
+     * <p>This is defined in GPS ICD200 documentation (e.g.,
+     * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
+     *
+     * <p>This field is valid if {@link #hasTimeOfClock()} is true.
+     */
+    @IntRange(from = 0, to = 604784)
+    public int getTimeOfClock() {
+        return mTimeOfClock;
+    }
+
+    /**
+     * Time of ephemeris.
+     *
+     * <p>This is defined in GPS ICD200 documentation (e.g.,
+     * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
+     *
+     * <p>This field is valid if {@link #hasTimeOfEphemeris()} is true.
+     */
+    @IntRange(from = 0, to = 604784)
+    public int getTimeOfEphemeris() {
+        return mTimeOfEphemeris;
+    }
+
+    /**
+     * Satellite ephemeris source.
+     */
+    @EphemerisSource
+    public int getEphemerisSource() {
+        return mEphemerisSource;
+    }
+
     /** Returns {@code true} if {@link #getPositionEcef()}, {@link #getVelocityEcef()},
      * and {@link #getClockInfo()} are valid.
      */
@@ -458,6 +595,26 @@
         return (mFlags & HAS_TROPO) != 0;
     }
 
+    /** Returns {@code true} if {@link #getIssueOfDataClock()} is valid. */
+    public boolean hasIssueOfDataClock() {
+        return (mFlags & HAS_ISSUE_OF_DATA_CLOCK) != 0;
+    }
+
+    /** Returns {@code true} if {@link #getIssueOfDataEphemeris()} is valid. */
+    public boolean hasIssueOfDataEphemeris() {
+        return (mFlags & HAS_ISSUE_OF_DATA_EPHEMERIS) != 0;
+    }
+
+    /** Returns {@code true} if {@link #getTimeOfClock()} ()} is valid. */
+    public boolean hasTimeOfClock() {
+        return (mFlags & HAS_TIME_OF_CLOCK) != 0;
+    }
+
+    /** Returns {@code true} if {@link #getTimeOfEphemeris()} is valid. */
+    public boolean hasTimeOfEphemeris() {
+        return (mFlags & HAS_TIME_OF_EPHEMERIS) != 0;
+    }
+
     public static final @android.annotation.NonNull Creator<SatellitePvt> CREATOR =
             new Creator<SatellitePvt>() {
                 @Override
@@ -465,11 +622,19 @@
                 public SatellitePvt createFromParcel(Parcel in) {
                     int flags = in.readInt();
                     ClassLoader classLoader = getClass().getClassLoader();
-                    PositionEcef positionEcef = in.readParcelable(classLoader, android.location.SatellitePvt.PositionEcef.class);
-                    VelocityEcef velocityEcef = in.readParcelable(classLoader, android.location.SatellitePvt.VelocityEcef.class);
-                    ClockInfo clockInfo = in.readParcelable(classLoader, android.location.SatellitePvt.ClockInfo.class);
+                    PositionEcef positionEcef = in.readParcelable(classLoader,
+                            android.location.SatellitePvt.PositionEcef.class);
+                    VelocityEcef velocityEcef = in.readParcelable(classLoader,
+                            android.location.SatellitePvt.VelocityEcef.class);
+                    ClockInfo clockInfo = in.readParcelable(classLoader,
+                            android.location.SatellitePvt.ClockInfo.class);
                     double ionoDelayMeters = in.readDouble();
                     double tropoDelayMeters = in.readDouble();
+                    int toc = in.readInt();
+                    int toe = in.readInt();
+                    int iodc = in.readInt();
+                    int iode = in.readInt();
+                    int ephemerisSource = in.readInt();
 
                     return new SatellitePvt(
                             flags,
@@ -477,7 +642,12 @@
                             velocityEcef,
                             clockInfo,
                             ionoDelayMeters,
-                            tropoDelayMeters);
+                            tropoDelayMeters,
+                            toc,
+                            toe,
+                            iodc,
+                            iode,
+                            ephemerisSource);
                 }
 
                 @Override
@@ -499,18 +669,28 @@
         parcel.writeParcelable(mClockInfo, flags);
         parcel.writeDouble(mIonoDelayMeters);
         parcel.writeDouble(mTropoDelayMeters);
+        parcel.writeInt(mTimeOfClock);
+        parcel.writeInt(mTimeOfEphemeris);
+        parcel.writeInt(mIssueOfDataClock);
+        parcel.writeInt(mIssueOfDataEphemeris);
+        parcel.writeInt(mEphemerisSource);
     }
 
     @Override
     public String toString() {
-        return "SatellitePvt{"
+        return "SatellitePvt["
                 + "Flags=" + mFlags
                 + ", PositionEcef=" + mPositionEcef
                 + ", VelocityEcef=" + mVelocityEcef
                 + ", ClockInfo=" + mClockInfo
                 + ", IonoDelayMeters=" + mIonoDelayMeters
                 + ", TropoDelayMeters=" + mTropoDelayMeters
-                + "}";
+                + ", TimeOfClock=" + mTimeOfClock
+                + ", TimeOfEphemeris=" + mTimeOfEphemeris
+                + ", IssueOfDataClock=" + mIssueOfDataClock
+                + ", IssueOfDataEphemeris=" + mIssueOfDataEphemeris
+                + ", EphemerisSource=" + mEphemerisSource
+                + "]";
     }
 
     /**
@@ -527,12 +707,18 @@
         @Nullable private ClockInfo mClockInfo;
         private double mIonoDelayMeters;
         private double mTropoDelayMeters;
+        private int mTimeOfClock;
+        private int mTimeOfEphemeris;
+        private int mIssueOfDataClock;
+        private int mIssueOfDataEphemeris;
+        @EphemerisSource
+        private int mEphemerisSource = EPHEMERIS_SOURCE_OTHER;
 
         /**
          * Set position ECEF.
          *
          * @param positionEcef position ECEF object
-         * @return Builder builder object
+         * @return builder object
          */
         @NonNull
         public Builder setPositionEcef(
@@ -546,7 +732,7 @@
          * Set velocity ECEF.
          *
          * @param velocityEcef velocity ECEF object
-         * @return Builder builder object
+         * @return builder object
          */
         @NonNull
         public Builder setVelocityEcef(
@@ -560,7 +746,7 @@
          * Set clock info.
          *
          * @param clockInfo clock info object
-         * @return Builder builder object
+         * @return builder object
          */
         @NonNull
         public Builder setClockInfo(
@@ -580,7 +766,7 @@
          * Set ionospheric delay in meters.
          *
          * @param ionoDelayMeters ionospheric delay (meters)
-         * @return Builder builder object
+         * @return builder object
          */
         @NonNull
         public Builder setIonoDelayMeters(
@@ -594,7 +780,7 @@
          * Set tropospheric delay in meters.
          *
          * @param tropoDelayMeters tropospheric delay (meters)
-         * @return Builder builder object
+         * @return builder object
          */
         @NonNull
         public Builder setTropoDelayMeters(
@@ -605,6 +791,80 @@
         }
 
         /**
+         * Set time of clock in seconds.
+         *
+         * @param timeOfClock time of clock (seconds)
+         * @return builder object
+         */
+        @NonNull
+        public Builder setTimeOfClock(@IntRange(from = 0, to = 604784) int timeOfClock) {
+            Preconditions.checkArgumentInRange(timeOfClock, 0, 604784, "timeOfClock");
+            mTimeOfClock = timeOfClock;
+            mFlags = (byte) (mFlags | HAS_TIME_OF_CLOCK);
+            return this;
+        }
+
+        /**
+         * Set time of ephemeris in seconds.
+         *
+         * @param timeOfEphemeris time of ephemeris (seconds)
+         * @return builder object
+         */
+        @NonNull
+        public Builder setTimeOfEphemeris(@IntRange(from = 0, to = 604784) int timeOfEphemeris) {
+            Preconditions.checkArgumentInRange(timeOfEphemeris, 0, 604784, "timeOfEphemeris");
+            mTimeOfEphemeris = timeOfEphemeris;
+            mFlags = (byte) (mFlags | HAS_TIME_OF_EPHEMERIS);
+            return this;
+        }
+
+        /**
+         * Set issue of data, clock.
+         *
+         * @param issueOfDataClock issue of data, clock.
+         * @return builder object
+         */
+        @NonNull
+        public Builder setIssueOfDataClock(@IntRange(from = 0, to = 1023) int issueOfDataClock) {
+            Preconditions.checkArgumentInRange(issueOfDataClock, 0, 1023, "issueOfDataClock");
+            mIssueOfDataClock = issueOfDataClock;
+            mFlags = (byte) (mFlags | HAS_ISSUE_OF_DATA_CLOCK);
+            return this;
+        }
+
+        /**
+         * Set issue of data, ephemeris.
+         *
+         * @param issueOfDataEphemeris issue of data, ephemeris.
+         * @return builder object
+         */
+        @NonNull
+        public Builder setIssueOfDataEphemeris(
+                @IntRange(from = 0, to = 255) int issueOfDataEphemeris) {
+            Preconditions.checkArgumentInRange(issueOfDataEphemeris, 0, 255,
+                    "issueOfDataEphemeris");
+            mIssueOfDataEphemeris = issueOfDataEphemeris;
+            mFlags = (byte) (mFlags | HAS_ISSUE_OF_DATA_EPHEMERIS);
+            return this;
+        }
+
+        /**
+         * Set satellite ephemeris source.
+         *
+         * @param ephemerisSource satellite ephemeris source
+         * @return builder object
+         */
+        @NonNull
+        public Builder setEphemerisSource(@EphemerisSource int ephemerisSource) {
+            Preconditions.checkArgument(ephemerisSource == EPHEMERIS_SOURCE_DEMODULATED
+                    || ephemerisSource == EPHEMERIS_SOURCE_SERVER_NORMAL
+                    || ephemerisSource == EPHEMERIS_SOURCE_SERVER_LONG_TERM
+                    || ephemerisSource == EPHEMERIS_SOURCE_OTHER);
+            mEphemerisSource = ephemerisSource;
+            return this;
+        }
+
+        /**
          * Build SatellitePvt object.
          *
          * @return instance of SatellitePvt
@@ -612,7 +872,9 @@
         @NonNull
         public SatellitePvt build() {
             return new SatellitePvt(mFlags, mPositionEcef, mVelocityEcef, mClockInfo,
-                    mIonoDelayMeters, mTropoDelayMeters);
+                    mIonoDelayMeters, mTropoDelayMeters, mTimeOfClock, mTimeOfEphemeris,
+                    mIssueOfDataClock, mIssueOfDataEphemeris,
+                    mEphemerisSource);
         }
     }
 }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c4cef4c..a2704f9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7242,6 +7242,24 @@
 
     /**
      * @hide
+     * Indicates whether a platform supports the Ultrasound feature which covers the playback
+     * and recording of 20kHz~ sounds. If platform supports Ultrasound, then the
+     * usage will be
+     * To start the Ultrasound playback:
+     *     - Create an AudioTrack with {@link AudioAttributes.CONTENT_TYPE_ULTRASOUND}.
+     * To start the Ultrasound capture:
+     *     - Create an AudioRecord with {@link MediaRecorder.AudioSource.ULTRASOUND}.
+     *
+     * @return whether the ultrasound feature is supported, true when platform supports both
+     * Ultrasound playback and capture, false otherwise.
+     */
+    @SystemApi
+    public static boolean isUltrasoundSupported() {
+        return AudioSystem.isUltrasoundSupported();
+    }
+
+    /**
+     * @hide
      * Introspection API to retrieve audio product strategies.
      * When implementing {Car|Oem}AudioManager, use this method  to retrieve the collection of
      * audio product strategies, which is indexed by a weakly typed index in order to be extended
@@ -7675,6 +7693,33 @@
     }
 
     /**
+     * Returns a list of direct {@link AudioProfile} that are supported for the specified
+     * {@link AudioAttributes}. This can be empty in case of an error or if no direct playback
+     * is possible.
+     *
+     * <p>Direct playback means that the audio stream is not resampled or downmixed
+     * by the framework. Checking for direct support can help the app select the representation
+     * of audio content that most closely matches the capabilities of the device and peripherals
+     * (e.g. A/V receiver) connected to it. Note that the provided stream can still be re-encoded
+     * or mixed with other streams, if needed.
+     * <p>When using this information to inform your application which audio format to play,
+     * query again whenever audio output devices change (see {@link AudioDeviceCallback}).
+     * @param attributes a non-null {@link AudioAttributes} instance.
+     * @return a list of {@link AudioProfile}
+     */
+    @NonNull
+    public List<AudioProfile> getDirectProfilesForAttributes(@NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
+        ArrayList<AudioProfile> audioProfilesList = new ArrayList<>();
+        int status = AudioSystem.getDirectProfilesForAttributes(attributes, audioProfilesList);
+        if (status != SUCCESS) {
+            Log.w(TAG, "getDirectProfilesForAttributes failed.");
+            return new ArrayList<>();
+        }
+        return audioProfilesList;
+    }
+
+    /**
      * @hide
      * Returns an {@link AudioDeviceInfo} corresponding to a connected device of the type provided.
      * The type must be a valid output type defined in <code>AudioDeviceInfo</code> class,
@@ -8352,4 +8397,4 @@
             return mHandler;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 306479a..1b46a50 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1871,6 +1871,12 @@
 
     /**
      * @hide
+     * @see AudioManager#isUltrasoundSupported()
+     */
+    public static native boolean isUltrasoundSupported();
+
+    /**
+     * @hide
      * Send audio HAL server process pids to native audioserver process for use
      * when generating audio HAL servers tombstones
      */
@@ -2115,6 +2121,15 @@
                                               AudioFormat format,
                                               AudioDeviceAttributes[] devices);
 
+    /**
+     * @hide
+     * @param attributes audio attributes describing the playback use case
+     * @param audioProfilesList the list of AudioProfiles that can be played as direct output
+     * @return {@link #SUCCESS} if the list of AudioProfiles was successfully created (can be empty)
+     */
+    public static native int getDirectProfilesForAttributes(@NonNull AudioAttributes attributes,
+            @NonNull ArrayList<AudioProfile> audioProfilesList);
+
     // Items shared with audio service
 
     /**
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 09d7fbd..e2e48d3 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -963,7 +963,7 @@
          *
          * @see HardwareBuffer
          */
-        public @NonNull Builder setUsage(long usage) {
+        public @NonNull Builder setUsage(@Usage long usage) {
             mUsage = usage;
             return this;
         }
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 6168c22..a1aedf1 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -102,11 +102,10 @@
     private int mWidth;
     private int mHeight;
     private final int mMaxImages;
-    private @Usage long mUsage = HardwareBuffer.USAGE_CPU_WRITE_OFTEN;
+    private 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;
@@ -255,35 +254,38 @@
                 + ", maxImages: " + 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, width, height,
             useSurfaceImageFormatInfo, hardwareBufferFormat, dataSpace, usage);
 
+        // if useSurfaceImageFormatInfo is true, imageformat should be read from the surface.
         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);
+        }
+
+        // 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);
@@ -307,7 +309,6 @@
     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);
 
@@ -566,6 +567,9 @@
     /**
      * Get the ImageWriter usage flag.
      *
+     * <p>It is not recommended to use this function if {@link Builder#setUsage} is not called.
+     * Invalid usage value will be returned if so.</p>
+     *
      * @return The ImageWriter usage flag.
      */
     public @Usage long getUsage() {
@@ -873,7 +877,7 @@
         private int mHeight = -1;
         private int mMaxImages = 1;
         private int mImageFormat = ImageFormat.UNKNOWN;
-        private @Usage long mUsage = HardwareBuffer.USAGE_CPU_WRITE_OFTEN;
+        private long mUsage = -1;
         private @HardwareBuffer.Format int mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
         private @NamedDataSpace long mDataSpace = DataSpace.DATASPACE_UNKNOWN;
         private boolean mUseSurfaceImageFormatInfo = true;
@@ -885,10 +889,19 @@
         /**
          * Constructs a new builder for {@link ImageWriter}.
          *
+         * <p>Uses {@code surface} input parameter to retrieve image format, hal format
+         * and hal dataspace value for default. </p>
+         *
          * @param surface The destination Surface this writer produces Image data into.
+         *
+         * @throws IllegalArgumentException if the surface is already abandoned.
          */
         public Builder(@NonNull Surface surface) {
             mSurface = surface;
+            // retrieve format from surface
+            mImageFormat = SurfaceUtils.getSurfaceFormat(surface);
+            mDataSpace = SurfaceUtils.getSurfaceDataspace(surface);
+            mHardwareBufferFormat = PublicFormatUtils.getHalFormat(mImageFormat);
         }
 
         /**
@@ -926,6 +939,8 @@
          * @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.
+         *
+         * @throws IllegalArgumentException if {@code imageFormat} is invalid.
          */
         @SuppressLint("MissingGetterMatchingBuilder")
         public @NonNull Builder setImageFormat(@Format int imageFormat) {
@@ -985,12 +1000,16 @@
 
         /**
          * Set the usage flag of this ImageWriter.
-         * Default value is {@link HardwareBuffer#USAGE_CPU_WRITE_OFTEN}.
+         *
+         * <p>If this function is not called, usage bit will be set
+         * to {@link HardwareBuffer#USAGE_CPU_WRITE_OFTEN} if the image format is not
+         * {@link ImageFormat#PRIVATE PRIVATE}.</p>
          *
          * @param usage The intended usage of the images produced by this ImageWriter.
          * @return the Builder instance with customized usage flag.
          *
          * @see HardwareBuffer
+         * @see #getUsage
          */
         public @NonNull Builder setUsage(@Usage long usage) {
             mUsage = usage;
@@ -1022,6 +1041,7 @@
         private int mHeight = -1;
         private int mWidth = -1;
         private int mFormat = -1;
+        private @NamedDataSpace long mDataSpace = DataSpace.DATASPACE_UNKNOWN;
         // When this default timestamp is used, timestamp for the input Image
         // will be generated automatically when queueInputBuffer is called.
         private final long DEFAULT_TIMESTAMP = Long.MIN_VALUE;
@@ -1034,19 +1054,34 @@
             mOwner = writer;
             mWidth = writer.mWidth;
             mHeight = writer.mHeight;
+            mDataSpace = writer.mDataSpace;
 
-            if (!writer.mUseLegacyImageFormat) {
+            if (!mOwner.mUseLegacyImageFormat) {
                 mFormat = PublicFormatUtils.getPublicFormat(
-                        writer.mHardwareBufferFormat, writer.mDataSpace);
+                    mOwner.mHardwareBufferFormat, mDataSpace);
             }
         }
 
         @Override
+        public @NamedDataSpace long getDataSpace() {
+            throwISEIfImageIsInvalid();
+
+            return mDataSpace;
+        }
+
+        @Override
+        public void setDataSpace(@NamedDataSpace long dataSpace) {
+            throwISEIfImageIsInvalid();
+
+            mDataSpace = dataSpace;
+        }
+
+        @Override
         public int getFormat() {
             throwISEIfImageIsInvalid();
 
-            if (mFormat == -1) {
-                mFormat = nativeGetFormat();
+            if (mOwner.mUseLegacyImageFormat && mFormat == -1) {
+                mFormat = nativeGetFormat(mDataSpace);
             }
             return mFormat;
         }
@@ -1114,7 +1149,8 @@
 
             if (mPlanes == null) {
                 int numPlanes = ImageUtils.getNumPlanesForFormat(getFormat());
-                mPlanes = nativeCreatePlanes(numPlanes, getOwner().getFormat());
+                mPlanes = nativeCreatePlanes(numPlanes, getOwner().getFormat(),
+                        getOwner().getDataSpace());
             }
 
             return mPlanes.clone();
@@ -1222,13 +1258,14 @@
         }
 
         // Create the SurfacePlane object and fill the information
-        private synchronized native SurfacePlane[] nativeCreatePlanes(int numPlanes, int writerFmt);
+        private synchronized native SurfacePlane[] nativeCreatePlanes(int numPlanes, int writerFmt,
+                long dataSpace);
 
         private synchronized native int nativeGetWidth();
 
         private synchronized native int nativeGetHeight();
 
-        private synchronized native int nativeGetFormat();
+        private synchronized native int nativeGetFormat(long dataSpace);
 
         private synchronized native HardwareBuffer nativeGetHardwareBuffer();
     }
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index e75df1d..5dee945 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -426,10 +426,30 @@
         /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */
         public static final int COLOR_Format24BitABGR6666           = 43;
 
-        /** @hide
-         * P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
-         * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
-         * little-endian value, with the lower 6 bits set to zero. */
+        /**
+         * P010 is 10-bit-per component 4:2:0 YCbCr semiplanar format.
+         * <p>
+         * This format uses 24 allocated bits per pixel with 15 bits of
+         * data per pixel. Chroma planes are subsampled by 2 both
+         * horizontally and vertically. Each chroma and luma component
+         * has 16 allocated bits in little-endian configuration with 10
+         * MSB of actual data.
+         *
+         * <pre>
+         *            byte                   byte
+         *  <--------- i --------> | <------ i + 1 ------>
+         * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+         * |     UNUSED      |      Y/Cb/Cr                |
+         * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+         *  0               5 6   7 0                    7
+         * bit
+         * </pre>
+         *
+         * Use this format with {@link Image}. This format corresponds
+         * to {@link android.graphics.ImageFormat#YCBCR_P010}.
+         * <p>
+         */
+        @SuppressLint("AllUpper")
         public static final int COLOR_FormatYUVP010                 = 54;
 
         /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
@@ -439,6 +459,25 @@
         public static final int COLOR_FormatSurface                   = 0x7F000789;
 
         /**
+         * 64 bits per pixel RGBA color format, with 16-bit signed
+         * floating point red, green, blue, and alpha components.
+         * <p>
+         *
+         * <pre>
+         *         byte              byte             byte              byte
+         *  <-- i -->|<- i+1 ->|<- i+2 ->|<- i+3 ->|<- i+4 ->|<- i+5 ->|<- i+6 ->|<- i+7 ->
+         * +---------+---------+-------------------+---------+---------+---------+---------+
+         * |        RED        |       GREEN       |       BLUE        |       ALPHA       |
+         * +---------+---------+-------------------+---------+---------+---------+---------+
+         *  0       7 0       7 0       7 0       7 0       7 0       7 0       7 0       7
+         * </pre>
+         *
+         * This corresponds to {@link android.graphics.PixelFormat#RGBA_F16}.
+         */
+        @SuppressLint("AllUpper")
+        public static final int COLOR_Format64bitABGRFloat            = 0x7F000F16;
+
+        /**
          * 32 bits per pixel RGBA color format, with 8-bit red, green, blue, and alpha components.
          * <p>
          * Using 32-bit little-endian representation, colors stored as Red 7:0, Green 15:8,
@@ -456,6 +495,26 @@
         public static final int COLOR_Format32bitABGR8888             = 0x7F00A000;
 
         /**
+         * 32 bits per pixel RGBA color format, with 10-bit red, green,
+         * blue, and 2-bit alpha components.
+         * <p>
+         * Using 32-bit little-endian representation, colors stored as
+         * Red 9:0, Green 19:10, Blue 29:20, and Alpha 31:30.
+         * <pre>
+         *         byte              byte             byte              byte
+         *  <------ i -----> | <---- i+1 ----> | <---- i+2 ----> | <---- i+3 ----->
+         * +-----------------+---+-------------+-------+---------+-----------+-----+
+         * |       RED           |      GREEN          |       BLUE          |ALPHA|
+         * +-----------------+---+-------------+-------+---------+-----------+-----+
+         *  0               7 0 1 2           7 0     3 4       7 0         5 6   7
+         * </pre>
+         *
+         * This corresponds to {@link android.graphics.PixelFormat#RGBA_1010102}.
+         */
+        @SuppressLint("AllUpper")
+        public static final int COLOR_Format32bitABGR2101010          = 0x7F00AAA2;
+
+        /**
          * Flexible 12 bits per pixel, subsampled YUV color format with 8-bit chroma and luma
          * components.
          * <p>
@@ -615,6 +674,24 @@
         public static final String FEATURE_EncodingStatistics = "encoding-statistics";
 
         /**
+         * <b>video encoder only</b>: codec supports HDR editing.
+         * <p>
+         * HDR editing support means that the codec accepts 10-bit HDR
+         * input surface, and it is capable of generating any HDR
+         * metadata required from both YUV and RGB input when the
+         * metadata is not present. This feature is only meaningful when
+         * using an HDR capable profile (and 10-bit HDR input).
+         * <p>
+         * This feature implies that the codec is capable of encoding at
+         * least one HDR format, and that it supports RGBA_1010102 as
+         * well as P010, and optionally RGBA_FP16 input formats, and
+         * that the encoder can generate HDR metadata for all supported
+         * HDR input formats.
+         */
+        @SuppressLint("AllUpper")
+        public static final String FEATURE_HdrEditing = "hdr-editing";
+
+        /**
          * Query codec feature capabilities.
          * <p>
          * These features are supported to be used by the codec.  These
@@ -654,6 +731,7 @@
             new Feature(FEATURE_DynamicTimestamp, (1 << 2), false),
             new Feature(FEATURE_QpBounds, (1 << 3), false),
             new Feature(FEATURE_EncodingStatistics, (1 << 4), false),
+            new Feature(FEATURE_HdrEditing, (1 << 5), false),
             // feature to exclude codec from REGULAR codec list
             new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true),
         };
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index ad8fc07..ea26185 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -1078,8 +1078,8 @@
      *
      * @throws IOException When an {@link IOException} is thrown while closing a {@link
      * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}. This throws clause exists
-     * since API 33, but this method can throw in earlier API versions where the exception is not
-     * declared.
+     * since API {@link android.os.Build.VERSION_CODES#TIRAMISU}, but this method can throw in
+     * earlier API versions where the exception is not declared.
      */
     @Override
     public void close() throws IOException {
@@ -1091,8 +1091,8 @@
      *
      * @throws IOException When an {@link IOException} is thrown while closing a {@link
      * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}. This throws clause exists
-     * since API 33, but this method can throw in earlier API versions where the exception is not
-     * declared.
+     * since API {@link android.os.Build.VERSION_CODES#TIRAMISU}, but this method can throw in
+     * earlier API versions where the exception is not declared.
      */
     public native void release() throws IOException;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index a6f244f..2772769 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -1059,7 +1059,8 @@
      * haptic channels or not. As this function doesn't has a context
      * to resolve the uri, the result may be wrong if the uri cannot be
      * resolved correctly.
-     * Use {@link #hasHapticChannels(int)} instead when possible.
+     * Use {@link #hasHapticChannels(int)} or {@link #hasHapticChannels(Context, Uri)}
+     * instead when possible.
      *
      * @param ringtoneUri The {@link Uri} of a sound or ringtone.
      * @return true if the ringtone contains haptic channels.
@@ -1069,6 +1070,17 @@
     }
 
     /**
+     * Returns if the {@link Ringtone} from a given sound URI contains haptics channels or not.
+     *
+     * @param context the {@link android.content.Context} to use when resolving the Uri.
+     * @param ringtoneUri the {@link Uri} of a sound or ringtone.
+     * @return true if the ringtone contains haptic channels.
+     */
+    public static boolean hasHapticChannels(@NonNull Context context, @NonNull Uri ringtoneUri) {
+        return AudioManager.hasHapticChannels(context, ringtoneUri);
+    }
+
+    /**
      * Attempts to create a context for the given user.
      *
      * @return created context, or null if package does not exist
diff --git a/media/java/android/media/tv/AdRequest.java b/media/java/android/media/tv/AdRequest.java
index 536baf2..0542c55 100644
--- a/media/java/android/media/tv/AdRequest.java
+++ b/media/java/android/media/tv/AdRequest.java
@@ -16,8 +16,9 @@
 
 package android.media.tv;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.StringDef;
+import android.annotation.Nullable;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
@@ -26,17 +27,26 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-/** @hide */
+/**
+ * An advertisement request which can be sent to TV input to request AD operations.
+ */
 public final class AdRequest implements Parcelable {
+    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @StringDef(prefix = "REQUEST_TYPE_", value = {
+    @IntDef(prefix = "REQUEST_TYPE_", value = {
             REQUEST_TYPE_START,
             REQUEST_TYPE_STOP
     })
     public @interface RequestType {}
 
-    public static final String REQUEST_TYPE_START = "START";
-    public static final String REQUEST_TYPE_STOP = "STOP";
+    /**
+     * Request to start an advertisement.
+     */
+    public static final int REQUEST_TYPE_START = 1;
+    /**
+     * Request to stop an advertisement.
+     */
+    public static final int REQUEST_TYPE_STOP = 2;
 
     public static final @NonNull Parcelable.Creator<AdRequest> CREATOR =
             new Parcelable.Creator<AdRequest>() {
@@ -52,7 +62,7 @@
             };
 
     private final int mId;
-    private final @RequestType String mRequestType;
+    private final @RequestType int mRequestType;
     private final ParcelFileDescriptor mFileDescriptor;
     private final long mStartTime;
     private final long mStopTime;
@@ -60,9 +70,9 @@
     private final String mMediaFileType;
     private final Bundle mMetadata;
 
-    public AdRequest(int id, @RequestType String requestType, ParcelFileDescriptor fileDescriptor,
-            long startTime, long stopTime, long echoInterval, String mediaFileType,
-            Bundle metadata) {
+    public AdRequest(int id, @RequestType int requestType,
+            @Nullable ParcelFileDescriptor fileDescriptor, long startTime, long stopTime,
+            long echoInterval, @Nullable String mediaFileType, @NonNull Bundle metadata) {
         mId = id;
         mRequestType = requestType;
         mFileDescriptor = fileDescriptor;
@@ -75,8 +85,12 @@
 
     private AdRequest(Parcel source) {
         mId = source.readInt();
-        mRequestType = source.readString();
-        mFileDescriptor = source.readFileDescriptor();
+        mRequestType = source.readInt();
+        if (source.readInt() != 0) {
+            mFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(source);
+        } else {
+            mFileDescriptor = null;
+        }
         mStartTime = source.readLong();
         mStopTime = source.readLong();
         mEchoInterval = source.readLong();
@@ -84,34 +98,77 @@
         mMetadata = source.readBundle();
     }
 
+    /**
+     * Gets the ID of AD request.
+     */
     public int getId() {
         return mId;
     }
 
-    public @RequestType String getRequestType() {
+    /**
+     * Gets the request type.
+     */
+    @RequestType
+    public int getRequestType() {
         return mRequestType;
     }
 
+    /**
+     * Gets the file descriptor of the AD media.
+     *
+     * @return The file descriptor of the AD media. Can be {@code null} for
+     *         {@link #REQUEST_TYPE_STOP}
+     */
+    @Nullable
     public ParcelFileDescriptor getFileDescriptor() {
         return mFileDescriptor;
     }
 
-    public long getStartTime() {
+    /**
+     * Gets the start time of the AD media in milliseconds.
+     * <p>0 means start immediately
+     */
+    public long getStartTimeMillis() {
         return mStartTime;
     }
 
-    public long getStopTime() {
+    /**
+     * Gets the stop time of the AD media in milliseconds.
+     * <p>-1 means until the end
+     */
+    public long getStopTimeMillis() {
         return mStopTime;
     }
 
-    public long getEchoInterval() {
+    /**
+     * Gets the echo interval in milliseconds.
+     * <p>The interval TV input needs to echo and inform TV interactive app service the video
+     * playback elapsed time.
+     *
+     * @see android.media.tv.AdResponse
+     */
+    public long getEchoIntervalMillis() {
         return mEchoInterval;
     }
 
+    /**
+     * Gets the media file type such as mp4, mob, avi.
+     *
+     * @return The media file type. Can be {@code null} for {@link #REQUEST_TYPE_STOP}.
+     */
+    @Nullable
     public String getMediaFileType() {
         return mMediaFileType;
     }
 
+    /**
+     * Gets the metadata of the media file.
+     * <p>This includes additional information the TV input needs to play the AD media.
+     *
+     * @return The metadata of the media file. Can be an empty bundle for
+     *         {@link #REQUEST_TYPE_STOP}.
+     */
+    @NonNull
     public Bundle getMetadata() {
         return mMetadata;
     }
@@ -124,8 +181,13 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mId);
-        dest.writeString(mRequestType);
-        mFileDescriptor.writeToParcel(dest, flags);
+        dest.writeInt(mRequestType);
+        if (mFileDescriptor != null) {
+            dest.writeInt(1);
+            mFileDescriptor.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
         dest.writeLong(mStartTime);
         dest.writeLong(mStopTime);
         dest.writeLong(mEchoInterval);
diff --git a/media/java/android/media/tv/AdResponse.java b/media/java/android/media/tv/AdResponse.java
index 28cf5ac..0c20954 100644
--- a/media/java/android/media/tv/AdResponse.java
+++ b/media/java/android/media/tv/AdResponse.java
@@ -16,19 +16,21 @@
 
 package android.media.tv;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringDef;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-/** @hide */
+/**
+ * An advertisement request which can be sent to TV interactive App service to inform AD status.
+ */
 public final class AdResponse implements Parcelable {
+    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @StringDef(prefix = "RESPONSE_TYPE_", value = {
+    @IntDef(prefix = "RESPONSE_TYPE_", value = {
             RESPONSE_TYPE_PLAYING,
             RESPONSE_TYPE_FINISHED,
             RESPONSE_TYPE_STOPPED,
@@ -36,10 +38,10 @@
     })
     public @interface ResponseType {}
 
-    public static final String RESPONSE_TYPE_PLAYING = "PLAYING";
-    public static final String RESPONSE_TYPE_FINISHED = "FINISHED";
-    public static final String RESPONSE_TYPE_STOPPED = "STOPPED";
-    public static final String RESPONSE_TYPE_ERROR = "ERROR";
+    public static final int RESPONSE_TYPE_PLAYING = 1;
+    public static final int RESPONSE_TYPE_FINISHED = 2;
+    public static final int RESPONSE_TYPE_STOPPED = 3;
+    public static final int RESPONSE_TYPE_ERROR = 4;
 
     public static final @NonNull Parcelable.Creator<AdResponse> CREATOR =
             new Parcelable.Creator<AdResponse>() {
@@ -55,10 +57,10 @@
             };
 
     private final int mId;
-    private final @ResponseType String mResponseType;
-    private final Long mElapsedTime;
+    private final @ResponseType int mResponseType;
+    private final long mElapsedTime;
 
-    public AdResponse(int id, @ResponseType String responseType, @Nullable Long elapsedTime) {
+    public AdResponse(int id, @ResponseType int responseType, long elapsedTime) {
         mId = id;
         mResponseType = responseType;
         mElapsedTime = elapsedTime;
@@ -66,19 +68,31 @@
 
     private AdResponse(Parcel source) {
         mId = source.readInt();
-        mResponseType = source.readString();
-        mElapsedTime = (Long) source.readValue(Long.class.getClassLoader());
+        mResponseType = source.readInt();
+        mElapsedTime = source.readLong();
     }
 
+    /**
+     * Gets the ID of AD response.
+     */
     public int getId() {
         return mId;
     }
 
-    public @ResponseType String getResponseType() {
+    /**
+     * Gets the response type.
+     */
+    @ResponseType
+    public int getResponseType() {
         return mResponseType;
     }
 
-    public Long getElapsedTime() {
+    /**
+     * Gets the playback elapsed time in milliseconds.
+     *
+     * @return The playback elapsed time. -1 if no valid elapsed time.
+     */
+    public long getElapsedTimeMillis() {
         return mElapsedTime;
     }
 
@@ -90,7 +104,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mId);
-        dest.writeString(mResponseType);
-        dest.writeValue(mElapsedTime);
+        dest.writeInt(mResponseType);
+        dest.writeLong(mElapsedTime);
     }
 }
diff --git a/media/java/android/media/tv/AitInfo.java b/media/java/android/media/tv/AitInfo.java
index 71b1634..8e80a62 100644
--- a/media/java/android/media/tv/AitInfo.java
+++ b/media/java/android/media/tv/AitInfo.java
@@ -23,7 +23,6 @@
 
 /**
  * AIT (Application Information Table) info.
- * @hide
  */
 public final class AitInfo implements Parcelable {
     static final String TAG = "AitInfo";
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index f438d29..cc33a1e 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -394,17 +394,14 @@
 
     /**
      * Signal lost.
-     * @hide
      */
     public static final int SIGNAL_STRENGTH_LOST = 1;
     /**
      * Weak signal.
-     * @hide
      */
     public static final int SIGNAL_STRENGTH_WEAK = 2;
     /**
      * Strong signal.
-     * @hide
      */
     public static final int SIGNAL_STRENGTH_STRONG = 3;
 
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 9bc7367..f63f444 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -592,7 +592,11 @@
             });
         }
 
-        /** @hide */
+        /**
+         * Informs the application that this session has been tuned to the given channel.
+         *
+         * @param channelUri The URI of the tuned channel.
+         */
         public void notifyTuned(@NonNull Uri channelUri) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
@@ -891,7 +895,7 @@
          * Notifies response for advertisement.
          *
          * @param response advertisement response.
-         * @hide
+         * @see android.media.tv.interactive.TvInteractiveAppService.Session#requestAd(AdRequest)
          */
         public void notifyAdResponse(@NonNull final AdResponse response) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -947,12 +951,11 @@
         /**
          * Informs the app that the AIT (Application Information Table) is updated.
          *
-         * <p>This method should also be call when
+         * <p>This method should also be called when
          * {@link #onSetInteractiveAppNotificationEnabled(boolean)} is called to send the first AIT
          * info.
          *
          * @see #onSetInteractiveAppNotificationEnabled(boolean)
-         * @hide
          */
         public void notifyAitInfoUpdated(@NonNull final AitInfo aitInfo) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -973,7 +976,6 @@
 
         /**
          * Notifies signal strength.
-         * @hide
          */
         public void notifySignalStrength(@TvInputManager.SignalStrength final int strength) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1121,10 +1123,9 @@
         }
 
         /**
-         * called when advertisement is requested.
+         * Called when advertisement request is received.
          *
-         * @param request advertisement request
-         * @hide
+         * @param request advertisement request received
          */
         public void onRequestAd(@NonNull AdRequest request) {
         }
@@ -1214,7 +1215,6 @@
          *
          * @see TvView#setInteractiveAppNotificationEnabled(boolean)
          * @see Session#notifyAitInfoUpdated(android.media.tv.AitInfo)
-         * @hide
          */
         public void onSetInteractiveAppNotificationEnabled(boolean enabled) {
         }
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index d2086c5..4d63af7 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -493,7 +493,6 @@
      *
      * @see TvInputService.Session#notifyAitInfoUpdated(android.media.tv.AitInfo)
      * @see android.media.tv.interactive.TvInteractiveAppView#setTvView(TvView)
-     * @hide
      */
     public void setInteractiveAppNotificationEnabled(boolean enabled) {
         if (mSession != null) {
@@ -1074,28 +1073,26 @@
          * This is called when the AIT (Application Information Table) info has been updated.
          *
          * @param aitInfo The current AIT info.
-         * @hide
          */
         public void onAitInfoUpdated(@NonNull String inputId, @NonNull AitInfo aitInfo) {
         }
 
         /**
          * This is called when signal strength is updated.
+         *
          * @param inputId The ID of the TV input bound to this view.
          * @param strength The current signal strength.
-         *
-         * @hide
          */
-        public void onSignalStrength(String inputId, @TvInputManager.SignalStrength int strength) {
+        public void onSignalStrength(
+                @NonNull String inputId, @TvInputManager.SignalStrength int strength) {
         }
 
         /**
          * This is called when the session has been tuned to the given channel.
          *
          * @param channelUri The URI of a channel.
-         * @hide
          */
-        public void onTuned(String inputId, Uri channelUri) {
+        public void onTuned(@NonNull String inputId, @NonNull Uri channelUri) {
         }
     }
 
diff --git a/media/java/android/media/tv/interactive/AppLinkInfo.aidl b/media/java/android/media/tv/interactive/AppLinkInfo.aidl
index 7c52d01..6759fc4 100644
--- a/media/java/android/media/tv/interactive/AppLinkInfo.aidl
+++ b/media/java/android/media/tv/interactive/AppLinkInfo.aidl
@@ -16,4 +16,4 @@
 
 package android.media.tv.interactive;
 
-parcelable AppLinkInfo;
\ No newline at end of file
+parcelable AppLinkInfo;
diff --git a/media/java/android/media/tv/interactive/AppLinkInfo.java b/media/java/android/media/tv/interactive/AppLinkInfo.java
index 5cce443..cd201f7 100644
--- a/media/java/android/media/tv/interactive/AppLinkInfo.java
+++ b/media/java/android/media/tv/interactive/AppLinkInfo.java
@@ -23,7 +23,6 @@
 
 /**
  * App link information used by TV interactive app to launch Android apps.
- * @hide
  */
 public final class AppLinkInfo implements Parcelable {
     private @NonNull String mPackageName;
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 39be501..f75aa56 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -28,6 +28,7 @@
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvContentRating;
+import android.media.tv.TvInputInfo;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvTrackInfo;
 import android.net.Uri;
@@ -57,7 +58,7 @@
 
 /**
  * Central system API to the overall TV interactive application framework (TIAF) architecture, which
- * arbitrates interaction between applications and interactive apps.
+ * arbitrates interaction between Android applications and TV interactive apps.
  */
 @SystemService(Context.TV_INTERACTIVE_APP_SERVICE)
 public final class TvInteractiveAppManager {
@@ -75,22 +76,22 @@
 
     /**
      * Unrealized state of interactive app service.
-     * @hide
      */
     public static final int SERVICE_STATE_UNREALIZED = 1;
     /**
      * Preparing state of interactive app service.
-     * @hide
      */
     public static final int SERVICE_STATE_PREPARING = 2;
     /**
      * Ready state of interactive app service.
-     * @hide
+     *
+     * <p>In this state, the interactive app service is ready, and interactive apps can be started.
+     *
+     * @see TvInteractiveAppView#startInteractiveApp()
      */
     public static final int SERVICE_STATE_READY = 3;
     /**
      * Error state of interactive app service.
-     * @hide
      */
     public static final int SERVICE_STATE_ERROR = 4;
 
@@ -105,17 +106,14 @@
 
     /**
      * Stopped (or not started) state of interactive application.
-     * @hide
      */
     public static final int INTERACTIVE_APP_STATE_STOPPED = 1;
     /**
      * Running state of interactive application.
-     * @hide
      */
     public static final int INTERACTIVE_APP_STATE_RUNNING = 2;
     /**
      * Error state of interactive application.
-     * @hide
      */
     public static final int INTERACTIVE_APP_STATE_ERROR = 3;
 
@@ -136,46 +134,37 @@
 
     /**
      * No error.
-     * @hide
      */
     public static final int ERROR_NONE = 0;
     /**
      * Unknown error code.
-     * @hide
      */
     public static final int ERROR_UNKNOWN = 1;
     /**
      * Error code for an unsupported channel.
-     * @hide
      */
     public static final int ERROR_NOT_SUPPORTED = 2;
     /**
      * Error code for weak signal.
-     * @hide
      */
     public static final int ERROR_WEAK_SIGNAL = 3;
     /**
      * Error code when resource (e.g. tuner) is unavailable.
-     * @hide
      */
     public static final int ERROR_RESOURCE_UNAVAILABLE = 4;
     /**
      * Error code for blocked contents.
-     * @hide
      */
     public static final int ERROR_BLOCKED = 5;
     /**
      * Error code when the key or module is missing for the encrypted channel.
-     * @hide
      */
     public static final int ERROR_ENCRYPTED = 6;
     /**
      * Error code when the current channel is an unknown channel.
-     * @hide
      */
     public static final int ERROR_UNKNOWN_CHANNEL = 7;
 
-
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = false, prefix = "TELETEXT_APP_STATE_", value = {
@@ -185,18 +174,15 @@
     public @interface TeletextAppState {}
 
     /**
-     * Show state of Teletext app.
-     * @hide
+     * State of Teletext app: show
      */
     public static final int TELETEXT_APP_STATE_SHOW = 1;
     /**
-     * Hide state of Teletext app.
-     * @hide
+     * State of Teletext app: hide
      */
     public static final int TELETEXT_APP_STATE_HIDE = 2;
     /**
-     * Error state of Teletext app.
-     * @hide
+     * State of Teletext app: error
      */
     public static final int TELETEXT_APP_STATE_ERROR = 3;
 
@@ -204,75 +190,100 @@
      * Key for package name in app link.
      * <p>Type: String
      *
-     * @see #registerAppLinkInfo(String, Bundle)
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
-    public static final String KEY_PACKAGE_NAME = "package_name";
+    public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
 
     /**
      * Key for class name in app link.
      * <p>Type: String
      *
-     * @see #registerAppLinkInfo(String, Bundle)
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
-    public static final String KEY_CLASS_NAME = "class_name";
-
-    /**
-     * Key for URI scheme in app link.
-     * <p>Type: String
-     *
-     * @see #registerAppLinkInfo(String, Bundle)
-     * @hide
-     */
-    public static final String KEY_URI_SCHEME = "uri_scheme";
-
-    /**
-     * Key for URI host in app link.
-     * <p>Type: String
-     *
-     * @see #registerAppLinkInfo(String, Bundle)
-     * @hide
-     */
-    public static final String KEY_URI_HOST = "uri_host";
-
-    /**
-     * Key for URI prefix in app link.
-     * <p>Type: String
-     *
-     * @see #registerAppLinkInfo(String, Bundle)
-     * @hide
-     */
-    public static final String KEY_URI_PREFIX = "uri_prefix";
+    public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
 
     /**
      * Key for command type in app link command.
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
-    public static final String KEY_COMMAND_TYPE = "command_type";
+    public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
 
     /**
      * Key for service ID in app link command.
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
-    public static final String KEY_SERVICE_ID = "service_id";
+    public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
 
     /**
      * Key for back URI in app link command.
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
-    public static final String KEY_BACK_URI = "back_uri";
+    public static final String APP_LINK_KEY_BACK_URI = "back_uri";
+
+    /**
+     * Broadcast intent action to send app command to TV app.
+     *
+     * @see #sendAppLinkCommand(String, Bundle)
+     */
+    public static final String ACTION_APP_LINK_COMMAND =
+            "android.media.tv.interactive.action.APP_LINK_COMMAND";
+
+    /**
+     * Intent key for TV input ID. It's used to send app command to TV app.
+     * <p>Type: String
+     *
+     * @see #sendAppLinkCommand(String, Bundle)
+     * @see #ACTION_APP_LINK_COMMAND
+     */
+    public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
+
+    /**
+     * Intent key for TV interactive app ID. It's used to send app command to TV app.
+     * <p>Type: String
+     *
+     * @see #sendAppLinkCommand(String, Bundle)
+     * @see #ACTION_APP_LINK_COMMAND
+     * @see android.media.tv.interactive.TvInteractiveAppInfo#getId()
+     */
+    public static final String INTENT_KEY_INTERACTIVE_APP_SERVICE_ID = "interactive_app_id";
+
+    /**
+     * Intent key for TV channel URI. It's used to send app command to TV app.
+     * <p>Type: android.net.Uri
+     *
+     * @see #sendAppLinkCommand(String, Bundle)
+     * @see #ACTION_APP_LINK_COMMAND
+     */
+    public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
+
+    /**
+     * Intent key for broadcast-independent(BI) interactive app type. It's used to send app command
+     * to TV app.
+     * <p>Type: int
+     *
+     * @see #sendAppLinkCommand(String, Bundle)
+     * @see #ACTION_APP_LINK_COMMAND
+     * @see android.media.tv.interactive.TvInteractiveAppInfo#getSupportedTypes()
+     * @see android.media.tv.interactive.TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
+     */
+    public static final String INTENT_KEY_BI_INTERACTIVE_APP_TYPE = "bi_interactive_app_type";
+
+    /**
+     * Intent key for broadcast-independent(BI) interactive app URI. It's used to send app command
+     * to TV app.
+     * <p>Type: android.net.Uri
+     *
+     * @see #sendAppLinkCommand(String, Bundle)
+     * @see #ACTION_APP_LINK_COMMAND
+     * @see android.media.tv.interactive.TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
+     */
+    public static final String INTENT_KEY_BI_INTERACTIVE_APP_URI = "bi_interactive_app_uri";
 
     private final ITvInteractiveAppManager mService;
     private final int mUserId;
@@ -369,7 +380,7 @@
 
             @Override
             public void onCommandRequest(
-                    @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
+                    @TvInteractiveAppService.PlaybackCommandType String cmdType,
                     Bundle parameters,
                     int seq) {
                 synchronized (mSessionCallbackRecordMap) {
@@ -561,7 +572,6 @@
 
     /**
      * Callback used to monitor status of the TV Interactive App.
-     * @hide
      */
     public abstract static class TvInteractiveAppCallback {
         /**
@@ -571,7 +581,6 @@
          * that implements {@link TvInteractiveAppService} interface.
          *
          * @param iAppServiceId The ID of the TV Interactive App service.
-         * @hide
          */
         public void onInteractiveAppServiceAdded(@NonNull String iAppServiceId) {
         }
@@ -583,7 +592,6 @@
          * App service package.
          *
          * @param iAppServiceId The ID of the TV Interactive App service.
-         * @hide
          */
         public void onInteractiveAppServiceRemoved(@NonNull String iAppServiceId) {
         }
@@ -595,7 +603,6 @@
          * 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) {
         }
@@ -742,8 +749,7 @@
     }
 
     /**
-     * Prepares TV Interactive App service for the given type.
-     * @hide
+     * Prepares TV Interactive App service environment for the given type.
      */
     public void prepare(@NonNull String tvIAppServiceId, int type) {
         try {
@@ -755,7 +761,6 @@
 
     /**
      * Registers app link info.
-     * @hide
      */
     public void registerAppLinkInfo(
             @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
@@ -768,7 +773,6 @@
 
     /**
      * Unregisters app link info.
-     * @hide
      */
     public void unregisterAppLinkInfo(
             @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
@@ -781,9 +785,12 @@
 
     /**
      * Sends app link command.
-     * @hide
+     *
+     * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
+     *                        ID can be found in {@link TvInputInfo#getId()}.
+     * @param command The command to be sent.
      */
-    public void sendAppLinkCommand(String tvIAppServiceId, Bundle command) {
+    public void sendAppLinkCommand(@NonNull String tvIAppServiceId, @NonNull Bundle command) {
         try {
             mService.sendAppLinkCommand(tvIAppServiceId, command, mUserId);
         } catch (RemoteException e) {
@@ -796,7 +803,6 @@
      *
      * @param callback A callback used to monitor status of the TV Interactive App services.
      * @param executor A {@link Executor} that the status change will be delivered to.
-     * @hide
      */
     public void registerCallback(
             @NonNull TvInteractiveAppCallback callback,
@@ -812,7 +818,6 @@
      * Unregisters the existing {@link TvInteractiveAppCallback}.
      *
      * @param callback The existing callback to remove.
-     * @hide
      */
     public void unregisterCallback(@NonNull final TvInteractiveAppCallback callback) {
         Preconditions.checkNotNull(callback);
@@ -1575,7 +1580,7 @@
         }
 
         void postCommandRequest(
-                final @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
+                final @TvInteractiveAppService.PlaybackCommandType String cmdType,
                 final Bundle parameters) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1723,7 +1728,7 @@
          */
         public void onCommandRequest(
                 Session session,
-                @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
+                @TvInteractiveAppService.PlaybackCommandType String cmdType,
                 Bundle parameters) {
         }
 
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index d599d0a..819707a 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -32,8 +32,10 @@
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvContentRating;
+import android.media.tv.TvInputInfo;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -65,7 +67,8 @@
 import java.util.List;
 
 /**
- * The TvInteractiveAppService class represents a TV interactive applications RTE.
+ * A TV interactive application service is a service that provides runtime environment and runs TV
+ * interactive applications.
  */
 public abstract class TvInteractiveAppService extends Service {
     private static final boolean DEBUG = false;
@@ -95,46 +98,82 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @StringDef(prefix = "INTERACTIVE_APP_SERVICE_COMMAND_TYPE_", value = {
-            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE,
-            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_NEXT,
-            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_PREV,
-            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_STOP,
-            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME,
-            INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SELECT_TRACK
+    @StringDef(prefix = "PLAYBACK_COMMAND_TYPE_", value = {
+            PLAYBACK_COMMAND_TYPE_TUNE,
+            PLAYBACK_COMMAND_TYPE_TUNE_NEXT,
+            PLAYBACK_COMMAND_TYPE_TUNE_PREV,
+            PLAYBACK_COMMAND_TYPE_STOP,
+            PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME,
+            PLAYBACK_COMMAND_TYPE_SELECT_TRACK
     })
-    public @interface InteractiveAppServiceCommandType {}
+    public @interface PlaybackCommandType {}
 
-    /** @hide */
-    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE = "tune";
-    /** @hide */
-    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_NEXT = "tune_next";
-    /** @hide */
-    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_PREV = "tune_previous";
-    /** @hide */
-    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_STOP = "stop";
-    /** @hide */
-    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME =
+    /**
+     * Playback command type: tune to the given channel.
+     * @see #COMMAND_PARAMETER_KEY_CHANNEL_URI
+     */
+    public static final String PLAYBACK_COMMAND_TYPE_TUNE = "tune";
+    /**
+     * Playback command type: tune to the next channel.
+     */
+    public static final String PLAYBACK_COMMAND_TYPE_TUNE_NEXT = "tune_next";
+    /**
+     * Playback command type: tune to the previous channel.
+     */
+    public static final String PLAYBACK_COMMAND_TYPE_TUNE_PREV = "tune_previous";
+    /**
+     * Playback command type: stop the playback.
+     */
+    public static final String PLAYBACK_COMMAND_TYPE_STOP = "stop";
+    /**
+     * Playback command type: set the volume.
+     */
+    public static final String PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME =
             "set_stream_volume";
-    /** @hide */
-    public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SELECT_TRACK = "select_track";
-    /** @hide */
+    /**
+     * Playback command type: select the given track.
+     */
+    public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track";
+    /**
+     * Playback command parameter: channel URI.
+     * <p>Type: android.net.Uri
+     *
+     * @see #PLAYBACK_COMMAND_TYPE_TUNE
+     */
     public static final String COMMAND_PARAMETER_KEY_CHANNEL_URI = "command_channel_uri";
-    /** @hide */
+    /**
+     * Playback command parameter: TV input ID.
+     * <p>Type: String
+     *
+     * @see TvInputInfo#getId()
+     */
     public static final String COMMAND_PARAMETER_KEY_INPUT_ID = "command_input_id";
-    /** @hide */
+    /**
+     * Playback command parameter: stream volume.
+     * <p>Type: float
+     *
+     * @see #PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME
+     */
     public static final String COMMAND_PARAMETER_KEY_VOLUME = "command_volume";
-    /** @hide */
+    /**
+     * Playback command parameter: track type.
+     * <p>Type: int
+     *
+     * @see #PLAYBACK_COMMAND_TYPE_SELECT_TRACK
+     * @see TvTrackInfo#getType()
+     */
     public static final String COMMAND_PARAMETER_KEY_TRACK_TYPE = "command_track_type";
-    /** @hide */
+    /**
+     * Playback command parameter: track ID.
+     * <p>Type: String
+     *
+     * @see #PLAYBACK_COMMAND_TYPE_SELECT_TRACK
+     * @see TvTrackInfo#getId()
+     */
     public static final String COMMAND_PARAMETER_KEY_TRACK_ID = "command_track_id";
-    /** @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";
@@ -143,9 +182,9 @@
     private final RemoteCallbackList<ITvInteractiveAppServiceCallback> mCallbacks =
             new RemoteCallbackList<>();
 
-    /** @hide */
     @Override
-    public final IBinder onBind(Intent intent) {
+    @Nullable
+    public final IBinder onBind(@NonNull Intent intent) {
         ITvInteractiveAppService.Stub tvIAppServiceBinder = new ITvInteractiveAppService.Stub() {
             @Override
             public void registerCallback(ITvInteractiveAppServiceCallback cb) {
@@ -201,33 +240,27 @@
 
     /**
      * Prepares TV Interactive App service for the given type.
-     * @hide
      */
-    public void onPrepare(@TvInteractiveAppInfo.InteractiveAppType int type) {
-    }
+    public abstract void onPrepare(@TvInteractiveAppInfo.InteractiveAppType int type);
 
     /**
      * Registers App link info.
-     * @hide
      */
-    public void onRegisterAppLinkInfo(AppLinkInfo appLinkInfo) {
-        // TODO: make it abstract when unhide
+    public void onRegisterAppLinkInfo(@NonNull AppLinkInfo appLinkInfo) {
     }
 
     /**
      * Unregisters App link info.
-     * @hide
      */
-    public void onUnregisterAppLinkInfo(AppLinkInfo appLinkInfo) {
-        // TODO: make it abstract when unhide
+    public void onUnregisterAppLinkInfo(@NonNull AppLinkInfo appLinkInfo) {
     }
 
     /**
-     * Sends App link info.
-     * @hide
+     * Called when app link command is received.
+     *
+     * @see android.media.tv.interactive.TvInteractiveAppManager#sendAppLinkCommand(String, Bundle)
      */
-    public void onAppLinkCommand(Bundle command) {
-        // TODO: make it abstract when unhide
+    public void onAppLinkCommand(@NonNull Bundle command) {
     }
 
 
@@ -239,14 +272,11 @@
      *
      * @param iAppServiceId The ID of the TV Interactive App associated with the session.
      * @param type The type of the TV Interactive App associated with the session.
-     * @hide
      */
     @Nullable
-    public Session onCreateSession(
+    public abstract Session onCreateSession(
             @NonNull String iAppServiceId,
-            @TvInteractiveAppInfo.InteractiveAppType int type) {
-        return null;
-    }
+            @TvInteractiveAppInfo.InteractiveAppType int type);
 
     /**
      * Notifies the system when the state of the interactive app RTE has been changed.
@@ -256,7 +286,6 @@
      * @param error the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} is
      *              used when the state is not
      *              {@link TvInteractiveAppManager#SERVICE_STATE_ERROR}.
-     * @hide
      */
     public final void notifyStateChanged(
             @TvInteractiveAppInfo.InteractiveAppType int type,
@@ -272,7 +301,12 @@
 
     /**
      * Base class for derived classes to implement to provide a TV interactive app session.
-     * @hide
+     *
+     * <p>A session is associated with a {@link TvInteractiveAppView} instance and handles
+     * corresponding communications. It also handles the communications with
+     * {@link android.media.tv.TvInputService.Session} if connected.
+     *
+     * @see TvInteractiveAppView#setTvView(TvView)
      */
     public abstract static class Session implements KeyEvent.Callback {
         private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
@@ -316,7 +350,6 @@
          *
          * @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() {
@@ -351,7 +384,6 @@
 
         /**
          * Resets TvIAppService session.
-         * @hide
          */
         public void onResetInteractiveApp() {
         }
@@ -359,8 +391,11 @@
         /**
          * Creates broadcast-independent(BI) interactive application.
          *
+         * <p>The implementation should call {@link #notifyBiInteractiveAppCreated(Uri, String)},
+         * no matter if it's created successfully or not.
+         *
+         * @see #notifyBiInteractiveAppCreated(Uri, String)
          * @see #onDestroyBiInteractiveApp(String)
-         * @hide
          */
         public void onCreateBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
         }
@@ -370,53 +405,46 @@
          * Destroys broadcast-independent(BI) interactive application.
          *
          * @param biIAppId the BI interactive app ID from
-         *        {@link #createBiInteractiveApp(Uri, Bundle)}
+         *                 {@link #onCreateBiInteractiveApp(Uri, Bundle)}}
          *
          * @see #onCreateBiInteractiveApp(Uri, Bundle)
-         * @hide
          */
         public void onDestroyBiInteractiveApp(@NonNull String biIAppId) {
         }
 
         /**
          * To toggle Digital Teletext Application if there is one in AIT app list.
-         * @param enable
-         * @hide
+         * @param enable {@code true} to enable teletext app; {@code false} otherwise.
          */
         public void onSetTeletextAppEnabled(boolean enable) {
         }
 
         /**
          * Receives current channel URI.
-         * @hide
          */
         public void onCurrentChannelUri(@Nullable Uri channelUri) {
         }
 
         /**
          * Receives logical channel number (LCN) of current channel.
-         * @hide
          */
         public void onCurrentChannelLcn(int lcn) {
         }
 
         /**
-         * Receives stream volume.
-         * @hide
+         * Receives current stream volume.
          */
         public void onStreamVolume(float volume) {
         }
 
         /**
          * Receives track list.
-         * @hide
          */
         public void onTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
         }
 
         /**
          * Receives current TV input ID.
-         * @hide
          */
         public void onCurrentTvInputId(@Nullable String inputId) {
         }
@@ -456,7 +484,6 @@
          *
          * @param width The width of the media view.
          * @param height The height of the media view.
-         * @hide
          */
         public void onMediaViewSizeChanged(int width, int height) {
         }
@@ -466,7 +493,6 @@
          * implementation can override this method and return its own view.
          *
          * @return a view attached to the media window
-         * @hide
          */
         @Nullable
         public View onCreateMediaView() {
@@ -475,63 +501,55 @@
 
         /**
          * Releases TvInteractiveAppService session.
-         * @hide
          */
-        public void onRelease() {
-        }
+        public abstract void onRelease();
 
         /**
          * Called when the corresponding TV input tuned to a channel.
-         * @hide
+         *
+         * @param channelUri The tuned channel URI.
          */
         public void onTuned(@NonNull Uri channelUri) {
         }
 
         /**
          * Called when the corresponding TV input selected to a track.
-         * @hide
          */
-        public void onTrackSelected(int type, String trackId) {
+        public void onTrackSelected(@TvTrackInfo.Type int type, @NonNull String trackId) {
         }
 
         /**
          * Called when the tracks are changed.
-         * @hide
          */
-        public void onTracksChanged(List<TvTrackInfo> tracks) {
+        public void onTracksChanged(@NonNull List<TvTrackInfo> tracks) {
         }
 
         /**
          * Called when video is available.
-         * @hide
          */
         public void onVideoAvailable() {
         }
 
         /**
          * Called when video is unavailable.
-         * @hide
          */
-        public void onVideoUnavailable(int reason) {
+        public void onVideoUnavailable(@TvInputManager.VideoUnavailableReason int reason) {
         }
 
         /**
          * Called when content is allowed.
-         * @hide
          */
         public void onContentAllowed() {
         }
 
         /**
          * Called when content is blocked.
-         * @hide
          */
-        public void onContentBlocked(TvContentRating rating) {
+        public void onContentBlocked(@NonNull TvContentRating rating) {
         }
 
         /**
          * Called when signal strength is changed.
-         * @hide
          */
         public void onSignalStrength(@TvInputManager.SignalStrength int strength) {
         }
@@ -545,60 +563,61 @@
 
         /**
          * Called when an advertisement response is received.
-         * @hide
          */
-        public void onAdResponse(AdResponse response) {
+        public void onAdResponse(@NonNull AdResponse response) {
         }
 
-        /**
-         * TODO: JavaDoc of APIs related to input events.
-         * @hide
-         */
         @Override
         public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
             return false;
         }
 
-        /**
-         * @hide
-         */
         @Override
         public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
             return false;
         }
 
-        /**
-         * @hide
-         */
         @Override
         public boolean onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event) {
             return false;
         }
 
-        /**
-         * @hide
-         */
         @Override
         public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
             return false;
         }
 
         /**
-         * @hide
+         * Implement this method to handle touch screen motion events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onTouchEvent
          */
         public boolean onTouchEvent(@NonNull MotionEvent event) {
             return false;
         }
 
         /**
-         * @hide
+         * Implement this method to handle trackball events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onTrackballEvent
          */
         public boolean onTrackballEvent(@NonNull MotionEvent event) {
             return false;
         }
 
         /**
-         * @hide
+         * Implement this method to handle generic motion events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onGenericMotionEvent
          */
         public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
             return false;
@@ -688,13 +707,13 @@
         }
 
         /**
-         * requests a specific command to be processed by the related TV input.
+         * Sends a specific playback 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) {
+        public void sendPlaybackCommandRequest(
+                @PlaybackCommandType @NonNull String cmdType, @Nullable Bundle parameters) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
@@ -716,9 +735,8 @@
 
         /**
          * Sets broadcast video bounds.
-         * @hide
          */
-        public void setVideoBounds(Rect rect) {
+        public void setVideoBounds(@NonNull Rect rect) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
@@ -739,7 +757,6 @@
 
         /**
          * Requests the URI of the current channel.
-         * @hide
          */
         public void requestCurrentChannelUri() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -762,7 +779,6 @@
 
         /**
          * Requests the logic channel number (LCN) of the current channel.
-         * @hide
          */
         public void requestCurrentChannelLcn() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -785,7 +801,6 @@
 
         /**
          * Requests stream volume.
-         * @hide
          */
         public void requestStreamVolume() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -808,7 +823,6 @@
 
         /**
          * Requests the list of {@link TvTrackInfo}.
-         * @hide
          */
         public void requestTrackInfoList() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -833,7 +847,6 @@
          * Requests current TV input ID.
          *
          * @see android.media.tv.TvInputInfo
-         * @hide
          */
         public void requestCurrentTvInputId() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -855,9 +868,9 @@
         }
 
         /**
-         * requests an advertisement request to be processed by the related TV input.
-         * @param request advertisement request
-         * @hide
+         * Sends an advertisement request to be processed by the related TV input.
+         *
+         * @param request The advertisement request
          */
         public void requestAd(@NonNull final AdRequest request) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1046,11 +1059,14 @@
 
         /**
          * Notifies the broadcast-independent(BI) interactive application has been created.
+         *
          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
-         *                 app.
-         * @hide
+         *                 app. {@code null} if it's not created successfully.
+         *
+         * @see #onCreateBiInteractiveApp(Uri, Bundle)
          */
-        public final void notifyBiInteractiveAppCreated(Uri biIAppUri, String biIAppId) {
+        public final void notifyBiInteractiveAppCreated(
+                @NonNull Uri biIAppUri, @Nullable String biIAppId) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
@@ -1073,7 +1089,6 @@
         /**
          * Notifies when the digital teletext app state is changed.
          * @param state the current state.
-         * @hide
          */
         public final void notifyTeletextAppStateChanged(
                 @TvInteractiveAppManager.TeletextAppState int state) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 12e2199..773e54f 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -170,23 +170,20 @@
         }
     }
 
-    /** @hide */
     @Override
-    protected void onAttachedToWindow() {
+    public void onAttachedToWindow() {
         super.onAttachedToWindow();
         createSessionMediaView();
     }
 
-    /** @hide */
     @Override
-    protected void onDetachedFromWindow() {
+    public void onDetachedFromWindow() {
         removeSessionMediaView();
         super.onDetachedFromWindow();
     }
 
-    /** @hide */
     @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (DEBUG) {
             Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
                     + ", bottom=" + bottom + ",)");
@@ -199,9 +196,8 @@
         }
     }
 
-    /** @hide */
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
         int width = mSurfaceView.getMeasuredWidth();
         int height = mSurfaceView.getMeasuredHeight();
@@ -211,9 +207,8 @@
                         childState << MEASURED_HEIGHT_STATE_SHIFT));
     }
 
-    /** @hide */
     @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
+    public void onVisibilityChanged(@NonNull View changedView, int visibility) {
         super.onVisibilityChanged(changedView, visibility);
         mSurfaceView.setVisibility(visibility);
         if (visibility == View.VISIBLE) {
@@ -244,7 +239,6 @@
 
     /**
      * Resets this TvInteractiveAppView.
-     * @hide
      */
     public void reset() {
         if (DEBUG) Log.d(TAG, "reset()");
@@ -330,7 +324,11 @@
 
     /**
      * Dispatches an unhandled input event to the next receiver.
-     * @hide
+     *
+     * It gives the host application a chance to dispatch the unhandled input events.
+     *
+     * @param event The input event.
+     * @return {@code true} if the event was handled by the view, {@code false} otherwise.
      */
     public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) {
         if (mOnUnhandledInputEventListener != null) {
@@ -349,21 +347,28 @@
      * @param event The input event.
      * @return If you handled the event, return {@code true}. If you want to allow the event to be
      *         handled by the next receiver, return {@code false}.
-     * @hide
      */
     public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
         return false;
     }
 
     /**
-     * Registers a callback to be invoked when an input event is not handled
+     * Sets a listener to be invoked when an input event is not handled
      * by the TV Interactive App.
      *
      * @param listener The callback to be invoked when the unhandled input event is received.
-     * @hide
      */
-    public void setOnUnhandledInputEventListener(@NonNull OnUnhandledInputEventListener listener) {
+    public void setOnUnhandledInputEventListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnUnhandledInputEventListener listener) {
         mOnUnhandledInputEventListener = listener;
+        // TODO: handle CallbackExecutor
+    }
+    /**
+     * Clears the {@link OnUnhandledInputEventListener}.
+     */
+    public void clearOnUnhandledInputEventListener() {
+        mOnUnhandledInputEventListener = null;
     }
 
     @Override
@@ -387,7 +392,6 @@
      *                      {@link TvInteractiveAppInfo#getId()}.
      *
      * @see android.media.tv.interactive.TvInteractiveAppManager#getTvInteractiveAppServiceList()
-     * @hide
      */
     public void prepareInteractiveApp(
             @NonNull String iAppServiceId,
@@ -416,7 +420,6 @@
 
     /**
      * Stops the interactive application.
-     * @hide
      */
     public void stopInteractiveApp() {
         if (DEBUG) {
@@ -429,7 +432,6 @@
 
     /**
      * Resets the interactive application.
-     * @hide
      */
     public void resetInteractiveApp() {
         if (DEBUG) {
@@ -442,9 +444,11 @@
 
     /**
      * Sends current channel URI to related TV interactive app.
-     * @hide
+     *
+     * @param channelUri The current channel URI; {@code null} if there is no currently tuned
+     *                   channel.
      */
-    public void sendCurrentChannelUri(Uri channelUri) {
+    public void sendCurrentChannelUri(@Nullable Uri channelUri) {
         if (DEBUG) {
             Log.d(TAG, "sendCurrentChannelUri");
         }
@@ -455,7 +459,6 @@
 
     /**
      * Sends current channel logical channel number (LCN) to related TV interactive app.
-     * @hide
      */
     public void sendCurrentChannelLcn(int lcn) {
         if (DEBUG) {
@@ -468,7 +471,6 @@
 
     /**
      * Sends stream volume to related TV interactive app.
-     * @hide
      */
     public void sendStreamVolume(float volume) {
         if (DEBUG) {
@@ -481,9 +483,8 @@
 
     /**
      * Sends track info list to related TV interactive app.
-     * @hide
      */
-    public void sendTrackInfoList(List<TvTrackInfo> tracks) {
+    public void sendTrackInfoList(@Nullable List<TvTrackInfo> tracks) {
         if (DEBUG) {
             Log.d(TAG, "sendTrackInfoList");
         }
@@ -498,7 +499,6 @@
      * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
      *                tuned.
      * @see android.media.tv.TvInputInfo
-     * @hide
      */
     public void sendCurrentTvInputId(@Nullable String inputId) {
         if (DEBUG) {
@@ -524,8 +524,10 @@
     /**
      * Creates broadcast-independent(BI) interactive application.
      *
-     * @see #destroyBiInteractiveApp(String)
-     * @hide
+     * <p>{@link TvInteractiveAppCallback#onBiInteractiveAppCreated(String, Uri, String)} will be
+     * called for the result.
+     *
+     * @see TvInteractiveAppCallback#onBiInteractiveAppCreated(String, Uri, String)
      */
     public void createBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
         if (DEBUG) {
@@ -542,7 +544,6 @@
      * @param biIAppId the BI interactive app ID from {@link #createBiInteractiveApp(Uri, Bundle)}
      *
      * @see #createBiInteractiveApp(Uri, Bundle)
-     * @hide
      */
     public void destroyBiInteractiveApp(@NonNull String biIAppId) {
         if (DEBUG) {
@@ -564,7 +565,6 @@
      *
      * @param tvView the TvView to be linked to this TvInteractiveAppView via linking of Sessions.
      * @return The result of the operation.
-     * @hide
      */
     public int setTvView(@Nullable TvView tvView) {
         if (tvView == null) {
@@ -590,8 +590,11 @@
 
     /**
      * To toggle Digital Teletext Application if there is one in AIT app list.
-     * @param enable
-     * @hide
+     *
+     * <p>A Teletext Application is a broadcast-related application to display text and basic
+     * graphics.
+     *
+     * @param enable {@code true} to enable Teletext app; {@code false} to disable it.
      */
     public void setTeletextAppEnabled(boolean enable) {
         if (DEBUG) {
@@ -609,28 +612,27 @@
         // TODO: unhide the following public APIs
 
         /**
-         * This is called when a command is requested to be processed by the related TV input.
+         * This is called when a playback command is requested to be processed by the related TV
+         * input.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          * @param cmdType type of the command
          * @param parameters parameters of the command
-         * @hide
          */
-        public void onCommandRequest(
+        public void onPlaybackCommandRequest(
                 @NonNull String iAppServiceId,
-                @NonNull @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
-                @Nullable Bundle parameters) {
+                @NonNull @TvInteractiveAppService.PlaybackCommandType String cmdType,
+                @NonNull Bundle parameters) {
         }
 
         /**
-         * This is called when the session state is changed.
+         * This is called when the state of corresponding interactive app is changed.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          * @param state the current state.
          * @param err the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE}
          *              is used when the state is not
          *              {@link TvInteractiveAppManager#INTERACTIVE_APP_STATE_ERROR}.
-         * @hide
          */
         public void onStateChanged(
                 @NonNull String iAppServiceId,
@@ -643,10 +645,12 @@
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          * @param biIAppUri URI associated this BI interactive app. This is the same URI in
-         *                  {@link Session#createBiInteractiveApp(Uri, Bundle)}
+         *                  {@link #createBiInteractiveApp(Uri, Bundle)}
          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
-         *                 app.
-         * @hide
+         *                 app. {@code null} if it's not created successfully.
+         *
+         * @see #createBiInteractiveApp(Uri, Bundle)
+         * @see #destroyBiInteractiveApp(String)
          */
         public void onBiInteractiveAppCreated(@NonNull String iAppServiceId, @NonNull Uri biIAppUri,
                 @Nullable String biIAppId) {
@@ -657,7 +661,6 @@
          *
          * @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,
@@ -665,59 +668,55 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#SetVideoBounds} is called.
+         * This is called when {@link TvInteractiveAppService.Session#setVideoBounds(Rect)} is
+         * called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
-         * @hide
          */
         public void onSetVideoBounds(@NonNull String iAppServiceId, @NonNull Rect rect) {
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.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.
-         * @hide
          */
         public void onRequestCurrentChannelUri(@NonNull String iAppServiceId) {
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.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.
-         * @hide
          */
         public void onRequestCurrentChannelLcn(@NonNull String iAppServiceId) {
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.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.
-         * @hide
          */
         public void onRequestStreamVolume(@NonNull String iAppServiceId) {
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.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.
-         * @hide
          */
         public void onRequestTrackInfoList(@NonNull String iAppServiceId) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestCurrentTvInputId} is called.
+         * This is called when {@link TvInteractiveAppService.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) {
         }
@@ -726,7 +725,6 @@
 
     /**
      * Interface definition for a callback to be invoked when the unhandled input event is received.
-     * @hide
      */
     public interface OnUnhandledInputEventListener {
         /**
@@ -819,7 +817,7 @@
         @Override
         public void onCommandRequest(
                 Session session,
-                @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
+                @TvInteractiveAppService.PlaybackCommandType String cmdType,
                 Bundle parameters) {
             if (DEBUG) {
                 Log.d(TAG, "onCommandRequest (cmdType=" + cmdType + ", parameters="
@@ -834,7 +832,8 @@
                     mCallbackExecutor.execute(() -> {
                         synchronized (mCallbackLock) {
                             if (mCallback != null) {
-                                mCallback.onCommandRequest(mIAppServiceId, cmdType, parameters);
+                                mCallback.onPlaybackCommandRequest(
+                                        mIAppServiceId, cmdType, parameters);
                             }
                         }
                     });
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 3157375..be114d4 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -47,6 +47,7 @@
 import android.media.tv.tuner.frontend.FrontendSettings;
 import android.media.tv.tuner.frontend.FrontendStatus;
 import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
+import android.media.tv.tuner.frontend.FrontendStatusReadiness;
 import android.media.tv.tuner.frontend.OnTuneEventListener;
 import android.media.tv.tuner.frontend.ScanCallback;
 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
@@ -61,9 +62,7 @@
 import android.os.Message;
 import android.os.Process;
 import android.util.Log;
-
 import com.android.internal.util.FrameworkStatsLog;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -1005,6 +1004,7 @@
     private native int nativeRemoveOutputPid(int pid);
     private native Lnb nativeOpenLnbByHandle(int handle);
     private native Lnb nativeOpenLnbByName(String name);
+    private native FrontendStatusReadiness[] nativeGetFrontendStatusReadiness(int[] statusTypes);
 
     private native Descrambler nativeOpenDescramblerByHandle(int handle);
     private native int nativeOpenDemuxByhandle(int handle);
@@ -1595,6 +1595,38 @@
     }
 
     /**
+     * Gets Frontend Status Readiness statuses for given status types.
+     *
+     * <p>This API is only supported by Tuner HAL 2.0 or higher. Unsupported versions would cause
+     * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     *
+     * @param statusTypes an array of status types.
+     *
+     * @return an array of current readiness states. {@code null} if the operation failed or
+     *         unsupported versions.
+     * @throws IllegalStateException if there is no active frontend currently.
+     */
+    @Nullable
+    @SuppressLint("ArrayReturn")
+    @SuppressWarnings("NullableCollection")
+    public FrontendStatusReadiness[] getFrontendStatusReadiness(
+            @NonNull @FrontendStatusType int[] statusTypes) {
+        mFrontendLock.lock();
+        try {
+            if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+                        TunerVersionChecker.TUNER_VERSION_2_0, "Remove output PID")) {
+                return null;
+            }
+            if (mFrontend == null) {
+                throw new IllegalStateException("frontend is not initialized");
+            }
+            return nativeGetFrontendStatusReadiness(statusTypes);
+        } finally {
+            mFrontendLock.unlock();
+        }
+    }
+
+    /**
      * Gets the currently initialized and activated frontend information. To get all the available
      * frontend info on the device, use {@link getAvailableFrontendInfos()}.
      *
diff --git a/media/java/android/media/tv/tuner/filter/SharedFilter.java b/media/java/android/media/tv/tuner/filter/SharedFilter.java
index 056c5d5..740ab9c 100644
--- a/media/java/android/media/tv/tuner/filter/SharedFilter.java
+++ b/media/java/android/media/tv/tuner/filter/SharedFilter.java
@@ -92,9 +92,21 @@
                     synchronized (mCallbackLock) {
                         if (mCallback != null) {
                             mCallback.onFilterEvent(this, events);
+                        } else {
+                            for (FilterEvent event : events) {
+                                if (event instanceof MediaEvent) {
+                                    ((MediaEvent)event).release();
+                                }
+                            }
                         }
                     }
                 });
+            } else {
+                for (FilterEvent event : events) {
+                    if (event instanceof MediaEvent) {
+                        ((MediaEvent)event).release();
+                    }
+                }
             }
         }
     }
@@ -187,6 +199,8 @@
             if (mIsClosed) {
                 return;
             }
+            mCallback = null;
+            mExecutor = null;
             nativeSharedClose();
             mIsClosed = true;
          }
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
new file mode 100644
index 0000000..52527b3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class contains the Frontend Status Readiness of a given type.
+ *
+ * @hide
+ */
+@SystemApi
+public class FrontendStatusReadiness {
+    /** @hide */
+    @IntDef({FRONTEND_STATUS_READINESS_UNDEFINED, FRONTEND_STATUS_READINESS_UNAVAILABLE,
+            FRONTEND_STATUS_READINESS_UNSTABLE, FRONTEND_STATUS_READINESS_STABLE,
+            FRONTEND_STATUS_READINESS_UNSUPPORTED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Readiness {}
+
+    /**
+     * The FrontendStatus readiness status for the given FrontendStatusType is undefined.
+     */
+    public static final int FRONTEND_STATUS_READINESS_UNDEFINED =
+            android.hardware.tv.tuner.FrontendStatusReadiness.UNDEFINED;
+
+    /**
+     * The FrontendStatus for the given FrontendStatusType is currently unavailable.
+     */
+    public static final int FRONTEND_STATUS_READINESS_UNAVAILABLE =
+            android.hardware.tv.tuner.FrontendStatusReadiness.UNAVAILABLE;
+
+    /**
+     * The FrontendStatus for the given FrontendStatusType is ready to read, but it’s unstable.
+     */
+    public static final int FRONTEND_STATUS_READINESS_UNSTABLE =
+            android.hardware.tv.tuner.FrontendStatusReadiness.UNSTABLE;
+
+    /**
+     * The FrontendStatus for the given FrontendStatusType is ready to read, and it’s stable.
+     */
+    public static final int FRONTEND_STATUS_READINESS_STABLE =
+            android.hardware.tv.tuner.FrontendStatusReadiness.STABLE;
+
+    /**
+     * The FrontendStatus for the given FrontendStatusType is not supported.
+     */
+    public static final int FRONTEND_STATUS_READINESS_UNSUPPORTED =
+            android.hardware.tv.tuner.FrontendStatusReadiness.UNSUPPORTED;
+
+    @FrontendStatusType private int mFrontendStatusType;
+    @Readiness private int mStatusReadiness;
+
+    private FrontendStatusReadiness(int type, int readiness) {
+        mFrontendStatusType = type;
+        mStatusReadiness = readiness;
+    }
+
+    /**
+     * Gets the frontend status type.
+     */
+    @FrontendStatusType
+    public int getStatusType() {
+        return mFrontendStatusType;
+    }
+    /**
+     * Gets the frontend status readiness.
+     */
+    @Readiness
+    public int getStatusReadiness() {
+        return mStatusReadiness;
+    }
+}
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index 2e419a6..eca26dc 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -460,8 +460,6 @@
     } else {
         // Set consumer buffer format to user specified format
         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",
@@ -478,20 +476,29 @@
             return 0;
         }
         ctx->setBufferDataSpace(nativeDataspace);
-        surfaceFormat = userFormat;
+        surfaceFormat = static_cast<int32_t>(mapHalFormatDataspaceToPublicFormat(
+            hardwareBufferFormat, nativeDataspace));
     }
 
     ctx->setBufferFormat(surfaceFormat);
     env->SetIntField(thiz,
             gImageWriterClassInfo.mWriterFormat, reinterpret_cast<jint>(surfaceFormat));
 
-    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;
+    // ndkUsage == -1 means setUsage in ImageWriter class is not called.
+    // skip usage setting if setUsage in ImageWriter is not called and imageformat is opaque.
+    if (!(ndkUsage == -1 && isFormatOpaque(surfaceFormat))) {
+        if (ndkUsage == -1) {
+            ndkUsage = GRALLOC_USAGE_SW_WRITE_OFTEN;
+        }
+        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;
@@ -952,7 +959,7 @@
     return buffer->getHeight();
 }
 
-static jint Image_getFormat(JNIEnv* env, jobject thiz) {
+static jint Image_getFormat(JNIEnv* env, jobject thiz, jlong dataSpace) {
     ALOGV("%s", __FUNCTION__);
     GraphicBuffer* buffer;
     Image_getNativeContext(env, thiz, &buffer, NULL);
@@ -962,9 +969,9 @@
         return 0;
     }
 
-    // ImageWriter doesn't support data space yet, assuming it is unknown.
     PublicFormat publicFmt = mapHalFormatDataspaceToPublicFormat(buffer->getPixelFormat(),
-                                                                 HAL_DATASPACE_UNKNOWN);
+        static_cast<android_dataspace>(dataSpace));
+
     return static_cast<jint>(publicFmt);
 }
 
@@ -1031,14 +1038,14 @@
 }
 
 static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz,
-        int numPlanes, int writerFormat) {
+        int numPlanes, int writerFormat, long dataSpace) {
     ALOGV("%s: create SurfacePlane array with size %d", __FUNCTION__, numPlanes);
     int rowStride, pixelStride;
     uint8_t *pData;
     uint32_t dataSize;
     jobject byteBuffer;
 
-    int format = Image_getFormat(env, thiz);
+    int format = Image_getFormat(env, thiz, dataSpace);
     if (isFormatOpaque(format) && numPlanes > 0) {
         String8 msg;
         msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
@@ -1108,11 +1115,11 @@
 };
 
 static JNINativeMethod gImageMethods[] = {
-    {"nativeCreatePlanes",      "(II)[Landroid/media/ImageWriter$WriterSurfaceImage$SurfacePlane;",
+    {"nativeCreatePlanes",      "(IIJ)[Landroid/media/ImageWriter$WriterSurfaceImage$SurfacePlane;",
                                                                (void*)Image_createSurfacePlanes },
     {"nativeGetWidth",          "()I",                         (void*)Image_getWidth },
     {"nativeGetHeight",         "()I",                         (void*)Image_getHeight },
-    {"nativeGetFormat",         "()I",                         (void*)Image_getFormat },
+    {"nativeGetFormat",         "(J)I",                        (void*)Image_getFormat },
     {"nativeGetHardwareBuffer", "()Landroid/hardware/HardwareBuffer;",
                                                                (void*)Image_getHardwareBuffer },
 };
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 41f3a67..9ed1ac0 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1631,6 +1631,36 @@
     return (jint)mFeClient->removeOutputPid(pid);
 }
 
+jobjectArray JTuner::getFrontendStatusReadiness(jintArray types) {
+    if (mFeClient == nullptr) {
+        ALOGE("frontend is not initialized");
+        return nullptr;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jsize size = env->GetArrayLength(types);
+    jint intTypes[size];
+    env->GetIntArrayRegion(types, 0, size, intTypes);
+    std::vector<FrontendStatusType> v;
+    for (int i = 0; i < size; i++) {
+        v.push_back(static_cast<FrontendStatusType>(intTypes[i]));
+    }
+
+    vector<FrontendStatusReadiness> readiness = mFeClient->getStatusReadiness(v);
+    if (readiness.size() < size) {
+        return nullptr;
+    }
+
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendStatusReadiness");
+    jmethodID init = env->GetMethodID(clazz, "<init>", "(II)V");
+    jobjectArray valObj = env->NewObjectArray(size, clazz, nullptr);
+    for (int i = 0; i < size; i++) {
+        jobject readinessObj = env->NewObject(clazz, init, intTypes[i], readiness[i]);
+        env->SetObjectArrayElement(valObj, i, readinessObj);
+    }
+    return valObj;
+}
+
 jobject JTuner::openLnbByHandle(int handle) {
     if (mTunerClient == nullptr) {
         return nullptr;
@@ -4117,6 +4147,7 @@
 
     Result r = filterClient->close();
     filterClient->decStrong(filter);
+    filterClient = nullptr;
     if (shared) {
         env->SetLongField(filter, gFields.sharedFilterContext, 0);
     } else {
@@ -4389,6 +4420,12 @@
     return tuner->removeOutputPid(pid);
 }
 
+static jobjectArray android_media_tv_Tuner_get_frontend_status_readiness(JNIEnv *env, jobject thiz,
+                                                                         jintArray types) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->getFrontendStatusReadiness(types);
+}
+
 static jint android_media_tv_Tuner_close_frontend(JNIEnv* env, jobject thiz, jint /* handle */) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->closeFrontend();
@@ -4710,6 +4747,9 @@
             (void *)android_media_tv_Tuner_get_maximum_frontends },
     { "nativeRemoveOutputPid", "(I)I",
             (void *)android_media_tv_Tuner_remove_output_pid },
+    { "nativeGetFrontendStatusReadiness",
+            "([I)[Landroid/media/tv/tuner/frontend/FrontendStatusReadiness;",
+            (void *)android_media_tv_Tuner_get_frontend_status_readiness },
 };
 
 static const JNINativeMethod gFilterMethods[] = {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index e9475dc..03e7fa9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -206,6 +206,7 @@
     jint setMaxNumberOfFrontends(int32_t frontendType, int32_t maxNumber);
     int32_t getMaxNumberOfFrontends(int32_t frontendType);
     jint removeOutputPid(int32_t pid);
+    jobjectArray getFrontendStatusReadiness(jintArray types);
 
     jweak getObject();
 
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index bea0342..c6337ec 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -152,6 +152,16 @@
     return Result::INVALID_STATE;
 }
 
+vector<FrontendStatusReadiness> FrontendClient::getStatusReadiness(
+        const std::vector<FrontendStatusType>& statusTypes) {
+    vector<FrontendStatusReadiness> readiness;
+    if (mTunerFrontend != nullptr) {
+        mTunerFrontend->getFrontendStatusReadiness(statusTypes, &readiness);
+    }
+
+    return readiness;
+}
+
 shared_ptr<ITunerFrontend> FrontendClient::getAidlFrontend() {
     return mTunerFrontend;
 }
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index c6838c8..85f6d56 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -35,6 +35,7 @@
 using ::aidl::android::hardware::tv::tuner::FrontendScanType;
 using ::aidl::android::hardware::tv::tuner::FrontendSettings;
 using ::aidl::android::hardware::tv::tuner::FrontendStatus;
+using ::aidl::android::hardware::tv::tuner::FrontendStatusReadiness;
 using ::aidl::android::hardware::tv::tuner::FrontendStatusType;
 using ::aidl::android::hardware::tv::tuner::FrontendType;
 using ::aidl::android::hardware::tv::tuner::Result;
@@ -125,6 +126,12 @@
      */
     Result removeOutputPid(int32_t pid);
 
+    /**
+     * Gets Frontend Status Readiness statuses for given status types.
+     */
+    vector<FrontendStatusReadiness> getStatusReadiness(
+            const std::vector<FrontendStatusType>& types);
+
     int32_t getId();
 
     shared_ptr<ITunerFrontend> getAidlFrontend();
diff --git a/packages/CompanionDeviceManager/res/values-af/strings.xml b/packages/CompanionDeviceManager/res/values-af/strings.xml
index 00a5210..34b573d 100644
--- a/packages/CompanionDeviceManager/res/values-af/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-af/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Laat toe"</string>
     <string name="consent_no" msgid="2640796915611404382">"Moenie toelaat nie"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml
index 2254e1f..d79b653 100644
--- a/packages/CompanionDeviceManager/res/values-am/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-am/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ፍቀድ"</string>
     <string name="consent_no" msgid="2640796915611404382">"አትፍቀድ"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index 5944dba3..7988111 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"السماح"</string>
     <string name="consent_no" msgid="2640796915611404382">"عدم السماح"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-as/strings.xml b/packages/CompanionDeviceManager/res/values-as/strings.xml
index e58aed7..17e2cb1 100644
--- a/packages/CompanionDeviceManager/res/values-as/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-as/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিয়ক"</string>
     <string name="consent_no" msgid="2640796915611404382">"অনুমতি নিদিব"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml
index 7577776..9d504f1 100644
--- a/packages/CompanionDeviceManager/res/values-az/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-az/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"İcazə verin"</string>
     <string name="consent_no" msgid="2640796915611404382">"İcazə verməyin"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
index 8a63b11..63b5094 100644
--- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne dozvoli"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index bf4fe3e..bd6ead2 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Дазволіць"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дазваляць"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml
index cc67b13..5f5320e 100644
--- a/packages/CompanionDeviceManager/res/values-bg/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Разрешаване"</string>
     <string name="consent_no" msgid="2640796915611404382">"Забраняване"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml
index 08ffab0..8bc47eb 100644
--- a/packages/CompanionDeviceManager/res/values-bn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিন"</string>
     <string name="consent_no" msgid="2640796915611404382">"অনুমতি দেবেন না"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml
index 8b0daaa..8644645 100644
--- a/packages/CompanionDeviceManager/res/values-bs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nemoj dozvoliti"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml
index c98feb3..9a5d4b8 100644
--- a/packages/CompanionDeviceManager/res/values-ca/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permet"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permetis"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml
index c758b6e..0210500 100644
--- a/packages/CompanionDeviceManager/res/values-cs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Povolit"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nepovolovat"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml
index b026bb1..7e89735 100644
--- a/packages/CompanionDeviceManager/res/values-da/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-da/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillad"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tillad ikke"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml
index 345b971..97f017e 100644
--- a/packages/CompanionDeviceManager/res/values-de/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-de/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Zulassen"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nicht zulassen"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml
index 64d500e..926f7151 100644
--- a/packages/CompanionDeviceManager/res/values-el/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-el/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Να επιτρέπεται"</string>
     <string name="consent_no" msgid="2640796915611404382">"Να μην επιτρέπεται"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
index 1756d22..e9452fd 100644
--- a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
     <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index 1756d22..e9452fd 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
     <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
index 1756d22..e9452fd 100644
--- a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
     <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
index 1756d22..e9452fd 100644
--- a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
     <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
index efda04e..2ed5310 100644
--- a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
@@ -32,4 +32,6 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎Allow‎‏‎‎‏‎"</string>
     <string name="consent_no" msgid="2640796915611404382">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‏‏‎‎Don’t allow‎‏‎‎‏‎"</string>
+    <string name="permission_sync_confirmation_title" msgid="667074294393493186">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎Transfer app permissions to your watch‎‏‎‎‏‎"</string>
+    <string name="permission_sync_summary" msgid="8873391306499120778">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‎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’s microphone and location.‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
index 90e33a5..705615d 100644
--- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml
index 78ac63f..b682490 100644
--- a/packages/CompanionDeviceManager/res/values-es/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml
index 165dc97..34c0fb2 100644
--- a/packages/CompanionDeviceManager/res/values-et/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-et/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Luba"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ära luba"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml
index d424359..808baf4 100644
--- a/packages/CompanionDeviceManager/res/values-eu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Eman baimena"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ez eman baimenik"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml
index d9053fd..6dea7ef 100644
--- a/packages/CompanionDeviceManager/res/values-fa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"مجاز بودن"</string>
     <string name="consent_no" msgid="2640796915611404382">"مجاز نبودن"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml
index e76f89d..5772ebf 100644
--- a/packages/CompanionDeviceManager/res/values-fi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Salli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Älä salli"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
index f6a4855..c09f1d6 100644
--- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml
index a214b89..63dd6a3 100644
--- a/packages/CompanionDeviceManager/res/values-fr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml
index c179378..8b31f97 100644
--- a/packages/CompanionDeviceManager/res/values-gl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Non permitir"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml
index ff9a89e..077ff27 100644
--- a/packages/CompanionDeviceManager/res/values-gu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"મંજૂરી આપો"</string>
     <string name="consent_no" msgid="2640796915611404382">"મંજૂરી આપશો નહીં"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml
index 557e1f8..57f18cd 100644
--- a/packages/CompanionDeviceManager/res/values-hi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमति दें"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमति न दें"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hr/strings.xml b/packages/CompanionDeviceManager/res/values-hr/strings.xml
index 453a4dd..a8bc9e6 100644
--- a/packages/CompanionDeviceManager/res/values-hr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hr/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Dopusti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nemoj dopustiti"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml
index dacc4e4..a862475 100644
--- a/packages/CompanionDeviceManager/res/values-hu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Engedélyezés"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tiltás"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml
index 9b79f4b..4eefc0b 100644
--- a/packages/CompanionDeviceManager/res/values-hy/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Թույլատրել"</string>
     <string name="consent_no" msgid="2640796915611404382">"Չթույլատրել"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml
index 684167e..533e81d 100644
--- a/packages/CompanionDeviceManager/res/values-in/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-in/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Izinkan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Jangan izinkan"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml
index cdfc47a..25438ce 100644
--- a/packages/CompanionDeviceManager/res/values-is/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-is/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Leyfa"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ekki leyfa"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-it/strings.xml b/packages/CompanionDeviceManager/res/values-it/strings.xml
index fc7100a..8f23b6a 100644
--- a/packages/CompanionDeviceManager/res/values-it/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-it/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Consenti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Non consentire"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml
index 295df783..ec21a10 100644
--- a/packages/CompanionDeviceManager/res/values-iw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"יש אישור"</string>
     <string name="consent_no" msgid="2640796915611404382">"אין אישור"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml
index a9438be..f6ef81a 100644
--- a/packages/CompanionDeviceManager/res/values-ja/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"許可"</string>
     <string name="consent_no" msgid="2640796915611404382">"許可しない"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml
index 8354f4a..9440227 100644
--- a/packages/CompanionDeviceManager/res/values-ka/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"დაშვება"</string>
     <string name="consent_no" msgid="2640796915611404382">"არ დაიშვას"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml
index 722b570..e99a61c 100644
--- a/packages/CompanionDeviceManager/res/values-kk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Рұқсат беру"</string>
     <string name="consent_no" msgid="2640796915611404382">"Рұқсат бермеу"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml
index d47d6c4..0f8820e 100644
--- a/packages/CompanionDeviceManager/res/values-km/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-km/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"អនុញ្ញាត"</string>
     <string name="consent_no" msgid="2640796915611404382">"កុំអនុញ្ញាត"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml
index ba9f8ff..81e956d 100644
--- a/packages/CompanionDeviceManager/res/values-kn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ಅನುಮತಿಸಿ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ಅನುಮತಿಸಬೇಡಿ"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml
index 8faab71..b2e5062 100644
--- a/packages/CompanionDeviceManager/res/values-ko/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"허용"</string>
     <string name="consent_no" msgid="2640796915611404382">"허용 안함"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml
index eec1775..6f05848 100644
--- a/packages/CompanionDeviceManager/res/values-ky/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Уруксат берүү"</string>
     <string name="consent_no" msgid="2640796915611404382">"Уруксат берилбесин"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml
index ed24422..314329f 100644
--- a/packages/CompanionDeviceManager/res/values-lo/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ອະນຸຍາດ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ບໍ່ອະນຸຍາດ"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml
index 8472d79..b3c789c 100644
--- a/packages/CompanionDeviceManager/res/values-lt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Leisti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Neleisti"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml
index 8b27a08..be7a95e 100644
--- a/packages/CompanionDeviceManager/res/values-lv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Atļaut"</string>
     <string name="consent_no" msgid="2640796915611404382">"Neatļaut"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml
index e6131e6..29d9660 100644
--- a/packages/CompanionDeviceManager/res/values-mk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволувај"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ml/strings.xml b/packages/CompanionDeviceManager/res/values-ml/strings.xml
index e35a733..ec09d65 100644
--- a/packages/CompanionDeviceManager/res/values-ml/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ml/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"അനുവദിക്കുക"</string>
     <string name="consent_no" msgid="2640796915611404382">"അനുവദിക്കരുത്"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml
index 1ea1c9b..f27698c 100644
--- a/packages/CompanionDeviceManager/res/values-mn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Зөвшөөрөх"</string>
     <string name="consent_no" msgid="2640796915611404382">"Бүү зөвшөөр"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml
index 1936ede..685250d 100644
--- a/packages/CompanionDeviceManager/res/values-mr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमती द्या"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमती देऊ नका"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml
index fb69cb1..e594d61 100644
--- a/packages/CompanionDeviceManager/res/values-ms/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Benarkan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Jangan benarkan"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml
index 31596a4..7d3ef90 100644
--- a/packages/CompanionDeviceManager/res/values-my/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-my/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ခွင့်ပြုရန်"</string>
     <string name="consent_no" msgid="2640796915611404382">"ခွင့်မပြုပါ"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml
index 52afcf0..23c7fbf 100644
--- a/packages/CompanionDeviceManager/res/values-nb/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillat"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ikke tillat"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ne/strings.xml b/packages/CompanionDeviceManager/res/values-ne/strings.xml
index 9b42c1e..4615733 100644
--- a/packages/CompanionDeviceManager/res/values-ne/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ne/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमति दिनुहोस्"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमति नदिनुहोस्"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index 354cb93..83acc79 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Toestaan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Niet toestaan"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml
index b58ebd34..8d3bb65 100644
--- a/packages/CompanionDeviceManager/res/values-or/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-or/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pa/strings.xml b/packages/CompanionDeviceManager/res/values-pa/strings.xml
index f2a5c29..692d67f 100644
--- a/packages/CompanionDeviceManager/res/values-pa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pa/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ਇਜਾਜ਼ਤ ਦਿਓ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ਇਜਾਜ਼ਤ ਨਾ ਦਿਓ"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml
index 9356792..3de6c5b 100644
--- a/packages/CompanionDeviceManager/res/values-pl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Zezwól"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
index 7d79608..b440215 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
index bc30ed8..73982a6 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml
index 7d79608..b440215 100644
--- a/packages/CompanionDeviceManager/res/values-pt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml
index dd38f1f..d3e725f 100644
--- a/packages/CompanionDeviceManager/res/values-ro/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permiteți"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nu permiteți"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml
index 8e2b4d8..5983a59 100644
--- a/packages/CompanionDeviceManager/res/values-ru/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Разрешить"</string>
     <string name="consent_no" msgid="2640796915611404382">"Запретить"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml
index 489ecf9..83a5156 100644
--- a/packages/CompanionDeviceManager/res/values-si/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-si/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ඉඩ දෙන්න"</string>
     <string name="consent_no" msgid="2640796915611404382">"ඉඩ නොදෙන්න"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml
index cbee372..3fe111c 100644
--- a/packages/CompanionDeviceManager/res/values-sk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Povoliť"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nepovoliť"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml
index 53eb85d..a3c9a07 100644
--- a/packages/CompanionDeviceManager/res/values-sl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Dovoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne dovoli"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml
index 0704b9b..bb9ae13 100644
--- a/packages/CompanionDeviceManager/res/values-sq/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Lejo"</string>
     <string name="consent_no" msgid="2640796915611404382">"Mos lejo"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml
index eb768a2..6da288c 100644
--- a/packages/CompanionDeviceManager/res/values-sr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволи"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sv/strings.xml b/packages/CompanionDeviceManager/res/values-sv/strings.xml
index 24db58d..5c821f2 100644
--- a/packages/CompanionDeviceManager/res/values-sv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sv/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillåt"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tillåt inte"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index d06f1c6..588addc 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Ruhusu"</string>
     <string name="consent_no" msgid="2640796915611404382">"Usiruhusu"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ta/strings.xml b/packages/CompanionDeviceManager/res/values-ta/strings.xml
index d58d2ae..9bbc9f5 100644
--- a/packages/CompanionDeviceManager/res/values-ta/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ta/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"அனுமதி"</string>
     <string name="consent_no" msgid="2640796915611404382">"அனுமதிக்க வேண்டாம்"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml
index 9e9fec5..759eded 100644
--- a/packages/CompanionDeviceManager/res/values-te/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-te/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"అనుమతించు"</string>
     <string name="consent_no" msgid="2640796915611404382">"అనుమతించవద్దు"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml
index 9d9c91d..233e242 100644
--- a/packages/CompanionDeviceManager/res/values-th/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-th/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"อนุญาต"</string>
     <string name="consent_no" msgid="2640796915611404382">"ไม่อนุญาต"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml
index 436097c..d5ee345 100644
--- a/packages/CompanionDeviceManager/res/values-tl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Payagan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Huwag payagan"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml
index 3a256a7..6129ea9 100644
--- a/packages/CompanionDeviceManager/res/values-tr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"İzin ver"</string>
     <string name="consent_no" msgid="2640796915611404382">"İzin verme"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml
index 9f40a0c..82aa0d7 100644
--- a/packages/CompanionDeviceManager/res/values-uk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволити"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволяти"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml
index 3c1fe5d..db8b472 100644
--- a/packages/CompanionDeviceManager/res/values-ur/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"اجازت دیں"</string>
     <string name="consent_no" msgid="2640796915611404382">"اجازت نہ دیں"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml
index ff5e4b9..e937f87 100644
--- a/packages/CompanionDeviceManager/res/values-uz/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Ruxsat"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ruxsat berilmasin"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml
index f52dde1..b17f61a 100644
--- a/packages/CompanionDeviceManager/res/values-vi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Cho phép"</string>
     <string name="consent_no" msgid="2640796915611404382">"Không cho phép"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
index f1facc1..61ffa09 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"允许"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允许"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
index aed008f..6842261 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"允許"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允許"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index 22a9d9c..c9449e6 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"允許"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允許"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml
index 5c5756b..e8ac64b 100644
--- a/packages/CompanionDeviceManager/res/values-zu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml
@@ -32,4 +32,8 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Vumela"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ungavumeli"</string>
+    <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+    <skip />
+    <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+    <skip />
 </resources>
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 8813f98..28f930f 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -39,14 +40,11 @@
 import android.net.NetworkStateSnapshot;
 import android.net.NetworkTemplate;
 import android.net.UnderlyingNetworkInfo;
+import android.net.netstats.IUsageCallback;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.net.netstats.provider.NetworkStatsProvider;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
 import android.os.RemoteException;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -57,6 +55,7 @@
 
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * Provides access to network usage history and statistics. Usage data is collected in
@@ -723,26 +722,36 @@
         }
     }
 
-    /** @hide */
-    public void registerUsageCallback(NetworkTemplate template, int networkType,
-            long thresholdBytes, UsageCallback callback, @Nullable Handler handler) {
+    /**
+     * Registers to receive notifications about data usage on specified networks.
+     *
+     * <p>The callbacks will continue to be called as long as the process is alive or
+     * {@link #unregisterUsageCallback} is called.
+     *
+     * @param template Template used to match networks. See {@link NetworkTemplate}.
+     * @param thresholdBytes Threshold in bytes to be notified on. The provided value that lower
+     *                       than 2MiB will be clamped for non-privileged callers.
+     * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+     *                 must run callback sequentially, otherwise the order of callbacks cannot be
+     *                 guaranteed.
+     * @param callback The {@link UsageCallback} that the system will call when data usage
+     *                 has exceeded the specified threshold.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public void registerUsageCallback(@NonNull NetworkTemplate template, long thresholdBytes,
+            @NonNull @CallbackExecutor Executor executor, @NonNull UsageCallback callback) {
+        Objects.requireNonNull(template, "NetworkTemplate cannot be null");
         Objects.requireNonNull(callback, "UsageCallback cannot be null");
+        Objects.requireNonNull(executor, "Executor cannot be null");
 
-        final Looper looper;
-        if (handler == null) {
-            looper = Looper.myLooper();
-        } else {
-            looper = handler.getLooper();
-        }
-
-        DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
+        final DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
                 template, thresholdBytes);
         try {
-            CallbackHandler callbackHandler = new CallbackHandler(looper, networkType,
-                    template.getSubscriberId(), callback);
+            final UsageCallbackWrapper callbackWrapper =
+                    new UsageCallbackWrapper(executor, callback);
             callback.request = mService.registerUsageCallback(
-                    mContext.getOpPackageName(), request, new Messenger(callbackHandler),
-                    new Binder());
+                    mContext.getOpPackageName(), request, callbackWrapper);
             if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request);
 
             if (callback.request == null) {
@@ -795,12 +804,15 @@
         NetworkTemplate template = createTemplate(networkType, subscriberId);
         if (DBG) {
             Log.d(TAG, "registerUsageCallback called with: {"
-                + " networkType=" + networkType
-                + " subscriberId=" + subscriberId
-                + " thresholdBytes=" + thresholdBytes
-                + " }");
+                    + " networkType=" + networkType
+                    + " subscriberId=" + subscriberId
+                    + " thresholdBytes=" + thresholdBytes
+                    + " }");
         }
-        registerUsageCallback(template, networkType, thresholdBytes, callback, handler);
+
+        final Executor executor = handler == null ? r -> r.run() : r -> handler.post(r);
+
+        registerUsageCallback(template, thresholdBytes, executor, callback);
     }
 
     /**
@@ -825,6 +837,26 @@
      * Base class for usage callbacks. Should be extended by applications wanting notifications.
      */
     public static abstract class UsageCallback {
+        /**
+         * Called when data usage has reached the given threshold.
+         *
+         * Called by {@code NetworkStatsService} when the registered threshold is reached.
+         * If a caller implements {@link #onThresholdReached(NetworkTemplate)}, the system
+         * will not call {@link #onThresholdReached(int, String)}.
+         *
+         * @param template The {@link NetworkTemplate} that associated with this callback.
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        public void onThresholdReached(@NonNull NetworkTemplate template) {
+            // Backward compatibility for those who didn't override this function.
+            final int networkType = networkTypeForTemplate(template);
+            if (networkType != ConnectivityManager.TYPE_NONE) {
+                final String subscriberId = template.getSubscriberIds().isEmpty() ? null
+                        : template.getSubscriberIds().iterator().next();
+                onThresholdReached(networkType, subscriberId);
+            }
+        }
 
         /**
          * Called when data usage has reached the given threshold.
@@ -835,6 +867,25 @@
          * @hide used for internal bookkeeping
          */
         private DataUsageRequest request;
+
+        /**
+         * Get network type from a template if feasible.
+         *
+         * @param template the target {@link NetworkTemplate}.
+         * @return legacy network type, only supports for the types which is already supported in
+         *         {@link #registerUsageCallback(int, String, long, UsageCallback, Handler)}.
+         *         {@link ConnectivityManager#TYPE_NONE} for other types.
+         */
+        private static int networkTypeForTemplate(@NonNull NetworkTemplate template) {
+            switch (template.getMatchRule()) {
+                case NetworkTemplate.MATCH_MOBILE:
+                    return ConnectivityManager.TYPE_MOBILE;
+                case NetworkTemplate.MATCH_WIFI:
+                    return ConnectivityManager.TYPE_WIFI;
+                default:
+                    return ConnectivityManager.TYPE_NONE;
+            }
+        }
     }
 
     /**
@@ -953,43 +1004,32 @@
         }
     }
 
-    private static class CallbackHandler extends Handler {
-        private final int mNetworkType;
-        private final String mSubscriberId;
-        private UsageCallback mCallback;
+    private static class UsageCallbackWrapper extends IUsageCallback.Stub {
+        // Null if unregistered.
+        private volatile UsageCallback mCallback;
 
-        CallbackHandler(Looper looper, int networkType, String subscriberId,
-                UsageCallback callback) {
-            super(looper);
-            mNetworkType = networkType;
-            mSubscriberId = subscriberId;
+        private final Executor mExecutor;
+
+        UsageCallbackWrapper(@NonNull Executor executor, @NonNull UsageCallback callback) {
             mCallback = callback;
+            mExecutor = executor;
         }
 
         @Override
-        public void handleMessage(Message message) {
-            DataUsageRequest request =
-                    (DataUsageRequest) getObject(message, DataUsageRequest.PARCELABLE_KEY);
-
-            switch (message.what) {
-                case CALLBACK_LIMIT_REACHED: {
-                    if (mCallback != null) {
-                        mCallback.onThresholdReached(mNetworkType, mSubscriberId);
-                    } else {
-                        Log.e(TAG, "limit reached with released callback for " + request);
-                    }
-                    break;
-                }
-                case CALLBACK_RELEASED: {
-                    if (DBG) Log.d(TAG, "callback released for " + request);
-                    mCallback = null;
-                    break;
-                }
+        public void onThresholdReached(DataUsageRequest request) {
+            // Copy it to a local variable in case mCallback changed inside the if condition.
+            final UsageCallback callback = mCallback;
+            if (callback != null) {
+                mExecutor.execute(() -> callback.onThresholdReached(request.template));
+            } else {
+                Log.e(TAG, "onThresholdReached with released callback for " + request);
             }
         }
 
-        private static Object getObject(Message msg, String key) {
-            return msg.getData().getParcelable(key);
+        @Override
+        public void onCallbackReleased(DataUsageRequest request) {
+            if (DBG) Log.d(TAG, "callback released for " + request);
+            mCallback = null;
         }
     }
 
diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
index da0aa99..efe626d 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
@@ -24,6 +24,7 @@
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.UnderlyingNetworkInfo;
+import android.net.netstats.IUsageCallback;
 import android.net.netstats.provider.INetworkStatsProvider;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.IBinder;
@@ -71,7 +72,7 @@
 
     /** Registers a callback on data usage. */
     DataUsageRequest registerUsageCallback(String callingPackage,
-            in DataUsageRequest request, in Messenger messenger, in IBinder binder);
+            in DataUsageRequest request, in IUsageCallback callback);
 
     /** Unregisters a callback on data usage. */
     void unregisterUsageRequest(in DataUsageRequest request);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
index d3d5a08..77fc171 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
@@ -434,7 +434,8 @@
         @NonNull
         public Builder setRatType(@Annotation.NetworkType int ratType) {
             if (!CollectionUtils.contains(TelephonyManager.getAllNetworkTypes(), ratType)
-                    && ratType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                    && ratType != TelephonyManager.NETWORK_TYPE_UNKNOWN
+                    && ratType != NetworkTemplate.NETWORK_TYPE_5G_NSA) {
                 throw new IllegalArgumentException("Invalid ratType " + ratType);
             }
             mRatType = ratType;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
index 58ca21f..735c44d 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
 import static android.net.NetworkStats.IFACE_ALL;
@@ -34,6 +35,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.NetworkStatsHistory.Entry;
 import android.os.Binder;
 import android.service.NetworkStatsCollectionKeyProto;
 import android.service.NetworkStatsCollectionProto;
@@ -71,6 +74,8 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -80,7 +85,7 @@
  *
  * @hide
  */
-// @SystemApi(client = MODULE_LIBRARIES)
+@SystemApi(client = MODULE_LIBRARIES)
 public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer {
     private static final String TAG = NetworkStatsCollection.class.getSimpleName();
     /** File header magic number: "ANET" */
@@ -810,6 +815,71 @@
     }
 
     /**
+     * Get the all historical stats of the collection {@link NetworkStatsCollection}.
+     *
+     * @return All {@link NetworkStatsHistory} in this collection.
+     */
+    @NonNull
+    public Map<Key, NetworkStatsHistory> getEntries() {
+        return new ArrayMap(mStats);
+    }
+
+    /**
+     * Builder class for {@link NetworkStatsCollection}.
+     */
+    public static final class Builder {
+        private final long mBucketDuration;
+        private final ArrayMap<Key, NetworkStatsHistory> mEntries = new ArrayMap<>();
+
+        /**
+         * Creates a new Builder with given bucket duration.
+         *
+         * @param bucketDuration Duration of the buckets of the object, in milliseconds.
+         */
+        public Builder(long bucketDuration) {
+            mBucketDuration = bucketDuration;
+        }
+
+        /**
+         * Add association of the history with the specified key in this map.
+         *
+         * @param key The object used to identify a network, see {@link Key}.
+         * @param history {@link NetworkStatsHistory} instance associated to the given {@link Key}.
+         * @return The builder object.
+         */
+        @NonNull
+        public NetworkStatsCollection.Builder addEntry(@NonNull Key key,
+                @NonNull NetworkStatsHistory history) {
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(history);
+            final List<Entry> historyEntries = history.getEntries();
+
+            final NetworkStatsHistory.Builder historyBuilder =
+                    new NetworkStatsHistory.Builder(mBucketDuration, historyEntries.size());
+            for (Entry entry : historyEntries) {
+                historyBuilder.addEntry(entry);
+            }
+
+            mEntries.put(key, historyBuilder.build());
+            return this;
+        }
+
+        /**
+         * Builds the instance of the {@link NetworkStatsCollection}.
+         *
+         * @return the built instance of {@link NetworkStatsCollection}.
+         */
+        @NonNull
+        public NetworkStatsCollection build() {
+            final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
+            for (int i = 0; i < mEntries.size(); i++) {
+                collection.recordHistory(mEntries.keyAt(i), mEntries.valueAt(i));
+            }
+            return collection;
+        }
+    }
+
+    /**
      * the identifier that associate with the {@link NetworkStatsHistory} object to identify
      * a certain record in the {@link NetworkStatsCollection} object.
      */
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
index cad8075..dba3991 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
@@ -652,7 +652,9 @@
      *
      * @hide
      */
-    public boolean matches(NetworkIdentity ident) {
+    @SystemApi(client = MODULE_LIBRARIES)
+    public boolean matches(@NonNull NetworkIdentity ident) {
+        Objects.requireNonNull(ident);
         if (!matchesMetered(ident)) return false;
         if (!matchesRoaming(ident)) return false;
         if (!matchesDefaultNetwork(ident)) return false;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
index c803a72..c2f0cdf 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -29,6 +31,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.RemoteException;
+import android.util.Log;
 
 import com.android.server.NetworkManagementSocketTagger;
 
@@ -210,10 +213,29 @@
         }
         final NetworkStatsManager statsManager =
                 context.getSystemService(NetworkStatsManager.class);
+        if (statsManager == null) {
+            // TODO: Currently Process.isSupplemental is not working yet, because it depends on
+            //  process to run in a certain UID range, which is not true for now. Change this
+            //  to Log.wtf once Process.isSupplemental is ready.
+            Log.e(TAG, "TrafficStats not initialized, uid=" + Binder.getCallingUid());
+            return;
+        }
         sStatsService = statsManager.getBinder();
     }
 
     /**
+     * Attach the socket tagger implementation to the current process, to
+     * get notified when a socket's {@link FileDescriptor} is assigned to
+     * a thread. See {@link SocketTagger#set(SocketTagger)}.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void attachSocketTagger() {
+        NetworkManagementSocketTagger.install();
+    }
+
+    /**
      * Set active tag to use when accounting {@link Socket} traffic originating
      * from the current thread. Only one active tag per thread is supported.
      * <p>
diff --git a/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl b/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl
new file mode 100644
index 0000000..4e8a5b2
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats;
+
+import android.net.DataUsageRequest;
+
+/**
+ * Interface for NetworkStatsService to notify events to the callers of registerUsageCallback.
+ *
+ * @hide
+ */
+oneway interface IUsageCallback {
+    void onThresholdReached(in DataUsageRequest request);
+    void onCallbackReleased(in DataUsageRequest request);
+}
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
index bb123a3..17f3455 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
@@ -26,10 +26,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.net.INetd;
+import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.NetworkStats;
 import android.net.UnderlyingNetworkInfo;
-import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.SystemClock;
 
@@ -70,7 +70,7 @@
 
     private final boolean mUseBpfStats;
 
-    private final INetd mNetd;
+    private final Context mContext;
 
     /**
      * Guards persistent data access in this class
@@ -158,12 +158,12 @@
         NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces);
     }
 
-    public NetworkStatsFactory(@NonNull INetd netd) {
-        this(new File("/proc/"), true, netd);
+    public NetworkStatsFactory(@NonNull Context ctx) {
+        this(ctx, new File("/proc/"), true);
     }
 
     @VisibleForTesting
-    public NetworkStatsFactory(File procRoot, boolean useBpfStats, @NonNull INetd netd) {
+    public NetworkStatsFactory(@NonNull Context ctx, File procRoot, boolean useBpfStats) {
         mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
         mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
         mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
@@ -172,7 +172,7 @@
             mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
             mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
         }
-        mNetd = netd;
+        mContext = ctx;
     }
 
     public NetworkStats readBpfNetworkStatsDev() throws IOException {
@@ -295,11 +295,12 @@
     }
 
     @GuardedBy("mPersistentDataLock")
-    private void requestSwapActiveStatsMapLocked() throws RemoteException {
-        // Ask netd to do a active map stats swap. When the binder call successfully returns,
+    private void requestSwapActiveStatsMapLocked() {
+        // Do a active map stats swap. When the binder call successfully returns,
         // the system server should be able to safely read and clean the inactive map
         // without race problem.
-        mNetd.trafficSwapActiveStatsMap();
+        final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+        cm.swapActiveStatsMap();
     }
 
     /**
@@ -327,7 +328,7 @@
                 if (mUseBpfStats) {
                     try {
                         requestSwapActiveStatsMapLocked();
-                    } catch (RemoteException e) {
+                    } catch (RuntimeException e) {
                         throw new IOException(e);
                     }
                     // Stats are always read from the inactive map, so they must be read after the
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
index b57a4f9..1953624 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
@@ -26,13 +26,12 @@
 import android.net.NetworkStatsCollection;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
-import android.os.Bundle;
+import android.net.netstats.IUsageCallback;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Messenger;
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArrayMap;
@@ -75,10 +74,10 @@
      *
      * @return the normalized request wrapped within {@link RequestInfo}.
      */
-    public DataUsageRequest register(DataUsageRequest inputRequest, Messenger messenger,
-                IBinder binder, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
-        DataUsageRequest request = buildRequest(inputRequest);
-        RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid,
+    public DataUsageRequest register(DataUsageRequest inputRequest, IUsageCallback callback,
+            int callingUid, @NetworkStatsAccess.Level int accessLevel) {
+        DataUsageRequest request = buildRequest(inputRequest, callingUid);
+        RequestInfo requestInfo = buildRequestInfo(request, callback, callingUid,
                 accessLevel);
 
         if (LOGV) Log.v(TAG, "Registering observer for " + request);
@@ -195,10 +194,12 @@
         }
     }
 
-    private DataUsageRequest buildRequest(DataUsageRequest request) {
-        // Cap the minimum threshold to a safe default to avoid too many callbacks
-        long thresholdInBytes = Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes);
-        if (thresholdInBytes < request.thresholdInBytes) {
+    private DataUsageRequest buildRequest(DataUsageRequest request, int callingUid) {
+        // For non-system uid, cap the minimum threshold to a safe default to avoid too
+        // many callbacks.
+        long thresholdInBytes = (callingUid == Process.SYSTEM_UID ? request.thresholdInBytes
+                : Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes));
+        if (thresholdInBytes > request.thresholdInBytes) {
             Log.w(TAG, "Threshold was too low for " + request
                     + ". Overriding to a safer default of " + thresholdInBytes + " bytes");
         }
@@ -206,11 +207,10 @@
                 request.template, thresholdInBytes);
     }
 
-    private RequestInfo buildRequestInfo(DataUsageRequest request,
-                Messenger messenger, IBinder binder, int callingUid,
-                @NetworkStatsAccess.Level int accessLevel) {
+    private RequestInfo buildRequestInfo(DataUsageRequest request, IUsageCallback callback,
+            int callingUid, @NetworkStatsAccess.Level int accessLevel) {
         if (accessLevel <= NetworkStatsAccess.Level.USER) {
-            return new UserUsageRequestInfo(this, request, messenger, binder, callingUid,
+            return new UserUsageRequestInfo(this, request, callback, callingUid,
                     accessLevel);
         } else {
             // Safety check in case a new access level is added and we forgot to update this
@@ -218,7 +218,7 @@
                 throw new IllegalArgumentException(
                         "accessLevel " + accessLevel + " is less than DEVICESUMMARY.");
             }
-            return new NetworkUsageRequestInfo(this, request, messenger, binder, callingUid,
+            return new NetworkUsageRequestInfo(this, request, callback, callingUid,
                     accessLevel);
         }
     }
@@ -230,25 +230,23 @@
     private abstract static class RequestInfo implements IBinder.DeathRecipient {
         private final NetworkStatsObservers mStatsObserver;
         protected final DataUsageRequest mRequest;
-        private final Messenger mMessenger;
-        private final IBinder mBinder;
+        private final IUsageCallback mCallback;
         protected final int mCallingUid;
         protected final @NetworkStatsAccess.Level int mAccessLevel;
         protected NetworkStatsRecorder mRecorder;
         protected NetworkStatsCollection mCollection;
 
         RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
-                    Messenger messenger, IBinder binder, int callingUid,
+                IUsageCallback callback, int callingUid,
                     @NetworkStatsAccess.Level int accessLevel) {
             mStatsObserver = statsObserver;
             mRequest = request;
-            mMessenger = messenger;
-            mBinder = binder;
+            mCallback = callback;
             mCallingUid = callingUid;
             mAccessLevel = accessLevel;
 
             try {
-                mBinder.linkToDeath(this, 0);
+                mCallback.asBinder().linkToDeath(this, 0);
             } catch (RemoteException e) {
                 binderDied();
             }
@@ -257,7 +255,7 @@
         @Override
         public void binderDied() {
             if (LOGV) {
-                Log.v(TAG, "RequestInfo binderDied(" + mRequest + ", " + mBinder + ")");
+                Log.v(TAG, "RequestInfo binderDied(" + mRequest + ", " + mCallback + ")");
             }
             mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
             callCallback(NetworkStatsManager.CALLBACK_RELEASED);
@@ -270,9 +268,7 @@
         }
 
         private void unlinkDeathRecipient() {
-            if (mBinder != null) {
-                mBinder.unlinkToDeath(this, 0);
-            }
+            mCallback.asBinder().unlinkToDeath(this, 0);
         }
 
         /**
@@ -294,17 +290,19 @@
         }
 
         private void callCallback(int callbackType) {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(DataUsageRequest.PARCELABLE_KEY, mRequest);
-            Message msg = Message.obtain();
-            msg.what = callbackType;
-            msg.setData(bundle);
             try {
                 if (LOGV) {
                     Log.v(TAG, "sending notification " + callbackTypeToName(callbackType)
                             + " for " + mRequest);
                 }
-                mMessenger.send(msg);
+                switch (callbackType) {
+                    case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
+                        mCallback.onThresholdReached(mRequest);
+                        break;
+                    case NetworkStatsManager.CALLBACK_RELEASED:
+                        mCallback.onCallbackReleased(mRequest);
+                        break;
+                }
             } catch (RemoteException e) {
                 // May occur naturally in the race of binder death.
                 Log.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
@@ -334,9 +332,9 @@
 
     private static class NetworkUsageRequestInfo extends RequestInfo {
         NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
-                    Messenger messenger, IBinder binder, int callingUid,
+                IUsageCallback callback, int callingUid,
                     @NetworkStatsAccess.Level int accessLevel) {
-            super(statsObserver, request, messenger, binder, callingUid, accessLevel);
+            super(statsObserver, request, callback, callingUid, accessLevel);
         }
 
         @Override
@@ -376,9 +374,9 @@
 
     private static class UserUsageRequestInfo extends RequestInfo {
         UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
-                    Messenger messenger, IBinder binder, int callingUid,
+                    IUsageCallback callback, int callingUid,
                     @NetworkStatsAccess.Level int accessLevel) {
-            super(statsObserver, request, messenger, binder, callingUid, accessLevel);
+            super(statsObserver, request, callback, callingUid, accessLevel);
         }
 
         @Override
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 4c78dcb..243d621 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -99,6 +99,7 @@
 import android.net.TrafficStats;
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
+import android.net.netstats.IUsageCallback;
 import android.net.netstats.provider.INetworkStatsProvider;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.net.netstats.provider.NetworkStatsProvider;
@@ -110,7 +111,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Messenger;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -422,7 +422,7 @@
         final NetworkStatsService service = new NetworkStatsService(context,
                 INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
                 alarmManager, wakeLock, getDefaultClock(),
-                new DefaultNetworkStatsSettings(), new NetworkStatsFactory(netd),
+                new DefaultNetworkStatsSettings(), new NetworkStatsFactory(context),
                 new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(),
                 new Dependencies());
 
@@ -1000,8 +1000,17 @@
         }
 
         // TODO: switch to data layer stats once kernel exports
-        // for now, read network layer stats and flatten across all ifaces
-        final NetworkStats networkLayer = readNetworkStatsUidDetail(uid, INTERFACES_ALL, TAG_ALL);
+        // for now, read network layer stats and flatten across all ifaces.
+        // This function is used to query NeworkStats for calle's uid. The only caller method
+        // TrafficStats#getDataLayerSnapshotForUid alrady claim no special permission to query
+        // its own NetworkStats.
+        final long ident = Binder.clearCallingIdentity();
+        final NetworkStats networkLayer;
+        try {
+            networkLayer = readNetworkStatsUidDetail(uid, INTERFACES_ALL, TAG_ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
 
         // splice in operation counts
         networkLayer.spliceOperationsFrom(mUidOperations);
@@ -1148,21 +1157,20 @@
     }
 
     @Override
-    public DataUsageRequest registerUsageCallback(String callingPackage,
-                DataUsageRequest request, Messenger messenger, IBinder binder) {
+    public DataUsageRequest registerUsageCallback(@NonNull String callingPackage,
+                @NonNull DataUsageRequest request, @NonNull IUsageCallback callback) {
         Objects.requireNonNull(callingPackage, "calling package is null");
         Objects.requireNonNull(request, "DataUsageRequest is null");
         Objects.requireNonNull(request.template, "NetworkTemplate is null");
-        Objects.requireNonNull(messenger, "messenger is null");
-        Objects.requireNonNull(binder, "binder is null");
+        Objects.requireNonNull(callback, "callback is null");
 
         int callingUid = Binder.getCallingUid();
         @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(callingPackage);
         DataUsageRequest normalizedRequest;
         final long token = Binder.clearCallingIdentity();
         try {
-            normalizedRequest = mStatsObservers.register(request, messenger, binder,
-                    callingUid, accessLevel);
+            normalizedRequest = mStatsObservers.register(
+                    request, callback, callingUid, accessLevel);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/packages/DynamicSystemInstallationService/res/values-gl/strings.xml b/packages/DynamicSystemInstallationService/res/values-gl/strings.xml
index 58a80a7..7ead44b 100644
--- a/packages/DynamicSystemInstallationService/res/values-gl/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values-gl/strings.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="keyguard_description" msgid="8582605799129954556">"Pon o teu contrasinal e vai a Dynamic System Updates"</string>
+    <string name="keyguard_description" msgid="8582605799129954556">"Pon o teu contrasinal e vai a Actualizacións dinámicas do sistema"</string>
     <string name="notification_install_completed" msgid="6252047868415172643">"O sistema dinámico está listo. Para utilizalo, reinicia o dispositivo."</string>
     <string name="notification_install_inprogress" msgid="7383334330065065017">"Instalación en curso"</string>
     <string name="notification_install_failed" msgid="4066039210317521404">"Produciuse un erro durante a instalación"</string>
diff --git a/packages/PrintSpooler/res/values-te/strings.xml b/packages/PrintSpooler/res/values-te/strings.xml
index a1ed2ca..038029d 100644
--- a/packages/PrintSpooler/res/values-te/strings.xml
+++ b/packages/PrintSpooler/res/values-te/strings.xml
@@ -88,7 +88,7 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ప్రింటర్‌కు కనెక్షన్ లేదు"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"తెలియదు"</string>
     <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g>ని ఉపయోగించాలా?"</string>
-    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"మీ పత్రం ప్రింటర్‌కు వెళ్లే మార్గంలో ఒకటి లేదా అంతకంటే ఎక్కువ సర్వర్‌ల గుండా వెళ్లవచ్చు."</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"మీ డాక్యుమెంట్‌ ప్రింటర్‌కు వెళ్లే మార్గంలో ఒకటి లేదా అంతకంటే ఎక్కువ సర్వర్‌ల గుండా వెళ్లవచ్చు."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"నలుపు &amp; తెలుపు"</item>
     <item msgid="2762241247228983754">"రంగు"</item>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
index 907863e..e3714db 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
@@ -1,71 +1,25 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
+  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.
+-->
 <androidx.coordinatorlayout.widget.CoordinatorLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/content_parent"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
 
-    <com.google.android.material.appbar.AppBarLayout
-        android:id="@+id/app_bar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:fitsSystemWindows="true"
-        android:outlineAmbientShadowColor="@android:color/transparent"
-        android:outlineSpotShadowColor="@android:color/transparent"
-        android:background="?android:attr/colorPrimary"
-        android:theme="@style/Theme.CollapsingToolbar.Settings">
-
-        <com.google.android.material.appbar.CollapsingToolbarLayout
-            android:id="@+id/collapsing_toolbar"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/settingslib_toolbar_layout_height"
-            android:clipToPadding="false"
-            app:forceApplySystemWindowInsetTop="true"
-            app:extraMultilineHeightEnabled="true"
-            app:contentScrim="@color/settingslib_colorSurfaceHeader"
-            app:maxLines="3"
-            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
-            app:scrimAnimationDuration="50"
-            app:scrimVisibleHeightTrigger="@dimen/settingslib_scrim_visible_height_trigger"
-            app:statusBarScrim="@null"
-            app:titleCollapseMode="fade"
-            app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed"
-            app:expandedTitleTextAppearance="@style/CollapsingToolbarTitle.Expanded"
-            app:expandedTitleMarginStart="@dimen/expanded_title_margin_start"
-            app:expandedTitleMarginEnd="@dimen/expanded_title_margin_end"
-            app:toolbarId="@id/action_bar">
-
-            <Toolbar
-                android:id="@+id/action_bar"
-                android:layout_width="match_parent"
-                android:layout_height="?attr/actionBarSize"
-                android:theme="?android:attr/actionBarTheme"
-                android:transitionName="shared_element_view"
-                app:layout_collapseMode="pin"/>
-
-        </com.google.android.material.appbar.CollapsingToolbarLayout>
-    </com.google.android.material.appbar.AppBarLayout>
-
-    <FrameLayout
-        android:id="@+id/content_frame"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+    <include layout="@layout/collapsing_toolbar_content_layout"/>
 </androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
new file mode 100644
index 0000000..25f0771
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
@@ -0,0 +1,67 @@
+<?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.
+-->
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fitsSystemWindows="true"
+        android:outlineAmbientShadowColor="@android:color/transparent"
+        android:outlineSpotShadowColor="@android:color/transparent"
+        android:background="?android:attr/colorPrimary"
+        android:theme="@style/Theme.CollapsingToolbar.Settings">
+
+        <com.google.android.material.appbar.CollapsingToolbarLayout
+            android:id="@+id/collapsing_toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/settingslib_toolbar_layout_height"
+            android:clipToPadding="false"
+            app:forceApplySystemWindowInsetTop="true"
+            app:extraMultilineHeightEnabled="true"
+            app:contentScrim="@color/settingslib_colorSurfaceHeader"
+            app:maxLines="3"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
+            app:scrimAnimationDuration="50"
+            app:scrimVisibleHeightTrigger="@dimen/settingslib_scrim_visible_height_trigger"
+            app:statusBarScrim="@null"
+            app:titleCollapseMode="fade"
+            app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed"
+            app:expandedTitleTextAppearance="@style/CollapsingToolbarTitle.Expanded"
+            app:expandedTitleMarginStart="@dimen/expanded_title_margin_start"
+            app:expandedTitleMarginEnd="@dimen/expanded_title_margin_end"
+            app:toolbarId="@id/action_bar">
+
+            <Toolbar
+                android:id="@+id/action_bar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                android:theme="?android:attr/actionBarTheme"
+                android:transitionName="shared_element_view"
+                app:layout_collapseMode="pin"/>
+
+        </com.google.android.material.appbar.CollapsingToolbarLayout>
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <FrameLayout
+        android:id="@+id/content_frame"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+</merge>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/support_toolbar.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/support_toolbar.xml
new file mode 100644
index 0000000..e57bff3
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/support_toolbar.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.
+-->
+<androidx.appcompat.widget.Toolbar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/support_action_bar"
+    android:layout_width="match_parent"
+    android:layout_height="?attr/actionBarSize"
+    android:theme="?android:attr/actionBarTheme"
+    android:transitionName="shared_element_view"
+    app:layout_collapseMode="pin" />
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/attrs.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/attrs.xml
new file mode 100644
index 0000000..6ddfb42
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/attrs.xml
@@ -0,0 +1,23 @@
+<?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>
+    <declare-styleable name="CollapsingCoordinatorLayout">
+        <!-- assign a title of collapsing toolbar title. -->
+        <attr name="collapsing_toolbar_title" format="string" />
+        <attr name="content_frame_height_match_parent" format="boolean" />
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
new file mode 100644
index 0000000..eec73ff
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.collapsingtoolbar.widget;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toolbar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+
+import com.android.settingslib.collapsingtoolbar.R;
+
+import com.google.android.material.appbar.AppBarLayout;
+import com.google.android.material.appbar.CollapsingToolbarLayout;
+
+/**
+ * This widget is wrapping the collapsing toolbar and can be directly used by the
+ * {@link AppCompatActivity}.
+ */
+public class CollapsingCoordinatorLayout extends CoordinatorLayout {
+    private static final String TAG = "CollapsingCoordinatorLayout";
+    private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f;
+
+    private CharSequence mToolbarTitle;
+    private boolean mIsMatchParentHeight;
+    private CollapsingToolbarLayout mCollapsingToolbarLayout;
+    private AppBarLayout mAppBarLayout;
+
+    public CollapsingCoordinatorLayout(@NonNull Context context) {
+        this(context, /* attrs= */ null);
+    }
+
+    public CollapsingCoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, /* defStyleAttr= */ 0);
+    }
+
+    public CollapsingCoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mIsMatchParentHeight = false;
+        if (attrs != null) {
+            final TypedArray a = context.obtainStyledAttributes(attrs,
+                    R.styleable.CollapsingCoordinatorLayout);
+            mToolbarTitle = a.getText(
+                    R.styleable.CollapsingCoordinatorLayout_collapsing_toolbar_title);
+            mIsMatchParentHeight = a.getBoolean(
+                    R.styleable.CollapsingCoordinatorLayout_content_frame_height_match_parent,
+                    false);
+            a.recycle();
+        }
+        init();
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (child.getId() == R.id.content_frame && mIsMatchParentHeight) {
+            // User want to change the height of content_frame view as match_parent.
+            params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+        }
+
+        final ViewGroup contentView = findViewById(R.id.content_frame);
+        if (contentView != null && isContentFrameChild(child.getId())) {
+            contentView.addView(child, index, params);
+        } else {
+            super.addView(child, index, params);
+        }
+    }
+
+    private boolean isContentFrameChild(int id) {
+        if (id == R.id.app_bar || id == R.id.content_frame) {
+            return false;
+        }
+        return true;
+    }
+
+    private void init() {
+        inflate(getContext(), R.layout.collapsing_toolbar_content_layout, this);
+        mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
+        mAppBarLayout = findViewById(R.id.app_bar);
+        if (mCollapsingToolbarLayout != null) {
+            mCollapsingToolbarLayout.setLineSpacingMultiplier(TOOLBAR_LINE_SPACING_MULTIPLIER);
+            if (!TextUtils.isEmpty(mToolbarTitle)) {
+                mCollapsingToolbarLayout.setTitle(mToolbarTitle);
+            }
+        }
+        disableCollapsingToolbarLayoutScrollingBehavior();
+    }
+
+    /**
+     * Initialize some attributes of {@link ActionBar}.
+     *
+     * @param activity The host activity using the CollapsingCoordinatorLayout.
+     */
+    public void initSettingsStyleToolBar(Activity activity) {
+        if (activity == null) {
+            Log.w(TAG, "initSettingsStyleToolBar: activity is null");
+            return;
+        }
+
+        if (activity instanceof AppCompatActivity) {
+            initSupportToolbar((AppCompatActivity) activity);
+            return;
+        }
+
+        final Toolbar toolbar = findViewById(R.id.action_bar);
+        activity.setActionBar(toolbar);
+
+        // Enable title and home button by default
+        final ActionBar actionBar = activity.getActionBar();
+        if (actionBar != null) {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            actionBar.setHomeButtonEnabled(true);
+            actionBar.setDisplayShowTitleEnabled(true);
+        }
+    }
+
+    /**
+     * Initialize some attributes of {@link ActionBar} and assign the title of collapsing toolbar.
+     *
+     * @param activity The host activity using the CollapsingCoordinatorLayout.
+     * @param title    The new title of collapsing toolbar.
+     */
+    public void initSettingsStyleToolBar(Activity activity, CharSequence title) {
+        if (activity == null) {
+            Log.w(TAG, "initSettingsStyleToolBar: activity is null");
+            return;
+        }
+        initSettingsStyleToolBar(activity);
+        if (!TextUtils.isEmpty(title) && mCollapsingToolbarLayout != null) {
+            mToolbarTitle = title;
+            mCollapsingToolbarLayout.setTitle(mToolbarTitle);
+        }
+    }
+
+    /**
+     * Returns an instance of collapsing toolbar.
+     */
+    public CollapsingToolbarLayout getCollapsingToolbarLayout() {
+        return mCollapsingToolbarLayout;
+    }
+
+    /**
+     * Return an instance of app bar.
+     */
+    public AppBarLayout getAppBarLayout() {
+        return mAppBarLayout;
+    }
+
+    private void disableCollapsingToolbarLayoutScrollingBehavior() {
+        if (mAppBarLayout == null) {
+            return;
+        }
+        final CoordinatorLayout.LayoutParams params =
+                (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
+        final AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
+        behavior.setDragCallback(
+                new AppBarLayout.Behavior.DragCallback() {
+                    @Override
+                    public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
+                        return false;
+                    }
+                });
+        params.setBehavior(behavior);
+    }
+
+    // This API is for supportActionBar of {@link AppCompatActivity}
+    private void initSupportToolbar(AppCompatActivity appCompatActivity) {
+        if (mCollapsingToolbarLayout == null) {
+            return;
+        }
+
+        mCollapsingToolbarLayout.removeAllViews();
+        inflate(getContext(), R.layout.support_toolbar, mCollapsingToolbarLayout);
+        final androidx.appcompat.widget.Toolbar supportToolbar =
+                mCollapsingToolbarLayout.findViewById(R.id.support_action_bar);
+
+        appCompatActivity.setSupportActionBar(supportToolbar);
+
+        // Enable title and home button by default
+        final androidx.appcompat.app.ActionBar actionBar = appCompatActivity.getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            actionBar.setHomeButtonEnabled(true);
+            actionBar.setDisplayShowTitleEnabled(true);
+        }
+    }
+}
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 13a5caf..7f5e8c4 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nie geregistreer nie"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Onbeskikbaar"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC word ewekansig gemaak"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d toestelle is gekoppel</item>
-      <item quantity="one">%1$d toestel is gekoppel</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 toestelle is gekoppel}=1{1 toestel is gekoppel}other{# toestelle is gekoppel}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Meer tyd."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Minder tyd."</string>
     <string name="cancel" msgid="5665114069455378395">"Kanselleer"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 7ad455a..0810032 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"አልተመዘገበም"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"አይገኝም"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"ማክ በዘፈቀደ ይሰራል"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d መሣሪያዎች ተገናኝተዋል</item>
-      <item quantity="other">%1$d መሣሪያዎች ተገናኝተዋል</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ተጨማሪ ጊዜ።"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ያነሰ ጊዜ።"</string>
     <string name="cancel" msgid="5665114069455378395">"ይቅር"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index a4646f1..6ebf521 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -526,14 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"غير مُسجَّل"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"غير متاح"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"‏يتم اختيار عنوان MAC بشكل انتقائي."</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="zero">‏عدد الأجهزة المتصلة ‎%1$d</item>
-      <item quantity="two">‏عدد الأجهزة المتصلة ‎%1$d</item>
-      <item quantity="few">‏عدد الأجهزة المتصلة ‎%1$d</item>
-      <item quantity="many">‏عدد الأجهزة المتصلة ‎%1$d</item>
-      <item quantity="other">‏عدد الأجهزة المتصلة ‎%1$d</item>
-      <item quantity="one">‏عدد الأجهزة المتصلة ‎%1$d</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"وقت أكثر."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"وقت أقل."</string>
     <string name="cancel" msgid="5665114069455378395">"إلغاء"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index c304921..a5c9324 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"পঞ্জীকৃত নহয়"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"উপলব্ধ নহয়"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ক্ৰমানুসৰি ছেট কৰা হোৱা নাই"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$dটা ডিভাইচ সংযোগ হ’ল</item>
-      <item quantity="other">%1$dটা ডিভাইচ সংযোগ হ’ল</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"অধিক সময়।"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"কম সময়।"</string>
     <string name="cancel" msgid="5665114069455378395">"বাতিল কৰক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 6e3947e..eafb2cb 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Qeydiyyatsız"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Əlçatmazdır"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ixtiyari olaraq seçildi"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d cihaz qoşuludur</item>
-      <item quantity="one">%1$d cihaz qoşuludur</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Daha çox vaxt."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Daha az vaxt."</string>
     <string name="cancel" msgid="5665114069455378395">"Ləğv edin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index a05dae9..21fcbdc 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -526,11 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nije registrovan"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Nedostupno"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC adresa je nasumično izabrana"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">Povezan je %1$d uređaj</item>
-      <item quantity="few">Povezana su %1$d uređaja</item>
-      <item quantity="other">Povezano je %1$d uređaja</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 uređaja je povezano}=1{1 uređaj je povezan}one{# uređaj je povezan}few{# uređaja su povezana}other{# uređaja je povezano}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Više vremena."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Manje vremena."</string>
     <string name="cancel" msgid="5665114069455378395">"Otkaži"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index e3b0567..8610554 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -526,12 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Не зарэгістраваны"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Адсутнічае"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Выпадковы MAC-адрас"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d прылада падключана</item>
-      <item quantity="few">%1$d прылады падключаны</item>
-      <item quantity="many">%1$d прылад падключана</item>
-      <item quantity="other">%1$d прылады падключана</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Больш часу."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Менш часу."</string>
     <string name="cancel" msgid="5665114069455378395">"Скасаваць"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index a8eaaf0..a2a5411 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Не е регистрирано"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Няма данни"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC адресът е рандомизиран"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d устройства са свързани</item>
-      <item quantity="one">%1$d устройство е свързано</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Повече време."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"По-малко време."</string>
     <string name="cancel" msgid="5665114069455378395">"Отказ"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index c28e927..f7cf7e5 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"রেজিস্টার করা নয়"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"অনুপলভ্য"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC র‍্যান্ডমাইজ করা হয়েছে"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$dটি ডিভাইস কানেক্ট রয়েছে</item>
-      <item quantity="other">%1$dটি ডিভাইস কানেক্ট রয়েছে</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{০টি ডিভাইস কানেক্ট করা হয়েছে}=1{১টি ডিভাইস কানেক্ট করা হয়েছে}one{#টি ডিভাইস কানেক্ট করা হয়েছে}other{#টি ডিভাইস কানেক্ট করা হয়েছে}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"আরও বেশি।"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"আরও কম।"</string>
     <string name="cancel" msgid="5665114069455378395">"বাতিল"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 232c22f..3f70e82 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -526,11 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nije registrirano"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Nije dostupno"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC adresa je nasumično odabrana"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">Povezan je %1$d uređaj</item>
-      <item quantity="few">Povezana su %1$duređaja</item>
-      <item quantity="other">Povezano je %1$d uređaja</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Povezano je 0 uređaja}=1{Povezan je 1 uređaj}one{Povezan je # uređaj}few{Povezana su # uređaja}other{Povezano je # uređaja}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Više vremena."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Manje vremena."</string>
     <string name="cancel" msgid="5665114069455378395">"Otkaži"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index bc393c3..49de565 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Sense registrar"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"No disponible"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"L\'adreça MAC és aleatòria"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d dispositius connectats</item>
-      <item quantity="one">%1$d dispositiu connectat</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Més temps"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menys temps"</string>
     <string name="cancel" msgid="5665114069455378395">"Cancel·la"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index f758365..bc755c5 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -526,12 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Neregistrováno"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Není k dispozici"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Adresa MAC je vybrána náhodně"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="few">Připojena %1$d zařízení</item>
-      <item quantity="many">Připojeno %1$d zařízení</item>
-      <item quantity="other">Připojeno %1$d zařízení</item>
-      <item quantity="one">Připojeno %1$d zařízení</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Delší doba"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kratší doba"</string>
     <string name="cancel" msgid="5665114069455378395">"Zrušit"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 0fd2569..21dc551 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ikke registreret"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Utilgængelig"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-adressen er tilfældig"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d enhed er tilsluttet</item>
-      <item quantity="other">%1$d enheder er tilsluttet</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mere tid."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mindre tid."</string>
     <string name="cancel" msgid="5665114069455378395">"Annuller"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index f75685b..bd6c74e 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nicht registriert"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Nicht verfügbar"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-Adresse wird zufällig festgelegt"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d Geräte verbunden</item>
-      <item quantity="one">%1$d Gerät verbunden</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 Geräte verbunden}=1{1 Gerät verbunden}other{# Geräte verbunden}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mehr Zeit."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Weniger Zeit."</string>
     <string name="cancel" msgid="5665114069455378395">"Abbrechen"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 5115728..2339f9b 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Μη εγγεγραμμένη"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Μη διαθέσιμο"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Η διεύθυνση MAC είναι τυχαία"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d συσκευές συνδέθηκαν</item>
-      <item quantity="one">%1$d συσκευή συνδέθηκε</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 συνδεδεμένες συσκευές}=1{1 συνδεδεμένη συσκευή}other{# συνδεδεμένες συσκευές}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Περισσότερη ώρα."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Λιγότερη ώρα."</string>
     <string name="cancel" msgid="5665114069455378395">"Ακύρωση"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 1481cee..72100ee 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d devices connected</item>
-      <item quantity="one">%1$d device connected</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
     <string name="cancel" msgid="5665114069455378395">"Cancel"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index b3dd58a..41bc981 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d devices connected</item>
-      <item quantity="one">%1$d device connected</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
     <string name="cancel" msgid="5665114069455378395">"Cancel"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 1481cee..72100ee 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d devices connected</item>
-      <item quantity="one">%1$d device connected</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
     <string name="cancel" msgid="5665114069455378395">"Cancel"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 1481cee..72100ee 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d devices connected</item>
-      <item quantity="one">%1$d device connected</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
     <string name="cancel" msgid="5665114069455378395">"Cancel"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 2a6c82c..e88b6b2 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎Not registered‎‏‎‎‏‎"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‏‏‎‏‏‏‎‏‎‎‎‎Unavailable‎‏‎‎‏‎"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‎‏‏‎‏‏‏‎‎‏‏‏‏‏‏‎‏‎MAC is randomized‎‏‎‎‏‎"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‎‏‏‎%1$d devices connected‎‏‎‎‏‎</item>
-      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‎‏‏‎%1$d device connected‎‏‎‎‏‎</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‏‏‏‏‏‎‎0 device connected‎‏‎‎‏‎}=1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‏‏‏‏‏‎‎1 device connected‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‏‏‏‏‏‎‎# devices connected‎‏‎‎‏‎}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎More time.‎‏‎‎‏‎"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‎Less time.‎‏‎‎‏‎"</string>
     <string name="cancel" msgid="5665114069455378395">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‏‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎Cancel‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index ee0e0bc..f3129c9 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Sin registrar"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"No disponible"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"La dirección MAC es aleatoria"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d dispositivos conectados</item>
-      <item quantity="one">%1$d dispositivo conectado</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Se conectaron 0 dispositivos}=1{Se conectó 1 dispositivo}other{Se conectaron # dispositivos}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Más tiempo"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tiempo"</string>
     <string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 1f64bad..26dd243 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"No registrado"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"No disponible"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"La dirección MAC es aleatoria"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d dispositivos conectados</item>
-      <item quantity="one">%1$d dispositivo conectado</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Más tiempo."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tiempo."</string>
     <string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index ed7b9dc..ccb2867 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ei ole registreeritud"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Pole saadaval"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-aadress on juhuslikuks muudetud"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d seadet on ühendatud</item>
-      <item quantity="one">%1$d seade on ühendatud</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Pikem aeg."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Lühem aeg."</string>
     <string name="cancel" msgid="5665114069455378395">"Tühista"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 5222d8a..e302bce 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Erregistratu gabe"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Ez dago erabilgarri"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Ausaz aukeratutako MAC helbidea"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d gailu daude konektatuta</item>
-      <item quantity="one">%1$d gailu dago konektatuta</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 gailu daude konektatuta}=1{1 gailu dago konektatuta}other{# gailu daude konektatuta}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Denbora gehiago."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Denbora gutxiago."</string>
     <string name="cancel" msgid="5665114069455378395">"Utzi"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 1b5f979..bf6ca7d 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ثبت نشده است"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"در دسترس نیست"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"‏ویژگی MAC تصادفی است"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">‏%1$d دستگاه متصل</item>
-      <item quantity="other">‏%1$d دستگاه متصل</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{هیچ دستگاهی متصل نیست}=1{یک دستگاه متصل است}one{# دستگاه متصل است}other{# دستگاه متصل است}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"زمان بیشتر."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"زمان کمتر."</string>
     <string name="cancel" msgid="5665114069455378395">"لغو"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 4103a9f..3909b31 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ei rekisteröity"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Ei käytettävissä"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-osoite satunnaistetaan"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d laitetta yhdistettynä</item>
-      <item quantity="one">%1$d laite yhdistettynä</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Enemmän aikaa"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Vähemmän aikaa"</string>
     <string name="cancel" msgid="5665114069455378395">"Peru"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index ab36e11..dd75461 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Non enregistré"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Non accessible"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Les adresses MAC sont randomisées"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d appareil connecté</item>
-      <item quantity="other">%1$d appareils connectés</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Plus longtemps."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Moins longtemps."</string>
     <string name="cancel" msgid="5665114069455378395">"Annuler"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 35f2fce..0079b45 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Non enregistré"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Non disponible"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"La sélection des adresses MAC est aléatoire"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d appareil connecté</item>
-      <item quantity="other">%1$d appareils connectés</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Plus longtemps."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Moins longtemps."</string>
     <string name="cancel" msgid="5665114069455378395">"Annuler"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 8d23864..4daa939 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Non rexistrado"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Non dispoñible"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"O enderezo MAC é aleatorio"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d dispositivos conectados</item>
-      <item quantity="one">%1$d dispositivo conectado</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Máis tempo."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tempo."</string>
     <string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index ac38a64..f9e1b9f 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"રજિસ્ટર કરેલ નથી"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"અનુપલબ્ધ"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MACને રેન્ડમ કરેલ છે"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d ડિવાઇસ કનેક્ટ કર્યું</item>
-      <item quantity="other">%1$d ડિવાઇસ કનેક્ટ કર્યા</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{કોઈ ડિવાઇસ કનેક્ટેડ નથી}=1{1 ડિવાઇસ કનેક્ટેડ છે}one{# ડિવાઇસ કનેક્ટેડ છે}other{# ડિવાઇસ કનેક્ટેડ છે}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"વધુ સમય."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ઓછો સમય."</string>
     <string name="cancel" msgid="5665114069455378395">"રદ કરો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 5c008f0..8bc4119 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"रजिस्टर नहीं है"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"मौजूद नहीं है"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"एमएसी पता रैंडम पर सेट है"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d डिवाइस जुड़ा है</item>
-      <item quantity="other">%1$d डिवाइस जुड़े हैं</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ज़्यादा समय."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"कम समय."</string>
     <string name="cancel" msgid="5665114069455378395">"रद्द करें"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index d681a1e..ace20cc 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -526,11 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nije registrirano"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Nije dostupno"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC adresa određena je nasumično"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">Povezan je %1$d uređaj</item>
-      <item quantity="few">Povezana su %1$d uređaja</item>
-      <item quantity="other">Povezano je %1$d uređaja</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Povezano je 0 uređaja}=1{Povezan je jedan uređaj}one{Povezan je # uređaj}few{Povezana su # uređaja}other{Povezano je # uređaja}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Više vremena."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Manje vremena."</string>
     <string name="cancel" msgid="5665114069455378395">"Odustani"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 57894a0..a0e4216 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nem regisztrált"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Nem érhető el"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"A MAC-cím generálása véletlenszerű."</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d eszköz csatlakozik</item>
-      <item quantity="one">%1$d eszköz csatlakozik</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Több idő."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kevesebb idő."</string>
     <string name="cancel" msgid="5665114069455378395">"Mégse"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index e6ed2b0..34c674b 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Գրանցված չէ"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Անհասանելի"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC հասցեն պատահականորեն է փոխվում"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">Միացված է %1$d սարք</item>
-      <item quantity="other">Միացված է %1$d սարք</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Թեժ կետին միացված սարքեր չկան}=1{Թեժ կետին 1 սարք է միացված}one{Թեժ կետին # սարք է միացված}other{Թեժ կետին # սարք է միացված}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Ավելացնել ժամանակը:"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Պակասեցնել ժամանակը:"</string>
     <string name="cancel" msgid="5665114069455378395">"Չեղարկել"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 6fbab2b..2748a50 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Tidak terdaftar"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Tidak tersedia"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC diacak"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d perangkat terhubung</item>
-      <item quantity="one">%1$d perangkat terhubung</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Lebih lama."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Lebih cepat."</string>
     <string name="cancel" msgid="5665114069455378395">"Batal"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index fa11dfb..eeaf89b 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ekki skráð"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Ekki tiltækt"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-vistfang er valið af handahófi"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d tæki tengt</item>
-      <item quantity="other">%1$d tæki tengd</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Meiri tími."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Minni tími."</string>
     <string name="cancel" msgid="5665114069455378395">"Hætta við"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 0db0448..e74ac154 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Non registrato"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Non disponibile"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Selezione casuale dell\'indirizzo MAC"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d devices connected</item>
-      <item quantity="other">%1$d dispositivi connessi</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Più tempo."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Meno tempo."</string>
     <string name="cancel" msgid="5665114069455378395">"Annulla"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 7ac5919..5da910b 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -526,12 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"לא רשום"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"לא זמין"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"‏כתובת ה-MAC אקראית"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="two">‏%1$d מכשירים מחוברים</item>
-      <item quantity="many">‏%1$d מכשירים מחוברים</item>
-      <item quantity="other">‏%1$d מכשירים מחוברים</item>
-      <item quantity="one">מכשיר אחד מחובר</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"יותר זמן."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"פחות זמן."</string>
     <string name="cancel" msgid="5665114069455378395">"ביטול"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index d82ca5d..c104b03 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"未登録"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"不明"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC はランダムに設定されます"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d 台のデバイスが接続されています</item>
-      <item quantity="one">%1$d 台のデバイスが接続されています</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{接続されているデバイスはありません}=1{1 台のデバイスが接続されています}other{# 台のデバイスが接続されています}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"長くします。"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"短くします。"</string>
     <string name="cancel" msgid="5665114069455378395">"キャンセル"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 02c1084..6c3e68a4 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"არარეგისტრირებული"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"მიუწვდომელია"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-ის მიმდევრობა არეულია"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">დაკავშირებულია %1$d მოწყობილობა</item>
-      <item quantity="one">დაკავშირებულია %1$d მოწყობილობა</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"მეტი დრო."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ნაკლები დრო."</string>
     <string name="cancel" msgid="5665114069455378395">"გაუქმება"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 6c55b62..2b700b5 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Тіркелмеген"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Қолжетімсіз"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC еркін таңдауға қойылды"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d құрылғы қосылды</item>
-      <item quantity="one">%1$d құрылғы қосылды</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Көбірек уақыт."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Азырақ уақыт."</string>
     <string name="cancel" msgid="5665114069455378395">"Бас тарту"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 58a2c7c..c8af0df 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"មិនបាន​ចុះឈ្មោះ"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"មិន​មាន"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ត្រូវ​បាន​ជ្រើសរើស​ដោយ​ចៃដន្យ"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">ឧបករណ៍ %1$d បានភ្ជាប់</item>
-      <item quantity="one">ឧបករណ៍ %1$d បានភ្ជាប់</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"រយៈពេល​ច្រើន​ជាង។"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"រយៈពេល​តិច​ជាង។"</string>
     <string name="cancel" msgid="5665114069455378395">"បោះ​បង់​"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 91e76e2..eeff165 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ನೋಂದಾಯಿಸಲಾಗಿಲ್ಲ"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ಯಾದೃಚ್ಛಿಕವಾಗಿದೆ"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d ಸಾಧನಗಳನ್ನು ಸಂಪರ್ಕಿಸಲಾಗಿದೆ</item>
-      <item quantity="other">%1$d ಸಾಧನಗಳನ್ನು ಸಂಪರ್ಕಿಸಲಾಗಿದೆ</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 ಸಾಧನವನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ}=1{1 ಸಾಧನವನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ}one{# ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ}other{# ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ಹೆಚ್ಚು ಸಮಯ."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ಕಡಿಮೆ ಸಮಯ."</string>
     <string name="cancel" msgid="5665114069455378395">"ರದ್ದುಮಾಡಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index d470560..15bf7bc 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"등록되지 않음"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"사용할 수 없음"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC가 임의 선택됨"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">기기 %1$d개 연결됨</item>
-      <item quantity="one">기기 %1$d개 연결됨</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"시간 늘리기"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"시간 줄이기"</string>
     <string name="cancel" msgid="5665114069455378395">"취소"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index e38bb51a..86f75eb 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Катталган эмес"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Жеткиликсиз"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC дарегин кокустан тандоо иштетилген"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d түзмөк туташып турат</item>
-      <item quantity="one">%1$d түзмөк туташып турат</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 түзмөк туташып турат}=1{1 түзмөк туташып турат}other{# түзмөк туташып турат}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Көбүрөөк убакыт."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Азыраак убакыт."</string>
     <string name="cancel" msgid="5665114069455378395">"Жок"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index cc02292..6133455 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ບໍ່ໄດ້ລົງທະບຽນ"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"ບໍ່ມີຂໍ້ມູນ"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomized"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">ເຊື່ອມຕໍ່ %1$d ອຸປະກອນແລ້ວ</item>
-      <item quantity="one">ເຊື່ອມຕໍ່ %1$d ອຸປະກອນແລ້ວ</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ເພີ່ມເວລາ."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ຫຼຸດເວລາ."</string>
     <string name="cancel" msgid="5665114069455378395">"ຍົກເລີກ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index fb645a0..d089a8c 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -526,12 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Neužregistruota"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Užimta"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC parinktas atsitiktine tvarka"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">Prijungtas %1$d įrenginys</item>
-      <item quantity="few">Prijungti %1$d įrenginiai</item>
-      <item quantity="many">Prijungta %1$d įrenginio</item>
-      <item quantity="other">Prijungta %1$d įrenginių</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Daugiau laiko."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mažiau laiko."</string>
     <string name="cancel" msgid="5665114069455378395">"Atšaukti"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 694ff0a..a28e8ab 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -526,11 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nav reģistrēts"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Nepieejams"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ir atlasīts nejaušā secībā"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="zero">Pievienotas %1$d ierīces</item>
-      <item quantity="one">Pievienota %1$d ierīce</item>
-      <item quantity="other">Pievienotas %1$d ierīces</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Vairāk laika."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mazāk laika."</string>
     <string name="cancel" msgid="5665114069455378395">"Atcelt"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 9d412fc..d02e341 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Не е регистриран"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Недостапно"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-адресата е рандомизирана"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">Поврзан е %1$d уред</item>
-      <item quantity="other">Поврзани се %1$d уреди</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 поврзани уреди}=1{1 поврзан уред}one{# поврзан уред}other{# поврзани уреди}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Повеќе време."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Помалку време."</string>
     <string name="cancel" msgid="5665114069455378395">"Откажи"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 82df16c0..c952b25 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"രജിസ്‌റ്റർ ചെയ്‌തിട്ടില്ല"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"ലഭ്യമല്ല"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC യാദൃച്ഛികമാക്കിയിരിക്കുന്നു"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d ഉപകരണങ്ങൾ കണക്‌റ്റ് ചെയ്‌തു</item>
-      <item quantity="one">%1$d ഉപകരണം കണക്‌റ്റ് ചെയ്‌തു</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 ഉപകരണം കണക്റ്റ് ചെയ്‌തു}=1{1 ഉപകരണം കണക്റ്റ് ചെയ്‌തു}other{# ഉപകരണങ്ങൾ കണക്‌റ്റ് ചെയ്‌തു}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"കൂടുതൽ സമയം."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"കുറഞ്ഞ സമയം."</string>
     <string name="cancel" msgid="5665114069455378395">"റദ്ദാക്കുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 027b722..dd5946b 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Бүртгээгүй"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Байхгүй"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC хаягийг үүсгэсэн"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d төхөөрөмж холбосон</item>
-      <item quantity="one">%1$d төхөөрөмж холбосон</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 төхөөрөмж холбогдсон}=1{1 төхөөрөмж холбогдсон}other{# төхөөрөмж холбогдсон}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Их хугацаа."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Бага хугацаа."</string>
     <string name="cancel" msgid="5665114069455378395">"Цуцлах"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 176d764..fdbd313 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"नोंदवलेले नाही"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"उपलब्ध नाही"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC रँडमाइझ केला आहे"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d डिव्हाइस कनेक्ट केली आहेत</item>
-      <item quantity="one">%1$d डिव्हाइस कनेक्ट केले आहे</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{एक डिव्हाइस कनेक्ट केले}other{# डिव्हाइस कनेक्ट केली}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"जास्त वेळ."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"कमी वेळ."</string>
     <string name="cancel" msgid="5665114069455378395">"रद्द करा"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 6bf15fe..f3be25f 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Tidak didaftarkan"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Tidak tersedia"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC dirawakkan"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d peranti disambungkan</item>
-      <item quantity="one">%1$d peranti disambungkan</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 peranti disambungkan}=1{1 peranti disambungkan}other{# peranti disambungkan}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Lagi masa."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kurang masa."</string>
     <string name="cancel" msgid="5665114069455378395">"Batal"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 13fc6ad..7fbfd60 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"မှတ်ပုံတင်မထားပါ"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"မရရှိနိုင်ပါ။"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ကို ကျပန်းပေးထားသည်"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">စက် %1$d ခု ချိတ်ဆက်ထားသည်</item>
-      <item quantity="one">စက် %1$d ခု ချိတ်ဆက်ထားသည်</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"အချိန်တိုးရန်။"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"အချိန်လျှော့ရန်။"</string>
     <string name="cancel" msgid="5665114069455378395">"မလုပ်တော့"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index c2e449b..5b999ba 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ikke registrert"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Ikke tilgjengelig"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC velges tilfeldig"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d enheter er tilkoblet</item>
-      <item quantity="one">%1$d enhet er tilkoblet</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mer tid."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mindre tid."</string>
     <string name="cancel" msgid="5665114069455378395">"Avbryt"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 85860f0..ea0da04 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"दर्ता नगरिएको"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"अनुपलब्ध"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC क्रमरहित छ"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d डिभाइस कनेक्ट गरिएको छ</item>
-      <item quantity="one">%1$d यन्त्र जडान गरियो</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"थप समय।"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"कम समय।"</string>
     <string name="cancel" msgid="5665114069455378395">"रद्द गर्नुहोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 2ffa559..957386a 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Niet geregistreerd"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Niet beschikbaar"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-adres is willekeurig"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d apparaten verbonden</item>
-      <item quantity="one">%1$d apparaat verbonden</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Meer tijd."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Minder tijd."</string>
     <string name="cancel" msgid="5665114069455378395">"Annuleren"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index e2fee13..5b15222 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ପଞ୍ଜିକୃତ ହୋଇନାହିଁ"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MACର ଠିକଣା ରାଣ୍ଡମ୍ ଭାବେ ସେଟ୍ କରାଯାଇଛି"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$dଟି ଡିଭାଇସ୍‌ ସଂଯୁକ୍ତ ହୋଇଛି</item>
-      <item quantity="one">%1$dଟି ଡିଭାଇସ୍ ସଂଯୁକ୍ତ ହୋଇଛି</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ଅଧିକ ସମୟ।"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"କମ୍ ସମୟ।"</string>
     <string name="cancel" msgid="5665114069455378395">"ବାତିଲ୍"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 11cd481..d044c04 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ਰਜਿਸਟਰ ਨਹੀਂ ਕੀਤੀ ਗਈ"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"ਅਣਉਪਲਬਧ"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ਬੇਤਰਤੀਬਾ ਹੈ"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d ਡੀਵਾਈਸ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ</item>
-      <item quantity="other">%1$d ਡੀਵਾਈਸ ਕਨੈਕਟ ਕੀਤੇ ਗਏ</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ਹੋਰ ਸਮਾਂ।"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ਘੱਟ ਸਮਾਂ।"</string>
     <string name="cancel" msgid="5665114069455378395">"ਰੱਦ ਕਰੋ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 0c670bc..44edc09 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -526,12 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Niezarejestrowane"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Niedostępny"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Adres MAC jest randomizowany"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="few">%1$d urządzenia podłączone</item>
-      <item quantity="many">%1$d urządzeń podłączonych</item>
-      <item quantity="other">%1$d urządzenia podłączonego</item>
-      <item quantity="one">%1$d urządzenie podłączone</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Więcej czasu."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mniej czasu."</string>
     <string name="cancel" msgid="5665114069455378395">"Anuluj"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 10568a1..7d7635c 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Não registrado"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Não disponível"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"O MAC é randomizado"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d dispositivo conectado</item>
-      <item quantity="other">%1$d dispositivos conectados</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 dispositivo conectado}=1{1 dispositivo conectado}one{# dispositivo conectado}other{# dispositivos conectados}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mais tempo."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tempo."</string>
     <string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 5458279..9cccaab 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Não registado"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Indisponível"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"O MAC é aleatório."</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d dispositivo ligado</item>
-      <item quantity="other">%1$d dispositivos ligados</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mais tempo."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tempo."</string>
     <string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 10568a1..7d7635c 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Não registrado"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Não disponível"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"O MAC é randomizado"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d dispositivo conectado</item>
-      <item quantity="other">%1$d dispositivos conectados</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 dispositivo conectado}=1{1 dispositivo conectado}one{# dispositivo conectado}other{# dispositivos conectados}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mais tempo."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tempo."</string>
     <string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 00fc8ff..3429b02 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -526,11 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Neînregistrat"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Indisponibilă"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC este aleatoriu"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="few">%1$d dispozitive conectate</item>
-      <item quantity="other">%1$d de dispozitive conectate</item>
-      <item quantity="one">%1$d dispozitiv conectat</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Niciun dispozitiv conectat}=1{Un dispozitiv conectat}few{# dispozitive conectate}other{# de dispozitive conectate}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mai mult timp."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mai puțin timp."</string>
     <string name="cancel" msgid="5665114069455378395">"Anulați"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 15e3456..2ecbcb7 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -526,12 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Не зарегистрирован"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Недоступно"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Случайный MAC-адрес"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">Подключено %1$d устройство</item>
-      <item quantity="few">Подключено %1$d устройства</item>
-      <item quantity="many">Подключено %1$d устройств</item>
-      <item quantity="other">Подключено %1$d устройства</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Увеличить продолжительность"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Уменьшить продолжительность"</string>
     <string name="cancel" msgid="5665114069455378395">"Отмена"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index d56dd34..8b8a1fb 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ලියාපදිංචි වී නැත"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"ලබාගත නොහැක"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC සසම්භාවී වේ"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">උපාංග %1$dක් සම්බන්ධ කරන ලදී</item>
-      <item quantity="other">උපාංග %1$dක් සම්බන්ධ කරන ලදී</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"වේලාව වැඩියෙන්."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"වේලාව අඩුවෙන්."</string>
     <string name="cancel" msgid="5665114069455378395">"අවලංගු කරන්න"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index e7ea915..b088caf 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -526,12 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Neregistrované"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Nie je k dispozícii"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Adresa MAC je náhodná"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="few">%1$d pripojené zariadenia</item>
-      <item quantity="many">%1$d pripojeného zariadenia</item>
-      <item quantity="other">%1$d pripojených zariadení</item>
-      <item quantity="one">%1$d pripojené zariadenie</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Dlhší čas."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kratší čas."</string>
     <string name="cancel" msgid="5665114069455378395">"Zrušiť"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 95a6fb9..c43addb 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -526,12 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ni registrirana"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Ni na voljo"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Naslov MAC je naključno izbran"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">Povezana je %1$d naprava</item>
-      <item quantity="two">Povezani sta %1$d napravi</item>
-      <item quantity="few">Povezane so %1$d naprave</item>
-      <item quantity="other">Povezanih je %1$d naprav</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 naprav ni povezanih}=1{1 naprava je povezana}one{# naprava je povezana}two{# napravi sta povezani}few{# naprave so povezane}other{# naprav je povezanih}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Daljši čas."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Krajši čas."</string>
     <string name="cancel" msgid="5665114069455378395">"Prekliči"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 8524164..b45e653 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Paregjistruar"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Nuk ofrohet"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Adresa MAC është e rastësishme"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d pajisje të lidhura</item>
-      <item quantity="one">%1$d pajisje e lidhur</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Më shumë kohë."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Më pak kohë."</string>
     <string name="cancel" msgid="5665114069455378395">"Anulo"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index b19198a..2b357b0 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -526,11 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Није регистрован"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Недоступно"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC адреса је насумично изабрана"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">Повезан је %1$d уређај</item>
-      <item quantity="few">Повезана су %1$d уређаја</item>
-      <item quantity="other">Повезано је %1$d уређаја</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 уређаја је повезано}=1{1 уређај је повезан}one{# уређај је повезан}few{# уређаја су повезана}other{# уређаја је повезано}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Више времена."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Мање времена."</string>
     <string name="cancel" msgid="5665114069455378395">"Откажи"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 59cde86..c26c1fe 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ej registrerad"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Inte tillgängligt"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-adressen slumpgenereras"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d enheter är anslutna</item>
-      <item quantity="one">%1$d enhet är ansluten</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Längre tid."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kortare tid."</string>
     <string name="cancel" msgid="5665114069455378395">"Avbryt"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 64c2e07..239bb28 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Haijasajiliwa"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Hamna"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Imechagua anwani ya MAC kwa nasibu"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">Vifaa %1$d vimeunganishwa</item>
-      <item quantity="one">Kifaa %1$d kimeunganishwa</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Hakuna kifaa kimeunganishwa}=1{Kifaa 1 kimeunganishwa}other{Vifaa # vimeunganishwa}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Muda zaidi."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Muda kidogo."</string>
     <string name="cancel" msgid="5665114069455378395">"Ghairi"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index ccef6b9..cf9fb97 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"பதிவு செய்யப்படவில்லை"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"கிடைக்கவில்லை"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC முகவரி சீரற்றுள்ளது"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d சாதனங்கள் இணைக்கப்பட்டன</item>
-      <item quantity="one">%1$d சாதனம் இணைக்கப்பட்டது</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 சாதனம் இணைக்கப்பட்டது}=1{1 சாதனம் இணைக்கப்பட்டது}other{# சாதனங்கள் இணைக்கப்பட்டன}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"நேரத்தை அதிகரிக்கும்."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"நேரத்தைக் குறைக்கும்."</string>
     <string name="cancel" msgid="5665114069455378395">"ரத்துசெய்"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 4013620..2b0e750 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"నమోదు కాలేదు"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"అందుబాటులో లేదు"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC యాదృచ్ఛికంగా ఉంది"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d పరికరాలు కనెక్ట్ చేయబడ్డాయి</item>
-      <item quantity="one">%1$d పరికరం కనెక్ట్ చేయబడింది</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 పరికరం కనెక్ట్ చేయబడింది}=1{1 పరికరం కనెక్ట్ చేయబడింది}other{# పరికరాలు కనెక్ట్ చేయబడ్డాయి}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ఎక్కువ సమయం."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"తక్కువ సమయం."</string>
     <string name="cancel" msgid="5665114069455378395">"రద్దు చేయి"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index c42295f..358431b 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ไม่ได้ลงทะเบียน"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"ไม่มี"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC เป็นแบบสุ่ม"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">มีอุปกรณ์ที่เชื่อมต่อ %1$d เครื่อง</item>
-      <item quantity="one">มีอุปกรณ์ที่เชื่อมต่อ %1$d เครื่อง</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{มีอุปกรณ์ที่เชื่อมต่ออยู่ 0 เครื่อง}=1{มีอุปกรณ์ที่เชื่อมต่ออยู่ 1 เครื่อง}other{มีอุปกรณ์ที่เชื่อมต่ออยู่ # เครื่อง}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"เวลามากขึ้น"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"เวลาน้อยลง"</string>
     <string name="cancel" msgid="5665114069455378395">"ยกเลิก"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index ab36a55..2fcd334 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Hindi nakarehistro"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Hindi available"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Naka-randomize ang MAC"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d device ang nakakonekta</item>
-      <item quantity="other">%1$d na device ang nakakonekta</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Dagdagan ang oras."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Bawasan ang oras."</string>
     <string name="cancel" msgid="5665114069455378395">"Kanselahin"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index ce71496..5e0843b 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Kaydettirilmedi"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Kullanılamıyor"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC rastgele yapıldı"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d cihaz bağlı</item>
-      <item quantity="one">%1$d cihaz bağlı</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Daha uzun süre."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Daha kısa süre."</string>
     <string name="cancel" msgid="5665114069455378395">"İptal"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index b7d370d2..f7ddb38 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -526,12 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Не зареєстровано"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Недоступно"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Для MAC-адреси вибрано функцію довільного вибору"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">Під’єднано %1$d пристрій</item>
-      <item quantity="few">Під’єднано %1$d пристрої</item>
-      <item quantity="many">Під’єднано %1$d пристроїв</item>
-      <item quantity="other">Під’єднано %1$d пристрою</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Більше часу."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Менше часу."</string>
     <string name="cancel" msgid="5665114069455378395">"Скасувати"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index c3f4d56..5268d53 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"رجسٹر نہیں ہے"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"غیر دستیاب"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"‏MAC پتہ رینڈم ہے"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">‏%1$d آلات منسلک ہیں</item>
-      <item quantity="one">‏%1$d آلہ منسلک ہے</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 آلہ منسلک ہے}=1{1 آلہ منسلک ہے}other{# آلات منسلک ہیں}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"زیادہ وقت۔"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"کم وقت۔"</string>
     <string name="cancel" msgid="5665114069455378395">"منسوخ کریں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index aa3d8826..845a966 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -526,10 +526,7 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Registratsiya qilinmagan"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Mavjud emas"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Tasodifiy MAC manzil"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d ta qurilma ulangan</item>
-      <item quantity="one">%1$d ta qurilma ulangan</item>
-    </plurals>
+    <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 ta qurilma ulangan}=1{1 ta qurilma ulangan}other{# ta qurilma ulangan}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Ko‘proq vaqt."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kamroq vaqt."</string>
     <string name="cancel" msgid="5665114069455378395">"Bekor qilish"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 7291d06..7046250 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Chưa được đăng ký"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Không có"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Địa chỉ MAC được gán ngẫu nhiên"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d thiết bị đã kết nối</item>
-      <item quantity="one">%1$d thiết bị đã kết nối</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Nhiều thời gian hơn."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Ít thời gian hơn."</string>
     <string name="cancel" msgid="5665114069455378395">"Hủy"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 3f1b9ae..caa598d 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"未注册"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"无法获取"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC 已随机化"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">已连接 %1$d 个设备</item>
-      <item quantity="one">已连接 %1$d 个设备</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"增加时间。"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"减少时间。"</string>
     <string name="cancel" msgid="5665114069455378395">"取消"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index c91a13f..3b1833a 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"未註冊"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"無法使用"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC 位址已隨機產生"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d 部已連線的裝置</item>
-      <item quantity="one">%1$d 部已連線的裝置</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"增加時間。"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"減少時間。"</string>
     <string name="cancel" msgid="5665114069455378395">"取消"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 8b469ea..70565bf 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"未註冊"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"無法取得"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC 位址已隨機化"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="other">%1$d 個已連線的裝置</item>
-      <item quantity="one">%1$d 個已連線的裝置</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"增加時間。"</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"減少時間。"</string>
     <string name="cancel" msgid="5665114069455378395">"取消"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 9eac12c..8c6496e 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -526,10 +526,8 @@
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Akubhalisiwe"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Ayitholakali"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"I-MAC ayihleliwe"</string>
-    <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
-      <item quantity="one">%1$d amadivayisi axhunyiwe</item>
-      <item quantity="other">%1$d amadivayisi axhunyiwe</item>
-    </plurals>
+    <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+    <skip />
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Isikhathi esiningi."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Isikhathi esincane."</string>
     <string name="cancel" msgid="5665114069455378395">"Khansela"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f9ac01d..e4eab4b 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1306,8 +1306,8 @@
 
     <!--  Do not disturb: Label for button in enable zen dialog that will turn on zen mode. [CHAR LIMIT=30] -->
     <string name="zen_mode_enable_dialog_turn_on">Turn on</string>
-    <!-- Do not disturb: Title for the Do not Disturb dialog to turn on Do not disturb. [CHAR LIMIT=50]-->
-    <string name="zen_mode_settings_turn_on_dialog_title">Turn on Do Not Disturb</string>
+    <!-- Priority mode: Title for the Priority mode dialog to turn on Priority mode. [CHAR LIMIT=50]-->
+    <string name="zen_mode_settings_turn_on_dialog_title" translatable="false">Turn on Priority mode</string>
     <!-- Sound: Summary for the Do not Disturb option when there is no automatic rules turned on. [CHAR LIMIT=NONE]-->
     <string name="zen_mode_settings_summary_off">Never</string>
     <!--[CHAR LIMIT=40] Zen Interruption level: Priority.  -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java b/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java
index f5aa652..b4e84dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java
+++ b/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java
@@ -27,9 +27,7 @@
 import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkTemplate;
-import android.net.wifi.WifiInfo;
 import android.os.AsyncTask;
-import android.text.TextUtils;
 import android.util.RecurrenceRule;
 
 import com.google.android.collect.Lists;
@@ -124,7 +122,7 @@
         if (policy != null) {
             return policy;
         } else {
-            return getPolicy(buildUnquotedNetworkTemplate(template));
+            return getPolicy(template);
         }
     }
 
@@ -207,21 +205,4 @@
         policy.clearSnooze();
         writeAsync();
     }
-
-    /**
-     * Build a revised {@link NetworkTemplate} that matches the same rule, but
-     * with an unquoted {@link NetworkTemplate#getNetworkId()}. Used to work
-     * around legacy bugs.
-     */
-    private static NetworkTemplate buildUnquotedNetworkTemplate(NetworkTemplate template) {
-        if (template == null) return null;
-        final String networkId = template.getNetworkId();
-        final String strippedNetworkId = WifiInfo.sanitizeSsid(networkId);
-        if (!TextUtils.equals(strippedNetworkId, networkId)) {
-            return new NetworkTemplate(
-                    template.getMatchRule(), template.getSubscriberId(), strippedNetworkId);
-        } else {
-            return null;
-        }
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index b56ae38..4939e04 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -88,14 +88,15 @@
     }
 
     /**
-     * Determine whether the device is plugged in (USB, power, or wireless).
+     * Determine whether the device is plugged in (USB, power, wireless or dock).
      *
      * @return true if the device is plugged in.
      */
     public boolean isPluggedIn() {
         return plugged == BatteryManager.BATTERY_PLUGGED_AC
                 || plugged == BatteryManager.BATTERY_PLUGGED_USB
-                || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
+                || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
+                || plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
     }
 
     /**
@@ -118,6 +119,15 @@
     }
 
     /**
+     * Determine whether the device is plugged in dock.
+     *
+     * @return true if the device is plugged in dock
+     */
+    public boolean isPluggedInDock() {
+        return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+    }
+
+    /**
      * Whether or not the device is charged. Note that some devices never return 100% for
      * battery level, so this allows either battery level or status to determine if the
      * battery is charged.
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/OWNERS b/packages/SettingsLib/src/com/android/settingslib/mobile/OWNERS
new file mode 100644
index 0000000..ab9b5dc
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/OWNERS
@@ -0,0 +1,4 @@
+# Default reviewers for this and subdirectories.
+bonianchen@google.com
+
+# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java
index e30aac5..a69c59c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java
@@ -16,12 +16,18 @@
 
 package com.android.settingslib.net;
 
-import android.net.NetworkStatsHistory;
+import android.app.usage.NetworkStats;
+
+import java.util.List;
 
 public class ChartData {
-    public NetworkStatsHistory network;
+    // Collect the data usage history of the network from the given {@link NetworkTemplate}.
+    public List<NetworkStats.Bucket> network;
 
-    public NetworkStatsHistory detail;
-    public NetworkStatsHistory detailDefault;
-    public NetworkStatsHistory detailForeground;
+    // Collect the detail datausage history (foreground + Background).
+    public List<NetworkStats.Bucket> detail;
+    // Collect background datausage history.
+    public List<NetworkStats.Bucket> detailDefault;
+    // Collect foreground datausage history.
+    public List<NetworkStats.Bucket> detailForeground;
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
index 60d22a0..573922f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
@@ -19,20 +19,21 @@
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.SET_FOREGROUND;
 import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
-import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 
+import android.annotation.NonNull;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
 import android.content.AsyncTaskLoader;
 import android.content.Context;
-import android.net.INetworkStatsSession;
-import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.os.Bundle;
 import android.os.RemoteException;
 
 import com.android.settingslib.AppItem;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Framework loader is deprecated, use the compat version instead.
  *
@@ -42,26 +43,20 @@
 public class ChartDataLoader extends AsyncTaskLoader<ChartData> {
     private static final String KEY_TEMPLATE = "template";
     private static final String KEY_APP = "app";
-    private static final String KEY_FIELDS = "fields";
 
-    private final INetworkStatsSession mSession;
+    private final NetworkStatsManager mNetworkStatsManager;
     private final Bundle mArgs;
 
     public static Bundle buildArgs(NetworkTemplate template, AppItem app) {
-        return buildArgs(template, app, FIELD_RX_BYTES | FIELD_TX_BYTES);
-    }
-
-    public static Bundle buildArgs(NetworkTemplate template, AppItem app, int fields) {
         final Bundle args = new Bundle();
         args.putParcelable(KEY_TEMPLATE, template);
         args.putParcelable(KEY_APP, app);
-        args.putInt(KEY_FIELDS, fields);
         return args;
     }
 
-    public ChartDataLoader(Context context, INetworkStatsSession session, Bundle args) {
+    public ChartDataLoader(Context context, NetworkStatsManager statsManager, Bundle args) {
         super(context);
-        mSession = session;
+        mNetworkStatsManager = statsManager;
         mArgs = args;
     }
 
@@ -75,10 +70,9 @@
     public ChartData loadInBackground() {
         final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
         final AppItem app = mArgs.getParcelable(KEY_APP);
-        final int fields = mArgs.getInt(KEY_FIELDS);
 
         try {
-            return loadInBackground(template, app, fields);
+            return loadInBackground(template, app);
         } catch (RemoteException e) {
             // since we can't do much without history, and we don't want to
             // leave with half-baked UI, we bail hard.
@@ -86,10 +80,22 @@
         }
     }
 
-    private ChartData loadInBackground(NetworkTemplate template, AppItem app, int fields)
+    @NonNull
+    private List<NetworkStats.Bucket> convertToBuckets(@NonNull NetworkStats stats) {
+        final List<NetworkStats.Bucket> ret = new ArrayList<>();
+        while (stats.hasNextBucket()) {
+            final NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+            stats.getNextBucket(bucket);
+            ret.add(bucket);
+        }
+        return ret;
+    }
+
+    private ChartData loadInBackground(NetworkTemplate template, AppItem app)
             throws RemoteException {
         final ChartData data = new ChartData();
-        data.network = mSession.getHistoryForNetwork(template, fields);
+        data.network = convertToBuckets(mNetworkStatsManager.queryDetailsForDevice(
+                template, Long.MIN_VALUE, Long.MAX_VALUE));
 
         if (app != null) {
             // load stats for current uid and template
@@ -103,13 +109,13 @@
             }
 
             if (size > 0) {
-                data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration());
-                data.detail.recordEntireHistory(data.detailDefault);
-                data.detail.recordEntireHistory(data.detailForeground);
+                data.detail = new ArrayList<>();
+                data.detail.addAll(data.detailDefault);
+                data.detail.addAll(data.detailForeground);
             } else {
-                data.detailDefault = new NetworkStatsHistory(HOUR_IN_MILLIS);
-                data.detailForeground = new NetworkStatsHistory(HOUR_IN_MILLIS);
-                data.detail = new NetworkStatsHistory(HOUR_IN_MILLIS);
+                data.detailDefault = new ArrayList<>();
+                data.detailForeground = new ArrayList<>();
+                data.detail = new ArrayList<>();
             }
         }
 
@@ -129,17 +135,17 @@
     }
 
     /**
-     * Collect {@link NetworkStatsHistory} for the requested UID, combining with
-     * an existing {@link NetworkStatsHistory} if provided.
+     * Collect {@link List<NetworkStats.Bucket>} for the requested UID, combining with
+     * an existing {@link List<NetworkStats.Bucket>} if provided.
      */
-    private NetworkStatsHistory collectHistoryForUid(
-            NetworkTemplate template, int uid, int set, NetworkStatsHistory existing)
-            throws RemoteException {
-        final NetworkStatsHistory history = mSession.getHistoryForUid(
-                template, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
+    private List<NetworkStats.Bucket> collectHistoryForUid(
+            NetworkTemplate template, int uid, int set, List<NetworkStats.Bucket> existing) {
+        final List<NetworkStats.Bucket> history = convertToBuckets(
+                mNetworkStatsManager.queryDetailsForUidTagState(template,
+                        Long.MIN_VALUE, Long.MAX_VALUE, uid, TAG_NONE, set));
 
         if (existing != null) {
-            existing.recordEntireHistory(history);
+            existing.addAll(history);
             return existing;
         } else {
             return history;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
index cff45c6..30c6645 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.net;
 
-import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.telephony.TelephonyManager.SIM_STATE_READY;
 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
 import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
@@ -49,6 +48,7 @@
     private static final StringBuilder PERIOD_BUILDER = new StringBuilder(50);
     private static final java.util.Formatter PERIOD_FORMATTER = new java.util.Formatter(
             PERIOD_BUILDER, Locale.getDefault());
+    private static final long MB_IN_BYTES = 1024 * 1024;
 
     private final Context mContext;
     private final NetworkPolicyManager mPolicyManager;
@@ -237,10 +237,8 @@
         final int matchRule = networkTemplate.getMatchRule();
         switch (matchRule) {
             case NetworkTemplate.MATCH_MOBILE:
-            case NetworkTemplate.MATCH_MOBILE_WILDCARD:
                 return ConnectivityManager.TYPE_MOBILE;
             case NetworkTemplate.MATCH_WIFI:
-            case NetworkTemplate.MATCH_WIFI_WILDCARD:
                 return  ConnectivityManager.TYPE_WIFI;
             case NetworkTemplate.MATCH_ETHERNET:
                 return  ConnectivityManager.TYPE_ETHERNET;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index afd44d5..386a47a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -63,14 +63,32 @@
     private static NetworkTemplate getNormalizedMobileTemplate(
             TelephonyManager telephonyManager, int subId) {
         final NetworkTemplate mobileTemplate = getMobileTemplateForSubId(telephonyManager, subId);
-        final String[] mergedSubscriberIds = telephonyManager
-                .createForSubscriptionId(subId).getMergedImsisFromGroup();
+        final Set<String> mergedSubscriberIds = Set.of(telephonyManager
+                .createForSubscriptionId(subId).getMergedImsisFromGroup());
         if (ArrayUtils.isEmpty(mergedSubscriberIds)) {
             Log.i(TAG, "mergedSubscriberIds is null.");
             return mobileTemplate;
         }
 
-        return NetworkTemplate.normalize(mobileTemplate, mergedSubscriberIds);
+        return normalizeMobileTemplate(mobileTemplate, mergedSubscriberIds);
+    }
+
+    private static NetworkTemplate normalizeMobileTemplate(
+            NetworkTemplate template, Set<String> mergedSet) {
+        if (template.getSubscriberIds().isEmpty()) return template;
+        // The input template should have at most 1 subscriberId.
+        final String subscriberId = template.getSubscriberIds().iterator().next();
+
+        if (mergedSet.contains(subscriberId)) {
+            // Requested template subscriber is part of the merge group; return
+            // a template that matches all merged subscribers.
+            return new NetworkTemplate.Builder(template.getMatchRule())
+                    .setSubscriberIds(mergedSet)
+                    .setWifiNetworkKeys(template.getWifiNetworkKeys())
+                    .setMeteredness(NetworkStats.METERED_YES).build();
+        }
+
+        return template;
     }
 
     private static NetworkTemplate getMobileTemplateForSubId(
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java
index 43c05b8..504390c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java
@@ -53,8 +53,9 @@
             long totalUsage = 0L;
             long totalForeground = 0L;
             for (int uid : mUids) {
-                final NetworkStats stats = mNetworkStatsManager.queryDetailsForUid(
-                        mNetworkTemplate, start, end, uid);
+                final NetworkStats stats = mNetworkStatsManager.queryDetailsForUidTagState(
+                        mNetworkTemplate, start, end, uid, NetworkStats.Bucket.TAG_NONE,
+                        NetworkStats.Bucket.STATE_ALL);
                 final long usage = getTotalUsage(stats);
                 if (usage > 0L) {
                     totalUsage += usage;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/OWNERS b/packages/SettingsLib/src/com/android/settingslib/net/OWNERS
new file mode 100644
index 0000000..ab9b5dc
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/OWNERS
@@ -0,0 +1,4 @@
+# Default reviewers for this and subdirectories.
+bonianchen@google.com
+
+# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
index 649aeff..8da6032 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
@@ -16,13 +16,12 @@
 
 package com.android.settingslib.net;
 
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
 import android.content.AsyncTaskLoader;
 import android.content.Context;
-import android.net.INetworkStatsSession;
-import android.net.NetworkStats;
 import android.net.NetworkTemplate;
 import android.os.Bundle;
-import android.os.RemoteException;
 
 /**
  * Framework loader is deprecated, use the compat version instead.
@@ -35,7 +34,7 @@
     private static final String KEY_START = "start";
     private static final String KEY_END = "end";
 
-    private final INetworkStatsSession mSession;
+    private final NetworkStatsManager mNetworkStatsManager;
     private final Bundle mArgs;
 
     public static Bundle buildArgs(NetworkTemplate template, long start, long end) {
@@ -46,9 +45,9 @@
         return args;
     }
 
-    public SummaryForAllUidLoader(Context context, INetworkStatsSession session, Bundle args) {
+    public SummaryForAllUidLoader(Context context, Bundle args) {
         super(context);
-        mSession = session;
+        mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class);
         mArgs = args;
     }
 
@@ -63,12 +62,7 @@
         final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
         final long start = mArgs.getLong(KEY_START);
         final long end = mArgs.getLong(KEY_END);
-
-        try {
-            return mSession.getSummaryForAllUid(template, start, end, false);
-        } catch (RemoteException e) {
-            return null;
-        }
+        return mNetworkStatsManager.querySummary(template, start, end);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java b/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java
index 02326ea..623eb33 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java
@@ -17,6 +17,7 @@
 package com.android.settingslib.net;
 
 import android.app.AppGlobals;
+import android.app.usage.NetworkStats;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
@@ -116,13 +117,13 @@
                 detail.label = res.getString(R.string.process_kernel_label);
                 detail.icon = pm.getDefaultActivityIcon();
                 return detail;
-            case TrafficStats.UID_REMOVED:
+            case NetworkStats.Bucket.UID_REMOVED:
                 detail.label = res.getString(UserManager.supportsMultipleUsers()
                         ? R.string.data_usage_uninstalled_apps_users
                         : R.string.data_usage_uninstalled_apps);
                 detail.icon = pm.getDefaultActivityIcon();
                 return detail;
-            case TrafficStats.UID_TETHERING:
+            case NetworkStats.Bucket.UID_TETHERING:
                 final TetheringManager tm = mContext.getSystemService(TetheringManager.class);
                 detail.label = res.getString(Utils.getTetheringLabel(tm));
                 detail.icon = pm.getDefaultActivityIcon();
diff --git a/packages/SettingsLib/tests/robotests/res/layout/collapsing_test_layout.xml b/packages/SettingsLib/tests/robotests/res/layout/collapsing_test_layout.xml
new file mode 100644
index 0000000..61b9b3b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/res/layout/collapsing_test_layout.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.settingslib.collapsingtoolbar.widget.CollapsingCoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:id="@+id/id_collapsing_test"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/text_hello_world"
+        android:text="Hello World!"/>
+
+</com.android.settingslib.collapsingtoolbar.widget.CollapsingCoordinatorLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
new file mode 100644
index 0000000..06343f5
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.collapsingtoolbar.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link CollapsingCoordinatorLayout}. */
+@RunWith(RobolectricTestRunner.class)
+public class CollapsingCoordinatorLayoutTest {
+    private static final String TEXT_HELLO_WORLD = "Hello World!";
+    private static final String TEST_TITLE = "RENO NAKAMURA";
+
+    private TestActivity mActivity;
+
+    @Before
+    public void setUp() {
+        mActivity = Robolectric.buildActivity(TestActivity.class).create().get();
+    }
+
+    @Test
+    public void onCreate_childViewsNumberShouldBeTwo() {
+        CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
+
+        assertThat(layout.getChildCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void onCreate_userAddedChildViewsBeMovedToContentFrame() {
+        CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
+        View contentFrameView = layout.findViewById(R.id.content_frame);
+
+        TextView textView = contentFrameView.findViewById(R.id.text_hello_world);
+
+        assertThat(textView).isNotNull();
+        assertThat(textView.getText().toString()).isEqualTo(TEXT_HELLO_WORLD);
+    }
+
+    @Test
+    public void initSettingsStyleToolBar_assignedTitle() {
+        CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
+
+        layout.initSettingsStyleToolBar(mActivity, TEST_TITLE);
+
+        assertThat(layout.getCollapsingToolbarLayout().getTitle().toString()).isEqualTo(TEST_TITLE);
+    }
+
+    public static class TestActivity extends Activity {
+        private CollapsingCoordinatorLayout mCollapsingCoordinatorLayout;
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setTheme(android.R.style.Theme_Light_NoTitleBar);
+            setContentView(R.layout.collapsing_test_layout);
+            mCollapsingCoordinatorLayout = findViewById(R.id.id_collapsing_test);
+        }
+
+        public CollapsingCoordinatorLayout getCollapsingCoordinatorLayout() {
+            return mCollapsingCoordinatorLayout;
+        }
+    }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 38ff18a..246466e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -313,6 +313,7 @@
         VALIDATORS.put(Global.USER_PREFERRED_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR);
         VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_HEIGHT, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_WIDTH, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.WET_MODE_ON, BOOLEAN_VALIDATOR);
     }
 }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index dec3245..dc7632d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1176,8 +1176,6 @@
                         com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
                 loadBooleanSetting(stmt, Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
                         com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
-                loadStringSetting(stmt, Settings.Secure.SCREENSAVER_COMPONENTS,
-                        com.android.internal.R.string.config_dreamsDefaultComponent);
                 loadStringSetting(stmt, Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
                         com.android.internal.R.string.config_dreamsDefaultComponent);
 
@@ -2362,8 +2360,6 @@
                     com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
             loadBooleanSetting(stmt, Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
                     com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
-            loadStringSetting(stmt, Settings.Secure.SCREENSAVER_COMPONENTS,
-                    com.android.internal.R.string.config_dreamsDefaultComponent);
             loadStringSetting(stmt, Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
                     com.android.internal.R.string.config_dreamsDefaultComponent);
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 720fb6c..52a708d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -656,7 +656,8 @@
                     Settings.Global.Wearable.CLOCKWORK_SYSUI_PACKAGE,
                     Settings.Global.Wearable.CLOCKWORK_SYSUI_MAIN_ACTIVITY,
                     Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED,
-                    Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER);
+                    Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER,
+                    Settings.Global.Wearable.WET_MODE_ON);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
              newHashSet(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 46e24fa..6dc7f56 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -110,6 +110,7 @@
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
     <uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
     <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
+    <uses-permission android:name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES" />
     <!-- ACCESS_BACKGROUND_LOCATION is needed for testing purposes only. -->
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     <!-- ACCESS_MTP is needed for testing purposes only. -->
@@ -335,6 +336,9 @@
     <!-- Permission needed to run keyguard manager tests in CTS -->
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" />
 
+    <!-- Permission needed to add/remove weak escrow token in CTS tests -->
+    <uses-permission android:name="android.permission.MANAGE_WEAK_ESCROW_TOKEN" />
+
     <!-- Permission needed to set/clear/verify lockscreen credentials in CTS tests -->
     <uses-permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS" />
 
@@ -525,6 +529,7 @@
     <!-- Permission needed for CTS test - WifiManagerTest -->
     <uses-permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" />
     <uses-permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" />
+    <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
 
     <!-- Permission required for CTS tests to enable/disable rate limiting toasts. -->
     <uses-permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" />
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 3ae85e7..8323e32 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -71,6 +71,7 @@
 yurilin@google.com
 xuqiu@google.com
 zakcohen@google.com
+jernej@google.com
 
 #Android Auto
 hseog@google.com
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 1601043..8e9e02a 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -37,6 +37,9 @@
     <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's wirelessly charging. [CHAR LIMIT=50]  -->
     <string name="keyguard_plugged_in_wireless"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging wirelessly</string>
 
+    <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's dock charging. [CHAR LIMIT=50]  -->
+    <string name="keyguard_plugged_in_dock"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging Dock</string>
+
     <!-- When the lock screen is showing and the phone plugged in, and the battery
          is not fully charged, say that it's charging.  -->
     <string name="keyguard_plugged_in"><xliff:g id="percentage">%s</xliff:g> • Charging</string>
diff --git a/packages/SystemUI/res/drawable/ic_list.xml b/packages/SystemUI/res/drawable/ic_list.xml
deleted file mode 100644
index 7ef5299..0000000
--- a/packages/SystemUI/res/drawable/ic_list.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2021 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<!-- Remove when Fgs manager tile is removed -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="48dp"
-    android:height="48dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-    <path
-        android:pathData="M2,4h4v4h-4z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M8,4h14v4h-14z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M2,10h4v4h-4z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M8,10h14v4h-14z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M2,16h4v4h-4z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M8,16h14v4h-14z"
-        android:fillColor="#000000"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/screenshot_border.xml b/packages/SystemUI/res/drawable/overlay_border.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_border.xml
rename to packages/SystemUI/res/drawable/overlay_border.xml
diff --git a/packages/SystemUI/res/drawable/screenshot_cancel.xml b/packages/SystemUI/res/drawable/overlay_cancel.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_cancel.xml
rename to packages/SystemUI/res/drawable/overlay_cancel.xml
diff --git a/packages/SystemUI/res/drawable/screenshot_preview_background.xml b/packages/SystemUI/res/drawable/overlay_preview_background.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_preview_background.xml
rename to packages/SystemUI/res/drawable/overlay_preview_background.xml
diff --git a/packages/SystemUI/res/layout/clipboard_content_preview.xml b/packages/SystemUI/res/layout/clipboard_content_preview.xml
deleted file mode 100644
index 7317a94..0000000
--- a/packages/SystemUI/res/layout/clipboard_content_preview.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             xmlns:app="http://schemas.android.com/apk/res-auto"
-             android:id="@+id/preview_border"
-             android:elevation="9dp"
-             android:layout_width="wrap_content"
-             android:layout_height="wrap_content"
-             android:layout_marginStart="@dimen/screenshot_offset_x"
-             android:layout_marginBottom="@dimen/screenshot_offset_y"
-             android:layout_gravity="bottom|start"
-             app:layout_constraintStart_toStartOf="parent"
-             app:layout_constraintBottom_toBottomOf="parent"
-             android:clipToPadding="false"
-             android:clipChildren="false"
-             android:padding="4dp"
-             android:background="@drawable/screenshot_border"
-             >
-    <FrameLayout
-        android:elevation="0dp"
-        android:background="@drawable/screenshot_preview_background"
-        android:clipChildren="true"
-        android:clipToOutline="true"
-        android:clipToPadding="true"
-        android:layout_width="@dimen/screenshot_x_scale"
-        android:layout_height="wrap_content">
-        <TextView android:id="@+id/text_preview"
-                  android:textFontWeight="500"
-                  android:padding="8dp"
-                  android:gravity="center|start"
-                  android:ellipsize="end"
-                  android:autoSizeTextType="uniform"
-                  android:autoSizeMinTextSize="10sp"
-                  android:autoSizeMaxTextSize="200sp"
-                  android:textColor="?android:attr/textColorPrimary"
-                  android:layout_width="@dimen/screenshot_x_scale"
-                  android:layout_height="@dimen/screenshot_x_scale"/>
-        <ImageView
-            android:id="@+id/image_preview"
-            android:scaleType="fitCenter"
-            android:adjustViewBounds="true"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"/>
-    </FrameLayout>
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 76280d8..7ffb3b2 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -17,9 +17,8 @@
 <com.android.systemui.clipboardoverlay.DraggableConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_gravity="bottom"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content">
+    android:layout_height="match_parent">
     <ImageView
         android:id="@+id/actions_container_background"
         android:visibility="gone"
@@ -57,5 +56,81 @@
                      android:id="@+id/edit_chip"/>
         </LinearLayout>
     </HorizontalScrollView>
-    <include layout="@layout/clipboard_content_preview" />
+    <View
+        android:id="@+id/preview_border"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="@dimen/overlay_offset_x"
+        android:layout_marginBottom="@dimen/overlay_offset_y"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:elevation="@dimen/overlay_preview_elevation"
+        app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
+        app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
+        android:background="@drawable/overlay_border"/>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_preview_end"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierMargin="@dimen/overlay_border_width"
+        app:barrierDirection="end"
+        app:constraint_referenced_ids="clipboard_preview"/>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_preview_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="top"
+        app:barrierMargin="@dimen/overlay_border_width_neg"
+        app:constraint_referenced_ids="clipboard_preview"/>
+    <FrameLayout
+        android:id="@+id/clipboard_preview"
+        android:elevation="@dimen/overlay_preview_elevation"
+        android:background="@drawable/overlay_preview_background"
+        android:clipChildren="true"
+        android:clipToOutline="true"
+        android:clipToPadding="true"
+        android:layout_width="@dimen/clipboard_preview_size"
+        android:layout_margin="@dimen/overlay_border_width"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        app:layout_constraintBottom_toBottomOf="@id/preview_border"
+        app:layout_constraintStart_toStartOf="@id/preview_border"
+        app:layout_constraintEnd_toEndOf="@id/preview_border"
+        app:layout_constraintTop_toTopOf="@id/preview_border">
+        <TextView android:id="@+id/text_preview"
+                  android:textFontWeight="500"
+                  android:padding="8dp"
+                  android:gravity="center|start"
+                  android:ellipsize="end"
+                  android:autoSizeTextType="uniform"
+                  android:autoSizeMinTextSize="10sp"
+                  android:autoSizeMaxTextSize="200sp"
+                  android:textColor="?android:attr/textColorPrimary"
+                  android:layout_width="@dimen/clipboard_preview_size"
+                  android:layout_height="@dimen/clipboard_preview_size"/>
+        <ImageView
+            android:id="@+id/image_preview"
+            android:scaleType="fitCenter"
+            android:adjustViewBounds="true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+    </FrameLayout>
+    <FrameLayout
+        android:id="@+id/dismiss_button"
+        android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+        android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+        android:elevation="@dimen/overlay_dismiss_button_elevation"
+        android:visibility="gone"
+        app:layout_constraintStart_toEndOf="@id/clipboard_preview"
+        app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
+        app:layout_constraintTop_toTopOf="@id/clipboard_preview"
+        app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+        android:contentDescription="@string/clipboard_dismiss_description">
+        <ImageView
+            android:id="@+id/dismiss_image"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_margin="@dimen/overlay_dismiss_button_margin"
+            android:src="@drawable/overlay_cancel"/>
+    </FrameLayout>
 </com.android.systemui.clipboardoverlay.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/controls_detail_dialog.xml b/packages/SystemUI/res/layout/controls_detail_dialog.xml
index 28fc863..4d2317c 100644
--- a/packages/SystemUI/res/layout/controls_detail_dialog.xml
+++ b/packages/SystemUI/res/layout/controls_detail_dialog.xml
@@ -15,47 +15,52 @@
      limitations under the License.
 -->
 
-<LinearLayout
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/control_detail_root"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="@android:color/black">
+    android:layout_height="match_parent">
   <LinearLayout
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:layout_marginBottom="4dp">
-    <ImageView
-        android:id="@+id/control_detail_close"
-        android:contentDescription="@string/accessibility_desc_close"
-        android:src="@drawable/ic_arrow_back"
-        android:background="?android:attr/selectableItemBackgroundBorderless"
-        android:tint="@color/control_primary_text"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:padding="12dp" />
-    <Space
-        android:layout_width="0dp"
-        android:layout_weight="1"
-        android:layout_height="1dp" />
-    <ImageView
-        android:id="@+id/control_detail_open_in_app"
-        android:contentDescription="@string/controls_open_app"
-        android:src="@drawable/ic_open_in_new"
-        android:background="?android:attr/selectableItemBackgroundBorderless"
-        android:tint="@color/control_primary_text"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:padding="12dp" />
-  </LinearLayout>
-
-  <FrameLayout
-      android:id="@+id/controls_activity_view"
+      android:id="@+id/control_task_view_container"
       android:layout_width="match_parent"
-      android:layout_height="0dp"
-      android:layout_weight="1"
-      android:orientation="vertical" />
-</LinearLayout>
+      android:layout_height="match_parent"
+      android:layout_gravity="right|top"
+      android:layout_marginRight="@dimen/controls_task_view_right_margin"
+      android:orientation="vertical">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_marginBottom="4dp">
+      <ImageView
+          android:id="@+id/control_detail_close"
+          android:contentDescription="@string/accessibility_desc_close"
+          android:src="@drawable/ic_close"
+          android:background="?android:attr/selectableItemBackgroundBorderless"
+          android:tint="@color/control_primary_text"
+          android:layout_width="48dp"
+          android:layout_height="48dp"
+          android:padding="12dp" />
+      <Space
+          android:layout_width="0dp"
+          android:layout_weight="1"
+          android:layout_height="1dp" />
+      <ImageView
+          android:id="@+id/control_detail_open_in_app"
+          android:contentDescription="@string/controls_open_app"
+          android:src="@drawable/ic_open_in_new"
+          android:background="?android:attr/selectableItemBackgroundBorderless"
+          android:tint="@color/control_primary_text"
+          android:layout_width="48dp"
+          android:layout_height="48dp"
+          android:padding="12dp" />
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/controls_activity_view"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+  </LinearLayout>
+</FrameLayout>
 
diff --git a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
index f898ef6..5135947 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
@@ -18,7 +18,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/dream_overlay_complications_layer"
-    android:padding="20dp"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
     <TextClock
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index c6b502e..4929f50 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -28,8 +28,6 @@
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent" />
 
-    <include layout="@layout/dream_overlay_complications_layer" />
-
     <com.android.systemui.dreams.DreamOverlayStatusBarView
         android:id="@+id/dream_overlay_status_bar"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
index 7c0ee66..227212b 100644
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ b/packages/SystemUI/res/layout/screenshot.xml
@@ -40,7 +40,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:visibility="gone"
-        android:elevation="@dimen/screenshot_preview_elevation"
+        android:elevation="@dimen/overlay_preview_elevation"
         android:src="@android:color/white"/>
     <com.android.systemui.screenshot.ScreenshotSelectorView
         android:id="@+id/screenshot_selector"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index f6e3f1a..8f791c3 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -63,20 +63,20 @@
         android:id="@+id/screenshot_preview_border"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        android:layout_marginStart="@dimen/screenshot_offset_x"
-        android:layout_marginBottom="@dimen/screenshot_offset_y"
-        android:elevation="@dimen/screenshot_preview_elevation"
+        android:layout_marginStart="@dimen/overlay_offset_x"
+        android:layout_marginBottom="@dimen/overlay_offset_y"
+        android:elevation="@dimen/overlay_preview_elevation"
         android:alpha="0"
-        android:background="@drawable/screenshot_border"
+        android:background="@drawable/overlay_border"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="@+id/screenshot_preview_end"
-        app:layout_constraintTop_toTopOf="@+id/screenshot_preview_top"/>
+        app:layout_constraintEnd_toEndOf="@id/screenshot_preview_end"
+        app:layout_constraintTop_toTopOf="@id/screenshot_preview_top"/>
     <androidx.constraintlayout.widget.Barrier
         android:id="@+id/screenshot_preview_end"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        app:barrierMargin="4dp"
+        app:barrierMargin="@dimen/overlay_border_width"
         app:barrierDirection="end"
         app:constraint_referenced_ids="screenshot_preview"/>
     <androidx.constraintlayout.widget.Barrier
@@ -84,30 +84,30 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:barrierDirection="top"
-        app:barrierMargin="-4dp"
+        app:barrierMargin="@dimen/overlay_border_width_neg"
         app:constraint_referenced_ids="screenshot_preview"/>
     <ImageView
         android:id="@+id/screenshot_preview"
         android:visibility="invisible"
         android:layout_width="@dimen/screenshot_x_scale"
-        android:layout_margin="4dp"
+        android:layout_margin="@dimen/overlay_border_width"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
-        android:elevation="@dimen/screenshot_preview_elevation"
+        android:elevation="@dimen/overlay_preview_elevation"
         android:contentDescription="@string/screenshot_edit_description"
         android:scaleType="fitEnd"
-        android:background="@drawable/screenshot_preview_background"
+        android:background="@drawable/overlay_preview_background"
         android:adjustViewBounds="true"
-        app:layout_constraintBottom_toBottomOf="@+id/screenshot_preview_border"
-        app:layout_constraintStart_toStartOf="@+id/screenshot_preview_border"
-        app:layout_constraintEnd_toEndOf="@+id/screenshot_preview_border"
-        app:layout_constraintTop_toTopOf="@+id/screenshot_preview_border">
+        app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
+        app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
+        app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"
+        app:layout_constraintTop_toTopOf="@id/screenshot_preview_border">
     </ImageView>
     <FrameLayout
         android:id="@+id/screenshot_dismiss_button"
-        android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
-        android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"
-        android:elevation="7dp"
+        android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+        android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+        android:elevation="@dimen/overlay_dismiss_button_elevation"
         android:visibility="gone"
         app:layout_constraintStart_toEndOf="@id/screenshot_preview"
         app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
@@ -118,8 +118,8 @@
             android:id="@+id/screenshot_dismiss_image"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:layout_margin="@dimen/screenshot_dismiss_button_margin"
-            android:src="@drawable/screenshot_cancel"/>
+            android:layout_margin="@dimen/overlay_dismiss_button_margin"
+            android:src="@drawable/overlay_cancel"/>
     </FrameLayout>
     <ImageView
         android:id="@+id/screenshot_scrollable_preview"
@@ -129,5 +129,5 @@
         android:visibility="gone"
         app:layout_constraintStart_toStartOf="@id/screenshot_preview"
         app:layout_constraintTop_toTopOf="@id/screenshot_preview"
-        android:elevation="@dimen/screenshot_preview_elevation"/>
+        android:elevation="@dimen/overlay_preview_elevation"/>
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 3cfe056..89d046b 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -25,4 +25,8 @@
     <!-- margin from keyguard status bar to clock. For split shade it should be
          keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
     <dimen name="keyguard_clock_top_margin">8dp</dimen>
+
+    <!-- Limit the TaskView to this percentage of the overall screen width (0.0 - 1.0) -->
+    <item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">0.45</item>
+    <dimen name="controls_task_view_right_margin">8dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 461a598..81e3e04 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -194,6 +194,7 @@
     <color name="control_enabled_cool_foreground">@color/GM2_blue_300</color>
     <color name="control_thumbnail_tint">#33000000</color>
     <color name="control_thumbnail_shadow_color">@*android:color/black</color>
+    <color name="controls_task_view_bg">#CC191C1D</color>
 
     <!-- Docked misalignment message -->
     <color name="misalignment_text_color">#F28B82</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index fc2756e..079f5d0 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -82,7 +82,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,fgsmanager,color_correction
+        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction
     </string>
 
     <!-- The tiles to display in QuickSettings -->
@@ -309,6 +309,7 @@
         <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
         <item>com.android.systemui.ScreenDecorations</item>
         <item>com.android.systemui.biometrics.AuthController</item>
+        <item>com.android.systemui.log.SessionTracker</item>
         <item>com.android.systemui.SliceBroadcastRelayHandler</item>
         <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
         <item>com.android.systemui.theme.ThemeOverlayController</item>
@@ -581,6 +582,9 @@
     <!-- Whether to use the split 2-column notification shade -->
     <bool name="config_use_split_notification_shade">false</bool>
 
+    <!-- Whether notification header should never show section headers. -->
+    <bool name="config_notification_never_show_section_headers">false</bool>
+
     <!-- Default udfps icon. Same path as ic_fingerprint.xml -->
     <string name="config_udfpsIcon" translatable="false">
         M25.5,16.3283C28.47,14.8433 31.9167,14 35.5834,14C39.2501,14 42.6968,14.8433 45.6668,16.3283
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ceaacfc..af7ef53 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -261,11 +261,6 @@
     <!-- The padding on the global screenshot background image -->
     <dimen name="screenshot_x_scale">80dp</dimen>
     <dimen name="screenshot_bg_protection_height">242dp</dimen>
-    <dimen name="screenshot_preview_elevation">4dp</dimen>
-    <dimen name="screenshot_offset_y">8dp</dimen>
-    <dimen name="screenshot_offset_x">16dp</dimen>
-    <dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen>
-    <dimen name="screenshot_dismiss_button_margin">8dp</dimen>
     <dimen name="screenshot_action_container_corner_radius">18dp</dimen>
     <dimen name="screenshot_action_container_padding_vertical">4dp</dimen>
     <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
@@ -289,6 +284,19 @@
     <dimen name="screenshot_crop_handle_thickness">3dp</dimen>
     <dimen name="long_screenshot_action_bar_top_margin">8dp</dimen>
 
+    <!-- Dimensions shared between "overlays" (clipboard and screenshot preview UIs) -->
+    <dimen name="overlay_offset_y">8dp</dimen>
+    <dimen name="overlay_offset_x">16dp</dimen>
+    <dimen name="overlay_preview_elevation">4dp</dimen>
+    <dimen name="overlay_dismiss_button_elevation">7dp</dimen>
+    <dimen name="overlay_dismiss_button_tappable_size">48dp</dimen>
+    <dimen name="overlay_dismiss_button_margin">8dp</dimen>
+    <dimen name="overlay_border_width">4dp</dimen>
+    <!-- need a negative margin for some of the constraints. should be overlay_border_width * -1 -->
+    <dimen name="overlay_border_width_neg">-4dp</dimen>
+
+    <dimen name="clipboard_preview_size">@dimen/screenshot_x_scale</dimen>
+
 
     <!-- The width of the view containing navigation buttons -->
     <dimen name="navigation_key_width">70dp</dimen>
@@ -1044,6 +1052,9 @@
     <dimen name="controls_setup_subtitle">14sp</dimen>
     <dimen name="controls_setup_vertical_padding">52dp</dimen>
     <dimen name="controls_detail_dialog_header_height">52dp</dimen>
+    <!-- Limit the TaskView to this percentage of the overall screen width (0.0 - 1.0) -->
+    <item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">1.0</item>
+    <dimen name="controls_task_view_right_margin">0dp</dimen>
 
     <!-- Home Controls activity view detail panel-->
     <dimen name="controls_activity_view_corner_radius">@*android:dimen/config_bottomDialogCornerRadius</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 41d5735..75ae52c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -429,8 +429,8 @@
     <string name="accessibility_quick_settings_dnd_none_on">total silence</string>
     <!-- Content description of the do not disturb tile in quick settings when on in alarms only (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_quick_settings_dnd_alarms_on">alarms only</string>
-     <!-- Content description of the do not disturb tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_quick_settings_dnd">Do Not Disturb.</string>
+     <!-- Content description of the priority mode tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_quick_settings_dnd" translatable="false">Priority mode.</string>
     <!-- Content description of the bluetooth tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_quick_settings_bluetooth">Bluetooth.</string>
     <!-- Content description of the bluetooth tile in quick settings when on (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -508,8 +508,8 @@
     <string name="ethernet_label">Ethernet</string>
 
     <!-- QuickSettings: Onboarding text that introduces users to long press on an option in order to view the option's menu in Settings [CHAR LIMIT=NONE] -->
-    <!-- QuickSettings: Do not disturb [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_dnd_label">Do Not Disturb</string>
+    <!-- QuickSettings: Priority mode [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_dnd_label" translatable="false">Priority mode</string>
     <!-- QuickSettings: Do not disturb - Priority only [CHAR LIMIT=NONE] -->
     <!-- QuickSettings: Do not disturb - Alarms only [CHAR LIMIT=NONE] -->
     <!-- QuickSettings: Do not disturb - Total silence [CHAR LIMIT=NONE] -->
@@ -796,6 +796,9 @@
     <!-- Indication on the keyguard that is shown when the device is charging slowly. Should match keyguard_plugged_in_charging_slowly [CHAR LIMIT=50]-->
     <string name="keyguard_indication_charging_time_slowly"><xliff:g id="percentage">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
 
+    <!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]-->
+    <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging Dock • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
+
     <!-- Related to user switcher --><skip/>
 
     <!-- Accessibility label for the button that opens the user switcher. -->
@@ -896,8 +899,8 @@
     <!-- Content description for accessibility: Tapping this button will dismiss all gentle notifications [CHAR LIMIT=NONE] -->
     <string name="accessibility_notification_section_header_gentle_clear_all">Clear all silent notifications</string>
 
-    <!-- The text to show in the notifications shade when dnd is suppressing notifications. [CHAR LIMIT=100] -->
-    <string name="dnd_suppressing_shade_text">Notifications paused by Do Not Disturb</string>
+    <!-- The text to show in the notifications shade when Priority mode is suppressing notifications. [CHAR LIMIT=100] -->
+    <string name="dnd_suppressing_shade_text" translatable="false">Notifications paused by Priority mode</string>
 
     <!-- Media projection permission dialog action text. [CHAR LIMIT=60] -->
     <string name="media_projection_action_text">Start now</string>
@@ -1319,8 +1322,8 @@
     <!-- [CHAR LIMIT=150] Notification Importance title: important conversation level summary -->
     <string name="notification_channel_summary_priority_baseline">Shows at the top of conversation notifications and as a profile picture on lock screen</string>
     <string name="notification_channel_summary_priority_bubble">Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble</string>
-    <string name="notification_channel_summary_priority_dnd">Shows at the top of conversation notifications and as a profile picture on lock screen, interrupts Do Not Disturb</string>
-    <string name="notification_channel_summary_priority_all">Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb</string>
+    <string name="notification_channel_summary_priority_dnd" translatable="false">Shows at the top of conversation notifications and as a profile picture on lock screen, interrupts Priority mode</string>
+    <string name="notification_channel_summary_priority_all" translatable="false">Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Priority mode</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: important conversation level -->
     <string name="notification_priority_title">Priority</string>
@@ -1511,8 +1514,8 @@
     <!-- User visible title for the keyboard shortcut that takes the user to the calendar app. -->
     <string name="keyboard_shortcut_group_applications_calendar">Calendar</string>
 
-    <!-- SysUI Tuner: Label for screen about do not disturb settings [CHAR LIMIT=60] -->
-    <string name="volume_and_do_not_disturb">Do Not Disturb</string>
+    <!-- SysUI Tuner: Label for screen about priority mode settings [CHAR LIMIT=60] -->
+    <string name="volume_and_do_not_disturb" translatable="false">Priority mode</string>
 
     <!-- SysUI Tuner: Switch to control whether volume buttons enter/exit do
          not disturb [CHAR LIMIT=60] -->
@@ -1873,17 +1876,17 @@
     <!-- Label for when bluetooth is off in QS detail panel [CHAR LIMIT=NONE] -->
     <string name="bt_is_off">Bluetooth is off</string>
 
-    <!-- Label for when Do not disturb is off in QS detail panel [CHAR LIMIT=NONE] -->
-    <string name="dnd_is_off">Do Not Disturb is off</string>
+    <!-- Label for when Priority mode is off in QS detail panel [CHAR LIMIT=NONE] -->
+    <string name="dnd_is_off" translatable="false">Priority mode is off</string>
 
-    <!-- Prompt for when Do not disturb is on from automatic rule in QS [CHAR LIMIT=NONE] -->
-    <string name="qs_dnd_prompt_auto_rule">Do Not Disturb was turned on by an automatic rule (<xliff:g name="rule">%s</xliff:g>).</string>
+    <!-- Prompt for when Priority mode is on from automatic rule in QS [CHAR LIMIT=NONE] -->
+    <string name="qs_dnd_prompt_auto_rule" translatable="false">Priority mode was turned on by an automatic rule (<xliff:g name="rule">%s</xliff:g>).</string>
 
-    <!-- Prompt for when Do not disturb is on from app in QS [CHAR LIMIT=NONE] -->
-    <string name="qs_dnd_prompt_app">Do Not Disturb was turned on by an app (<xliff:g name="app">%s</xliff:g>).</string>
+    <!-- Prompt for when Priority mode is on from app in QS [CHAR LIMIT=NONE] -->
+    <string name="qs_dnd_prompt_app" translatable="false">Priority mode was turned on by an app (<xliff:g name="app">%s</xliff:g>).</string>
 
-    <!-- Prompt for when Do not disturb is on from automatic rule or app in QS [CHAR LIMIT=NONE] -->
-    <string name="qs_dnd_prompt_auto_rule_app">Do Not Disturb was turned on by an automatic rule or app.</string>
+    <!-- Prompt for when Priority mode is on from automatic rule or app in QS [CHAR LIMIT=NONE] -->
+    <string name="qs_dnd_prompt_auto_rule_app" translatable="false">Priority mode was turned on by an automatic rule or app.</string>
 
     <!-- Title of the "running foreground services" dialog. [CHAR LIMIT=NONE] -->
     <string name="running_foreground_services_title">Apps running in background</string>
@@ -2268,8 +2271,8 @@
     <string name="people_tile_description">See recent messages, missed calls, and status updates</string>
     <!-- Title text displayed for the Conversation widget [CHAR LIMIT=50] -->
     <string name="people_tile_title">Conversation</string>
-    <!-- Text when the Conversation widget when Do Not Disturb is suppressing the notification. [CHAR LIMIT=50] -->
-    <string name="paused_by_dnd">Paused by Do Not Disturb</string>
+    <!-- Text when the Conversation widget when Priority mode is suppressing the notification. [CHAR LIMIT=50] -->
+    <string name="paused_by_dnd" translatable="false">Paused by Priority mode</string>
     <!-- Content description text on the Conversation widget when a person has sent a new text message [CHAR LIMIT=150] -->
     <string name="new_notification_text_content_description"><xliff:g id="name" example="Anna">%1$s</xliff:g> sent a message: <xliff:g id="notification" example="Hey! How is your day going">%2$s</xliff:g></string>
     <!-- Content description text on the Conversation widget when a person has sent a new image message [CHAR LIMIT=150] -->
@@ -2355,13 +2358,20 @@
     <!-- Title for User Switch dialog. [CHAR LIMIT=20] -->
     <string name="qs_user_switch_dialog_title">Select user</string>
 
-    <!-- Title for dialog listing applications currently running in the backing [CHAR LIMIT=NONE]-->
-    <string name="fgs_manager_dialog_title">Apps running in the background</string>
-    <!-- Label of the button to stop the app from running in the background [CHAR LIMIT=12]-->
+    <!-- Label for the entry point to open the dialog which shows currently running applications [CHAR LIMIT=NONE]-->
+    <plurals name="fgs_manager_footer_label">
+        <item quantity="one"><xliff:g id="count" example="1">%s</xliff:g> active app</item>
+        <item quantity="other"><xliff:g id="count" example="2">%s</xliff:g> active apps</item>
+    </plurals>
+    <!-- Title for dialog listing applications currently running [CHAR LIMIT=NONE]-->
+    <string name="fgs_manager_dialog_title">Active apps</string>
+    <!-- Label of the button to stop an app from running [CHAR LIMIT=12]-->
     <string name="fgs_manager_app_item_stop_button_label">Stop</string>
 
     <!-- Label for button to copy edited text back to the clipboard [CHAR LIMIT=20] -->
     <string name="clipboard_edit_text_copy">Copy</string>
     <!-- Text informing user that content has been copied to the system clipboard [CHAR LIMIT=NONE] -->
     <string name="clipboard_overlay_text_copied">Copied</string>
+    <!-- Label for button to dismiss clipboard overlay [CHAR LIMIT=NONE] -->
+    <string name="clipboard_dismiss_description">Dismiss copy UI</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ff5699b..ac98739 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -746,7 +746,7 @@
      <style name="Theme.SystemUI.Dialog.Control.DetailPanel" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
       <item name="android:windowFullscreen">false</item>
       <item name="android:windowIsFloating">false</item>
-      <item name="android:windowBackground">@android:color/black</item>
+      <item name="android:windowBackground">@color/controls_task_view_bg</item>
       <item name="android:backgroundDimEnabled">false</item>
       <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
     </style>
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index a610caa..e273416 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -308,14 +308,4 @@
         <item>Off</item>
         <item>On</item>
     </string-array>
-
-    <!-- State names for fgsmanager tile: unavailable, off, on.
-         This subtitle is shown when the tile is in that particular state but does not set its own
-         subtitle, so some of these may never appear on screen. They should still be translated as
-         if they could appear.[CHAR LIMIT=32] -->
-    <string-array name="tile_states_fgsmanager">
-        <item>Unavailable</item>
-        <item>Off</item>
-        <item>On</item>
-    </string-array>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index f2f382d..ace7938 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -277,7 +277,6 @@
                 mLeashMap.put(mOpeningLeashes.get(i), target.leash);
                 t.reparent(target.leash, mInfo.getRootLeash());
                 t.setLayer(target.leash, layer);
-                t.hide(target.leash);
                 targets[i] = target;
             }
             t.apply();
@@ -332,13 +331,6 @@
                 }
             } else {
                 wct = null;
-                if (mOpeningLeashes != null) {
-                    // TODO: the launcher animation should handle this
-                    for (int i = 0; i < mOpeningLeashes.size(); ++i) {
-                        t.show(mOpeningLeashes.get(i));
-                        t.setAlpha(mOpeningLeashes.get(i), 1.f);
-                    }
-                }
                 if (mPipTask != null && mPipTransaction != null) {
                     t.show(mInfo.getChange(mPipTask).getLeash());
                     PictureInPictureSurfaceTransaction.apply(mPipTransaction,
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java b/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
index 710980a..e6d5719 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
@@ -58,7 +58,7 @@
 
     LayoutInflater mInflater;
 
-    private MetricsLogger mMetricsLogger;
+    private final MetricsLogger mMetricsLogger;
 
     private String[] mPackages;
     private PackageItemAdapter mAdapter;
@@ -75,16 +75,15 @@
             };
 
     @Inject
-    ForegroundServicesDialog() {
+    ForegroundServicesDialog(MetricsLogger metricsLogger) {
         super();
+        mMetricsLogger = metricsLogger;
     }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mMetricsLogger = Dependency.get(MetricsLogger.class);
-
         mInflater = LayoutInflater.from(this);
 
         mAdapter = new PackageItemAdapter(this);
diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
index 3f5c2c8..aedaf96 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
@@ -28,7 +28,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.BootCompleteCache;
-import com.android.systemui.Dependency;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -78,10 +77,11 @@
 
     @Inject
     PhoneStateMonitor(Context context, BroadcastDispatcher broadcastDispatcher,
-            Lazy<Optional<StatusBar>> statusBarOptionalLazy, BootCompleteCache bootCompleteCache) {
+            Lazy<Optional<StatusBar>> statusBarOptionalLazy, BootCompleteCache bootCompleteCache,
+            StatusBarStateController statusBarStateController) {
         mContext = context;
         mStatusBarOptionalLazy = statusBarOptionalLazy;
-        mStatusBarStateController = Dependency.get(StatusBarStateController.class);
+        mStatusBarStateController = statusBarStateController;
 
         mDefaultHome = getCurrentDefaultHome();
         bootCompleteCache.addListener(() -> mDefaultHome = getCurrentDefaultHome());
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 5732145..1c98099 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -35,7 +35,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.assist.AssistLogger;
 import com.android.systemui.assist.AssistManager;
@@ -46,6 +45,8 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /**
  * Default UiController implementation. Shows white edge lights along the bottom of the phone,
  * expanding from the corners to meet in the center.
@@ -65,6 +66,8 @@
     protected final AssistLogger mAssistLogger;
 
     private final WindowManager mWindowManager;
+    private final MetricsLogger mMetricsLogger;
+    private final Lazy<AssistManager> mAssistManagerLazy;
     private final WindowManager.LayoutParams mLayoutParams;
     private final PathInterpolator mProgressInterpolator = new PathInterpolator(.83f, 0, .84f, 1);
 
@@ -75,10 +78,14 @@
     private ValueAnimator mInvocationAnimator = new ValueAnimator();
 
     @Inject
-    public DefaultUiController(Context context, AssistLogger assistLogger) {
+    public DefaultUiController(Context context, AssistLogger assistLogger,
+            WindowManager windowManager, MetricsLogger metricsLogger,
+            Lazy<AssistManager> assistManagerLazy) {
         mAssistLogger = assistLogger;
         mRoot = new FrameLayout(context);
-        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        mWindowManager = windowManager;
+        mMetricsLogger = metricsLogger;
+        mAssistManagerLazy = assistManagerLazy;
 
         mLayoutParams = new WindowManager.LayoutParams(
                 WindowManager.LayoutParams.MATCH_PARENT,
@@ -152,9 +159,9 @@
                     /* isInvocationComplete = */ false,
                     /* assistantComponent = */ null,
                     /* legacyDeviceState = */ null);
-            MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT)
+            mMetricsLogger.write(new LogMaker(MetricsEvent.ASSISTANT)
                     .setType(MetricsEvent.TYPE_ACTION)
-                    .setSubtype(Dependency.get(AssistManager.class).toLoggingSubType(type)));
+                    .setSubtype(mAssistManagerLazy.get().toLoggingSubType(type)));
         }
         // Logs assistant invocation cancelled.
         if ((mInvocationAnimator == null || !mInvocationAnimator.isRunning())
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index fd37b35..21edb24 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -52,7 +52,6 @@
 import android.widget.ScrollView;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -106,7 +105,7 @@
 
     private final float mTranslationY;
 
-    @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
 
     @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
 
@@ -187,10 +186,12 @@
 
         public AuthContainerView build(int[] sensorIds, boolean credentialAllowed,
                 @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
-                @Nullable List<FaceSensorPropertiesInternal> faceProps) {
+                @Nullable List<FaceSensorPropertiesInternal> faceProps,
+                WakefulnessLifecycle wakefulnessLifecycle) {
             mConfig.mSensorIds = sensorIds;
             mConfig.mCredentialAllowed = credentialAllowed;
-            return new AuthContainerView(mConfig, new Injector(), fpProps, faceProps);
+            return new AuthContainerView(
+                    mConfig, new Injector(), fpProps, faceProps, wakefulnessLifecycle);
         }
     }
 
@@ -276,7 +277,8 @@
     @VisibleForTesting
     AuthContainerView(Config config, Injector injector,
             @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
-            @Nullable List<FaceSensorPropertiesInternal> faceProps) {
+            @Nullable List<FaceSensorPropertiesInternal> faceProps,
+            WakefulnessLifecycle wakefulnessLifecycle) {
         super(config.mContext);
 
         mConfig = config;
@@ -289,7 +291,7 @@
 
         mHandler = new Handler(Looper.getMainLooper());
         mWindowManager = mContext.getSystemService(WindowManager.class);
-        mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
+        mWakefulnessLifecycle = wakefulnessLifecycle;
 
         mTranslationY = getResources()
                 .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 2b12f67..b0f7e55 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -63,6 +63,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.util.concurrency.Execution;
 
@@ -130,6 +131,7 @@
 
     @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
     private SensorPrivacyManager mSensorPrivacyManager;
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
 
     private class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -168,6 +170,10 @@
                 mCurrentDialog = null;
                 mOrientationListener.disable();
 
+                for (Callback cb : mCallbacks) {
+                    cb.onBiometricPromptDismissed();
+                }
+
                 try {
                     if (mReceiver != null) {
                         mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
@@ -198,6 +204,10 @@
                         mCurrentDialog = null;
                         mOrientationListener.disable();
 
+                        for (Callback cb : mCallbacks) {
+                            cb.onBiometricPromptDismissed();
+                        }
+
                         if (mReceiver != null) {
                             mReceiver.onDialogDismissed(
                                     BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
@@ -460,6 +470,7 @@
             Log.e(TAG, "sendResultAndCleanUp: Receiver is null");
             return;
         }
+
         try {
             mReceiver.onDialogDismissed(reason, credentialAttestation);
         } catch (RemoteException e) {
@@ -479,9 +490,11 @@
             Provider<UdfpsController> udfpsControllerFactory,
             Provider<SidefpsController> sidefpsControllerFactory,
             @NonNull DisplayManager displayManager,
+            WakefulnessLifecycle wakefulnessLifecycle,
             @Main Handler handler) {
         super(context);
         mExecution = execution;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
         mHandler = handler;
         mCommandQueue = commandQueue;
         mActivityTaskManager = activityTaskManager;
@@ -788,7 +801,8 @@
                 skipAnimation,
                 operationId,
                 requestId,
-                multiSensorConfig);
+                multiSensorConfig,
+                mWakefulnessLifecycle);
 
         if (newDialog == null) {
             Log.e(TAG, "Unsupported type configuration");
@@ -811,6 +825,9 @@
         }
 
         mReceiver = (IBiometricSysuiReceiver) args.arg2;
+        for (Callback cb : mCallbacks) {
+            cb.onBiometricPromptShown();
+        }
         mCurrentDialog = newDialog;
         mCurrentDialog.show(mWindowManager, savedState);
         mOrientationListener.enable();
@@ -821,6 +838,11 @@
         if (mCurrentDialog == null) {
             Log.w(TAG, "Dialog already dismissed");
         }
+
+        for (Callback cb : mCallbacks) {
+            cb.onBiometricPromptDismissed();
+        }
+
         mReceiver = null;
         mCurrentDialog = null;
         mOrientationListener.disable();
@@ -868,7 +890,8 @@
     protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
             int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
             boolean skipIntro, long operationId, long requestId,
-            @BiometricMultiSensorMode int multiSensorConfig) {
+            @BiometricMultiSensorMode int multiSensorConfig,
+            WakefulnessLifecycle wakefulnessLifecycle) {
         return new AuthContainerView.Builder(mContext)
                 .setCallback(this)
                 .setPromptInfo(promptInfo)
@@ -879,7 +902,7 @@
                 .setOperationId(operationId)
                 .setRequestId(requestId)
                 .setMultiSensorConfig(multiSensorConfig)
-                .build(sensorIds, credentialAllowed, mFpProps, mFaceProps);
+                .build(sensorIds, credentialAllowed, mFpProps, mFaceProps, wakefulnessLifecycle);
     }
 
     /**
@@ -891,12 +914,22 @@
          * Called when authenticators are registered. If authenticators are already
          * registered before this call, this callback will never be triggered.
          */
-        void onAllAuthenticatorsRegistered();
+        default void onAllAuthenticatorsRegistered() {}
 
         /**
          * Called when UDFPS enrollments have changed. This is called after boot and on changes to
          * enrollment.
          */
-        void onEnrollmentsChanged();
+        default void onEnrollmentsChanged() {}
+
+        /**
+         * Called when the biometric prompt starts showing.
+         */
+        default void onBiometricPromptShown() {}
+
+        /**
+         * Called when the biometric prompt is no longer showing.
+         */
+        default void onBiometricPromptDismissed() {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
index b7404df..dfbe348 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
@@ -37,7 +37,7 @@
     private val onChanged: () -> Unit
 ) : DisplayManager.DisplayListener {
 
-    private var lastRotation = context.display?.rotation ?: Surface.ROTATION_0
+    private var lastRotation = Surface.ROTATION_0
 
     override fun onDisplayAdded(displayId: Int) {}
     override fun onDisplayRemoved(displayId: Int) {}
@@ -63,6 +63,7 @@
 
     /** Listen for changes. */
     fun enable() {
+        lastRotation = context.display?.rotation ?: Surface.ROTATION_0
         displayManager.registerDisplayListener(this, handler)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 6581490..5ddfd75 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Point;
 import android.graphics.RectF;
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.display.DisplayManager;
@@ -45,6 +46,7 @@
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.Surface;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.WindowManager;
@@ -414,8 +416,33 @@
                         final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
                         if (!isIlluminationRequested && !mGoodCaptureReceived &&
                                 !exceedsVelocityThreshold) {
-                            onFingerDown((int) event.getRawX(), (int) event.getRawY(), minor,
-                                    major);
+                            final int rawX = (int) event.getRawX();
+                            final int rawY = (int) event.getRawY();
+                            // Default coordinates assume portrait mode.
+                            int x = rawX;
+                            int y = rawY;
+
+                            // Gets the size based on the current rotation of the display.
+                            Point p = new Point();
+                            mContext.getDisplay().getRealSize(p);
+
+                            // Transform x, y to portrait mode if the device is in landscape mode.
+                            switch (mContext.getDisplay().getRotation()) {
+                                case Surface.ROTATION_90:
+                                    x = p.y - rawY;
+                                    y = rawX;
+                                    break;
+
+                                case Surface.ROTATION_270:
+                                    x = rawY;
+                                    y = p.x - rawX;
+                                    break;
+
+                                default:
+                                    // Do nothing to stay in portrait mode.
+                            }
+
+                            onFingerDown(x, y, minor, major);
                             Log.v(TAG, "onTouch | finger down: " + touchInfo);
                             mTouchLogTime = mSystemClock.elapsedRealtime();
                             mPowerManager.userActivity(mSystemClock.uptimeMillis(),
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index ae0702c..caf0307 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -59,6 +59,7 @@
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -91,6 +92,7 @@
     private final WindowManager.LayoutParams mWindowLayoutParams;
     private final PhoneWindow mWindow;
     private final TimeoutHandler mTimeoutHandler;
+    private final AccessibilityManager mAccessibilityManager;
 
     private final DraggableConstraintLayout mView;
     private final ImageView mImagePreview;
@@ -98,6 +100,7 @@
     private final ScreenshotActionChip mEditChip;
     private final ScreenshotActionChip mRemoteCopyChip;
     private final View mActionContainerBackground;
+    private final View mDismissButton;
 
     private Runnable mOnSessionCompleteListener;
 
@@ -113,6 +116,8 @@
         final Context displayContext = context.createDisplayContext(getDefaultDisplay());
         mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
 
+        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+
         mWindowManager = mContext.getSystemService(WindowManager.class);
 
         mTimeoutHandler = timeoutHandler;
@@ -135,10 +140,13 @@
         mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
         mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
         mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
+        mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
 
         mView.setOnDismissCallback(this::hideImmediate);
         mView.setOnInteractionCallback(() -> mTimeoutHandler.resetTimeout());
 
+        mDismissButton.setOnClickListener(view -> animateOut());
+
         mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
         mRemoteCopyChip.setIcon(
                 Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
@@ -158,10 +166,10 @@
         withWindowAttached(() -> {
             mWindow.setContentView(mView);
             updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
-            getEnterAnimation().start();
+            mView.post(() -> getEnterAnimation().start());
         });
 
-        mTimeoutHandler.setOnTimeoutRunnable(() -> animateOut());
+        mTimeoutHandler.setOnTimeoutRunnable(this::animateOut);
 
         mCloseDialogsReceiver = new BroadcastReceiver() {
             @Override
@@ -226,8 +234,8 @@
                         mView.getLocationOnScreen(pt);
                         Rect rect = new Rect(pt[0], pt[1], pt[0] + mView.getWidth(),
                                 pt[1] + mView.getHeight());
-                        if (!rect.contains((int) motionEvent.getRawX(),
-                                (int) motionEvent.getRawY())) {
+                        if (!rect.contains(
+                                (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
                             animateOut();
                         }
                     }
@@ -311,11 +319,15 @@
         ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
 
         mView.setAlpha(0);
+        mDismissButton.setVisibility(View.GONE);
         final View previewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
         final View actionBackground = requireNonNull(
                 mView.findViewById(R.id.actions_container_background));
         mImagePreview.setVisibility(View.VISIBLE);
         mActionContainerBackground.setVisibility(View.VISIBLE);
+        if (mAccessibilityManager.isEnabled()) {
+            mDismissButton.setVisibility(View.VISIBLE);
+        }
 
         anim.addUpdateListener(animation -> {
             mView.setAlpha(animation.getAnimatedFraction());
@@ -385,7 +397,7 @@
 
     private void reset() {
         mView.setTranslationX(0);
-        mView.setAlpha(1);
+        mView.setAlpha(0);
         mTimeoutHandler.cancelTimeout();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 4758ab0..dc3d1b5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -41,12 +41,12 @@
  * The activity being launched is specified by {@link android.service.controls.Control#getAppIntent}.
  */
 class DetailDialog(
-    val activityContext: Context?,
+    val activityContext: Context,
     val taskView: TaskView,
     val pendingIntent: PendingIntent,
     val cvh: ControlViewHolder
 ) : Dialog(
-    activityContext ?: cvh.context,
+    activityContext,
     R.style.Theme_SystemUI_Dialog_Control_DetailPanel
 ) {
     companion object {
@@ -58,6 +58,10 @@
     }
 
     var detailTaskId = INVALID_TASK_ID
+    private lateinit var taskViewContainer: View
+    private val taskWidthPercentWidth = activityContext.resources.getFloat(
+        R.dimen.controls_task_view_width_percentage
+    )
 
     private val fillInIntent = Intent().apply {
         putExtra(EXTRA_USE_PANEL, true)
@@ -75,13 +79,18 @@
 
     val stateCallback = object : TaskView.Listener {
         override fun onInitialized() {
-            val options = activityContext?.let {
-                ActivityOptions.makeCustomAnimation(
-                    it,
-                    0 /* enterResId */,
-                    0 /* exitResId */
-                )
-            } ?: ActivityOptions.makeBasic()
+            taskViewContainer.apply {
+                // For some devices, limit the overall width of the taskView
+                val lp = getLayoutParams()
+                lp.width = (getWidth() * taskWidthPercentWidth).toInt()
+                setLayoutParams(lp)
+            }
+
+            val options = ActivityOptions.makeCustomAnimation(
+                activityContext,
+                0 /* enterResId */,
+                0 /* exitResId */
+            )
             taskView.startActivity(
                 pendingIntent,
                 fillInIntent,
@@ -112,16 +121,14 @@
     }
 
     init {
-        if (activityContext == null) {
-            window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
-        }
-
         // To pass touches to the task inside TaskView.
         window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
         window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
 
         setContentView(R.layout.controls_detail_dialog)
 
+        taskViewContainer = requireViewById<ViewGroup>(R.id.control_task_view_container)
+
         requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
             addView(taskView)
             setAlpha(0f)
@@ -130,6 +137,9 @@
         requireViewById<ImageView>(R.id.control_detail_close).apply {
             setOnClickListener { _: View -> dismiss() }
         }
+        requireViewById<View>(R.id.control_detail_root).apply {
+            setOnClickListener { _: View -> dismiss() }
+        }
 
         requireViewById<ImageView>(R.id.control_detail_open_in_app).apply {
             setOnClickListener { v: View ->
@@ -145,17 +155,10 @@
         // consume all insets to achieve slide under effect
         window.getDecorView().setOnApplyWindowInsetsListener {
             v: View, insets: WindowInsets ->
-                taskView.apply {
-                    val l = getPaddingLeft()
-                    val t = getPaddingTop()
-                    val r = getPaddingRight()
-                    setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom)
-                }
-
                 val l = v.getPaddingLeft()
-                val b = v.getPaddingBottom()
                 val r = v.getPaddingRight()
-                v.setPadding(l, insets.getInsets(Type.systemBars()).top, r, b)
+                val insets = insets.getInsets(Type.systemBars())
+                v.setPadding(l, insets.top, r, insets.bottom)
 
                 WindowInsets.CONSUMED
         }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index bbe9dbd..96e2302 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -29,6 +29,7 @@
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.media.systemsounds.HomeSoundEffectController;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.privacy.television.TvOngoingPrivacyChip;
@@ -66,6 +67,12 @@
     @ClassKey(AuthController.class)
     public abstract CoreStartable bindAuthController(AuthController service);
 
+    /** Inject into SessionTracker. */
+    @Binds
+    @IntoMap
+    @ClassKey(SessionTracker.class)
+    public abstract CoreStartable bindSessionTracker(SessionTracker service);
+
     /** Inject into GarbageMonitor.Service. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHost.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHost.java
deleted file mode 100644
index 7c3152f..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHost.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams;
-
-import android.view.View;
-
-/**
- * A collection of interfaces related to hosting a complication.
- */
-public abstract class ComplicationHost {
-    /**
-     * An interface for the callback from the complication provider to indicate when the
-     * complication is ready.
-     */
-    public interface CreationCallback {
-        /**
-         * Called to inform the complication view is ready to be placed within the visual space.
-         * @param view The view representing the complication.
-         * @param layoutParams The parameters to create the view with.
-         */
-        void onCreated(View view, ComplicationHostView.LayoutParams layoutParams);
-    }
-
-    /**
-     * An interface for the callback from the complication provider to signal interactions in the
-     * complication.
-     */
-    public interface InteractionCallback {
-        /**
-         * Called to signal the calling complication would like to exit the dream.
-         */
-        void onExit();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHostView.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHostView.java
deleted file mode 100644
index a67dd5c..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHostView.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-import androidx.constraintlayout.widget.ConstraintLayout;
-
-/**
- * {@link ComplicationHostView} is the container view for housing complications above of a dream.
- */
-public class ComplicationHostView extends ConstraintLayout {
-    public ComplicationHostView(Context context) {
-        super(context, null);
-    }
-
-    public ComplicationHostView(Context context, AttributeSet attrs) {
-        super(context, attrs, 0);
-    }
-
-    public ComplicationHostView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr, 0);
-    }
-
-    public ComplicationHostView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java
deleted file mode 100644
index e5d6319..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams;
-
-import android.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
- * implementations with the necessary context for constructing such overlays.
- */
-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.
-     * @param creationCallback The callback to inform the complication has been created.
-     * @param interactionCallback The callback to inform the complication has been interacted with.
-     */
-    void onCreateComplication(Context context, ComplicationHost.CreationCallback creationCallback,
-            ComplicationHost.InteractionCallback interactionCallback);
-
-    /**
-     * 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/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 5b46079..be76e8f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -25,11 +25,11 @@
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 
-import androidx.constraintlayout.widget.ConstraintLayout;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
+import com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayModule;
 import com.android.systemui.util.ViewController;
@@ -47,6 +47,8 @@
     private final int mDreamOverlayNotificationsDragAreaHeight;
     private final DreamOverlayStatusBarViewController mStatusBarViewController;
 
+    private final ComplicationHostViewController mComplicationHostViewController;
+
     // The dream overlay's content view, which is located below the status bar (in z-order) and is
     // the space into which widgets are placed.
     private final ViewGroup mDreamOverlayContentView;
@@ -74,6 +76,13 @@
                     final int childCount = mDreamOverlayContentView.getChildCount();
                     for (int i = 0; i < childCount; i++) {
                         View child = mDreamOverlayContentView.getChildAt(i);
+
+                        if (mComplicationHostViewController.getView() == child) {
+                            region.op(mComplicationHostViewController.getTouchRegions(),
+                                    Region.Op.UNION);
+                            continue;
+                        }
+
                         if (child.getGlobalVisibleRect(rect)) {
                             region.op(rect, Region.Op.UNION);
                         }
@@ -93,6 +102,7 @@
     @Inject
     public DreamOverlayContainerViewController(
             DreamOverlayContainerView containerView,
+            ComplicationHostViewComponent.Factory complicationHostViewFactory,
             @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
             DreamOverlayStatusBarViewController statusBarViewController,
             @Main Handler handler,
@@ -106,6 +116,13 @@
                 mView.getResources().getDimensionPixelSize(
                         R.dimen.dream_overlay_notifications_drag_area_height);
 
+        mComplicationHostViewController = complicationHostViewFactory.create().getController();
+        final View view = mComplicationHostViewController.getView();
+
+        mDreamOverlayContentView.addView(view,
+                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.MATCH_PARENT));
+
         mHandler = handler;
         mMaxBurnInOffset = maxBurnInOffset;
         mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
@@ -114,6 +131,7 @@
     @Override
     protected void onInit() {
         mStatusBarViewController.init();
+        mComplicationHostViewController.init();
     }
 
     @Override
@@ -130,18 +148,10 @@
                 .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
     }
 
-    void addOverlay(View overlayView, ConstraintLayout.LayoutParams layoutParams) {
-        mDreamOverlayContentView.addView(overlayView, layoutParams);
-    }
-
     View getContainerView() {
         return mView;
     }
 
-    void removeAllOverlays() {
-        mDreamOverlayContentView.removeAllViews();
-    }
-
     @VisibleForTesting
     int getDreamOverlayNotificationsDragAreaHeight() {
         return mDreamOverlayNotificationsDragAreaHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index a53120f..16ed1fb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -24,10 +24,13 @@
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ViewModelStore;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.PhoneWindow;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 
 import java.util.concurrent.Executor;
@@ -47,8 +50,6 @@
     private final Context mContext;
     // The Executor ensures actions and ui updates happen on the same thread.
     private final Executor mExecutor;
-    // The state controller informs the service of updates to the complications present.
-    private final DreamOverlayStateController mStateController;
     // A controller for the dream overlay container view (which contains both the status bar and the
     // content area).
     private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
@@ -56,47 +57,51 @@
     // A reference to the {@link Window} used to hold the dream overlay.
     private Window mWindow;
 
-    private final DreamOverlayStateController.Callback mOverlayStateCallback =
-            new DreamOverlayStateController.Callback() {
-                @Override
-                public void onComplicationsChanged() {
-                    mExecutor.execute(() -> reloadComplicationsLocked());
-                }
-            };
+    private final Complication.Host mHost = new Complication.Host() {
+        @Override
+        public void requestExitDream() {
+            mExecutor.execute(DreamOverlayService.this::requestExit);
+        }
+    };
+
+    private final LifecycleRegistry mLifecycleRegistry;
+
+    private ViewModelStore mViewModelStore = new ViewModelStore();
 
     @Inject
     public DreamOverlayService(
             Context context,
             @Main Executor executor,
-            DreamOverlayStateController overlayStateController,
             DreamOverlayComponent.Factory dreamOverlayComponentFactory) {
         mContext = context;
         mExecutor = executor;
-        mStateController = overlayStateController;
-        mDreamOverlayContainerViewController =
-                dreamOverlayComponentFactory.create().getDreamOverlayContainerViewController();
 
-        mStateController.addCallback(mOverlayStateCallback);
+        final DreamOverlayComponent component =
+                dreamOverlayComponentFactory.create(mViewModelStore, mHost);
+        mDreamOverlayContainerViewController = component.getDreamOverlayContainerViewController();
+        setCurrentState(Lifecycle.State.CREATED);
+        mLifecycleRegistry = component.getLifecycleRegistry();
+    }
+
+    private void setCurrentState(Lifecycle.State state) {
+        mExecutor.execute(() -> mLifecycleRegistry.setCurrentState(state));
     }
 
     @Override
     public void onDestroy() {
+        setCurrentState(Lifecycle.State.DESTROYED);
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         windowManager.removeView(mWindow.getDecorView());
-        mStateController.removeCallback(mOverlayStateCallback);
         super.onDestroy();
     }
 
     @Override
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
-        mExecutor.execute(() -> addOverlayWindowLocked(layoutParams));
-    }
-
-    private void reloadComplicationsLocked() {
-        mDreamOverlayContainerViewController.removeAllOverlays();
-        for (ComplicationProvider overlayProvider : mStateController.getComplications()) {
-            addComplication(overlayProvider);
-        }
+        setCurrentState(Lifecycle.State.STARTED);
+        mExecutor.execute(() -> {
+            addOverlayWindowLocked(layoutParams);
+            setCurrentState(Lifecycle.State.RESUMED);
+        });
     }
 
     /**
@@ -129,20 +134,5 @@
 
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
-        mExecutor.execute(this::reloadComplicationsLocked);
-    }
-
-    @VisibleForTesting
-    protected void addComplication(ComplicationProvider provider) {
-        provider.onCreateComplication(mContext,
-                (view, layoutParams) -> {
-                    // Always move UI related work to the main thread.
-                    mExecutor.execute(() -> mDreamOverlayContainerViewController
-                            .addOverlay(view, layoutParams));
-                },
-                () -> {
-                    // The Callback is set on the main thread.
-                    mExecutor.execute(this::requestExit);
-                });
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 66679bb..e838848 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -17,18 +17,17 @@
 package com.android.systemui.dreams;
 
 import androidx.annotation.NonNull;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.statusbar.policy.CallbackController;
 
-import com.google.common.util.concurrent.ListenableFuture;
-
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -42,35 +41,6 @@
 @SysUISingleton
 public class DreamOverlayStateController implements
         CallbackController<DreamOverlayStateController.Callback> {
-    // A counter for guaranteeing unique complications tokens within the scope of this state
-    // controller.
-    private int mNextComplicationTokenId = 0;
-
-    /**
-     * {@link ComplicationToken} provides a unique key for identifying {@link ComplicationProvider}
-     * instances registered with {@link DreamOverlayStateController}.
-     */
-    public static class ComplicationToken {
-        private final int mId;
-
-        private ComplicationToken(int id) {
-            mId = id;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (!(o instanceof ComplicationToken)) return false;
-            ComplicationToken that = (ComplicationToken) o;
-            return mId == that.mId;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mId);
-        }
-    }
-
     /**
      * Callback for dream overlay events.
      */
@@ -84,7 +54,8 @@
 
     private final Executor mExecutor;
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
-    private final HashMap<ComplicationToken, ComplicationProvider> mComplications = new HashMap<>();
+
+    private final Collection<Complication> mComplications = new HashSet();
 
     @VisibleForTesting
     @Inject
@@ -93,47 +64,32 @@
     }
 
     /**
-     * Adds a complication to be presented on top of dreams.
-     * @param provider The {@link ComplicationProvider} providing the dream.
-     * @return The {@link ComplicationToken} tied to the supplied {@link ComplicationProvider}.
+     * Adds a complication to be included on the dream overlay.
      */
-    public ListenableFuture<ComplicationToken> addComplication(ComplicationProvider provider) {
-        return CallbackToFutureAdapter.getFuture(completer -> {
-            mExecutor.execute(() -> {
-                final ComplicationToken token = new ComplicationToken(mNextComplicationTokenId++);
-                mComplications.put(token, provider);
-                notifyCallbacks();
-                completer.set(token);
-            });
-            return "DreamOverlayStateController::addComplication";
+    public void addComplication(Complication complication) {
+        mExecutor.execute(() -> {
+            if (mComplications.add(complication)) {
+                mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
+            }
         });
     }
 
     /**
-     * Removes a complication from being shown on dreams.
-     * @param token The {@link ComplicationToken} associated with the {@link ComplicationProvider}
-     *              to be removed.
-     * @return The removed {@link ComplicationProvider}, {@code null} if not found.
+     * Removes a complication from inclusion on the dream overlay.
      */
-    public ListenableFuture<ComplicationProvider> removeComplication(ComplicationToken token) {
-        return CallbackToFutureAdapter.getFuture(completer -> {
-            mExecutor.execute(() -> {
-                final ComplicationProvider removedComplication = mComplications.remove(token);
-
-                if (removedComplication != null) {
-                    notifyCallbacks();
-                }
-                completer.set(removedComplication);
-            });
-
-            return "DreamOverlayStateController::removeComplication";
+    public void removeComplication(Complication complication) {
+        mExecutor.execute(() -> {
+            if (mComplications.remove(complication)) {
+                mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
+            }
         });
     }
 
-    private void notifyCallbacks() {
-        for (Callback callback : mCallbacks) {
-            callback.onComplicationsChanged();
-        }
+    /**
+     * Returns collection of present {@link Complication}.
+     */
+    public Collection<Complication> getComplications() {
+        return Collections.unmodifiableCollection(mComplications);
     }
 
     @Override
@@ -161,12 +117,4 @@
             mCallbacks.remove(callback);
         });
     }
-
-    /**
-     * Returns all registered {@link ComplicationProvider} instances.
-     * @return A collection of {@link ComplicationProvider}.
-     */
-    public Collection<ComplicationProvider> getComplications() {
-        return mComplications.values();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
new file mode 100644
index 0000000..96cf50d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import android.annotation.IntDef;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * {@link Complication} is an interface for defining a complication, a visual component rendered
+ * above a dream. {@link Complication} instances encapsulate the logic for generating the view to be
+ * shown, along with the supporting control/logic. The decision for including the
+ * {@link Complication} is not the responsibility of the {@link Complication}. This is instead
+ * handled by domain logic and invocations to add and remove the {@link Complication} through
+ * {@link com.android.systemui.dreams.DreamOverlayStateController#addComplication(Complication)} and
+ * {@link com.android.systemui.dreams.DreamOverlayStateController#removeComplication(Complication)}.
+ * A {@link Complication} also does not represent a specific instance of the view. Instead, it
+ * should be viewed as a provider, where view instances are requested from it. The associated
+ * {@link ViewHolder} interface is requested for each view request. This object is retained for the
+ * view's lifetime, providing a container for any associated logic. The complication rendering
+ * system will consult this {@link ViewHolder} for {@link View} to show and
+ * {@link ComplicationLayoutParams} to position the view. {@link ComplicationLayoutParams} allow for
+ * specifying the sizing and position of the {@link Complication}.
+ *
+ * The following code sample exhibits the entities and lifecycle involved with a
+ * {@link Complication}.
+ *
+ * <pre>{@code
+ * // This component allows for the complication to generate a new ViewHolder for every request.
+ * @Subcomponent
+ * interface ExampleViewHolderComponent {
+ *     @Subcomponent.Factory
+ *     interface Factory {
+ *         ExampleViewHolderComponent create();
+ *     }
+ *
+ *     ExampleViewHolder getViewHolder();
+ * }
+ *
+ * // An example entity that controls whether or not a complication should be included on dreams.
+ * // Note how the complication is tracked by reference for removal.
+ * public class ExampleComplicationProvider {
+ *     private final DreamOverlayStateController mDreamOverlayStateController;
+ *     private final ExampleComplication mComplication;
+ *     @Inject
+ *     public ExampleComplicationProvider(
+ *             ExampleComplication complication,
+ *             DreamOverlayStateController stateController) {
+ *         mDreamOverlayStateController = stateController;
+ *         mComplication = complication;
+ *     }
+ *
+ *     public void onShowConditionsMet(boolean met) {
+ *         if (met) {
+ *             mDreamOverlayStateController.addComplication(mComplication);
+ *         } else {
+ *             mDreamOverlayStateController.removeComplication(mComplication);
+ *         }
+ *     }
+ * }
+ *
+ * // An example complication. Note how a factory is created to supply a unique ViewHolder for each
+ * // request. Also, there is no particular view instance members defined in the complication.
+ * class ExampleComplication implements Complication {
+ *     private final ExampleViewHolderComponent.Factory mFactory;
+ *     @Inject
+ *     public ExampleComplication(ExampleViewHolderComponent.Factory viewHolderComponentFactory) {
+ *         mFactory = viewHolderComponentFactory;
+ *     }
+ *
+ *     @Override
+ *     public ViewHolder createView(ComplicationViewModel model) {
+ *         return mFactory.create().getViewHolder();
+ *     }
+ * }
+ *
+ * // Not every ViewHolder needs to include a view controller. It is included here as an example of
+ * // how such logic can be contained and associated with the ViewHolder lifecycle.
+ * class ExampleViewController extends ViewController<FrameLayout> {
+ *     protected ExampleViewController(FrameLayout view) {
+ *         super(view);
+ *     }
+ *
+ *     @Override
+ *     protected void onViewAttached() { }
+ *
+ *     @Override
+ *     protected void onViewDetached() { }
+ * }
+ *
+ * // An example ViewHolder. This is the correct place to contain any value/logic associated with a
+ * // particular instance of the ComplicationView.
+ * class ExampleViewHolder implements Complication.ViewHolder {
+ *     final FrameLayout mView;
+ *     final ExampleViewController mController;
+ *
+ *     @Inject
+ *     public ExampleViewHolder(Context context) {
+ *         mView = new FrameLayout(context);
+ *         mController = new ExampleViewController(mView);
+ *     }
+ *     @Override
+ *     public View getView() {
+ *         return mView;
+ *     }
+ *
+ *     @Override
+ *     public ComplicationLayoutParams getLayoutParams() {
+ *         return new ComplicationLayoutParams(
+ *                 200,
+ *                 100,
+ *                 ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.DIRECTION_END,
+ *                 ComplicationLayoutParams.DIRECTION_DOWN,
+ *                 4);
+ *     }
+ * }
+ * }
+ * </pre>
+ */
+public interface Complication {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "CATEGORY_" }, value = {
+            CATEGORY_STANDARD,
+            CATEGORY_SYSTEM,
+    })
+
+    @interface Category {}
+    /**
+     * {@code CATEGORY_STANDARD} indicates the complication is a normal component. Rules and
+     * settings, such as hiding all complications, will apply to this complication.
+     */
+    int CATEGORY_STANDARD = 1 << 0;
+    /**
+     * {@code CATEGORY_SYSTEM} indicates complications driven by SystemUI. Usually, these are
+     * core components that are not user controlled. These can potentially deviate from given
+     * rule sets that would normally apply to {@code CATEGORY_STANDARD}.
+     */
+    int CATEGORY_SYSTEM = 1 << 1;
+
+    /**
+     * The {@link Host} interface specifies a way a {@link Complication} to communicate with its
+     * parent entity for information and actions.
+     */
+    interface Host {
+        /**
+         * Called to signal a {@link Complication} has requested to exit the dream.
+         */
+        void requestExitDream();
+    }
+
+    /**
+     * Returned through {@link Complication#createView(ComplicationViewModel)}, {@link ViewHolder}
+     * is a container for a single {@link Complication} instance. The {@link Host} guarantees that
+     * the {@link ViewHolder} will be retained for the lifetime of the {@link Complication}
+     * instance's user. The view is responsible for providing the view that represents the
+     * {@link Complication}. This object is the proper place to store any related entities, such as
+     * a {@link com.android.systemui.util.ViewController} for the view.
+     */
+    interface ViewHolder {
+        /**
+         * Returns the {@link View} associated with the {@link ViewHolder}. This {@link View} should
+         * be stable and generated once.
+         * @return
+         */
+        View getView();
+
+        /**
+         * Returns the {@link Category} associated with the {@link Complication}. {@link Category}
+         * is a grouping which helps define the relationship of the {@link Complication} to
+         * System UI and the rest of the system. It is used for presentation and other decisions.
+         */
+        @Complication.Category
+        default int getCategory() {
+            return Complication.CATEGORY_STANDARD;
+        }
+
+        /**
+         * Returns the {@link ComplicationLayoutParams} associated with this complication. The
+         * values expressed here are treated as preference rather than requirement. The hosting
+         * entity is free to modify/interpret the parameters as deemed fit.
+         */
+        ComplicationLayoutParams getLayoutParams();
+    }
+
+    /**
+     * Generates a {@link ViewHolder} for the {@link Complication}. This captures both the view and
+     * control logic for a single instance of the complication. The {@link Complication} may be
+     * asked at any time to generate another view.
+     * @param model The {@link ComplicationViewModel} associated with this particular
+     *              {@link Complication} instance.
+     * @return a {@link ViewHolder} for this {@link Complication} instance.
+     */
+    ViewHolder createView(ComplicationViewModel model);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
new file mode 100644
index 0000000..76818fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import androidx.lifecycle.LiveData;
+
+import com.android.systemui.dreams.DreamOverlayStateController;
+
+import java.util.Collection;
+
+import javax.inject.Inject;
+
+/**
+ * {@link ComplicationCollectionLiveData} wraps
+ * {@link DreamOverlayStateController#getComplications()} to provide an observable
+ * {@link Complication} data set tied to a lifecycle. This should not be directly accessed. Instead,
+ * clients should access the data from {@link ComplicationCollectionViewModel}.
+ */
+public class ComplicationCollectionLiveData extends LiveData<Collection<Complication>> {
+    final DreamOverlayStateController mDreamOverlayStateController;
+
+    final DreamOverlayStateController.Callback mStateControllerCallback;
+
+    {
+        mStateControllerCallback = new DreamOverlayStateController.Callback() {
+            @Override
+            public void onComplicationsChanged() {
+                setValue(mDreamOverlayStateController.getComplications());
+            }
+
+        };
+    }
+
+    @Inject
+    public ComplicationCollectionLiveData(DreamOverlayStateController stateController) {
+        super();
+        mDreamOverlayStateController = stateController;
+    }
+
+    @Override
+    protected void onActive() {
+        super.onActive();
+        mDreamOverlayStateController.addCallback(mStateControllerCallback);
+        setValue(mDreamOverlayStateController.getComplications());
+    }
+
+    @Override
+    protected void onInactive() {
+        mDreamOverlayStateController.removeCallback(mStateControllerCallback);
+        super.onInactive();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionViewModel.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionViewModel.java
new file mode 100644
index 0000000..7190d7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionViewModel.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Transformations;
+import androidx.lifecycle.ViewModel;
+
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+/**
+ * {@link ComplicationCollectionViewModel} is an abstraction for observing and accessing
+ * {@link ComplicationViewModel} for registered {@link Complication}.
+ */
+public class ComplicationCollectionViewModel extends ViewModel {
+    private final LiveData<Collection<ComplicationViewModel>> mComplications;
+    private final ComplicationViewModelTransformer mTransformer;
+
+    /**
+     * Injectable constructor for {@link ComplicationCollectionViewModel}. Note that this cannot
+     * be implicitly injected. Clients must bind scoped instance values through the corresponding
+     * dagger subcomponent.
+     */
+    @Inject
+    public ComplicationCollectionViewModel(
+            ComplicationCollectionLiveData complications,
+            ComplicationViewModelTransformer transformer) {
+        mComplications = Transformations.map(complications, collection -> convert(collection));
+        mTransformer = transformer;
+    }
+
+    private Collection<ComplicationViewModel> convert(Collection<Complication> complications) {
+        return complications
+                .stream()
+                .map(complication -> mTransformer.getViewModel(complication))
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * Returns {@link LiveData} for the collection of {@link Complication} represented as
+     * {@link ComplicationViewModel}.
+     */
+    public LiveData<Collection<ComplicationViewModel>> getComplications() {
+        return mComplications;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
new file mode 100644
index 0000000..f627f15
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent.SCOPED_COMPLICATIONS_LAYOUT;
+import static com.android.systemui.dreams.complication.dagger.ComplicationModule.SCOPED_COMPLICATIONS_MODEL;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.LifecycleOwner;
+
+import com.android.systemui.util.ViewController;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * The {@link ComplicationHostViewController} is responsible for displaying complications within
+ * a given container. It monitors the available {@link Complication} instances from
+ * {@link com.android.systemui.dreams.DreamOverlayStateController} and inserts/removes them through
+ * a {@link ComplicationLayoutEngine}.
+ */
+public class ComplicationHostViewController extends ViewController<ConstraintLayout> {
+    private final ComplicationLayoutEngine mLayoutEngine;
+    private final LifecycleOwner mLifecycleOwner;
+    private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
+    private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
+
+    @Inject
+    protected ComplicationHostViewController(
+            @Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
+            ComplicationLayoutEngine layoutEngine,
+            LifecycleOwner lifecycleOwner,
+            @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
+        super(view);
+        mLayoutEngine = layoutEngine;
+        mLifecycleOwner = lifecycleOwner;
+        mComplicationCollectionViewModel = viewModel;
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        mComplicationCollectionViewModel.getComplications().observe(mLifecycleOwner,
+                complicationViewModels -> updateComplications(complicationViewModels));
+    }
+
+    /**
+     * Returns the region in display space occupied by complications. Touches in this region
+     * (composed of a collection of individual rectangular regions) should be directed to the
+     * complications rather than the region underneath.
+     */
+    public Region getTouchRegions() {
+        final Region region = new Region();
+        final Rect rect = new Rect();
+        final int childCount = mView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mView.getChildAt(i);
+            if (child.getGlobalVisibleRect(rect)) {
+                region.op(rect, Region.Op.UNION);
+            }
+        }
+
+        return region;
+    }
+
+    private void updateComplications(Collection<ComplicationViewModel> complications) {
+        final Collection<ComplicationId> ids = complications.stream()
+                .map(complicationViewModel -> complicationViewModel.getId())
+                .collect(Collectors.toSet());
+
+        final Collection<ComplicationId> removedComplicationIds =
+                mComplications.keySet().stream()
+                        .filter(complicationId -> !ids.contains(complicationId))
+                        .collect(Collectors.toSet());
+
+        // Trim removed complications
+        removedComplicationIds.forEach(complicationId -> {
+            mLayoutEngine.removeComplication(complicationId);
+            mComplications.remove(complicationId);
+        });
+
+        // Add new complications
+        final Collection<ComplicationViewModel> newComplications = complications
+                .stream()
+                .filter(complication -> !mComplications.containsKey(complication.getId()))
+                .collect(Collectors.toSet());
+
+        newComplications
+                .forEach(complication -> {
+                    final ComplicationId id = complication.getId();
+                    final Complication.ViewHolder viewHolder = complication.getComplication()
+                            .createView(complication);
+                    mComplications.put(id, viewHolder);
+                    mLayoutEngine.addComplication(id, viewHolder.getView(),
+                            viewHolder.getLayoutParams(), viewHolder.getCategory());
+                });
+    }
+
+    @Override
+    protected void onViewAttached() {
+    }
+
+    @Override
+    protected void onViewDetached() {
+    }
+
+    /**
+     * Exposes the associated {@link View}. Since this {@link View} is instantiated through dagger
+     * in the {@link ComplicationHostViewController} constructor, the
+     * {@link ComplicationHostViewController} is responsible for surfacing it so that it can be
+     * included in the parent view hierarchy.
+     */
+    public View getView() {
+        return mView;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationId.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationId.java
new file mode 100644
index 0000000..420359c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationId.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+/**
+ * A {@link ComplicationId} is a value to uniquely identify a complication during the current
+ * runtime and within a particular scope. Any guarantees beyond this will need to be enforced
+ * externally.
+ */
+public class ComplicationId {
+    /**
+     * An associated factory for minting ids that are unique in for the factory's scope.
+     */
+    public static class Factory {
+        private int mNextId;
+
+        ComplicationId getNextId() {
+            return new ComplicationId(mNextId++);
+        }
+    }
+
+    private int mId;
+
+    private ComplicationId(int id) {
+        mId = id;
+    }
+
+    @Override
+    public String toString() {
+        return "ComplicationId{" + "mId=" + mId + "}";
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
new file mode 100644
index 0000000..cb24ae6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent.SCOPED_COMPLICATIONS_LAYOUT;
+
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.Constraints;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link ComplicationLayoutEngine} arranges a collection of {@link ComplicationViewModel} based on
+ * their layout parameters and attributes. The management of this set is done by
+ * {@link ComplicationHostViewController}.
+ */
+public class ComplicationLayoutEngine  {
+    public static final String TAG = "ComplicationLayoutEngine";
+
+    /**
+     * {@link ViewEntry} is an internal container, capturing information necessary for working with
+     * a particular {@link Complication} view.
+     */
+    private static class ViewEntry implements Comparable<ViewEntry> {
+        private final View mView;
+        private final ComplicationLayoutParams mLayoutParams;
+        private final Parent mParent;
+        @Complication.Category
+        private final int mCategory;
+
+        /**
+         * Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding
+         * view hierarchy to be accessed without traversing the entire view tree.
+         */
+        ViewEntry(View view, ComplicationLayoutParams layoutParams, int category, Parent parent) {
+            mView = view;
+            // Views that are generated programmatically do not have a unique id assigned to them
+            // at construction. A new id is assigned here to enable ConstraintLayout relative
+            // specifications. Existing ids for inflated views are not preserved.
+            // {@link Complication.ViewHolder} should not reference the root container by id.
+            mView.setId(View.generateViewId());
+            mLayoutParams = layoutParams;
+            mCategory = category;
+            mParent = parent;
+        }
+
+        /**
+         * Returns the {@link View} associated with the {@link Complication}. This is the instance
+         * passed in at construction. The reference to this {@link View} is captured when the
+         * {@link Complication} is added to the {@link ComplicationLayoutEngine}. The
+         * {@link Complication} cannot modify the {@link View} reference beyond this point.
+         */
+        private View getView() {
+            return mView;
+        }
+
+        /**
+         * Returns The {@link ComplicationLayoutParams} associated with the view.
+         */
+        public ComplicationLayoutParams getLayoutParams() {
+            return mLayoutParams;
+        }
+
+        /**
+         * Interprets the {@link #getLayoutParams()} into {@link ConstraintLayout.LayoutParams} and
+         * applies them to the view. The method accounts for the relationship of the {@link View} to
+         * the other {@link Complication} views around it. The organization of the {@link View}
+         * instances in {@link ComplicationLayoutEngine} can be seen as lists. A {@link View} is
+         * either the head of its list or a following node. This head is passed into this method,
+         * which can be a reference to the {@link View} to indicate it is the head.
+         */
+        public void applyLayoutParams(View head) {
+            // Only the basic dimension parameters from the base ViewGroup.LayoutParams are carried
+            // over verbatim from the complication specified LayoutParam. Other fields are
+            // interpreted.
+            final ConstraintLayout.LayoutParams params =
+                    new Constraints.LayoutParams(mLayoutParams.width, mLayoutParams.height);
+
+            final int direction = getLayoutParams().getDirection();
+
+            // If no parent, view is the anchor. In this case, it is given the highest priority for
+            // alignment. All alignment preferences are done in relation to the parent container.
+            final boolean isRoot = head == mView;
+
+            // Each view can be seen as a vector, having a point (described here as position) and
+            // direction. When a view is the head of a position, then it is the first in a sequence
+            // of complications to appear from that position. For example, being the head for
+            // position POSITION_TOP | POSITION_END will cause the view to be shown as the first
+            // view in that corner. In this case, the positions specify which sides to align with
+            // the parent. If the view is not the head, the positions perpendicular to the direction
+            // of the view specify which side to align with the opposing side of the head view.
+            // Otherwise, the position aligns with the containing view. This means a
+            // POSITION_BOTTOM | POSITION_START with DIRECTION_UP non-head view's bottom to be
+            // aligned with the preceding view node's top and start to be aligned with the
+            // parent's start.
+            mLayoutParams.iteratePositions(position -> {
+                switch(position) {
+                    case ComplicationLayoutParams.POSITION_START:
+                        if (isRoot || direction != ComplicationLayoutParams.DIRECTION_END) {
+                            params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
+                        } else {
+                            params.startToEnd = head.getId();
+                        }
+                        break;
+                    case ComplicationLayoutParams.POSITION_TOP:
+                        if (isRoot || direction != ComplicationLayoutParams.DIRECTION_DOWN) {
+                            params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
+                        } else {
+                            params.topToBottom = head.getId();
+                        }
+                        break;
+                    case ComplicationLayoutParams.POSITION_BOTTOM:
+                        if (isRoot || direction != ComplicationLayoutParams.DIRECTION_UP) {
+                            params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
+                        } else {
+                            params.bottomToTop = head.getId();
+                        }
+                        break;
+                    case ComplicationLayoutParams.POSITION_END:
+                        if (isRoot || direction != ComplicationLayoutParams.DIRECTION_START) {
+                            params.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
+                        } else {
+                            params.endToStart = head.getId();
+                        }
+                        break;
+                }
+            });
+
+            mView.setLayoutParams(params);
+        }
+
+        /**
+         * Informs the {@link ViewEntry}'s parent entity to remove the {@link ViewEntry} from
+         * being shown further.
+         */
+        public void remove() {
+            mParent.removeEntry(this);
+
+            ((ViewGroup) mView.getParent()).removeView(mView);
+        }
+
+        @Override
+        public int compareTo(ViewEntry viewEntry) {
+            // If the two entries have different categories, system complications take precedence.
+            if (viewEntry.mCategory != mCategory) {
+                // Note that this logic will need to be adjusted if more categories are introduced.
+                return mCategory == Complication.CATEGORY_SYSTEM ? 1 : -1;
+            }
+
+            // A higher weight indicates greater precedence if all else being equal.
+            if (viewEntry.mLayoutParams.getWeight() != mLayoutParams.getWeight()) {
+                return mLayoutParams.getWeight() > viewEntry.mLayoutParams.getWeight() ? 1 : -1;
+            }
+
+            return 0;
+        }
+
+        /**
+         * {@link Builder} allows for a multiple entities to contribute to the {@link ViewEntry}
+         * construction. This is necessary for setting an immutable parent, which might not be
+         * known until the view hierarchy is traversed.
+         */
+        private static class Builder {
+            private final View mView;
+            private final ComplicationLayoutParams mLayoutParams;
+            private final int mCategory;
+            private Parent mParent;
+
+            Builder(View view, ComplicationLayoutParams lp, @Complication.Category int category) {
+                mView = view;
+                mLayoutParams = lp;
+                mCategory = category;
+            }
+
+            /**
+             * Returns the set {@link ComplicationLayoutParams}
+             */
+            public ComplicationLayoutParams getLayoutParams() {
+                return mLayoutParams;
+            }
+
+            /**
+             * Returns the set {@link Complication.Category}.
+             */
+            @Complication.Category
+            public int getCategory() {
+                return mCategory;
+            }
+
+            /**
+             * Sets the parent. Note that this references to the entity for handling events, such as
+             * requesting the removal of the {@link View}. It is not the
+             * {@link android.view.ViewGroup} which contains the {@link View}.
+             */
+            Builder setParent(Parent parent) {
+                mParent = parent;
+                return this;
+            }
+
+            /**
+             * Builds and returns the resulting {@link ViewEntry}.
+             */
+            ViewEntry build() {
+                return new ViewEntry(mView, mLayoutParams, mCategory, mParent);
+            }
+        }
+
+        /**
+         * An interface allowing an {@link ViewEntry} to signal events.
+         */
+        interface Parent {
+            /**
+             * Indicates the {@link ViewEntry} requests removal.
+             */
+            void removeEntry(ViewEntry entry);
+        }
+    }
+
+    /**
+     * {@link PositionGroup} represents a collection of {@link Complication} at a given location.
+     * It further organizes the {@link Complication} by the direction in which they emanate from
+     * this position.
+     */
+    private static class PositionGroup implements DirectionGroup.Parent {
+        private final HashMap<Integer, DirectionGroup> mDirectionGroups = new HashMap<>();
+
+        /**
+         * Invoked by the {@link PositionGroup} holder to introduce a {@link Complication} view to
+         * this group. It is assumed that the caller has correctly identified this
+         * {@link PositionGroup} as the proper home for the {@link Complication} based on its
+         * declared position.
+         */
+        public ViewEntry add(ViewEntry.Builder entryBuilder) {
+            final int direction = entryBuilder.getLayoutParams().getDirection();
+            if (!mDirectionGroups.containsKey(direction)) {
+                mDirectionGroups.put(direction, new DirectionGroup(this));
+            }
+
+            return mDirectionGroups.get(direction).add(entryBuilder);
+        }
+
+        @Override
+        public void onEntriesChanged() {
+            // Whenever an entry is added/removed from a child {@link DirectionGroup}, it is vital
+            // that all {@link DirectionGroup} children are visited. It is possible the overall
+            // head has changed, requiring constraints to be adjusted.
+            updateViews();
+        }
+
+        private void updateViews() {
+            ViewEntry head = null;
+
+            // Identify which {@link Complication} head from the set of {@link DirectionGroup}
+            // should be treated as the {@link PositionGroup} head.
+            for (DirectionGroup directionGroup : mDirectionGroups.values()) {
+                final ViewEntry groupHead = directionGroup.getHead();
+                if (head == null || (groupHead != null && groupHead.compareTo(head) > 0)) {
+                    head = groupHead;
+                }
+            }
+
+            // A headless position group indicates no complications.
+            if (head == null) {
+                return;
+            }
+
+            for (DirectionGroup directionGroup : mDirectionGroups.values()) {
+                // Tell each {@link DirectionGroup} to update its containing {@link ViewEntry} based
+                // on the identified head. This iteration will also capture any newly added views.
+                directionGroup.updateViews(head.getView());
+            }
+        }
+    }
+
+    /**
+     * A {@link DirectionGroup} organizes the {@link ViewEntry} of a parent group that point are
+     * laid out in the same direction.
+     */
+    private static class DirectionGroup implements ViewEntry.Parent {
+        /**
+         * An interface implemented by the {@link DirectionGroup} parent to receive updates.
+         */
+        interface Parent {
+            /**
+             * Invoked to indicate a change to the {@link ViewEntry} composition for this
+             * {@link DirectionGroup}.
+             */
+            void onEntriesChanged();
+        }
+        private final ArrayList<ViewEntry> mViews = new ArrayList<>();
+        private final Parent mParent;
+
+        /**
+         * Creates a new {@link DirectionGroup} with the specified parent. Note that the
+         * {@link DirectionGroup} does not store its own direction. It is the responsibility of the
+         * {@link DirectionGroup.Parent} to maintain this association.
+         */
+        DirectionGroup(Parent parent) {
+            mParent = parent;
+        }
+
+        /**
+         * Returns the head of the group. It is assumed that the order of the {@link ViewEntry} is
+         * proactively maintained.
+         */
+        public ViewEntry getHead() {
+            return mViews.isEmpty() ? null : mViews.get(0);
+        }
+
+        /**
+         * Adds a {@link ViewEntry} via {@link ViewEntry.Builder} to this group.
+         */
+        public ViewEntry add(ViewEntry.Builder entryBuilder) {
+            final ViewEntry entry = entryBuilder.setParent(this).build();
+            mViews.add(entry);
+
+            // After adding view, reverse sort collection.
+            Collections.sort(mViews);
+            Collections.reverse(mViews);
+
+            mParent.onEntriesChanged();
+
+            return entry;
+        }
+
+        @Override
+        public void removeEntry(ViewEntry entry) {
+            // Sort is handled when the view is added, so should still be correct after removal.
+            // However, the head may have been removed, which may affect the layout of views in
+            // other DirectionGroups of the same PositionGroup.
+            mViews.remove(entry);
+            mParent.onEntriesChanged();
+        }
+
+        /**
+         * Invoked by {@link Parent} to update the layout of all children {@link ViewEntry} with
+         * the specified head. Note that the head might not be in this group and instead part of a
+         * neighboring group.
+         */
+        public void updateViews(View groupHead) {
+            Iterator<ViewEntry> it = mViews.iterator();
+
+            while (it.hasNext()) {
+                final ViewEntry viewEntry = it.next();
+                viewEntry.applyLayoutParams(groupHead);
+                groupHead = viewEntry.getView();
+            }
+        }
+    }
+
+    private final ConstraintLayout mLayout;
+    private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>();
+    private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>();
+
+    /** */
+    @Inject
+    public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout) {
+        mLayout = layout;
+    }
+
+    /**
+     * Adds a complication to this {@link ComplicationLayoutEngine}.
+     * @param id A {@link ComplicationId} unique to this complication. If this matches a
+     *           complication within this {@link ComplicationViewModel}, the existing complication
+     *           will be removed.
+     * @param view The {@link View} to be shown.
+     * @param lp The {@link ComplicationLayoutParams} as expressed by the {@link Complication}.
+     *           These will be interpreted into the final applied parameters.
+     * @param category The {@link Complication.Category} for the {@link Complication}.
+     */
+    public void addComplication(ComplicationId id, View view,
+            ComplicationLayoutParams lp, @Complication.Category int category) {
+        // If the complication is present, remove.
+        if (mEntries.containsKey(id)) {
+            removeComplication(id);
+        }
+
+        final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, lp, category);
+
+        // Add position group if doesn't already exist
+        final int position = lp.getPosition();
+        if (!mPositions.containsKey(position)) {
+            mPositions.put(position, new PositionGroup());
+        }
+
+        // Insert entry into group
+        final ViewEntry entry = mPositions.get(position).add(entryBuilder);
+        mEntries.put(id, entry);
+
+        mLayout.addView(entry.getView());
+    }
+
+    /**
+     * Removes a complication by {@link ComplicationId}.
+     */
+    public void removeComplication(ComplicationId id) {
+        if (!mEntries.containsKey(id)) {
+            Log.e(TAG, "could not find id:" + id);
+            return;
+        }
+
+        final ViewEntry entry = mEntries.get(id);
+        entry.remove();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
new file mode 100644
index 0000000..f9a69fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import android.annotation.IntDef;
+import android.view.ViewGroup;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * {@link ComplicationLayoutParams} allows a {@link Complication} to express its preferred location
+ * and dimensions. Note that these parameters are not directly applied by any {@link ViewGroup}.
+ * They are instead consulted for the final parameters which best seem fit for usage.
+ */
+public class ComplicationLayoutParams extends ViewGroup.LayoutParams {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "POSITION_" }, value = {
+            POSITION_TOP,
+            POSITION_END,
+            POSITION_BOTTOM,
+            POSITION_START,
+    })
+
+    @interface Position {}
+    /** Align view with the top of parent or bottom of preceding {@link Complication}. */
+    static final int POSITION_TOP = 1 << 0;
+    /** Align view with the bottom of parent or top of preceding {@link Complication}. */
+    static final int POSITION_BOTTOM = 1 << 1;
+    /** Align view with the start of parent or end of preceding {@link Complication}. */
+    static final int POSITION_START = 1 << 2;
+    /** Align view with the end of parent or start of preceding {@link Complication}. */
+    static final int POSITION_END = 1 << 3;
+
+    private static final int FIRST_POSITION = POSITION_TOP;
+    private static final int LAST_POSITION = POSITION_END;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "DIRECTION_" }, value = {
+            DIRECTION_UP,
+            DIRECTION_DOWN,
+            DIRECTION_START,
+            DIRECTION_END,
+    })
+
+    @interface Direction {}
+    /** Position view upward from position. */
+    static final int DIRECTION_UP = 1 << 0;
+    /** Position view downward from position. */
+    static final int DIRECTION_DOWN = 1 << 1;
+    /** Position view towards the start of the parent. */
+    static final int DIRECTION_START = 1 << 2;
+    /** Position view towards the end of parent. */
+    static final int DIRECTION_END = 1 << 3;
+
+    @Position
+    private final int mPosition;
+
+    @Direction
+    private final int mDirection;
+
+    private final int mWeight;
+
+    // Do not allow specifying opposite positions
+    private static final int[] INVALID_POSITIONS =
+            { POSITION_BOTTOM | POSITION_TOP, POSITION_END | POSITION_START };
+
+    // Do not allow for specifying a direction towards the outside of the container.
+    private static final Map<Integer, Integer> INVALID_DIRECTIONS;
+    static {
+        INVALID_DIRECTIONS = new HashMap<>();
+        INVALID_DIRECTIONS.put(POSITION_BOTTOM, DIRECTION_DOWN);
+        INVALID_DIRECTIONS.put(POSITION_TOP, DIRECTION_UP);
+        INVALID_DIRECTIONS.put(POSITION_START, DIRECTION_START);
+        INVALID_DIRECTIONS.put(POSITION_END, DIRECTION_END);
+    }
+
+    /**
+     * Constructs a {@link ComplicationLayoutParams}.
+     * @param width The width {@link android.view.View.MeasureSpec} for the view.
+     * @param height The height {@link android.view.View.MeasureSpec} for the view.
+     * @param position The place within the parent container where the view should be positioned.
+     * @param direction The direction the view should be laid out from either the parent container
+     *                  or preceding view.
+     * @param weight The weight that should be considered for this view when compared to other
+     *               views. This has an impact on the placement of the view but not the rendering of
+     *               the view.
+     */
+    public ComplicationLayoutParams(int width, int height, @Position int position,
+            @Direction int direction, int weight) {
+        super(width, height);
+
+        if (!validatePosition(position)) {
+            throw new IllegalArgumentException("invalid position:" + position);
+        }
+        mPosition = position;
+
+        if (!validateDirection(position, direction)) {
+            throw new IllegalArgumentException("invalid direction:" + direction);
+        }
+
+        mDirection = direction;
+
+        mWeight = weight;
+    }
+
+    /**
+     * Constructs {@link ComplicationLayoutParams} from an existing instance.
+     */
+    public ComplicationLayoutParams(ComplicationLayoutParams source) {
+        super(source);
+        mPosition = source.mPosition;
+        mDirection = source.mDirection;
+        mWeight = source.mWeight;
+    }
+
+    private static boolean validateDirection(@Position int position, @Direction int direction) {
+        for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
+                currentPosition <<= 1) {
+            if ((position & currentPosition) == currentPosition
+                    && INVALID_DIRECTIONS.containsKey(currentPosition)
+                    && (direction & INVALID_DIRECTIONS.get(currentPosition)) != 0) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Iterates over the defined positions and invokes the specified {@link Consumer} for each
+     * position specified for this {@link ComplicationLayoutParams}.
+     */
+    public void iteratePositions(Consumer<Integer> consumer) {
+        for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
+                currentPosition <<= 1) {
+            if ((mPosition & currentPosition) == currentPosition) {
+                consumer.accept(currentPosition);
+            }
+        }
+    }
+
+    private static boolean validatePosition(@Position int position) {
+        if (position == 0) {
+            return false;
+        }
+
+        for (int combination : INVALID_POSITIONS) {
+            if ((position & combination) == combination) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Direction
+    public int getDirection() {
+        return mDirection;
+    }
+
+    @Position
+    public int getPosition() {
+        return mPosition;
+    }
+
+    public int getWeight() {
+        return mWeight;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
new file mode 100644
index 0000000..f023937
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import androidx.lifecycle.ViewModel;
+
+import javax.inject.Inject;
+
+/**
+ * {@link ComplicationViewModel} is an abstraction over {@link Complication}, providing the model
+ * from which any view-related interpretation of the {@link Complication} should be derived from.
+ */
+public class ComplicationViewModel extends ViewModel {
+    private final Complication mComplication;
+    private final ComplicationId mId;
+    private final Complication.Host mHost;
+
+    /**
+     * Default constructor for generating a {@link ComplicationViewModel}.
+     * @param complication The {@link Complication} represented by the view model.
+     * @param id The {@link ComplicationId} tied to this {@link Complication}.
+     * @param host The environment {@link Complication.Host}.
+     */
+    @Inject
+    public ComplicationViewModel(Complication complication, ComplicationId id,
+            Complication.Host host) {
+        mComplication = complication;
+        mId = id;
+        mHost = host;
+    }
+
+    /**
+     * Returns the {@link ComplicationId} for this {@link Complication} for stable id association.
+     */
+    public ComplicationId getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the underlying {@link Complication}. Should only as a redirection - for example,
+     * using the {@link Complication} to generate view. Any property should be surfaced through
+     * this ViewModel.
+     */
+    public Complication getComplication() {
+        return mComplication;
+    }
+
+    /**
+     * Requests the dream exit on behalf of the {@link Complication}.
+     */
+    public void exitDream() {
+        mHost.requestExitDream();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelProvider.java
new file mode 100644
index 0000000..cc17ea1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelStore;
+
+import com.android.systemui.dreams.complication.dagger.DaggerViewModelProviderFactory;
+
+import javax.inject.Inject;
+
+/**
+ * An intermediary to generate {@link ComplicationViewModel} tracked with a {@link ViewModelStore}.
+ */
+public class ComplicationViewModelProvider extends ViewModelProvider {
+    @Inject
+    public ComplicationViewModelProvider(ViewModelStore store, ComplicationViewModel viewModel) {
+        super(store, new DaggerViewModelProviderFactory(() -> viewModel));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformer.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformer.java
new file mode 100644
index 0000000..5d113dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import com.android.systemui.dreams.complication.dagger.ComplicationViewModelComponent;
+
+import java.util.HashMap;
+
+import javax.inject.Inject;
+
+/**
+ * The {@link ComplicationViewModelTransformer} responsibility is provide a mapping from
+ * {@link Complication} to {@link ComplicationViewModel}.
+ */
+public class ComplicationViewModelTransformer {
+    private final ComplicationId.Factory mComplicationIdFactory = new ComplicationId.Factory();
+    private final HashMap<Complication, ComplicationId> mComplicationIdMapping = new HashMap<>();
+    private final ComplicationViewModelComponent.Factory mViewModelComponentFactory;
+
+    @Inject
+    public ComplicationViewModelTransformer(
+            ComplicationViewModelComponent.Factory viewModelComponentFactory) {
+        mViewModelComponentFactory = viewModelComponentFactory;
+    }
+
+    /**
+     * Generates {@link ComplicationViewModel} from a {@link Complication}.
+     */
+    public ComplicationViewModel getViewModel(Complication complication) {
+        final ComplicationId id = getComplicationId(complication);
+        return mViewModelComponentFactory.create(complication, id)
+                .getViewModelProvider().get(id.toString(), ComplicationViewModel.class);
+    }
+
+    private ComplicationId getComplicationId(Complication complication) {
+        if (!mComplicationIdMapping.containsKey(complication)) {
+            mComplicationIdMapping.put(complication, mComplicationIdFactory.getNextId());
+        }
+
+        return mComplicationIdMapping.get(complication);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewComponent.java
new file mode 100644
index 0000000..4cc905e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewComponent.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link ComplicationHostViewComponent} encapsulates the shared logic around the host view layer
+ * for complications. Anything that references the layout should be provided through this component
+ * and its child module. The factory should be used in order to best tie the lifetime of the view
+ * to components.
+ */
+@Subcomponent(modules = {
+        ComplicationHostViewComponent.ComplicationHostViewModule.class,
+})
+@ComplicationHostViewComponent.ComplicationHostViewScope
+public interface ComplicationHostViewComponent {
+    String SCOPED_COMPLICATIONS_LAYOUT = "scoped_complications_layout";
+
+    /** Scope annotation for singleton items within {@link ComplicationHostViewComponent}. */
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface ComplicationHostViewScope {}
+
+    /**
+     * Factory for generating a new scoped component.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        ComplicationHostViewComponent create();
+    }
+
+    /** */
+    ComplicationHostViewController getController();
+
+    /**
+     * Module for providing a scoped host view.
+     */
+    @Module
+    abstract class ComplicationHostViewModule {
+        /**
+         * Generates a {@link ConstraintLayout}, which can host
+         * {@link com.android.systemui.dreams.complication.Complication} instances.
+         */
+        @Provides
+        @Named(SCOPED_COMPLICATIONS_LAYOUT)
+        @ComplicationHostViewScope
+        static ConstraintLayout providesComplicationHostView(
+                LayoutInflater layoutInflater) {
+            return Preconditions.checkNotNull((ConstraintLayout)
+                            layoutInflater.inflate(R.layout.dream_overlay_complications_layer,
+                                    null),
+                    "R.layout.dream_overlay_complications_layer did not properly inflated");
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
new file mode 100644
index 0000000..b29e8c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelStore;
+
+import com.android.systemui.dreams.complication.ComplicationCollectionViewModel;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Module for housing components related to rendering complications.
+ */
+@Module(subcomponents = {
+        ComplicationViewModelComponent.class,
+        ComplicationHostViewComponent.class,
+})
+public interface ComplicationModule {
+    String SCOPED_COMPLICATIONS_MODEL = "scoped_complications_model";
+
+    /** Scope annotation for singleton items within the {@link ComplicationModule}. */
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface ComplicationScope {}
+
+    /**
+     * The complication collection is provided through this way to ensure that the instances are
+     * tied to the {@link ViewModelStore}.
+     */
+    @Provides
+    @Named(SCOPED_COMPLICATIONS_MODEL)
+    static ComplicationCollectionViewModel providesComplicationCollectionViewModel(
+            ViewModelStore store, ComplicationCollectionViewModel viewModel) {
+        final ViewModelProvider provider = new ViewModelProvider(store,
+                new DaggerViewModelProviderFactory(() -> viewModel));
+
+        return provider.get(ComplicationCollectionViewModel.class);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationViewModelComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationViewModelComponent.java
new file mode 100644
index 0000000..703cd28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationViewModelComponent.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication.dagger;
+
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationId;
+import com.android.systemui.dreams.complication.ComplicationViewModelProvider;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * The {@link ComplicationViewModelComponent} allows for a
+ * {@link com.android.systemui.dreams.complication.ComplicationViewModel} for a particular
+ * {@link Complication}. This component binds these instance specific values to allow injection with
+ * values provided at the wider scope.
+ */
+@Subcomponent
+public interface ComplicationViewModelComponent {
+    /**
+     * Factory for generating {@link ComplicationViewModelComponent}.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        ComplicationViewModelComponent create(@BindsInstance Complication complication,
+                @BindsInstance ComplicationId id);
+    }
+
+    /** */
+    ComplicationViewModelProvider getViewModelProvider();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DaggerViewModelProviderFactory.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DaggerViewModelProviderFactory.java
new file mode 100644
index 0000000..8ffedec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DaggerViewModelProviderFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication.dagger;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+/**
+ * {@link DaggerViewModelProviderFactory} is a wrapper around a lambda allowing for uses of Dagger
+ * component.
+ */
+public class DaggerViewModelProviderFactory implements ViewModelProvider.Factory {
+    /**
+     * An interface for providing a {@link ViewModel} through
+     * {@link DaggerViewModelProviderFactory}.
+     */
+    public interface ViewModelCreator {
+        /**
+         * Creates a {@link ViewModel} to be returned.
+         */
+        ViewModel create();
+    }
+
+    private final ViewModelCreator mCreator;
+
+    public DaggerViewModelProviderFactory(ViewModelCreator creator) {
+        mCreator = creator;
+    }
+
+    @NonNull
+    @Override
+    public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
+        return (T) mCreator.create();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 072f50d..d5053a0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -22,6 +22,7 @@
  * Dagger Module providing Communal-related functionality.
  */
 @Module(subcomponents = {
-        DreamOverlayComponent.class})
+        DreamOverlayComponent.class,
+})
 public interface DreamModule {
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
index c90332b..f0ab696 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -18,25 +18,36 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ViewModelStore;
+
 import com.android.systemui.dreams.DreamOverlayContainerViewController;
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.dagger.ComplicationModule;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 
 import javax.inject.Scope;
 
+import dagger.BindsInstance;
 import dagger.Subcomponent;
 
 /**
  * Dagger subcomponent for {@link DreamOverlayModule}.
  */
-@Subcomponent(modules = {DreamOverlayModule.class})
+@Subcomponent(modules = {
+        DreamOverlayModule.class,
+        ComplicationModule.class,
+})
 @DreamOverlayComponent.DreamOverlayScope
 public interface DreamOverlayComponent {
     /** Simple factory for {@link DreamOverlayComponent}. */
     @Subcomponent.Factory
     interface Factory {
-        DreamOverlayComponent create();
+        DreamOverlayComponent create(@BindsInstance ViewModelStore store,
+                @BindsInstance Complication.Host host);
     }
 
     /** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
@@ -46,6 +57,11 @@
     @interface DreamOverlayScope {}
 
     /** Builds a {@link DreamOverlayContainerViewController}. */
-    @DreamOverlayScope
     DreamOverlayContainerViewController getDreamOverlayContainerViewController();
+
+    /** Builds a {@link androidx.lifecycle.LifecycleRegistry} */
+    LifecycleRegistry getLifecycleRegistry();
+
+    /** Builds a {@link androidx.lifecycle.LifecycleOwner} */
+    LifecycleOwner getLifecycleOwner();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index d291203..b56aa2c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -22,6 +22,9 @@
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
@@ -36,6 +39,7 @@
 
 import javax.inject.Named;
 
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -124,4 +128,16 @@
         return resources.getInteger(
                 R.integer.config_dreamOverlayBurnInProtectionUpdateIntervalMillis);
     }
+
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
+        return () -> lifecycleRegistryLazy.get();
+    }
+
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    static LifecycleRegistry providesLifecycleRegistry(LifecycleOwner lifecycleOwner) {
+        return new LifecycleRegistry(lifecycleOwner);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index e24df30..490f7c1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -55,6 +55,9 @@
     public static final BooleanFlag NSSL_DEBUG_REMOVE_ANIMATION =
             new BooleanFlag(106, false);
 
+    public static final BooleanFlag NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE =
+            new BooleanFlag(107, false);
+
     /***************************************/
     // 200 - keyguard/lockscreen
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 08e1654..701d139 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -113,6 +113,7 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -318,6 +319,7 @@
     // the properties of the keyguard
 
     private final KeyguardUpdateMonitor mUpdateMonitor;
+    private final Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
 
     /**
      * Last SIM state reported by the telephony system.
@@ -833,7 +835,8 @@
             ScreenOffAnimationController screenOffAnimationController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             ScreenOnCoordinator screenOnCoordinator,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy) {
         super(context);
         mFalsingCollector = falsingCollector;
         mLockPatternUtils = lockPatternUtils;
@@ -850,6 +853,7 @@
         dumpManager.registerDumpable(getClass().getName(), this);
         mDeviceConfig = deviceConfig;
         mScreenOnCoordinator = screenOnCoordinator;
+        mNotificationShadeWindowControllerLazy = notificationShadeWindowControllerLazy;
         mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
@@ -1837,10 +1841,14 @@
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
                     StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
-                    handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration,
-                            params.mApps, params.mWallpapers, params.mNonApps,
-                            params.mFinishedCallback);
-                    mFalsingCollector.onSuccessfulUnlock();
+                    mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams(
+                            () -> {
+                                handleStartKeyguardExitAnimation(params.startTime,
+                                        params.fadeoutDuration,
+                                        params.mApps, params.mWallpapers, params.mNonApps,
+                                        params.mFinishedCallback);
+                                mFalsingCollector.onSuccessfulUnlock();
+                            });
                     Trace.endSection();
                     break;
                 case CANCEL_KEYGUARD_EXIT_ANIM:
@@ -2139,10 +2147,12 @@
                 mKeyguardGoingAwayRunnable.run();
             } else {
                 // TODO(bc-unlock): Fill parameters
-                handleStartKeyguardExitAnimation(
-                        SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
-                        mHideAnimation.getDuration(), null /* apps */,  null /* wallpapers */,
-                        null /* nonApps */, null /* finishedCallback */);
+                mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams(() -> {
+                    handleStartKeyguardExitAnimation(
+                            SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
+                            mHideAnimation.getDuration(), null /* apps */, null /* wallpapers */,
+                            null /* nonApps */, null /* finishedCallback */);
+                });
             }
         }
         Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index dd844e8..f14d130 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -44,6 +44,7 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
@@ -98,7 +99,8 @@
             ScreenOffAnimationController screenOffAnimationController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             ScreenOnCoordinator screenOnCoordinator,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            Lazy<NotificationShadeWindowController> notificationShadeWindowController) {
         return new KeyguardViewMediator(
                 context,
                 falsingCollector,
@@ -122,7 +124,8 @@
                 screenOffAnimationController,
                 notificationShadeDepthController,
                 screenOnCoordinator,
-                interactionJankMonitor
+                interactionJankMonitor,
+                notificationShadeWindowController
         );
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
new file mode 100644
index 0000000..0656f5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.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 com.android.systemui.log;
+
+import static android.app.StatusBarManager.ALL_SESSIONS;
+import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.CoreStartable;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Track Session InstanceIds to be used for metrics logging to correlate logs in the same
+ * session. Can be used across processes via StatusBarManagerService#registerSessionListener
+ */
+@SysUISingleton
+public class SessionTracker extends CoreStartable {
+    private static final String TAG = "SessionTracker";
+    private static final boolean DEBUG = false;
+
+    // At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
+    private final InstanceIdSequence mInstanceIdGenerator = new InstanceIdSequence(1 << 20);
+
+    private final IStatusBarService mStatusBarManagerService;
+    private final AuthController mAuthController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final KeyguardStateController mKeyguardStateController;
+    private final Map<Integer, InstanceId> mSessionToInstanceId = new HashMap<>();
+
+    private boolean mKeyguardSessionStarted;
+
+    @Inject
+    public SessionTracker(
+            Context context,
+            IStatusBarService statusBarService,
+            AuthController authController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            KeyguardStateController keyguardStateController
+    ) {
+        super(context);
+        mStatusBarManagerService = statusBarService;
+        mAuthController = authController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mKeyguardStateController = keyguardStateController;
+    }
+
+    @Override
+    public void start() {
+        mAuthController.addCallback(mAuthControllerCallback);
+        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+        mKeyguardStateController.addCallback(mKeyguardStateCallback);
+
+        mKeyguardSessionStarted = mKeyguardStateController.isShowing();
+        if (mKeyguardSessionStarted) {
+            startSession(SESSION_KEYGUARD);
+        }
+    }
+
+    /**
+     * Get the session ID associated with the passed session type.
+     */
+    public @Nullable InstanceId getSessionId(int type) {
+        return mSessionToInstanceId.getOrDefault(type, null);
+    }
+
+    private void startSession(int type) {
+        if (mSessionToInstanceId.getOrDefault(type, null) != null) {
+            Log.e(TAG, "session [" + getString(type) + "] was already started");
+            return;
+        }
+
+        final InstanceId instanceId = mInstanceIdGenerator.newInstanceId();
+        mSessionToInstanceId.put(type, instanceId);
+        try {
+            if (DEBUG) {
+                Log.d(TAG, "Session start for [" + getString(type) + "] id=" + instanceId);
+            }
+            mStatusBarManagerService.onSessionStarted(type, instanceId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to send onSessionStarted for session="
+                    + "[" + getString(type) + "]", e);
+        }
+    }
+
+    private void endSession(int type) {
+        if (mSessionToInstanceId.getOrDefault(type, null) == null) {
+            Log.e(TAG, "session [" + getString(type) + "] was not started");
+            return;
+        }
+
+        final InstanceId instanceId = mSessionToInstanceId.get(type);
+        mSessionToInstanceId.put(type, null);
+        try {
+            if (DEBUG) {
+                Log.d(TAG, "Session end for [" + getString(type) + "] id=" + instanceId);
+            }
+            mStatusBarManagerService.onSessionEnded(type, instanceId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to send onSessionEnded for session="
+                    + "[" + getString(type) + "]", e);
+        }
+    }
+
+    public KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+            new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onStartedGoingToSleep(int why) {
+            // we need to register to the KeyguardUpdateMonitor lifecycle b/c it gets called
+            // before the WakefulnessLifecycle
+            if (mKeyguardSessionStarted) {
+                return;
+            }
+
+            mKeyguardSessionStarted = true;
+            startSession(SESSION_KEYGUARD);
+        }
+    };
+
+
+    public KeyguardStateController.Callback mKeyguardStateCallback =
+            new KeyguardStateController.Callback() {
+        public void onKeyguardShowingChanged() {
+            boolean wasSessionStarted = mKeyguardSessionStarted;
+            boolean keyguardShowing = mKeyguardStateController.isShowing();
+            if (keyguardShowing && !wasSessionStarted) {
+                mKeyguardSessionStarted = true;
+                startSession(SESSION_KEYGUARD);
+            } else if (!keyguardShowing && wasSessionStarted) {
+                mKeyguardSessionStarted = false;
+                endSession(SESSION_KEYGUARD);
+            }
+        }
+    };
+
+    public AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+        @Override
+        public void onBiometricPromptShown() {
+            startSession(SESSION_BIOMETRIC_PROMPT);
+        }
+
+        @Override
+        public void onBiometricPromptDismissed() {
+            endSession(SESSION_BIOMETRIC_PROMPT);
+        }
+    };
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        for (int session : ALL_SESSIONS) {
+            pw.println("  " + getString(session)
+                    + " instanceId=" + mSessionToInstanceId.get(session));
+        }
+    }
+
+    /**
+     * @return the string representation of a SINGLE SessionFlag. Combined SessionFlags will be
+     * considered unknown.
+     */
+    public static String getString(int sessionType) {
+        if (sessionType == SESSION_KEYGUARD) {
+            return "KEYGUARD";
+        } else if (sessionType == SESSION_BIOMETRIC_PROMPT) {
+            return "BIOMETRIC_PROMPT";
+        }
+
+        return "unknownType=" + sessionType;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index e465ae4..85c9644 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -77,6 +77,10 @@
 
     @Override
     public int getItemCount() {
+        if (mController.isZeroMode()) {
+            // Add extra one for "pair new" or dynamic group
+            return mController.getMediaDevices().size() + 1;
+        }
         return mController.getMediaDevices().size();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt
new file mode 100644
index 0000000..0453fdb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.media.nearby
+
+import com.android.systemui.dagger.SysUISingleton
+
+/**
+ * A manager that returns information about devices that are nearby and can receive media transfers.
+ */
+@SysUISingleton
+class MediaNearbyDevicesManager {
+
+    /** Returns a list containing the current nearby devices. */
+    fun getCurrentNearbyDevices(): List<NearbyDevice> {
+        // TODO(b/216313420): Implement this function.
+        return emptyList()
+    }
+
+    /**
+     * Registers [callback] to be notified each time a device's range changes or when a new device
+     * comes within range.
+     */
+    fun registerNearbyDevicesCallback(
+        callback: (device: NearbyDevice) -> Unit
+    ) {
+        // TODO(b/216313420): Implement this function.
+    }
+
+    /**
+     * Un-registers [callback]. See [registerNearbyDevicesCallback].
+     */
+    fun unregisterNearbyDevicesCallback(
+        callback: (device: NearbyDevice) -> Unit
+    ) {
+        // TODO(b/216313420): Implement this function.
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt
new file mode 100644
index 0000000..96b853f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt
@@ -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.systemui.media.nearby
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A parcelable representing a nearby device that can be used for media transfer.
+ *
+ * This class includes:
+ *   - [routeId] identifying the media route
+ *   - [rangeZone] specifying how far away the device with the media route is from this device.
+ */
+class NearbyDevice(parcel: Parcel) : Parcelable {
+    var routeId: String? = null
+    @RangeZone val rangeZone: Int
+
+    init {
+        routeId = parcel.readString() ?: "unknown"
+        rangeZone = parcel.readInt()
+    }
+
+    override fun describeContents() = 0
+
+    override fun writeToParcel(out: Parcel, flags: Int) {
+        out.writeString(routeId)
+        out.writeInt(rangeZone)
+    }
+
+    companion object CREATOR : Parcelable.Creator<NearbyDevice?> {
+        override fun createFromParcel(parcel: Parcel) = NearbyDevice(parcel)
+        override fun newArray(size: Int) = arrayOfNulls<NearbyDevice?>(size)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt
new file mode 100644
index 0000000..3c890bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.nearby
+
+import androidx.annotation.IntDef
+import kotlin.annotation.AnnotationRetention
+
+@IntDef(
+        RangeZone.RANGE_UNKNOWN,
+        RangeZone.RANGE_FAR,
+        RangeZone.RANGE_LONG,
+        RangeZone.RANGE_CLOSE,
+        RangeZone.RANGE_WITHIN_REACH
+)
+@Retention(AnnotationRetention.SOURCE)
+/** The various range zones a device can be in, in relation to the current device. */
+annotation class RangeZone {
+    companion object {
+        /** Unknown distance range. */
+        const val RANGE_UNKNOWN = 0
+        /** Distance is very far away from the peer device. */
+        const val RANGE_FAR = 1
+        /** Distance is relatively long from the peer device, typically a few meters. */
+        const val RANGE_LONG = 2
+        /** Distance is close to the peer device, typically with one or two meter. */
+        const val RANGE_CLOSE = 3
+        /** Distance is very close to the peer device, typically within one meter or less. */
+        const val RANGE_WITHIN_REACH = 4
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index ad4a2f4..dbd641b 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -58,7 +58,6 @@
 import com.android.settingslib.Utils;
 import com.android.settingslib.fuelgauge.BatterySaverUtils;
 import com.android.settingslib.utils.PowerUtil;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.dagger.SysUISingleton;
@@ -421,7 +420,7 @@
                                     new Intent(Intent.ACTION_VIEW)
                                             .setData(Uri.parse(url))
                                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                            Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+                            mActivityStarter.startActivity(helpIntent,
                                     true /* dismissShade */, resultCode -> {
                                         mHighTempDialog = null;
                                     });
@@ -456,7 +455,7 @@
                                     new Intent(Intent.ACTION_VIEW)
                                             .setData(Uri.parse(url))
                                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                            Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+                            mActivityStarter.startActivity(helpIntent,
                                     true /* dismissShade */, resultCode -> {
                                         mThermalShutdownDialog = null;
                                     });
@@ -516,7 +515,7 @@
                     helpIntent.setClassName("com.android.settings",
                             "com.android.settings.HelpTrampoline");
                     helpIntent.putExtra(Intent.EXTRA_TEXT, contextString);
-                    Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+                    mActivityStarter.startActivity(helpIntent,
                             true /* dismissShade */, resultCode -> {
                                 mUsbHighTempDialog = null;
                             });
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 37a0f59..642af59 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -43,7 +43,6 @@
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
@@ -80,13 +79,13 @@
     @VisibleForTesting
     final Receiver mReceiver = new Receiver();
 
-    private PowerManager mPowerManager;
-    private WarningsUI mWarnings;
+    private final PowerManager mPowerManager;
+    private final WarningsUI mWarnings;
     private InattentiveSleepWarningView mOverlayView;
     private final Configuration mLastConfiguration = new Configuration();
     private int mPlugType = 0;
     private int mInvalidCharger = 0;
-    private EnhancedEstimates mEnhancedEstimates;
+    private final EnhancedEstimates mEnhancedEstimates;
     private Future mLastShowWarningTask;
     private boolean mEnableSkinTemperatureWarning;
     private boolean mEnableUsbTemperatureAlarm;
@@ -113,18 +112,20 @@
 
     @Inject
     public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher,
-            CommandQueue commandQueue, Lazy<Optional<StatusBar>> statusBarOptionalLazy) {
+            CommandQueue commandQueue, Lazy<Optional<StatusBar>> statusBarOptionalLazy,
+            WarningsUI warningsUI, EnhancedEstimates enhancedEstimates,
+            PowerManager powerManager) {
         super(context);
         mBroadcastDispatcher = broadcastDispatcher;
         mCommandQueue = commandQueue;
         mStatusBarOptionalLazy = statusBarOptionalLazy;
+        mWarnings = warningsUI;
+        mEnhancedEstimates = enhancedEstimates;
+        mPowerManager = powerManager;
     }
 
     public void start() {
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
-        mWarnings = Dependency.get(WarningsUI.class);
-        mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);
         mLastConfiguration.setTo(mContext.getResources().getConfiguration());
 
         ContentObserver obs = new ContentObserver(mHandler) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 1dba536..ded6ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -85,6 +85,7 @@
     private final QSPanelController mQsPanelController;
     private final QuickQSPanelController mQuickQSPanelController;
     private final QuickStatusBarHeader mQuickStatusBarHeader;
+    private final QSFgsManagerFooter mFgsManagerFooter;
     private final QSSecurityFooter mSecurityFooter;
     private final QS mQs;
     private final View mQSFooterActions;
@@ -151,7 +152,8 @@
     public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
             QSPanelController qsPanelController,
             QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
-            QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService,
+            QSFgsManagerFooter fgsManagerFooter, QSSecurityFooter securityFooter,
+            @Main Executor executor, TunerService tunerService,
             QSExpansionPathInterpolator qsExpansionPathInterpolator,
             @Named(QS_FOOTER) FooterActionsView qsFooterActionsView,
             @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) {
@@ -162,6 +164,7 @@
         mQuickStatusBarHeader = quickStatusBarHeader;
         mQQSFooterActions = qqsFooterActionsView;
         mQSFooterActions = qsFooterActionsView;
+        mFgsManagerFooter = fgsManagerFooter;
         mSecurityFooter = securityFooter;
         mHost = qsTileHost;
         mExecutor = executor;
@@ -481,6 +484,7 @@
 
             // Fade in the security footer and the divider as we reach the final position
             Builder builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
+            builder.addFloat(mFgsManagerFooter.getView(), "alpha", 0, 1);
             builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1);
             if (mQsPanelController.shouldUseHorizontalLayout()
                     && mQsPanelController.mMediaHost.hostView != null) {
@@ -490,6 +494,7 @@
                 mQsPanelController.mMediaHost.hostView.setAlpha(1.0f);
             }
             mAllPagesDelayedAnimator = builder.build();
+            mAllViews.add(mFgsManagerFooter.getView());
             mAllViews.add(mSecurityFooter.getView());
             translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
             qqsTranslationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
new file mode 100644
index 0000000..082de16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.qs;
+
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FGS_MANAGER_FOOTER_VIEW;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.fgsmanager.FgsManagerDialogFactory;
+import com.android.systemui.statusbar.policy.RunningFgsController;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Footer entry point for the foreground service manager
+ */
+public class QSFgsManagerFooter implements View.OnClickListener {
+
+    private final View mRootView;
+    private final TextView mFooterText;
+    private final Context mContext;
+    private final Executor mMainExecutor;
+    private final Executor mExecutor;
+    private final RunningFgsController mRunningFgsController;
+    private final FgsManagerDialogFactory mFgsManagerDialogFactory;
+
+    private boolean mIsInitialized = false;
+    private boolean mIsAvailable = false;
+
+    @Inject
+    QSFgsManagerFooter(@Named(QS_FGS_MANAGER_FOOTER_VIEW) View rootView,
+            @Main Executor mainExecutor, RunningFgsController runningFgsController,
+            @Background Executor executor,
+            FgsManagerDialogFactory fgsManagerDialogFactory) {
+        mRootView = rootView;
+        mFooterText = mRootView.findViewById(R.id.footer_text);
+        ImageView icon = mRootView.findViewById(R.id.primary_footer_icon);
+        icon.setImageResource(R.drawable.ic_info_outline);
+        mContext = rootView.getContext();
+        mMainExecutor = mainExecutor;
+        mExecutor = executor;
+        mRunningFgsController = runningFgsController;
+        mFgsManagerDialogFactory = fgsManagerDialogFactory;
+    }
+
+    public void init() {
+        if (mIsInitialized) {
+            return;
+        }
+
+        mRootView.setOnClickListener(this);
+
+        mRunningFgsController.addCallback(packages -> refreshState());
+
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, mExecutor,
+                (DeviceConfig.OnPropertiesChangedListener) properties -> {
+                    mIsAvailable = properties.getBoolean(TASK_MANAGER_ENABLED, mIsAvailable);
+                });
+        mIsAvailable = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, TASK_MANAGER_ENABLED, false);
+
+        mIsInitialized = true;
+    }
+
+    @Override
+    public void onClick(View view) {
+        mFgsManagerDialogFactory.create(mRootView);
+    }
+
+    public void refreshState() {
+        mExecutor.execute(this::handleRefreshState);
+    }
+
+    public View getView() {
+        return mRootView;
+    }
+
+    private boolean isAvailable() {
+        return mIsAvailable;
+    }
+
+    public void handleRefreshState() {
+        int numPackages = mRunningFgsController.getPackagesWithFgs().size();
+        mMainExecutor.execute(() -> {
+            mFooterText.setText(mContext.getResources().getQuantityString(
+                    R.plurals.fgs_manager_footer_label, numPackages, numPackages));
+            mRootView.setVisibility(numPackages > 0 && isAvailable() ? View.VISIBLE : View.GONE);
+        });
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 38061a8..6b515c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -86,6 +86,8 @@
             new ArrayList<>();
 
     @Nullable
+    protected View mFgsManagerFooter;
+    @Nullable
     protected View mSecurityFooter;
 
     @Nullable
@@ -448,7 +450,12 @@
             switchToParent(mSecurityFooter, mHeaderContainer, 0);
         } else {
             // Add after the footer
-            int index = indexOfChild(mFooter);
+            int index;
+            if (mFgsManagerFooter != null) {
+                index = indexOfChild(mFgsManagerFooter);
+            } else {
+                index = indexOfChild(mFooter);
+            }
             switchToParent(mSecurityFooter, this, index + 1);
         }
     }
@@ -722,6 +729,17 @@
         switchSecurityFooter(shouldUseSplitNotificationShade);
     }
 
+    /**
+     * Set the fgs manager footer view and switch it into the right place
+     * @param view the view in question
+     */
+    public void setFgsManagerFooter(View view) {
+        mFgsManagerFooter = view;
+        // Add after the footer
+        int index = indexOfChild(mFooter);
+        switchToParent(mFgsManagerFooter, this, index + 1);
+    }
+
     protected void setPageMargin(int pageMargin) {
         if (mTileLayout instanceof PagedTileLayout) {
             ((PagedTileLayout) mTileLayout).setPageMargin(pageMargin);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 001c740e..cbfe944 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -57,6 +57,7 @@
 public class QSPanelController extends QSPanelControllerBase<QSPanel> {
     public static final String QS_REMOVE_LABELS = "sysui_remove_labels";
 
+    private final QSFgsManagerFooter mQSFgsManagerFooter;
     private final QSSecurityFooter mQsSecurityFooter;
     private final TunerService mTunerService;
     private final QSCustomizerController mQsCustomizerController;
@@ -94,7 +95,8 @@
     };
 
     @Inject
-    QSPanelController(QSPanel view, QSSecurityFooter qsSecurityFooter, TunerService tunerService,
+    QSPanelController(QSPanel view, QSFgsManagerFooter qsFgsManagerFooter,
+            QSSecurityFooter qsSecurityFooter, TunerService tunerService,
             QSTileHost qstileHost, QSCustomizerController qsCustomizerController,
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QS_PANEL) MediaHost mediaHost,
@@ -105,6 +107,7 @@
             FalsingManager falsingManager, CommandQueue commandQueue) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager);
+        mQSFgsManagerFooter = qsFgsManagerFooter;
         mQsSecurityFooter = qsSecurityFooter;
         mTunerService = tunerService;
         mQsCustomizerController = qsCustomizerController;
@@ -128,6 +131,7 @@
         mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
         mQsCustomizerController.init();
         mBrightnessSliderController.init();
+        mQSFgsManagerFooter.init();
     }
 
     private void updateMediaExpansion() {
@@ -146,6 +150,7 @@
             refreshAllTiles();
         }
         mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
+        mView.setFgsManagerFooter(mQSFgsManagerFooter.getView());
         mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
         switchTileLayout(true);
         mBrightnessMirrorHandler.onQsPanelAttached();
@@ -230,6 +235,7 @@
     public void refreshAllTiles() {
         mBrightnessController.checkRestrictionAndSetEnabled();
         super.refreshAllTiles();
+        mQSFgsManagerFooter.refreshState();
         mQsSecurityFooter.refreshState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index d3bad16..90cf92a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -181,6 +181,7 @@
     }
 
     private void clearAccessibilityState() {
+        mNeedsFocus = false;
         if (mAccessibilityAction == ACTION_ADD) {
             // Remove blank tile from last spot
             mTiles.remove(--mEditIndex);
@@ -415,9 +416,6 @@
                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
                     holder.mTileView.removeOnLayoutChangeListener(this);
                     holder.mTileView.requestAccessibilityFocus();
-                    if (mAccessibilityAction == ACTION_NONE) {
-                        holder.mTileView.clearAccessibilityFocus();
-                    }
                 }
             });
             mNeedsFocus = false;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index 11e5b6e..1958caf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -53,6 +53,7 @@
  */
 @Module
 public interface QSFragmentModule {
+    String QS_FGS_MANAGER_FOOTER_VIEW = "qs_fgs_manager_footer";
     String QS_SECURITY_FOOTER_VIEW = "qs_security_footer";
     String QQS_FOOTER = "qqs_footer";
     String QS_FOOTER = "qs_footer";
@@ -205,4 +206,15 @@
     static StatusIconContainer providesStatusIconContainer(QuickStatusBarHeader qsHeader) {
         return qsHeader.findViewById(R.id.statusIcons);
     }
+
+    /** */
+    @Provides
+    @QSScope
+    @Named(QS_FGS_MANAGER_FOOTER_VIEW)
+    static View providesQSFgsManagerFooterView(
+            @QSThemedContext LayoutInflater layoutInflater,
+            QSPanel qsPanel
+    ) {
+        return layoutInflater.inflate(R.layout.quick_settings_security_footer, qsPanel, false);
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 5e68f61..86fc4de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -39,7 +39,6 @@
 import com.android.systemui.qs.tiles.DataSaverTile;
 import com.android.systemui.qs.tiles.DeviceControlsTile;
 import com.android.systemui.qs.tiles.DndTile;
-import com.android.systemui.qs.tiles.FgsManagerTile;
 import com.android.systemui.qs.tiles.FlashlightTile;
 import com.android.systemui.qs.tiles.HotspotTile;
 import com.android.systemui.qs.tiles.InternetTile;
@@ -99,7 +98,6 @@
     private final Provider<QuickAccessWalletTile> mQuickAccessWalletTileProvider;
     private final Provider<QRCodeScannerTile> mQRCodeScannerTileProvider;
     private final Provider<OneHandedModeTile> mOneHandedModeTileProvider;
-    private final Provider<FgsManagerTile> mFgsManagerTileProvider;
 
     private final Lazy<QSHost> mQsHostLazy;
     private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -137,7 +135,6 @@
             Provider<QuickAccessWalletTile> quickAccessWalletTileProvider,
             Provider<QRCodeScannerTile> qrCodeScannerTileProvider,
             Provider<OneHandedModeTile> oneHandedModeTileProvider,
-            Provider<FgsManagerTile> fgsManagerTileProvider,
             Provider<ColorCorrectionTile> colorCorrectionTileProvider) {
         mQsHostLazy = qsHostLazy;
         mCustomTileBuilderProvider = customTileBuilderProvider;
@@ -171,7 +168,6 @@
         mQuickAccessWalletTileProvider = quickAccessWalletTileProvider;
         mQRCodeScannerTileProvider = qrCodeScannerTileProvider;
         mOneHandedModeTileProvider = oneHandedModeTileProvider;
-        mFgsManagerTileProvider = fgsManagerTileProvider;
         mColorCorrectionTileProvider = colorCorrectionTileProvider;
     }
 
@@ -246,8 +242,6 @@
                 return mQRCodeScannerTileProvider.get();
             case "onehanded":
                 return mOneHandedModeTileProvider.get();
-            case "fgsmanager":
-                return mFgsManagerTileProvider.get();
             case "color_correction":
                 return mColorCorrectionTileProvider.get();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 7efb983..821dfa5f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -653,7 +653,6 @@
         "qr_code_scanner" to R.array.tile_states_qr_code_scanner,
         "alarm" to R.array.tile_states_alarm,
         "onehanded" to R.array.tile_states_onehanded,
-        "fgsmanager" to R.array.tile_states_fgsmanager,
         "color_correction" to R.array.tile_states_color_correction
     )
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
deleted file mode 100644
index 939a297..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
+++ /dev/null
@@ -1,96 +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.qs.tiles
-
-import android.content.Intent
-import android.os.Handler
-import android.os.Looper
-import android.provider.DeviceConfig
-import android.view.View
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
-import com.android.internal.logging.MetricsLogger
-import com.android.systemui.DejankUtils
-import com.android.systemui.R
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.fgsmanager.FgsManagerDialogFactory
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.qs.QSTile
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSHost
-import com.android.systemui.qs.logging.QSLogger
-import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.statusbar.policy.RunningFgsController
-import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/**
- * Quicksettings tile for the foreground services manager (task manager)
- */
-class FgsManagerTile @Inject constructor(
-    host: QSHost?,
-    @Background backgroundLooper: Looper?,
-    @Background private val backgroundExecutor: Executor?,
-    @Main mainHandler: Handler?,
-    falsingManager: FalsingManager?,
-    metricsLogger: MetricsLogger?,
-    statusBarStateController: StatusBarStateController?,
-    activityStarter: ActivityStarter?,
-    qsLogger: QSLogger?,
-    private val fgsManagerDialogFactory: FgsManagerDialogFactory,
-    private val runningFgsController: RunningFgsController
-) : QSTileImpl<QSTile.State>(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
-        statusBarStateController, activityStarter, qsLogger), RunningFgsController.Callback {
-
-    override fun handleInitialize() {
-        super.handleInitialize()
-        mUiHandler.post { runningFgsController.observe(lifecycle, this) }
-    }
-
-    override fun isAvailable(): Boolean {
-        return DejankUtils.whitelistIpcs<Boolean> {
-            DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                    SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, false)
-        }
-    }
-
-    override fun newTileState(): QSTile.State {
-        return QSTile.State()
-    }
-
-    override fun handleClick(view: View?) {
-        mUiHandler.post { fgsManagerDialogFactory.create(view) }
-    }
-
-    override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
-        state?.label = tileLabel
-        state?.secondaryLabel = runningFgsController.getPackagesWithFgs().size.toString()
-        state?.handlesLongClick = false
-        state?.icon = ResourceIcon.get(R.drawable.ic_list)
-    }
-
-    override fun getMetricsCategory(): Int = 0
-
-    override fun getLongClickIntent(): Intent? = null
-
-    // Inline the string so we don't waste translator time since this isn't used in the mocks.
-    // TODO If mocks change need to remember to move this to strings.xml
-    override fun getTileLabel(): CharSequence = "Active apps"
-
-    override fun onFgsPackagesChanged(packages: List<UserPackageTime>) = refreshState()
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index d7d1de0..991a68f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -398,6 +398,11 @@
             min = mBrightnessMin;
             max = mBrightnessMax;
         }
+
+        // Ensure the slider is in a fixed position first, then check if we should animate.
+        if (mSliderAnimator != null && mSliderAnimator.isStarted()) {
+            mSliderAnimator.cancel();
+        }
         // convertGammaToLinearFloat returns 0-1
         if (BrightnessSynchronizer.floatEquals(brightnessValue,
                 convertGammaToLinearFloat(mControl.getValue(), min, max))) {
@@ -420,9 +425,6 @@
             mControl.setValue(target);
             mControlValueInitialized = true;
         }
-        if (mSliderAnimator != null && mSliderAnimator.isStarted()) {
-            mSliderAnimator.cancel();
-        }
         mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target);
         mSliderAnimator.addUpdateListener((ValueAnimator animation) -> {
             mExternalChange = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 2a21f42..6cfbb43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -320,7 +320,7 @@
          * @param updatePostTime whether or not to refresh the post time
          */
         public void updateEntry(boolean updatePostTime) {
-            mLogger.logUpdateEntry(updatePostTime);
+            mLogger.logUpdateEntry(mEntry.getKey(), updatePostTime);
 
             long currentTime = mClock.currentTimeMillis();
             mEarliestRemovaltime = currentTime + mMinimumDisplayTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 963a0d7..6335f88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -151,6 +151,7 @@
     private boolean mPowerPluggedIn;
     private boolean mPowerPluggedInWired;
     private boolean mPowerPluggedInWireless;
+    private boolean mPowerPluggedInDock;
     private boolean mPowerCharged;
     private boolean mBatteryOverheated;
     private boolean mEnableBatteryDefender;
@@ -786,6 +787,10 @@
             chargingId = hasChargingTime
                     ? R.string.keyguard_indication_charging_time_wireless
                     : R.string.keyguard_plugged_in_wireless;
+        } else if (mPowerPluggedInDock) {
+            chargingId = hasChargingTime
+                    ? R.string.keyguard_indication_charging_time_dock
+                    : R.string.keyguard_plugged_in_dock;
         } else {
             chargingId = hasChargingTime
                     ? R.string.keyguard_indication_charging_time
@@ -911,6 +916,7 @@
             boolean wasPluggedIn = mPowerPluggedIn;
             mPowerPluggedInWired = status.isPluggedInWired() && isChargingOrFull;
             mPowerPluggedInWireless = status.isPluggedInWireless() && isChargingOrFull;
+            mPowerPluggedInDock = status.isPluggedInDock() && isChargingOrFull;
             mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull;
             mPowerCharged = status.isCharged();
             mChargingWattage = status.maxChargingWattage;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 4adf2bc..ebd610b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -19,6 +19,7 @@
 import android.graphics.Region;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
@@ -192,6 +193,14 @@
     default void setLightRevealScrimOpaque(boolean opaque) {}
 
     /**
+     * Defer any application of window {@link WindowManager.LayoutParams} until {@code scope} is
+     * fully applied.
+     */
+    default void batchApplyWindowLayoutParams(@NonNull Runnable scope) {
+        scope.run();
+    }
+
+    /**
      * Custom listener to pipe data back to plugins about whether or not the status bar would be
      * collapsed if not for the plugin.
      * TODO: Find cleaner way to do this.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 1432f78..f6a55e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -21,6 +21,7 @@
 import android.widget.Toast
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.util.Compile
 import javax.inject.Inject
 
 class NotifPipelineFlags @Inject constructor(
@@ -31,8 +32,16 @@
         if (!isNewPipelineEnabled()) {
             return true
         }
-        Log.d("NotifPipeline", "Old pipeline code running w/ new pipeline enabled", Exception())
-        Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show()
+
+        if (Compile.IS_DEBUG) {
+            Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show()
+        }
+        if (featureFlags.isEnabled(Flags.NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE)) {
+            throw RuntimeException("Old pipeline code running with new pipeline enabled")
+        } else {
+            Log.d("NotifPipeline", "Old pipeline code running with new pipeline enabled",
+                    Exception())
+        }
         return false
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt
index 03b978e..68bdd18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.notification
 
+import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.R
 import javax.inject.Inject
 
 /**
@@ -29,6 +31,10 @@
  * visibility when it invalidates, and we just store that state here.)
  */
 @SysUISingleton
-class SectionHeaderVisibilityProvider @Inject constructor() {
+class SectionHeaderVisibilityProvider @Inject constructor(
+    context: Context
+) {
+    var neverShowSectionHeaders = context.resources.getBoolean(R.bool.config_notification_never_show_section_headers)
+        private set
     var sectionHeadersVisible = true
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index f22acb7..0fd9272 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -638,22 +638,6 @@
         if (row != null) row.setHeadsUpAnimatingAway(animatingAway);
     }
 
-    /**
-     * Set that this notification was automatically heads upped. This happens for example when
-     * the user bypasses the lockscreen and media is playing.
-     */
-    public void setAutoHeadsUp(boolean autoHeadsUp) {
-        mAutoHeadsUp = autoHeadsUp;
-    }
-
-    /**
-     * @return if this notification was automatically heads upped. This happens for example when
-     *      * the user bypasses the lockscreen and media is playing.
-     */
-    public boolean isAutoHeadsUp() {
-        return mAutoHeadsUp;
-    }
-
     public boolean mustStayOnScreen() {
         return row != null && row.mustStayOnScreen();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index b84b382..0bf21af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -100,7 +100,7 @@
             if (wasHeadsUp) {
                 if (shouldHeadsUp) {
                     mHeadsUpManager.updateNotification(entry.key, hunAgain)
-                } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.key)) {
+                } else {
                     // We don't want this to be interrupting anymore, let's remove it
                     mHeadsUpManager.removeNotification(
                         entry.key, false /* removeImmediately */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 733be9c..0ce07cb 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
@@ -220,8 +220,10 @@
     }
 
     private void invalidateListFromFilter(String reason) {
-        mSectionHeaderVisibilityProvider.setSectionHeadersVisible(
-                mStatusBarStateController.getState() != StatusBarState.KEYGUARD);
+        boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+        boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
+        boolean showSections = !onKeyguard && !neverShowSections;
+        mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
         mNotifFilter.invalidateList();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
deleted file mode 100644
index b61a540..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification.interruption
-
-import android.content.Context
-import android.media.MediaMetadata
-import android.provider.Settings
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.NotificationMediaManager
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.tuner.TunerService
-import javax.inject.Inject
-
-/**
- * A class that automatically creates heads up for important notification when bypassing the
- * lockscreen
- */
-@SysUISingleton
-class BypassHeadsUpNotifier @Inject constructor(
-    private val context: Context,
-    private val bypassController: KeyguardBypassController,
-    private val statusBarStateController: StatusBarStateController,
-    private val headsUpManager: HeadsUpManagerPhone,
-    private val notificationLockscreenUserManager: NotificationLockscreenUserManager,
-    private val mediaManager: NotificationMediaManager,
-    private val commonNotifCollection: CommonNotifCollection,
-    tunerService: TunerService
-) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener {
-
-    private var currentMediaEntry: NotificationEntry? = null
-    private var enabled = true
-
-    var fullyAwake = false
-        set(value) {
-            field = value
-            if (value) {
-                updateAutoHeadsUp(currentMediaEntry)
-            }
-        }
-
-    init {
-        statusBarStateController.addCallback(this)
-        tunerService.addTunable(
-                TunerService.Tunable { _, _ ->
-                    enabled = Settings.Secure.getIntForUser(
-                            context.contentResolver,
-                            Settings.Secure.SHOW_MEDIA_WHEN_BYPASSING,
-                            0 /* default */,
-                            KeyguardUpdateMonitor.getCurrentUser()) != 0
-                }, Settings.Secure.SHOW_MEDIA_WHEN_BYPASSING)
-    }
-
-    fun setUp() {
-        mediaManager.addCallback(this)
-    }
-
-    override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
-        val previous = currentMediaEntry
-        val mediaNotificationKey = mediaManager.mediaNotificationKey
-        currentMediaEntry =
-            if (mediaNotificationKey != null && NotificationMediaManager.isPlayingState(state))
-                commonNotifCollection.getEntry(mediaNotificationKey)
-            else null
-        updateAutoHeadsUp(previous)
-        updateAutoHeadsUp(currentMediaEntry)
-    }
-
-    private fun updateAutoHeadsUp(entry: NotificationEntry?) {
-        entry?.let {
-            val autoHeadsUp = it == currentMediaEntry && canAutoHeadsUp(it)
-            it.isAutoHeadsUp = autoHeadsUp
-            if (autoHeadsUp) {
-                headsUpManager.showNotification(it)
-            }
-        }
-    }
-
-    /**
-     * @return {@code true} if this entry be autoHeadsUpped right now.
-     */
-    private fun canAutoHeadsUp(entry: NotificationEntry): Boolean {
-        if (!isAutoHeadsUpAllowed()) {
-            return false
-        }
-        if (entry.isSensitive) {
-            // filter sensitive notifications
-            return false
-        }
-        if (!notificationLockscreenUserManager.shouldShowOnKeyguard(entry)) {
-            // filter notifications invisible on Keyguard
-            return false
-        }
-        if (commonNotifCollection.getEntry(entry.key) != null) {
-            // filter notifications not the active list currently
-            return false
-        }
-        return true
-    }
-
-    override fun onStatePostChange() {
-        updateAutoHeadsUp(currentMediaEntry)
-    }
-
-    /**
-     * @return {@code true} if autoHeadsUp is possible right now.
-     */
-    private fun isAutoHeadsUpAllowed(): Boolean {
-        if (!enabled) {
-            return false
-        }
-        if (!bypassController.bypassEnabled) {
-            return false
-        }
-        if (statusBarStateController.state != StatusBarState.KEYGUARD) {
-            return false
-        }
-        if (!fullyAwake) {
-            return false
-        }
-        return true
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
index b1c69e4..74fb3f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
@@ -124,7 +124,7 @@
         if (wasHeadsUp) {
             if (shouldHeadsUp) {
                 mHeadsUpManager.updateNotification(entry.getKey(), hunAgain);
-            } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) {
+            } else {
                 // We don't want this to be interrupting anymore, let's remove it
                 mHeadsUpManager.removeNotification(entry.getKey(), false /* removeImmediately */);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 63cb4ae..5d6d0f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -424,7 +424,8 @@
     }
 
     @Override
-    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
+    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+            Runnable onFinishRunnable) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAppear;
         if (mDrawingAppearAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 6eff799..8ffdb69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -358,7 +358,12 @@
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener);
 
-    public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear);
+    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
+        performAddAnimation(delay, duration, isHeadsUpAppear, null);
+    }
+
+    public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+            Runnable onEndRunnable);
 
     /**
      * Set the notification appearance to be below the speed bump.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index f26598d..ec406f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.log.dagger.NotificationLog
 import javax.inject.Inject
 
@@ -50,7 +49,7 @@
     }
 
     fun logRequestPipelineRowNotSet(notifKey: String) {
-        buffer.log(TAG, WARNING, {
+        buffer.log(TAG, INFO, {
             str1 = notifKey
         }, {
             "Row is not set so pipeline will not run. notif = $str1"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 3cdaa9a..aa3e027 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -242,6 +242,13 @@
     }
 
     @Override
+    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+            Runnable endRunnable) {
+        // TODO: use delay and duration
+        setContentVisible(true);
+    }
+
+    @Override
     public boolean needsClippingToShelf() {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
index c9a0f6c..bd5b7d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
@@ -39,7 +39,8 @@
     }
 
     @Override
-    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
+    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+            Runnable onEnd) {
         // No animation, it doesn't need it, this would be local
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 90f5179..4283343 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5658,6 +5658,10 @@
         }
     }
 
+    protected void setLogger(StackStateLogger logger) {
+        mStateAnimator.setLogger(logger);
+    }
+
     /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 5833ec2..51ce779 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -184,6 +184,7 @@
     private final SectionHeaderController mSilentHeaderController;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final InteractionJankMonitor mJankMonitor;
+    private final StackStateLogger mStackStateLogger;
 
     private NotificationStackScrollLayout mView;
     private boolean mFadeNotificationsOnDismiss;
@@ -660,7 +661,9 @@
             NotificationRemoteInputManager remoteInputManager,
             VisualStabilityManager visualStabilityManager,
             ShadeController shadeController,
-            InteractionJankMonitor jankMonitor) {
+            InteractionJankMonitor jankMonitor,
+            StackStateLogger stackLogger) {
+        mStackStateLogger = stackLogger;
         mAllowLongPress = allowLongPress;
         mNotificationGutsManager = notificationGutsManager;
         mVisibilityProvider = visibilityProvider;
@@ -712,6 +715,7 @@
 
     public void attach(NotificationStackScrollLayout view) {
         mView = view;
+        mView.setLogger(mStackStateLogger);
         mView.setController(this);
         mView.setTouchHandler(new TouchHandler());
         mView.setStatusBar(mStatusBar);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 1d0d374..0d2bddc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -86,6 +86,7 @@
     private NotificationShelf mShelf;
     private float mStatusBarIconLocation;
     private int[] mTmpLocation = new int[2];
+    private StackStateLogger mLogger;
 
     public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
         mHostLayout = hostLayout;
@@ -113,6 +114,10 @@
         };
     }
 
+    protected void setLogger(StackStateLogger logger) {
+        mLogger = logger;
+    }
+
     public boolean isRunning() {
         return !mAnimatorSet.isEmpty();
     }
@@ -337,6 +342,12 @@
             ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) {
         for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
             final ExpandableView changingView = (ExpandableView) event.mChangingView;
+            boolean loggable = false;
+            String key = null;
+            if (changingView instanceof ExpandableNotificationRow && mLogger != null) {
+                loggable = true;
+                key = ((ExpandableNotificationRow) changingView).getEntry().getKey();
+            }
             if (event.animationType ==
                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
 
@@ -407,10 +418,22 @@
                 if (event.headsUpFromBottom) {
                     mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
                 } else {
+                    Runnable onAnimationEnd = null;
+                    if (loggable) {
+                        String finalKey = key;
+                        onAnimationEnd = () -> mLogger.appearAnimationEnded(finalKey);
+                    }
                     changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
-                            true /* isHeadsUpAppear */);
+                            true /* isHeadsUpAppear */, onAnimationEnd);
                 }
                 mHeadsUpAppearChildren.add(changingView);
+                // this only captures HEADS_UP_APPEAR animations, but HUNs can appear with normal
+                // ADD animations, which would not be logged here.
+                if (loggable) {
+                    mLogger.logHUNViewAppearing(
+                            ((ExpandableNotificationRow) changingView).getEntry().getKey());
+                }
+
                 mTmpState.applyToView(changingView);
             } else if (event.animationType == NotificationStackScrollLayout
                             .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR ||
@@ -453,10 +476,21 @@
                     // We need to add the global animation listener, since once no animations are
                     // running anymore, the panel will instantly hide itself. We need to wait until
                     // the animation is fully finished for this though.
+                    Runnable postAnimation = endRunnable;
+                    if (loggable) {
+                        mLogger.logHUNViewDisappearing(key);
+
+                        Runnable finalEndRunnable = endRunnable;
+                        String finalKey1 = key;
+                        postAnimation = () -> {
+                            mLogger.disappearAnimationEnded(finalKey1);
+                            if (finalEndRunnable != null) finalEndRunnable.run();
+                        };
+                    }
                     long removeAnimationDelay = changingView.performRemoveAnimation(
                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
                             0, 0.0f, true /* isHeadsUpAppear */, targetLocation,
-                            endRunnable, getGlobalAnimationFinishedListener());
+                            postAnimation, getGlobalAnimationFinishedListener());
                     mAnimationProperties.delay += removeAnimationDelay;
                 } else if (endRunnable != null) {
                     endRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
new file mode 100644
index 0000000..4315265
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -0,0 +1,44 @@
+package com.android.systemui.statusbar.notification.stack
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import javax.inject.Inject
+
+class StackStateLogger @Inject constructor(
+    @NotificationHeadsUpLog private val buffer: LogBuffer
+) {
+    fun logHUNViewDisappearing(key: String) {
+        buffer.log(TAG, LogLevel.INFO, {
+            str1 = key
+        }, {
+            "Heads up view disappearing $str1 "
+        })
+    }
+
+    fun logHUNViewAppearing(key: String) {
+        buffer.log(TAG, LogLevel.INFO, {
+            str1 = key
+        }, {
+            "Heads up notification view appearing $str1 "
+        })
+    }
+
+    fun disappearAnimationEnded(key: String) {
+        buffer.log(TAG, LogLevel.INFO, {
+            str1 = key
+        }, {
+            "Heads up notification disappear animation ended $str1 "
+        })
+    }
+
+    fun appearAnimationEnded(key: String) {
+        buffer.log(TAG, LogLevel.INFO, {
+            str1 = key
+        }, {
+            "Heads up notification appear animation ended $str1 "
+        })
+    }
+}
+
+private const val TAG = "StackScroll"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 2824ab8..77cff34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -60,16 +60,14 @@
     private final KeyguardBypassController mBypassController;
     private final GroupMembershipManager mGroupMembershipManager;
     private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
-    private final int mAutoHeadsUpNotificationDecay;
     // TODO (b/162832756): remove visual stability manager when migrating to new pipeline
     private VisualStabilityManager mVisualStabilityManager;
     private boolean mReleaseOnExpandFinish;
 
     private boolean mTrackingHeadsUp;
-    private HashSet<String> mSwipedOutKeys = new HashSet<>();
-    private HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
-    private HashSet<String> mKeysToRemoveWhenLeavingKeyguard = new HashSet<>();
-    private ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
+    private final HashSet<String> mSwipedOutKeys = new HashSet<>();
+    private final HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
+    private final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
             = new ArraySet<>();
     private boolean mIsExpanded;
     private boolean mHeadsUpGoingAway;
@@ -110,8 +108,6 @@
         super(context, logger);
         Resources resources = mContext.getResources();
         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
-        mAutoHeadsUpNotificationDecay = resources.getInteger(
-                R.integer.auto_heads_up_notification_decay);
         statusBarStateController.addCallback(mStatusBarStateListener);
         mBypassController = bypassController;
         mGroupMembershipManager = groupMembershipManager;
@@ -234,15 +230,6 @@
         }
     }
 
-    @Override
-    public boolean isEntryAutoHeadsUpped(String key) {
-        HeadsUpEntryPhone headsUpEntryPhone = getHeadsUpEntryPhone(key);
-        if (headsUpEntryPhone == null) {
-            return false;
-        }
-        return headsUpEntryPhone.isAutoHeadsUp();
-    }
-
     /**
      * Set that we are exiting the headsUp pinned mode, but some notifications might still be
      * animating out. This is used to keep the touchable regions in a reasonable state.
@@ -375,7 +362,6 @@
 
     @Override
     protected void onAlertEntryRemoved(AlertEntry alertEntry) {
-        mKeysToRemoveWhenLeavingKeyguard.remove(alertEntry.mEntry.getKey());
         super.onAlertEntryRemoved(alertEntry);
         mEntryPool.release((HeadsUpEntryPhone) alertEntry);
     }
@@ -437,11 +423,6 @@
          */
         private boolean extended;
 
-        /**
-         * Was this entry received while on keyguard
-         */
-        private boolean mIsAutoHeadsUp;
-
 
         @Override
         public boolean isSticky() {
@@ -459,8 +440,6 @@
                             false  /* persistent */);
                 } else if (mTrackingHeadsUp) {
                     mEntriesToRemoveAfterExpand.add(entry);
-                } else if (mIsAutoHeadsUp && mStatusBarState == StatusBarState.KEYGUARD) {
-                    mKeysToRemoveWhenLeavingKeyguard.add(entry.getKey());
                 } else {
                     removeAlertEntry(entry.getKey());
                 }
@@ -471,7 +450,6 @@
 
         @Override
         public void updateEntry(boolean updatePostTime) {
-            mIsAutoHeadsUp = mEntry.isAutoHeadsUp();
             super.updateEntry(updatePostTime);
 
             if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
@@ -480,7 +458,6 @@
             if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
                 mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
             }
-            mKeysToRemoveWhenLeavingKeyguard.remove(mEntry.getKey());
         }
 
         @Override
@@ -515,7 +492,6 @@
             super.reset();
             mMenuShownPinned = false;
             extended = false;
-            mIsAutoHeadsUp = false;
         }
 
         private void extendPulse() {
@@ -526,33 +502,8 @@
         }
 
         @Override
-        public int compareTo(AlertEntry alertEntry) {
-            HeadsUpEntryPhone headsUpEntry = (HeadsUpEntryPhone) alertEntry;
-            boolean autoShown = isAutoHeadsUp();
-            boolean otherAutoShown = headsUpEntry.isAutoHeadsUp();
-            if (autoShown && !otherAutoShown) {
-                return 1;
-            } else if (!autoShown && otherAutoShown) {
-                return -1;
-            }
-            return super.compareTo(alertEntry);
-        }
-
-        @Override
         protected long calculateFinishTime() {
-            return mPostTime + getDecayDuration() + (extended ? mExtensionTime : 0);
-        }
-
-        private int getDecayDuration() {
-            if (isAutoHeadsUp()) {
-                return getRecommendedHeadsUpTimeoutMs(mAutoHeadsUpNotificationDecay);
-            } else {
-                return getRecommendedHeadsUpTimeoutMs(mAutoDismissNotificationDecay);
-            }
-        }
-
-        private boolean isAutoHeadsUp() {
-            return mIsAutoHeadsUp;
+            return super.calculateFinishTime() + (extended ? mExtensionTime : 0);
         }
     }
 
@@ -577,13 +528,6 @@
             boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
             boolean isKeyguard = newState == StatusBarState.KEYGUARD;
             mStatusBarState = newState;
-            if (wasKeyguard && !isKeyguard && mKeysToRemoveWhenLeavingKeyguard.size() != 0) {
-                String[] keys = mKeysToRemoveWhenLeavingKeyguard.toArray(new String[0]);
-                for (String key : keys) {
-                    removeAlertEntry(key);
-                }
-                mKeysToRemoveWhenLeavingKeyguard.clear();
-            }
             if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
                 ArrayList<String> keysToRemove = new ArrayList<>();
                 for (AlertEntry entry : mAlertEntries.values()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 769f689..34009f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -162,6 +162,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.QsFrameTranslateController;
@@ -741,6 +742,7 @@
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
             StatusBarWindowStateController statusBarWindowStateController,
+            NotificationShadeWindowController notificationShadeWindowController,
             DozeLog dozeLog,
             DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper,
             LatencyTracker latencyTracker, PowerManager powerManager,
@@ -800,6 +802,7 @@
                 dozeLog,
                 keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController,
+                notificationShadeWindowController,
                 vibratorHelper,
                 statusBarKeyguardViewManager,
                 latencyTracker,
@@ -3928,6 +3931,15 @@
         mKeyguardStatusViewController.animateFoldToAod();
     }
 
+    /**
+     * Cancels fold to AOD transition and resets view state
+     */
+    public void cancelFoldToAodAnimation() {
+        cancelAnimation();
+        resetAlpha();
+        resetTranslation();
+    }
+
     /** */
     public void setImportantForAccessibility(int mode) {
         mView.setImportantForAccessibility(mode);
@@ -4046,6 +4058,10 @@
         mView.setTranslationX(0f);
     }
 
+    public void resetAlpha() {
+        mView.setAlpha(1f);
+    }
+
     public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
         return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
                 durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index c859e70..474653b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -111,6 +111,12 @@
     private final SysuiColorExtractor mColorExtractor;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private float mFaceAuthDisplayBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
+    /**
+     * Layout params would be aggregated and dispatched all at once if this is > 0.
+     *
+     * @see #batchApplyWindowLayoutParams(Runnable)
+     */
+    private int mDeferWindowLayoutParams;
 
     @Inject
     public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager,
@@ -437,6 +443,20 @@
         }
     }
 
+    private void applyWindowLayoutParams() {
+        if (mDeferWindowLayoutParams == 0 && mLp != null && mLp.copyFrom(mLpChanged) != 0) {
+            mWindowManager.updateViewLayout(mNotificationShadeView, mLp);
+        }
+    }
+
+    @Override
+    public void batchApplyWindowLayoutParams(Runnable scope) {
+        mDeferWindowLayoutParams++;
+        scope.run();
+        mDeferWindowLayoutParams--;
+        applyWindowLayoutParams();
+    }
+
     private void apply(State state) {
         applyKeyguardFlags(state);
         applyFocusableFlag(state);
@@ -451,9 +471,8 @@
         applyHasTopUi(state);
         applyNotTouchable(state);
         applyStatusBarColorSpaceAgnosticFlag(state);
-        if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
-            mWindowManager.updateViewLayout(mNotificationShadeView, mLp);
-        }
+        applyWindowLayoutParams();
+
         if (mHasTopUi != mHasTopUiChanged) {
             whitelistIpcs(() -> {
                 try {
@@ -739,6 +758,7 @@
         pw.println(TAG + ":");
         pw.println("  mKeyguardMaxRefreshRate=" + mKeyguardMaxRefreshRate);
         pw.println("  mKeyguardPreferredRefreshRate=" + mKeyguardPreferredRefreshRate);
+        pw.println("  mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
         pw.println(mCurrentState);
         if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) {
             mNotificationShadeView.getViewRootImpl().dump("  ", pw);
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 05ac2a3..54d0b03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -57,6 +57,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -180,6 +181,7 @@
     private boolean mExpandLatencyTracking;
     private final PanelView mView;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     protected final Resources mResources;
     protected final KeyguardStateController mKeyguardStateController;
     protected final SysuiStatusBarStateController mStatusBarStateController;
@@ -222,6 +224,7 @@
             DozeLog dozeLog,
             KeyguardStateController keyguardStateController,
             SysuiStatusBarStateController statusBarStateController,
+            NotificationShadeWindowController notificationShadeWindowController,
             VibratorHelper vibratorHelper,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             LatencyTracker latencyTracker,
@@ -263,6 +266,7 @@
         mResources = mView.getResources();
         mKeyguardStateController = keyguardStateController;
         mStatusBarStateController = statusBarStateController;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mFlingAnimationUtils = flingAnimationUtilsBuilder
                 .reset()
                 .setMaxLengthSeconds(0.6f)
@@ -760,34 +764,36 @@
         if (isNaN(h)) {
             Log.wtf(TAG, "ExpandedHeight set to NaN");
         }
-        if (mExpandLatencyTracking && h != 0f) {
-            DejankUtils.postAfterTraversal(
-                    () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
-            mExpandLatencyTracking = false;
-        }
-        float maxPanelHeight = getMaxPanelHeight();
-        if (mHeightAnimator == null) {
-            if (mTracking) {
-                float overExpansionPixels = Math.max(0, h - maxPanelHeight);
-                setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
+        mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+            if (mExpandLatencyTracking && h != 0f) {
+                DejankUtils.postAfterTraversal(
+                        () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
+                mExpandLatencyTracking = false;
             }
-            mExpandedHeight = Math.min(h, maxPanelHeight);
-        } else {
-            mExpandedHeight = h;
-        }
+            float maxPanelHeight = getMaxPanelHeight();
+            if (mHeightAnimator == null) {
+                if (mTracking) {
+                    float overExpansionPixels = Math.max(0, h - maxPanelHeight);
+                    setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
+                }
+                mExpandedHeight = Math.min(h, maxPanelHeight);
+            } else {
+                mExpandedHeight = h;
+            }
 
-        // If we are closing the panel and we are almost there due to a slow decelerating
-        // interpolator, abort the animation.
-        if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
-            mExpandedHeight = 0f;
-            if (mHeightAnimator != null) {
-                mHeightAnimator.end();
+            // If we are closing the panel and we are almost there due to a slow decelerating
+            // interpolator, abort the animation.
+            if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
+                mExpandedHeight = 0f;
+                if (mHeightAnimator != null) {
+                    mHeightAnimator.end();
+                }
             }
-        }
-        mExpandedFraction = Math.min(1f,
-                maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
-        onHeightUpdated(mExpandedHeight);
-        updatePanelExpansionAndVisibility();
+            mExpandedFraction = Math.min(1f,
+                    maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
+            onHeightUpdated(mExpandedHeight);
+            updatePanelExpansionAndVisibility();
+        });
     }
 
     /**
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 ae4a19e..80ae070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -211,7 +211,6 @@
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -482,7 +481,6 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     private final DynamicPrivacyController mDynamicPrivacyController;
-    private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
     private final FalsingCollector mFalsingCollector;
     private final FalsingManager mFalsingManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -703,7 +701,6 @@
             KeyguardStateController keyguardStateController,
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
-            BypassHeadsUpNotifier bypassHeadsUpNotifier,
             FalsingManager falsingManager,
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
@@ -800,7 +797,6 @@
         mKeyguardIndicationController = keyguardIndicationController;
         mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
         mDynamicPrivacyController = dynamicPrivacyController;
-        mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
         mFalsingCollector = falsingCollector;
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -917,7 +913,6 @@
         mScreenLifecycle.addObserver(mScreenObserver);
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
-        mBypassHeadsUpNotifier.setUp();
         if (mBubblesOptional.isPresent()) {
             mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
         }
@@ -3003,7 +2998,7 @@
     }
 
     private void onLaunchTransitionFadingEnded() {
-        mNotificationPanelViewController.setAlpha(1.0f);
+        mNotificationPanelViewController.resetAlpha();
         mNotificationPanelViewController.onAffordanceLaunchEnded();
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
@@ -3034,7 +3029,7 @@
             }
             updateScrimController();
             mPresenter.updateMediaMetaData(false, true);
-            mNotificationPanelViewController.setAlpha(1);
+            mNotificationPanelViewController.resetAlpha();
             mNotificationPanelViewController.fadeOut(
                     FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION,
                     this::onLaunchTransitionFadingEnded);
@@ -3135,7 +3130,7 @@
         releaseGestureWakeLock();
         mNotificationPanelViewController.onAffordanceLaunchEnded();
         mNotificationPanelViewController.cancelAnimation();
-        mNotificationPanelViewController.setAlpha(1f);
+        mNotificationPanelViewController.resetAlpha();
         mNotificationPanelViewController.resetTranslation();
         mNotificationPanelViewController.resetViewGroupFade();
         updateDozingState();
@@ -3581,7 +3576,6 @@
             maybeEscalateHeadsUp();
             dismissVolumeDialog();
             mWakeUpCoordinator.setFullyAwake(false);
-            mBypassHeadsUpNotifier.setFullyAwake(false);
             mKeyguardBypassController.onStartedGoingToSleep();
 
             // The unlocked screen off and fold to aod animations might use our LightRevealScrim -
@@ -3597,33 +3591,34 @@
         public void onStartedWakingUp() {
             String tag = "StatusBar#onStartedWakingUp";
             DejankUtils.startDetectingBlockingIpcs(tag);
-            mDeviceInteractive = true;
-            mWakeUpCoordinator.setWakingUp(true);
-            if (!mKeyguardBypassController.getBypassEnabled()) {
-                mHeadsUpManager.releaseAllImmediately();
-            }
-            updateVisibleToUser();
-            updateIsKeyguard();
-            mDozeServiceHost.stopDozing();
-            // This is intentionally below the stopDozing call above, since it avoids that we're
-            // unnecessarily animating the wakeUp transition. Animations should only be enabled
-            // once we fully woke up.
-            updateRevealEffect(true /* wakingUp */);
-            updateNotificationPanelTouchState();
+            mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+                mDeviceInteractive = true;
+                mWakeUpCoordinator.setWakingUp(true);
+                if (!mKeyguardBypassController.getBypassEnabled()) {
+                    mHeadsUpManager.releaseAllImmediately();
+                }
+                updateVisibleToUser();
+                updateIsKeyguard();
+                mDozeServiceHost.stopDozing();
+                // This is intentionally below the stopDozing call above, since it avoids that we're
+                // unnecessarily animating the wakeUp transition. Animations should only be enabled
+                // once we fully woke up.
+                updateRevealEffect(true /* wakingUp */);
+                updateNotificationPanelTouchState();
 
-            // If we are waking up during the screen off animation, we should undo making the
-            // expanded visible (we did that so the LightRevealScrim would be visible).
-            if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
-                makeExpandedInvisible();
-            }
+                // If we are waking up during the screen off animation, we should undo making the
+                // expanded visible (we did that so the LightRevealScrim would be visible).
+                if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
+                    makeExpandedInvisible();
+                }
 
+            });
             DejankUtils.stopDetectingBlockingIpcs(tag);
         }
 
         @Override
         public void onFinishedWakingUp() {
             mWakeUpCoordinator.setFullyAwake(true);
-            mBypassHeadsUpNotifier.setFullyAwake(true);
             mWakeUpCoordinator.setWakingUp(false);
             if (mLaunchCameraWhenFinishedWaking) {
                 mNotificationPanelViewController.launchCamera(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 977fe9c..f5364b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -71,7 +71,6 @@
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -152,7 +151,6 @@
             KeyguardStateController keyguardStateController,
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
-            BypassHeadsUpNotifier bypassHeadsUpNotifier,
             FalsingManager falsingManager,
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
@@ -250,7 +248,6 @@
                 keyguardStateController,
                 headsUpManagerPhone,
                 dynamicPrivacyController,
-                bypassHeadsUpNotifier,
                 falsingManager,
                 falsingCollector,
                 broadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 9587261..3084a95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -184,6 +184,7 @@
         entry.setHeadsUp(false);
         setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
         EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
+        mLogger.logNotificationActuallyRemoved(entry.getKey());
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpStateChanged(entry, false);
         }
@@ -378,10 +379,6 @@
     public void onDensityOrFontScaleChanged() {
     }
 
-    public boolean isEntryAutoHeadsUpped(String key) {
-        return false;
-    }
-
     /**
      * Determines if the notification is for a critical call that must display on top of an active
      * input notification.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 2bdf62bb..6a74ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -73,6 +73,14 @@
         })
     }
 
+    fun logNotificationActuallyRemoved(key: String) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+        }, {
+            "notification removed $str1 "
+        })
+    }
+
     fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) {
         buffer.log(TAG, INFO, {
             str1 = key
@@ -83,11 +91,12 @@
         })
     }
 
-    fun logUpdateEntry(updatePostTime: Boolean) {
+    fun logUpdateEntry(key: String, updatePostTime: Boolean) {
         buffer.log(TAG, INFO, {
+            str1 = key
             bool1 = updatePostTime
         }, {
-            "update entry updatePostTime: $bool1"
+            "update entry $key updatePostTime: $bool1"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 59969c0..5e91a25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -30,6 +30,7 @@
 import android.content.PermissionChecker;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.database.ContentObserver;
 import android.location.LocationManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -43,6 +44,8 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.appops.AppOpItem;
 import com.android.systemui.appops.AppOpsController;
@@ -53,6 +56,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.Utils;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -71,30 +75,48 @@
     private final DeviceConfigProxy mDeviceConfigProxy;
     private final BootCompleteCache mBootCompleteCache;
     private final UserTracker mUserTracker;
+    private final UiEventLogger mUiEventLogger;
     private final H mHandler;
     private final Handler mBackgroundHandler;
     private final PackageManager mPackageManager;
+    private final ContentObserver mContentObserver;
+    private final SecureSettings mSecureSettings;
 
     private boolean mAreActiveLocationRequests;
     private boolean mShouldDisplayAllAccesses;
-    private boolean mShowSystemAccesses;
+    private boolean mShowSystemAccessesFlag;
+    private boolean mShowSystemAccessesSetting;
 
     @Inject
     public LocationControllerImpl(Context context, AppOpsController appOpsController,
             DeviceConfigProxy deviceConfigProxy,
             @Main Looper mainLooper, @Background Handler backgroundHandler,
             BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache,
-            UserTracker userTracker, PackageManager packageManager) {
+            UserTracker userTracker, PackageManager packageManager, UiEventLogger uiEventLogger,
+            SecureSettings secureSettings) {
         mContext = context;
         mAppOpsController = appOpsController;
         mDeviceConfigProxy = deviceConfigProxy;
         mBootCompleteCache = bootCompleteCache;
         mHandler = new H(mainLooper);
         mUserTracker = userTracker;
+        mUiEventLogger = uiEventLogger;
+        mSecureSettings = secureSettings;
         mBackgroundHandler = backgroundHandler;
         mPackageManager = packageManager;
         mShouldDisplayAllAccesses = getAllAccessesSetting();
-        mShowSystemAccesses = getShowSystemSetting();
+        mShowSystemAccessesFlag = getShowSystemFlag();
+        mShowSystemAccessesSetting = getShowSystemSetting();
+        mContentObserver = new ContentObserver(mBackgroundHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mShowSystemAccessesSetting = getShowSystemSetting();
+            }
+        };
+
+        // Register to listen for changes in Settings.Secure settings.
+        mSecureSettings.registerContentObserver(
+                Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, mContentObserver);
 
         // Register to listen for changes in DeviceConfig settings.
         mDeviceConfigProxy.addOnPropertiesChangedListener(
@@ -102,7 +124,7 @@
                 backgroundHandler::post,
                 properties -> {
                     mShouldDisplayAllAccesses = getAllAccessesSetting();
-                    mShowSystemAccesses = getShowSystemSetting();
+                    mShowSystemAccessesFlag = getShowSystemSetting();
                     updateActiveLocationRequests();
                 });
 
@@ -191,10 +213,15 @@
                 SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false);
     }
 
-    private boolean getShowSystemSetting() {
+    private boolean getShowSystemFlag() {
         return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
                 SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM, false);
     }
+
+    private boolean getShowSystemSetting() {
+        return mSecureSettings.getInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1;
+    }
+
     /**
      * Returns true if there currently exist active high power location requests.
      */
@@ -222,6 +249,10 @@
         }
         boolean hadActiveLocationRequests = mAreActiveLocationRequests;
         boolean shouldDisplay = false;
+        boolean showSystem = mShowSystemAccessesFlag || mShowSystemAccessesSetting;
+        boolean systemAppOp = false;
+        boolean nonSystemAppOp = false;
+        boolean isSystemApp;
 
         List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps();
         final List<UserInfo> profiles = mUserTracker.getUserProfiles();
@@ -229,18 +260,38 @@
         for (int i = 0; i < numItems; i++) {
             if (appOpsItems.get(i).getCode() == OP_FINE_LOCATION
                     || appOpsItems.get(i).getCode() == OP_COARSE_LOCATION) {
-                if (mShowSystemAccesses) {
-                    shouldDisplay = true;
+                isSystemApp = isSystemApp(profiles, appOpsItems.get(i));
+                if (isSystemApp) {
+                    systemAppOp = true;
                 } else {
-                    shouldDisplay |= !isSystemApp(profiles, appOpsItems.get(i));
+                    nonSystemAppOp = true;
                 }
+
+                shouldDisplay = showSystem || shouldDisplay || !isSystemApp;
             }
         }
 
-        mAreActiveLocationRequests = areActiveHighPowerLocationRequests() || shouldDisplay;
+        boolean highPowerOp = areActiveHighPowerLocationRequests();
+        mAreActiveLocationRequests = highPowerOp || shouldDisplay;
         if (mAreActiveLocationRequests != hadActiveLocationRequests) {
             mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED);
         }
+
+        // Log each of the types of location access that would cause the location indicator to be
+        // shown, regardless of device's setting state. This is used to understand how often a
+        // user would see the location indicator based on any settings state the device could be in.
+        if (!hadActiveLocationRequests && (highPowerOp || systemAppOp || nonSystemAppOp)) {
+            if (highPowerOp) {
+                mUiEventLogger.log(
+                        LocationIndicatorEvent.LOCATION_INDICATOR_MONITOR_HIGH_POWER);
+            }
+            if (systemAppOp) {
+                mUiEventLogger.log(LocationIndicatorEvent.LOCATION_INDICATOR_SYSTEM_APP);
+            }
+            if (nonSystemAppOp) {
+                mUiEventLogger.log(LocationIndicatorEvent.LOCATION_INDICATOR_NON_SYSTEM_APP);
+            }
+        }
     }
 
     private boolean isSystemApp(List<UserInfo> profiles, AppOpItem item) {
@@ -283,6 +334,11 @@
             mAreActiveLocationRequests = areActiveHighPowerLocationRequests();
             if (mAreActiveLocationRequests != hadActiveLocationRequests) {
                 mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED);
+                if (mAreActiveLocationRequests) {
+                    // Log that the indicator was shown for a high power op.
+                    mUiEventLogger.log(
+                            LocationIndicatorEvent.LOCATION_INDICATOR_MONITOR_HIGH_POWER);
+                }
             }
         }
     }
@@ -341,4 +397,24 @@
                     cb -> cb.onLocationSettingsChanged(isEnabled));
         }
     }
-}
+
+    /**
+     * Enum for events which prompt the location indicator to appear.
+     */
+    enum LocationIndicatorEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Location indicator shown for high power access")
+        LOCATION_INDICATOR_MONITOR_HIGH_POWER(935),
+        @UiEvent(doc = "Location indicator shown for system app access")
+        LOCATION_INDICATOR_SYSTEM_APP(936),
+        @UiEvent(doc = "Location indicator shown for non system app access")
+        LOCATION_INDICATOR_NON_SYSTEM_APP(937);
+
+        private final int mId;
+        LocationIndicatorEvent(int id) {
+            mId = id;
+        }
+        @Override public int getId() {
+            return mId;
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt
index e25a105..c199744 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt
@@ -25,7 +25,6 @@
 import com.android.internal.messages.nano.SystemMessageProto
 import com.android.internal.net.VpnConfig
 import com.android.systemui.CoreStartable
-import com.android.systemui.Dependency
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.policy.SecurityController
@@ -35,12 +34,13 @@
  * Observes if a vpn connection is active and displays a notification to the user
  */
 @SysUISingleton
-class VpnStatusObserver @Inject constructor(context: Context) : CoreStartable(context),
+class VpnStatusObserver @Inject constructor(
+    context: Context,
+    private val securityController: SecurityController
+) : CoreStartable(context),
         SecurityController.SecurityControllerCallback {
 
     private var vpnConnected = false
-    private val securityController: SecurityController =
-            Dependency.get(SecurityController::class.java)
     private val notificationManager = NotificationManager.from(context)
     private val notificationChannel = createNotificationChannel()
     private val vpnConnectedNotificationBuilder = createVpnConnectedNotificationBuilder()
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index aaf35af..c481fc9 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.unfold
 
+import android.os.Handler
 import android.os.PowerManager
 import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.statusbar.LightRevealScrim
@@ -37,6 +39,7 @@
 class FoldAodAnimationController
 @Inject
 constructor(
+    @Main private val handler: Handler,
     private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
     private val globalSettings: GlobalSettings
@@ -50,6 +53,14 @@
     private var shouldPlayAnimation = false
     private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
 
+    private val startAnimationRunnable = Runnable {
+        statusBar.notificationPanelViewController.startFoldToAodAnimation {
+            // End action
+            isAnimationPlaying = false
+            keyguardViewMediatorLazy.get().maybeHandlePendingLock()
+        }
+    }
+
     private var isAnimationPlaying = false
 
     override fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {
@@ -79,6 +90,11 @@
         }
 
     override fun onStartedWakingUp() {
+        if (isAnimationPlaying) {
+            handler.removeCallbacks(startAnimationRunnable)
+            statusBar.notificationPanelViewController.cancelFoldToAodAnimation();
+        }
+
         shouldPlayAnimation = false
         isAnimationPlaying = false
     }
@@ -115,11 +131,10 @@
 
     fun onScreenTurnedOn() {
         if (shouldPlayAnimation) {
-            statusBar.notificationPanelViewController.startFoldToAodAnimation {
-                // End action
-                isAnimationPlaying = false
-                keyguardViewMediatorLazy.get().maybeHandlePendingLock()
-            }
+            handler.removeCallbacks(startAnimationRunnable)
+
+            // Post starting the animation to the next frame to avoid junk due to inset changes
+            handler.post(startAnimationRunnable)
             shouldPlayAnimation = false
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java b/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
index be5e0a0..089650c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
@@ -26,8 +26,6 @@
 
 import androidx.core.content.FileProvider;
 
-import com.android.systemui.Dependency;
-
 import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -35,7 +33,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -53,12 +50,14 @@
     private static final int BUFSIZ = 1024 * 1024; // 1MB
 
     private final Context context;
+    private GarbageMonitor mGarbageMonitor;
     private Uri hprofUri;
     private long rss;
     final StringBuilder body = new StringBuilder();
 
-    public DumpTruck(Context context) {
+    public DumpTruck(Context context, GarbageMonitor garbageMonitor) {
         this.context = context;
+        mGarbageMonitor = garbageMonitor;
     }
 
     /**
@@ -68,8 +67,6 @@
      * @return this, for chaining
      */
     public DumpTruck captureHeaps(List<Long> pids) {
-        final GarbageMonitor gm = Dependency.get(GarbageMonitor.class);
-
         final File dumpDir = new File(context.getCacheDir(), FILEPROVIDER_PATH);
         dumpDir.mkdirs();
         hprofUri = null;
@@ -83,16 +80,14 @@
         for (Long pidL : pids) {
             final int pid = pidL.intValue();
             body.append("  pid ").append(pid);
-            if (gm != null) {
-                GarbageMonitor.ProcessMemInfo info = gm.getMemInfo(pid);
-                if (info != null) {
-                    body.append(":")
-                            .append(" up=")
-                            .append(info.getUptime())
-                            .append(" rss=")
-                            .append(info.currentRss);
-                    rss = info.currentRss;
-                }
+            GarbageMonitor.ProcessMemInfo info = mGarbageMonitor.getMemInfo(pid);
+            if (info != null) {
+                body.append(":")
+                        .append(" up=")
+                        .append(info.getUptime())
+                        .append(" rss=")
+                        .append(info.currentRss);
+                rss = info.currentRss;
             }
             if (pid == myPid) {
                 final String path =
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 612b7cb..9fed158 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -150,7 +150,7 @@
         mTrackedGarbage = leakDetector.getTrackedGarbage();
         mLeakReporter = leakReporter;
 
-        mDumpTruck = new DumpTruck(mContext);
+        mDumpTruck = new DumpTruck(mContext, this);
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
 
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 31b17f8..6fefce2 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -41,7 +41,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.Dependency;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -125,6 +124,7 @@
     private final SysUiState mSysUiState;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final ProtoTracer mProtoTracer;
+    private final UserInfoController mUserInfoController;
     private final Executor mSysUiMainExecutor;
 
     private boolean mIsSysUiStateValid;
@@ -153,6 +153,7 @@
             SysUiState sysUiState,
             ProtoTracer protoTracer,
             WakefulnessLifecycle wakefulnessLifecycle,
+            UserInfoController userInfoController,
             @Main Executor sysUiMainExecutor) {
         super(context);
         mCommandQueue = commandQueue;
@@ -171,6 +172,7 @@
         mShellCommandHandler = shellCommandHandler;
         mCompatUIOptional = sizeCompatUIOptional;
         mDragAndDropOptional = dragAndDropOptional;
+        mUserInfoController = userInfoController;
         mSysUiMainExecutor = sysUiMainExecutor;
     }
 
@@ -231,8 +233,7 @@
         });
 
         // The media session listener needs to be re-registered when switching users
-        UserInfoController userInfoController = Dependency.get(UserInfoController.class);
-        userInfoController.addCallback((String name, Drawable picture, String userAccount) ->
+        mUserInfoController.addCallback((String name, Drawable picture, String userAccount) ->
                 pip.registerSessionListenerForCurrentUser());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index 0b399cf..ae1268d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -56,6 +56,7 @@
 import android.widget.ScrollView;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -75,6 +76,7 @@
 
     private @Mock AuthDialogCallback mCallback;
     private @Mock UserManager mUserManager;
+    private @Mock WakefulnessLifecycle mWakefulnessLifecycle;
 
     @Before
     public void setup() {
@@ -263,15 +265,17 @@
                 componentInfo,
                 FingerprintSensorProperties.TYPE_REAR,
                 false /* resetLockoutRequiresHardwareAuthToken */));
-        mAuthContainer = new TestableAuthContainer(config, fpProps, null /* faceProps */);
+        mAuthContainer = new TestableAuthContainer(config, fpProps, null /* faceProps */,
+                mWakefulnessLifecycle);
     }
 
     private class TestableAuthContainer extends AuthContainerView {
         TestableAuthContainer(AuthContainerView.Config config,
                 @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
-                @Nullable List<FaceSensorPropertiesInternal> faceProps) {
+                @Nullable List<FaceSensorPropertiesInternal> faceProps,
+                WakefulnessLifecycle wakefulnessLifecycle) {
 
-            super(config, new MockInjector(), fpProps, faceProps);
+            super(config, new MockInjector(), fpProps, faceProps, wakefulnessLifecycle);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 08c7714..5d39eef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -69,6 +69,7 @@
 
 import com.android.internal.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.util.concurrency.Execution;
 import com.android.systemui.util.concurrency.FakeExecution;
@@ -117,6 +118,8 @@
     private SidefpsController mSidefpsController;
     @Mock
     private DisplayManager mDisplayManager;
+    @Mock
+    private WakefulnessLifecycle mWakefulnessLifecycle;
     @Captor
     ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
     @Captor
@@ -625,6 +628,34 @@
         verify(mDisplayManager).unregisterDisplayListener(any());
     }
 
+    @Test
+    public void testOnBiometricPromptShownCallback() {
+        // GIVEN a callback is registered
+        AuthController.Callback callback = mock(AuthController.Callback.class);
+        mAuthController.addCallback(callback);
+
+        // WHEN dialog is shown
+        showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+
+        // THEN callback should be received
+        verify(callback).onBiometricPromptShown();
+    }
+
+    @Test
+    public void testOnBiometricPromptDismissedCallback() {
+        // GIVEN a callback is registered
+        AuthController.Callback callback = mock(AuthController.Callback.class);
+        mAuthController.addCallback(callback);
+
+        // WHEN dialog is shown and then dismissed
+        showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
+                null /* credentialAttestation */);
+
+        // THEN callback should be received
+        verify(callback).onBiometricPromptDismissed();
+    }
+
     // Helpers
 
     private void showDialog(int[] sensorIds, boolean credentialAllowed) {
@@ -677,14 +708,15 @@
                 Provider<SidefpsController> sidefpsControllerFactory) {
             super(context, execution, commandQueue, activityTaskManager, windowManager,
                     fingerprintManager, faceManager, udfpsControllerFactory,
-                    sidefpsControllerFactory, mDisplayManager, mHandler);
+                    sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle, mHandler);
         }
 
         @Override
         protected AuthDialog buildDialog(PromptInfo promptInfo,
                 boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed,
                 String opPackageName, boolean skipIntro, long operationId, long requestId,
-                @BiometricManager.BiometricMultiSensorMode int multiSensorConfig) {
+                @BiometricManager.BiometricMultiSensorMode int multiSensorConfig,
+                WakefulnessLifecycle wakefulnessLifecycle) {
 
             mLastBiometricPromptInfo = promptInfo;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java
new file mode 100644
index 0000000..40f335d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.biometrics;
+
+import static com.android.systemui.biometrics.BiometricDisplayListener.SensorType.SideFingerprint;
+import static com.android.systemui.biometrics.BiometricDisplayListener.SensorType.UnderDisplayFingerprint;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.Display;
+import android.view.Surface;
+import android.view.Surface.Rotation;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+public class BiometricDisplayListenerTest extends SysuiTestCase {
+
+    // Dependencies
+    @Mock private DisplayManager mDisplayManager;
+    @Mock private Display mDisplay;
+    @Mock private Function0<Unit> mOnChangedCallback;
+    @Mock private UnderDisplayFingerprint mUdfpsType;
+    @Mock private SideFingerprint mSidefpsType;
+    private Handler mHandler;
+    private Context mContextSpy;
+
+    // Captors
+    @Captor private ArgumentCaptor<DisplayManager.DisplayListener> mDisplayListenerCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        // Set up mocks
+        mContextSpy = spy(mContext);
+        when(mContextSpy.getDisplay()).thenReturn(mDisplay);
+
+        // Create a real handler with a TestableLooper.
+        TestableLooper testableLooper = TestableLooper.get(this);
+        mHandler = new Handler(testableLooper.getLooper());
+    }
+
+    @Test
+    public void registersDisplayListener_whenEnabled() {
+        BiometricDisplayListener listener = new BiometricDisplayListener(
+                mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback);
+
+        listener.enable();
+        verify(mDisplayManager).registerDisplayListener(any(), same(mHandler));
+    }
+
+    @Test
+    public void unregistersDisplayListener_whenDisabled() {
+        BiometricDisplayListener listener = new BiometricDisplayListener(
+                mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback);
+
+        listener.enable();
+        listener.disable();
+        verify(mDisplayManager).unregisterDisplayListener(any());
+    }
+
+    @Test
+    public void detectsRotationChanges_forUdfps_relativeToRotationWhenEnabled() {
+        // Create a listener when the rotation is portrait.
+        when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_0);
+        BiometricDisplayListener listener = new BiometricDisplayListener(
+                mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback);
+
+        // Rotate the device to landscape and then enable the listener.
+        when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_90);
+        listener.enable();
+        verify(mDisplayManager).registerDisplayListener(mDisplayListenerCaptor.capture(),
+                same(mHandler));
+
+        // Rotate the device back to portrait and ensure the rotation is detected.
+        when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_0);
+        mDisplayListenerCaptor.getValue().onDisplayChanged(999);
+        verify(mOnChangedCallback).invoke();
+    }
+
+    @Test
+    public void callsOnChanged_forUdfps_onlyWhenRotationChanges() {
+        final @Rotation int[] rotations =
+                new int[]{
+                        Surface.ROTATION_0,
+                        Surface.ROTATION_90,
+                        Surface.ROTATION_180,
+                        Surface.ROTATION_270
+                };
+
+        for (@Rotation int rot1 : rotations) {
+            for (@Rotation int rot2 : rotations) {
+                // Make the third rotation the same as the first one to simplify this test.
+                @Rotation int rot3 = rot1;
+
+                // Clean up prior interactions.
+                reset(mDisplayManager);
+                reset(mDisplay);
+                reset(mOnChangedCallback);
+
+                // Set up the mock for 3 invocations.
+                when(mDisplay.getRotation()).thenReturn(rot1, rot2, rot3);
+
+                BiometricDisplayListener listener = new BiometricDisplayListener(
+                        mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback);
+                listener.enable();
+
+                // The listener should record the current rotation and register a display listener.
+                verify(mDisplay).getRotation();
+                verify(mDisplayManager)
+                        .registerDisplayListener(mDisplayListenerCaptor.capture(), same(mHandler));
+
+                // Test the first rotation since the listener was enabled.
+                mDisplayListenerCaptor.getValue().onDisplayChanged(123);
+                if (rot2 != rot1) {
+                    verify(mOnChangedCallback).invoke();
+                } else {
+                    verify(mOnChangedCallback, never()).invoke();
+                }
+
+                // Test continued rotations.
+                mDisplayListenerCaptor.getValue().onDisplayChanged(123);
+                if (rot3 != rot2) {
+                    verify(mOnChangedCallback, times(2)).invoke();
+                } else {
+                    verify(mOnChangedCallback, never()).invoke();
+                }
+            }
+        }
+    }
+
+    @Test
+    public void callsOnChanged_forSideFingerprint_whenAnythingDisplayChanges() {
+        // Any rotation will do for this test, we just need to return something.
+        when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_0);
+
+        BiometricDisplayListener listener = new BiometricDisplayListener(
+                mContextSpy, mDisplayManager, mHandler, mSidefpsType, mOnChangedCallback);
+        listener.enable();
+
+        // The listener should register a display listener.
+        verify(mDisplayManager)
+                .registerDisplayListener(mDisplayListenerCaptor.capture(), same(mHandler));
+
+        // mOnChangedCallback should be invoked for all calls to onDisplayChanged.
+        mDisplayListenerCaptor.getValue().onDisplayChanged(123);
+        mDisplayListenerCaptor.getValue().onDisplayChanged(123);
+        verify(mOnChangedCallback, times(2)).invoke();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java
deleted file mode 100644
index ada7ddb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java
+++ /dev/null
@@ -1,66 +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;
-
-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/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 7c5f57f..7af039b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -27,15 +27,15 @@
 import android.content.res.Resources;
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
-import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 
-import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
+import com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -64,6 +64,15 @@
     DreamOverlayContainerView mDreamOverlayContainerView;
 
     @Mock
+    ComplicationHostViewController mComplicationHostViewController;
+
+    @Mock
+    ComplicationHostViewComponent.Factory mComplicationHostViewComponentFactory;
+
+    @Mock
+    ComplicationHostViewComponent mComplicationHostViewComponent;
+
+    @Mock
     ViewGroup mDreamOverlayContentView;
 
     @Mock
@@ -80,9 +89,14 @@
                         DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT);
         when(mDreamOverlayContainerView.getResources()).thenReturn(mResources);
         when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+        when(mComplicationHostViewComponentFactory.create())
+                .thenReturn(mComplicationHostViewComponent);
+        when(mComplicationHostViewComponent.getController())
+                .thenReturn(mComplicationHostViewController);
 
         mController = new DreamOverlayContainerViewController(
                 mDreamOverlayContainerView,
+                mComplicationHostViewComponentFactory,
                 mDreamOverlayContentView,
                 mDreamOverlayStatusBarViewController,
                 mHandler,
@@ -104,20 +118,6 @@
     }
 
     @Test
-    public void testAddOverlayAddsOverlayToContentView() {
-        View overlay = new View(getContext());
-        ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(100, 100);
-        mController.addOverlay(overlay, layoutParams);
-        verify(mDreamOverlayContentView).addView(overlay, layoutParams);
-    }
-
-    @Test
-    public void testRemoveAllOverlaysRemovesOverlaysFromContentView() {
-        mController.removeAllOverlays();
-        verify(mDreamOverlayContentView).removeAllViews();
-    }
-
-    @Test
     public void testOnViewAttachedRegistersComputeInsetsListener() {
         mController.onViewAttached();
         verify(mViewTreeObserver).addOnComputeInternalInsetsListener(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index c0b7271..6b156a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -28,12 +28,11 @@
 import android.service.dreams.IDreamOverlay;
 import android.service.dreams.IDreamOverlayCallback;
 import android.testing.AndroidTestingRunner;
-import android.view.View;
-import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
 
-import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
@@ -48,18 +47,21 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Arrays;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class DreamOverlayServiceTest extends SysuiTestCase {
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
 
+    @Mock
+    LifecycleOwner mLifecycleOwner;
+
+    @Mock
+    LifecycleRegistry mLifecycleRegistry;
+
     @Rule
     public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
 
@@ -76,12 +78,6 @@
     WindowManagerImpl mWindowManager;
 
     @Mock
-    ComplicationProvider mProvider;
-
-    @Mock
-    DreamOverlayStateController mDreamOverlayStateController;
-
-    @Mock
     DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
 
     @Mock
@@ -102,13 +98,18 @@
 
         when(mDreamOverlayComponent.getDreamOverlayContainerViewController())
                 .thenReturn(mDreamOverlayContainerViewController);
-        when(mDreamOverlayComponentFactory.create())
+        when(mDreamOverlayComponent.getLifecycleOwner())
+                .thenReturn(mLifecycleOwner);
+        when(mDreamOverlayComponent.getLifecycleRegistry())
+                .thenReturn(mLifecycleRegistry);
+        when(mDreamOverlayComponentFactory
+                .create(any(), any()))
                 .thenReturn(mDreamOverlayComponent);
         when(mDreamOverlayContainerViewController.getContainerView())
                 .thenReturn(mDreamOverlayContainerView);
 
         mService = new DreamOverlayService(mContext, mMainExecutor,
-                mDreamOverlayStateController, mDreamOverlayComponentFactory);
+                mDreamOverlayComponentFactory);
         final IBinder proxy = mService.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
 
@@ -128,78 +129,6 @@
     }
 
     @Test
-    public void testAddingOverlayToDream() throws Exception {
-        // Add overlay.
-        mService.addComplication(mProvider);
-        mMainExecutor.runAllReady();
-
-        final ArgumentCaptor<ComplicationHost.CreationCallback> creationCallbackCapture =
-                ArgumentCaptor.forClass(ComplicationHost.CreationCallback.class);
-        final ArgumentCaptor<ComplicationHost.InteractionCallback> interactionCallbackCapture =
-                ArgumentCaptor.forClass(ComplicationHost.InteractionCallback.class);
-
-        // Ensure overlay provider is asked to create view.
-        verify(mProvider).onCreateComplication(any(), creationCallbackCapture.capture(),
-                interactionCallbackCapture.capture());
-        mMainExecutor.runAllReady();
-
-        // Inform service of overlay view creation.
-        final View view = new View(mContext);
-        final ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
-        );
-        creationCallbackCapture.getValue().onCreated(view, lp);
-        mMainExecutor.runAllReady();
-
-        // Verify that DreamOverlayContainerViewController is asked to add an overlay for the view.
-        verify(mDreamOverlayContainerViewController).addOverlay(view, lp);
-    }
-
-    @Test
-    public void testDreamOverlayExit() throws Exception {
-        // Add overlay.
-        mService.addComplication(mProvider);
-        mMainExecutor.runAllReady();
-
-        // Capture interaction callback from overlay creation.
-        final ArgumentCaptor<ComplicationHost.InteractionCallback> interactionCallbackCapture =
-                ArgumentCaptor.forClass(ComplicationHost.InteractionCallback.class);
-        verify(mProvider).onCreateComplication(any(), any(), interactionCallbackCapture.capture());
-
-        // Ask service to exit.
-        interactionCallbackCapture.getValue().onExit();
-        mMainExecutor.runAllReady();
-
-        // Ensure service informs dream host of exit.
-        verify(mDreamOverlayCallback).onExitRequested();
-    }
-
-    @Test
-    public void testListenerRegisteredWithDreamOverlayStateController() {
-        // Verify overlay service registered as listener with DreamOverlayStateController
-        // and inform callback of addition.
-        final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
-                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
-
-        verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
-        when(mDreamOverlayStateController.getComplications()).thenReturn(Arrays.asList(mProvider));
-        callbackCapture.getValue().onComplicationsChanged();
-        mMainExecutor.runAllReady();
-
-        // Verify provider is asked to create overlay.
-        verify(mProvider).onCreateComplication(any(), any(), any());
-    }
-
-    @Test
-    public void testOnDestroyRemovesOverlayStateCallback() {
-        final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
-                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
-        verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
-        mService.onDestroy();
-        verify(mDreamOverlayStateController).removeCallback(callbackCapture.getValue());
-    }
-
-    @Test
     public void testShouldShowComplicationsTrueByDefault() {
         assertThat(mService.shouldShowComplications()).isTrue();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index efc3c7c..7d0833d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -27,11 +27,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
-import com.google.common.util.concurrent.ListenableFuture;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,7 +46,7 @@
     DreamOverlayStateController.Callback mCallback;
 
     @Mock
-    ComplicationProvider mProvider;
+    Complication mComplication;
 
     final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -63,24 +62,23 @@
         stateController.addCallback(mCallback);
 
         // Add complication and verify callback is notified.
-        final ListenableFuture<DreamOverlayStateController.ComplicationToken> tokenFuture =
-                stateController.addComplication(mProvider);
+        stateController.addComplication(mComplication);
 
         mExecutor.runAllReady();
 
         verify(mCallback, times(1)).onComplicationsChanged();
 
-        final Collection<ComplicationProvider> providers = stateController.getComplications();
-        assertEquals(providers.size(), 1);
-        assertTrue(providers.contains(mProvider));
+        final Collection<Complication> complications = stateController.getComplications();
+        assertEquals(complications.size(), 1);
+        assertTrue(complications.contains(mComplication));
 
         clearInvocations(mCallback);
 
         // Remove complication and verify callback is notified.
-        stateController.removeComplication(tokenFuture.get());
+        stateController.removeComplication(mComplication);
         mExecutor.runAllReady();
         verify(mCallback, times(1)).onComplicationsChanged();
-        assertTrue(providers.isEmpty());
+        assertTrue(stateController.getComplications().isEmpty());
     }
 
     @Test
@@ -88,7 +86,7 @@
         final DreamOverlayStateController stateController =
                 new DreamOverlayStateController(mExecutor);
 
-        stateController.addComplication(mProvider);
+        stateController.addComplication(mComplication);
         mExecutor.runAllReady();
 
         // Verify callback occurs on add when an overlay is already present.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
new file mode 100644
index 0000000..afc0309d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.lifecycle.Observer;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ComplicationCollectionLiveDataTest extends SysuiTestCase {
+    @Before
+    public void setUp() throws Exception {
+        allowTestableLooperAsMainThread();
+    }
+
+    @Test
+    /**
+     * Ensures registration and callback lifecycles are respected.
+     */
+    public void testLifecycle() {
+        getContext().getMainExecutor().execute(() -> {
+            final DreamOverlayStateController stateController =
+                    Mockito.mock(DreamOverlayStateController.class);
+            final ComplicationCollectionLiveData liveData =
+                    new ComplicationCollectionLiveData(stateController);
+            final HashSet<Complication> complications = new HashSet<>();
+            final Observer<Collection<Complication>> observer = Mockito.mock(Observer.class);
+            complications.add(Mockito.mock(Complication.class));
+
+            when(stateController.getComplications()).thenReturn(complications);
+
+            liveData.observeForever(observer);
+            ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
+                    ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+
+            verify(stateController).addCallback(callbackCaptor.capture());
+            verifyUpdate(observer, complications);
+
+            complications.add(Mockito.mock(Complication.class));
+            callbackCaptor.getValue().onComplicationsChanged();
+
+            verifyUpdate(observer, complications);
+        });
+    }
+
+    void verifyUpdate(Observer<Collection<Complication>> observer,
+            Collection<Complication> targetCollection) {
+        ArgumentCaptor<Collection<Complication>> collectionCaptor =
+                ArgumentCaptor.forClass(Collection.class);
+
+        verify(observer).onChanged(collectionCaptor.capture());
+
+        assertThat(collectionCaptor.getValue().equals(targetCollection)).isTrue();
+        Mockito.clearInvocations(observer);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
new file mode 100644
index 0000000..3b9e398
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationHostViewControllerTest extends SysuiTestCase {
+    @Mock
+    ConstraintLayout mComplicationHostView;
+
+    @Mock
+    LifecycleOwner mLifecycleOwner;
+
+    @Mock
+    LiveData<Collection<ComplicationViewModel>> mComplicationViewModelLiveData;
+
+    @Mock
+    ComplicationCollectionViewModel mViewModel;
+
+    @Mock
+    ComplicationViewModel mComplicationViewModel;
+
+    @Mock
+    ComplicationLayoutEngine mLayoutEngine;
+
+    @Mock
+    ComplicationId mComplicationId;
+
+    @Mock
+    Complication mComplication;
+
+    @Mock
+    Complication.ViewHolder mViewHolder;
+
+    @Mock
+    View mComplicationView;
+
+    @Mock
+    ComplicationLayoutParams mComplicationLayoutParams;
+
+    @Complication.Category
+    static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mViewModel.getComplications()).thenReturn(mComplicationViewModelLiveData);
+        when(mComplicationViewModel.getId()).thenReturn(mComplicationId);
+        when(mComplicationViewModel.getComplication()).thenReturn(mComplication);
+        when(mComplication.createView(eq(mComplicationViewModel))).thenReturn(mViewHolder);
+        when(mViewHolder.getView()).thenReturn(mComplicationView);
+        when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY);
+        when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
+        when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
+    }
+
+    /**
+     * Ensures the lifecycle of complications is properly handled.
+     */
+    @Test
+    public void testViewModelObservation() {
+        final ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> observerArgumentCaptor =
+                ArgumentCaptor.forClass(Observer.class);
+        final ComplicationHostViewController controller = new ComplicationHostViewController(
+                mComplicationHostView,
+                mLayoutEngine,
+                mLifecycleOwner,
+                mViewModel);
+
+        controller.init();
+
+        verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
+                observerArgumentCaptor.capture());
+
+        final Observer<Collection<ComplicationViewModel>> observer =
+                observerArgumentCaptor.getValue();
+
+        // Add complication and ensure it is added to the view.
+        final HashSet<ComplicationViewModel> complications = new HashSet<>(
+                Arrays.asList(mComplicationViewModel));
+        observer.onChanged(complications);
+
+        verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView),
+                eq(mComplicationLayoutParams), eq(COMPLICATION_CATEGORY));
+
+        // Remove complication and ensure it is removed from the view by id.
+        observer.onChanged(new HashSet<>());
+
+        verify(mLayoutEngine).removeComplication(eq(mComplicationId));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
new file mode 100644
index 0000000..f227a9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationLayoutEngineTest extends SysuiTestCase {
+    @Mock
+    ConstraintLayout mLayout;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    private static class ViewInfo {
+        private static int sNextId = 1;
+        public final ComplicationId id;
+        public final View view;
+        public final ComplicationLayoutParams lp;
+
+        @Complication.Category
+        public final int category;
+
+        private static ComplicationId.Factory sFactory = new ComplicationId.Factory();
+
+        ViewInfo(ComplicationLayoutParams params, @Complication.Category int category,
+                ConstraintLayout layout) {
+            this.lp = params;
+            this.category = category;
+            this.view = Mockito.mock(View.class);
+            this.id = sFactory.getNextId();
+            when(view.getId()).thenReturn(sNextId++);
+            when(view.getParent()).thenReturn(layout);
+        }
+
+        void clearInvocations() {
+            Mockito.clearInvocations(view);
+        }
+    }
+
+    private void verifyChange(ViewInfo viewInfo,
+            boolean verifyAdd,
+            Consumer<ConstraintLayout.LayoutParams> paramConsumer) {
+        ArgumentCaptor<ConstraintLayout.LayoutParams> lpCaptor =
+                ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class);
+        verify(viewInfo.view).setLayoutParams(lpCaptor.capture());
+
+        if (verifyAdd) {
+            verify(mLayout).addView(eq(viewInfo.view));
+        }
+
+        ConstraintLayout.LayoutParams capturedParams = lpCaptor.getValue();
+        paramConsumer.accept(capturedParams);
+    }
+
+    private void addComplication(ComplicationLayoutEngine engine, ViewInfo info) {
+        engine.addComplication(info.id, info.view, info.lp, info.category);
+    }
+
+    /**
+     * Makes sure the engine properly places a view within the {@link ConstraintLayout}.
+     */
+    @Test
+    public void testSingleLayout() {
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                        | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+        addComplication(engine, firstViewInfo);
+
+        // Ensure the view is added to the top end corner
+        verifyChange(firstViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+    }
+
+    /**
+     * Ensures layout in a particular direction updates.
+     */
+    @Test
+    public void testDirectionLayout() {
+        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, firstViewInfo);
+
+        firstViewInfo.clearInvocations();
+
+        final ViewInfo secondViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        addComplication(engine, secondViewInfo);
+
+        // The first added view should now be underneath the second view.
+        verifyChange(firstViewInfo, false, lp -> {
+            assertThat(lp.topToBottom == secondViewInfo.view.getId()).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+
+        // The second view should be in the top position.
+        verifyChange(secondViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+    }
+
+    /**
+     * Ensures layout in a particular position updates.
+     */
+    @Test
+    public void testPositionLayout() {
+        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, firstViewInfo);
+
+        final ViewInfo secondViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        addComplication(engine, secondViewInfo);
+
+        firstViewInfo.clearInvocations();
+        secondViewInfo.clearInvocations();
+
+        final ViewInfo thirdViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_START,
+                        1),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        addComplication(engine, thirdViewInfo);
+
+        // The first added view should now be underneath the second view.
+        verifyChange(firstViewInfo, false, lp -> {
+            assertThat(lp.topToBottom == secondViewInfo.view.getId()).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+
+        // The second view should be in underneath the third view.
+        verifyChange(secondViewInfo, false, lp -> {
+            assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+
+        // The third view should be in at the top.
+        verifyChange(thirdViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+
+        final ViewInfo fourthViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_START,
+                        1),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, fourthViewInfo);
+
+        verifyChange(fourthViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
+        });
+    }
+
+    /**
+     * Ensures layout in a particular position updates.
+     */
+    @Test
+    public void testRemoval() {
+        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        engine.addComplication(firstViewInfo.id, firstViewInfo.view, firstViewInfo.lp,
+                firstViewInfo.category);
+
+        final ViewInfo secondViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        engine.addComplication(secondViewInfo.id, secondViewInfo.view, secondViewInfo.lp,
+                secondViewInfo.category);
+
+        firstViewInfo.clearInvocations();
+
+        engine.removeComplication(secondViewInfo.id);
+        verify(mLayout).removeView(eq(secondViewInfo.view));
+
+        verifyChange(firstViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
new file mode 100644
index 0000000..d080bbc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationLayoutParamsTest extends SysuiTestCase {
+    /**
+     * Ensures ComplicationLayoutParams cannot be constructed with improper position or direction.
+     */
+    @Test
+    public void testPositionValidation() {
+        final HashSet<Integer> invalidCombinations = new HashSet(Arrays.asList(
+                ComplicationLayoutParams.POSITION_BOTTOM | ComplicationLayoutParams.POSITION_TOP,
+                ComplicationLayoutParams.POSITION_END | ComplicationLayoutParams.POSITION_START
+        ));
+
+        final int allPositions = ComplicationLayoutParams.POSITION_TOP
+                | ComplicationLayoutParams.POSITION_START
+                | ComplicationLayoutParams.POSITION_END
+                | ComplicationLayoutParams.POSITION_BOTTOM;
+
+        final HashSet<Integer> allDirections = new HashSet(Arrays.asList(
+                ComplicationLayoutParams.DIRECTION_DOWN,
+                ComplicationLayoutParams.DIRECTION_UP,
+                ComplicationLayoutParams.DIRECTION_START,
+                ComplicationLayoutParams.DIRECTION_END
+        ));
+
+        final HashMap<Integer, Integer> invalidDirections = new HashMap<>();
+        invalidDirections.put(ComplicationLayoutParams.DIRECTION_DOWN,
+                ComplicationLayoutParams.POSITION_BOTTOM);
+        invalidDirections.put(ComplicationLayoutParams.DIRECTION_UP,
+                ComplicationLayoutParams.POSITION_TOP);
+        invalidDirections.put(ComplicationLayoutParams.DIRECTION_START,
+                ComplicationLayoutParams.POSITION_START);
+        invalidDirections.put(ComplicationLayoutParams.DIRECTION_END,
+                ComplicationLayoutParams.POSITION_END);
+
+
+        for (int position = 0; position <= allPositions; ++position) {
+            boolean properPosition = position != 0;
+            if (properPosition) {
+                for (Integer combination : invalidCombinations) {
+                    if ((combination & position) == combination) {
+                        properPosition = false;
+                    }
+                }
+            }
+            boolean exceptionEncountered = false;
+            for (Integer direction : allDirections) {
+                final int invalidPosition = invalidDirections.get(direction);
+                final boolean properDirection = (invalidPosition & position) != invalidPosition;
+
+                try {
+                    final ComplicationLayoutParams params = new ComplicationLayoutParams(
+                            100,
+                            100,
+                            position,
+                            direction,
+                            0);
+                } catch (Exception e) {
+                    exceptionEncountered = true;
+                }
+
+                assertThat((properPosition && properDirection) || exceptionEncountered).isTrue();
+            }
+        }
+    }
+
+    /**
+     * Ensures ComplicationLayoutParams is properly duplicated on copy construction.
+     */
+    @Test
+    public void testCopyConstruction() {
+        final ComplicationLayoutParams params = new ComplicationLayoutParams(
+                100,
+                100,
+                ComplicationLayoutParams.POSITION_TOP,
+                ComplicationLayoutParams.DIRECTION_DOWN,
+                3);
+        final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
+
+        assertThat(copy.getDirection() == params.getDirection()).isTrue();
+        assertThat(copy.getPosition() == params.getPosition()).isTrue();
+        assertThat(copy.getWeight() == params.getWeight()).isTrue();
+        assertThat(copy.height == params.height).isTrue();
+        assertThat(copy.width == params.width).isTrue();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformerTest.java
new file mode 100644
index 0000000..2bc427d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformerTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.dagger.ComplicationViewModelComponent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationViewModelTransformerTest extends SysuiTestCase {
+    @Mock
+    ComplicationViewModelComponent.Factory mFactory;
+
+    @Mock
+    ComplicationViewModelComponent mComponent;
+
+    @Mock
+    ComplicationViewModelProvider mViewModelProvider;
+
+    @Mock
+    ComplicationViewModel mViewModel;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mFactory.create(Mockito.any(), Mockito.any())).thenReturn(mComponent);
+        when(mComponent.getViewModelProvider()).thenReturn(mViewModelProvider);
+        when(mViewModelProvider.get(Mockito.any(), Mockito.any())).thenReturn(mViewModel);
+    }
+
+    /**
+     * Ensure the same id is returned for the same complication across invocations.
+     */
+    @Test
+    public void testStableIds() {
+        final ComplicationViewModelTransformer transformer =
+                new ComplicationViewModelTransformer(mFactory);
+
+        final Complication complication = Mockito.mock(Complication.class);
+
+        ArgumentCaptor<ComplicationId> idCaptor = ArgumentCaptor.forClass(ComplicationId.class);
+
+        transformer.getViewModel(complication);
+        verify(mFactory).create(Mockito.any(), idCaptor.capture());
+        final ComplicationId firstId = idCaptor.getValue();
+
+        Mockito.clearInvocations(mFactory);
+
+        transformer.getViewModel(complication);
+        verify(mFactory).create(Mockito.any(), idCaptor.capture());
+        final ComplicationId secondId = idCaptor.getValue();
+
+        assertEquals(secondId, firstId);
+    }
+
+    /**
+     * Ensure unique ids are assigned to different complications.
+     */
+    @Test
+    public void testUniqueIds() {
+        final ComplicationViewModelTransformer transformer =
+                new ComplicationViewModelTransformer(mFactory);
+
+        final Complication firstComplication = Mockito.mock(Complication.class);
+        final Complication secondComplication = Mockito.mock(Complication.class);
+
+        ArgumentCaptor<ComplicationId> idCaptor = ArgumentCaptor.forClass(ComplicationId.class);
+
+        transformer.getViewModel(firstComplication);
+        verify(mFactory).create(Mockito.any(), idCaptor.capture());
+        final ComplicationId firstId = idCaptor.getValue();
+
+        Mockito.clearInvocations(mFactory);
+
+        transformer.getViewModel(secondComplication);
+        verify(mFactory).create(Mockito.any(), idCaptor.capture());
+        final ComplicationId secondId = idCaptor.getValue();
+
+        assertNotEquals(secondId, firstId);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 9827d21..da8ab27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -51,6 +51,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -68,6 +69,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import dagger.Lazy;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
@@ -94,6 +97,7 @@
     private @Mock ScreenOffAnimationController mScreenOffAnimationController;
     private @Mock InteractionJankMonitor mInteractionJankMonitor;
     private @Mock ScreenOnCoordinator mScreenOnCoordinator;
+    private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -197,7 +201,8 @@
                 mScreenOffAnimationController,
                 () -> mNotificationShadeDepthController,
                 mScreenOnCoordinator,
-                mInteractionJankMonitor);
+                mInteractionJankMonitor,
+                mNotificationShadeWindowControllerLazy);
         mViewMediator.start();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
new file mode 100644
index 0000000..b8e9cf4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.log;
+
+import static android.app.StatusBarManager.ALL_SESSIONS;
+import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class SessionTrackerTest extends SysuiTestCase {
+    @Mock
+    private IStatusBarService mStatusBarService;
+    @Mock
+    private AuthController mAuthController;
+    @Mock
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+
+    @Captor
+    ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
+    KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+
+    @Captor
+    ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCallbackCaptor;
+    KeyguardStateController.Callback mKeyguardStateCallback;
+
+    @Captor
+    ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
+    AuthController.Callback mAuthControllerCallback;
+
+    private SessionTracker mSessionTracker;
+
+    @Before
+    public void setup() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+
+        mSessionTracker = new SessionTracker(
+                mContext,
+                mStatusBarService,
+                mAuthController,
+                mKeyguardUpdateMonitor,
+                mKeyguardStateController
+        );
+    }
+
+    @Test
+    public void testOnStartShowingKeyguard() throws RemoteException {
+        // GIVEN the keyguard is showing before start
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+        // WHEN started
+        mSessionTracker.start();
+
+        // THEN keyguard session has a session id
+        assertNotNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
+
+        // THEN send event to status bar service
+        verify(mStatusBarService).onSessionStarted(eq(SESSION_KEYGUARD), any(InstanceId.class));
+    }
+
+    @Test
+    public void testNoSessions() throws RemoteException {
+        // GIVEN no sessions
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+        // WHEN started
+        mSessionTracker.start();
+
+        // THEN all sessions are null
+        for (int sessionType : ALL_SESSIONS) {
+            assertNull(mSessionTracker.getSessionId(sessionType));
+        }
+    }
+
+    @Test
+    public void testBiometricPromptShowing() throws RemoteException {
+        // GIVEN session tracker started w/o any sessions
+        mSessionTracker.start();
+        captureAuthControllerCallback();
+
+        // WHEN auth controller shows the biometric prompt
+        mAuthControllerCallback.onBiometricPromptShown();
+
+        // THEN the biometric prompt session has a session id
+        assertNotNull(mSessionTracker.getSessionId(SESSION_BIOMETRIC_PROMPT));
+
+        // THEN session started event gets sent to status bar service
+        verify(mStatusBarService).onSessionStarted(
+                eq(SESSION_BIOMETRIC_PROMPT), any(InstanceId.class));
+    }
+
+    @Test
+    public void testBiometricPromptDismissed() throws RemoteException {
+        // GIVEN session tracker started w/o any sessions
+        mSessionTracker.start();
+        captureAuthControllerCallback();
+
+        // WHEN auth controller shows the biometric prompt and then hides it
+        mAuthControllerCallback.onBiometricPromptShown();
+        mAuthControllerCallback.onBiometricPromptDismissed();
+
+        // THEN the biometric prompt session no longer has a session id
+        assertNull(mSessionTracker.getSessionId(SESSION_BIOMETRIC_PROMPT));
+
+        // THEN session end event gets sent to status bar service
+        verify(mStatusBarService).onSessionEnded(
+                eq(SESSION_BIOMETRIC_PROMPT), any(InstanceId.class));
+    }
+
+    @Test
+    public void testKeyguardSessionOnDeviceStartsSleeping() throws RemoteException {
+        // GIVEN session tracker started w/o any sessions
+        mSessionTracker.start();
+        captureKeyguardUpdateMonitorCallback();
+
+        // WHEN device starts going to sleep
+        mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+        // THEN the keyguard session has a session id
+        assertNotNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
+
+        // THEN session start event gets sent to status bar service
+        verify(mStatusBarService).onSessionStarted(
+                eq(SESSION_KEYGUARD), any(InstanceId.class));
+    }
+
+    @Test
+    public void testKeyguardSessionOnKeyguardShowingChange() throws RemoteException {
+        // GIVEN session tracker started w/o any sessions
+        mSessionTracker.start();
+        captureKeyguardStateControllerCallback();
+
+        // WHEN keyguard becomes visible (ie: from lockdown)
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        mKeyguardStateCallback.onKeyguardShowingChanged();
+
+        // THEN the keyguard session has a session id
+        assertNotNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
+
+        // THEN session start event gets sent to status bar service
+        verify(mStatusBarService).onSessionStarted(
+                eq(SESSION_KEYGUARD), any(InstanceId.class));
+    }
+
+    @Test
+    public void testKeyguardSessionOnKeyguardNotShowing() throws RemoteException {
+        // GIVEN session tracker started w/o any sessions
+        mSessionTracker.start();
+        captureKeyguardStateControllerCallback();
+
+        // WHEN keyguard was showing and now it's not
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        mKeyguardStateCallback.onKeyguardShowingChanged();
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        mKeyguardStateCallback.onKeyguardShowingChanged();
+
+        // THEN the keyguard session no longer has a session id
+        assertNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
+
+        // THEN session end event gets sent to status bar service
+        verify(mStatusBarService).onSessionEnded(
+                eq(SESSION_KEYGUARD), any(InstanceId.class));
+    }
+
+    void captureKeyguardUpdateMonitorCallback() {
+        verify(mKeyguardUpdateMonitor).registerCallback(
+                mKeyguardUpdateMonitorCallbackCaptor.capture());
+        mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+    }
+
+    void captureKeyguardStateControllerCallback() {
+        verify(mKeyguardStateController).addCallback(
+                mKeyguardStateCallbackCaptor.capture());
+        mKeyguardStateCallback = mKeyguardStateCallbackCaptor.getValue();
+    }
+
+    void captureAuthControllerCallback() {
+        verify(mAuthController).addCallback(
+                mAuthControllerCallbackCaptor.capture());
+        mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 89c0712..421ae03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -100,7 +100,7 @@
     public void getItemCount_zeroMode_containExtraOneForPairNew() {
         when(mMediaOutputController.isZeroMode()).thenReturn(true);
 
-        assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaDevices.size());
+        assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaDevices.size() + 1);
     }
 
     @Test
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 a1ec38f..81ae209 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.util.mockito.capture
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.mockito.Mock
 import org.mockito.Mockito.anyString
@@ -42,6 +43,7 @@
 import java.util.concurrent.Executor
 
 @SmallTest
+@Ignore("b/216286227")
 class MediaTttCommandLineHelperTest : SysuiTestCase() {
 
     private val inlineExecutor = Executor { command -> command.run() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 927ca7a..242fd19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
@@ -38,6 +39,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@Ignore("b/216286227")
 class MediaTttChipControllerCommonTest : SysuiTestCase() {
     private lateinit var controllerCommon: MediaTttChipControllerCommon<MediaTttChipState>
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index afaab80..1d1265b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
@@ -34,6 +35,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@Ignore("b/216286227")
 class MediaTttChipControllerReceiverTest : SysuiTestCase() {
     private lateinit var controllerReceiver: MediaTttChipControllerReceiver
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 509ae33..6b4eebe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
@@ -37,6 +38,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@Ignore("b/216286227")
 class MediaTttChipControllerSenderTest : SysuiTestCase() {
     private lateinit var appIconDrawable: Drawable
 
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 11b727e..64542cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
@@ -11,12 +11,14 @@
 import com.android.systemui.util.mockito.capture
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@Ignore("b/216286227")
 class MediaTttSenderServiceTest : SysuiTestCase() {
 
     private lateinit var service: IDeviceSenderService
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index e73e5ff..721809c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -28,7 +28,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.IntentFilter;
 import android.os.BatteryManager;
 import android.os.Handler;
@@ -81,9 +80,9 @@
     private static final int OLD_BATTERY_LEVEL_10 = 10;
     private static final long VERY_BELOW_SEVERE_HYBRID_THRESHOLD = TimeUnit.MINUTES.toMillis(15);
     public static final int BATTERY_LEVEL_10 = 10;
-    private WarningsUI mMockWarnings;
+    @Mock private WarningsUI mMockWarnings;
     private PowerUI mPowerUI;
-    private EnhancedEstimates mEnhancedEstimates;
+    @Mock private EnhancedEstimates mEnhancedEstimates;
     @Mock private PowerManager mPowerManager;
     @Mock private IThermalService mThermalServiceMock;
     private IThermalEventListener mUsbThermalEventListener;
@@ -96,13 +95,9 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mMockWarnings = mDependency.injectMockDependency(WarningsUI.class);
-        mEnhancedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class);
 
         when(mStatusBarOptionalLazy.get()).thenReturn(Optional.of(mStatusBar));
 
-        mContext.addMockSystemService(Context.POWER_SERVICE, mPowerManager);
-
         createPowerUi();
         mSkinThermalEventListener = mPowerUI.new SkinThermalEventListener();
         mUsbThermalEventListener = mPowerUI.new UsbThermalEventListener();
@@ -690,7 +685,8 @@
 
     private void createPowerUi() {
         mPowerUI = new PowerUI(
-                mContext, mBroadcastDispatcher, mCommandQueue, mStatusBarOptionalLazy);
+                mContext, mBroadcastDispatcher, mCommandQueue, mStatusBarOptionalLazy,
+                mMockWarnings, mEnhancedEstimates, mPowerManager);
         mPowerUI.mThermalService = mThermalServiceMock;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index 3242adb..b5ce706 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -80,6 +80,8 @@
     @Mock
     private TunerService mTunerService;
     @Mock
+    private QSFgsManagerFooter mQSFgsManagerFooter;
+    @Mock
     private QSSecurityFooter mQSSecurityFooter;
     @Mock
     private QSLogger mQSLogger;
@@ -127,8 +129,8 @@
                 .thenReturn(mQSTileRevealController);
         when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
 
-        mController = new QSPanelController(mQSPanel, mQSSecurityFooter, mTunerService,
-                mQSTileHost, mQSCustomizerController, true, mMediaHost,
+        mController = new QSPanelController(mQSPanel, mQSFgsManagerFooter, mQSSecurityFooter,
+                mTunerService, mQSTileHost, mQSCustomizerController, true, mMediaHost,
                 mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
                 mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
                 mFalsingManager, mCommandQueue
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index 968b12a..88b133e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -34,7 +34,6 @@
 import com.android.systemui.qs.tiles.DataSaverTile
 import com.android.systemui.qs.tiles.DeviceControlsTile
 import com.android.systemui.qs.tiles.DndTile
-import com.android.systemui.qs.tiles.FgsManagerTile
 import com.android.systemui.qs.tiles.FlashlightTile
 import com.android.systemui.qs.tiles.HotspotTile
 import com.android.systemui.qs.tiles.InternetTile
@@ -92,7 +91,6 @@
         "wallet" to QuickAccessWalletTile::class.java,
         "qr_code_scanner" to QRCodeScannerTile::class.java,
         "onehanded" to OneHandedModeTile::class.java,
-        "fgsmanager" to FgsManagerTile::class.java,
         "color_correction" to ColorCorrectionTile::class.java
 )
 
@@ -133,7 +131,6 @@
     @Mock private lateinit var quickAccessWalletTile: QuickAccessWalletTile
     @Mock private lateinit var qrCodeScannerTile: QRCodeScannerTile
     @Mock private lateinit var oneHandedModeTile: OneHandedModeTile
-    @Mock private lateinit var fgsManagerTile: FgsManagerTile
     @Mock private lateinit var colorCorrectionTile: ColorCorrectionTile
 
     private lateinit var factory: QSFactoryImpl
@@ -178,7 +175,6 @@
                 { quickAccessWalletTile },
                 { qrCodeScannerTile },
                 { oneHandedModeTile },
-                { fgsManagerTile },
                 { colorCorrectionTile }
         )
         // When adding/removing tiles, fix also [specMap]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 04c6f6c..86a705f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -137,6 +137,7 @@
     @Mock private VisualStabilityManager mVisualStabilityManager;
     @Mock private ShadeController mShadeController;
     @Mock private InteractionJankMonitor mJankMonitor;
+    @Mock private StackStateLogger mStackLogger;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -191,7 +192,8 @@
                 mRemoteInputManager,
                 mVisualStabilityManager,
                 mShadeController,
-                mJankMonitor
+                mJankMonitor,
+                mStackLogger
         );
 
         when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 1c8b35a..dee88db 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
@@ -23,7 +23,6 @@
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
-import static com.android.systemui.statusbar.notification.ViewGroupFadeHelper.reset;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -38,6 +37,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -120,6 +120,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.QsFrameTranslateController;
@@ -365,6 +366,8 @@
     private StatusBarWindowStateController mStatusBarWindowStateController;
     @Mock
     private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    @Mock
+    private NotificationShadeWindowController mNotificationShadeWindowController;
     private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
     private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
@@ -490,7 +493,10 @@
                 .thenReturn(true);
         when(mInteractionJankMonitor.end(anyInt()))
                 .thenReturn(true);
-        reset(mView);
+        doAnswer(invocation -> {
+            ((Runnable) invocation.getArgument(0)).run();
+            return null;
+        }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
 
         mMainHandler = new Handler(Looper.getMainLooper());
 
@@ -505,6 +511,7 @@
                 mCommunalStateController, mKeyguardStateController,
                 mStatusBarStateController,
                 mStatusBarWindowStateController,
+                mNotificationShadeWindowController,
                 mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
                 mCommunalSourceMonitor, mMetricsLogger, mActivityManager, mConfigurationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index a5651f3..671ab59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -225,4 +226,17 @@
         assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) != 0).isTrue();
         assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue();
     }
+
+    @Test
+    public void batchApplyWindowLayoutParams_doesNotDispatchEvents() {
+        mNotificationShadeWindowController.setForceDozeBrightness(true);
+        verify(mWindowManager).updateViewLayout(any(), any());
+
+        clearInvocations(mWindowManager);
+        mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+            mNotificationShadeWindowController.setForceDozeBrightness(false);
+            verify(mWindowManager, never()).updateViewLayout(any(), any());
+        });
+        verify(mWindowManager).updateViewLayout(any(), any());
+    }
 }
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 77065b2..b7c00fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -129,7 +129,6 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
@@ -226,7 +225,6 @@
     @Mock private NotificationMediaManager mNotificationMediaManager;
     @Mock private NavigationBarController mNavigationBarController;
     @Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController;
-    @Mock private BypassHeadsUpNotifier mBypassHeadsUpNotifier;
     @Mock private SysuiColorExtractor mColorExtractor;
     @Mock private ColorExtractor.GradientColors mGradientColors;
     @Mock private PulseExpansionHandler mPulseExpansionHandler;
@@ -369,6 +367,10 @@
         when(mStatusBarComponentFactory.create()).thenReturn(mStatusBarComponent);
         when(mStatusBarComponent.getNotificationShadeWindowViewController()).thenReturn(
                 mNotificationShadeWindowViewController);
+        doAnswer(invocation -> {
+            ((Runnable) invocation.getArgument(0)).run();
+            return null;
+        }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
 
         mShadeController = new ShadeControllerImpl(mCommandQueue,
                 mStatusBarStateController, mNotificationShadeWindowController,
@@ -394,7 +396,6 @@
                 mKeyguardStateController,
                 mHeadsUpManager,
                 mDynamicPrivacyController,
-                mBypassHeadsUpNotifier,
                 new FalsingManagerFake(),
                 new FalsingCollectorFake(),
                 mBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index d15ba26..d325840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -23,14 +23,20 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
 
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Context;
 import android.content.Intent;
+import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -40,12 +46,14 @@
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -58,10 +66,15 @@
     private HeadsUpManager mHeadsUpManager;
     private boolean mLivesPastNormalTime;
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
+    @Mock private HeadsUpManager.HeadsUpEntry mAlertEntry;
+    @Mock private NotificationEntry mEntry;
+    @Mock private StatusBarNotification mSbn;
+    @Mock private Notification mNotification;
+    @Mock private HeadsUpManagerLogger mLogger;
 
     private final class TestableHeadsUpManager extends HeadsUpManager {
-        TestableHeadsUpManager(Context context) {
-            super(context, mock(HeadsUpManagerLogger.class));
+        TestableHeadsUpManager(Context context, HeadsUpManagerLogger logger) {
+            super(context, logger);
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
         }
@@ -73,10 +86,12 @@
 
     @Before
     public void setUp() {
+        initMocks(this);
         mAccessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
         mDependency.injectTestDependency(UiEventLogger.class, mUiEventLoggerFake);
-
-        mHeadsUpManager = new TestableHeadsUpManager(mContext);
+        when(mEntry.getSbn()).thenReturn(mSbn);
+        when(mSbn.getNotification()).thenReturn(mNotification);
+        mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger);
         super.setUp();
         mHeadsUpManager.mHandler.removeCallbacksAndMessages(null);
         mHeadsUpManager.mHandler = mTestHandler;
@@ -88,6 +103,13 @@
     }
 
     @Test
+    public void testHunRemovedLogging() {
+        mAlertEntry.mEntry = mEntry;
+        mHeadsUpManager.onAlertEntryRemoved(mAlertEntry);
+        verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry.getKey()));
+    }
+
+    @Test
     public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
         doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
                 .getRecommendedTimeoutMillis(anyInt(), anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index 2126dda..f5554c5 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
@@ -14,6 +14,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -27,6 +29,7 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -34,6 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.appops.AppOpItem;
@@ -43,6 +47,7 @@
 import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.settings.SecureSettings;
 
 import com.google.common.collect.ImmutableList;
 
@@ -60,9 +65,11 @@
     private LocationControllerImpl mLocationController;
     private TestableLooper mTestableLooper;
     private DeviceConfigProxy mDeviceConfigProxy;
+    private UiEventLoggerFake mUiEventLogger;
 
     @Mock private AppOpsController mAppOpsController;
     @Mock private UserTracker mUserTracker;
+    @Mock private SecureSettings mSecureSettings;
 
     @Before
     public void setup() {
@@ -72,6 +79,7 @@
         when(mUserTracker.getUserProfiles())
                 .thenReturn(ImmutableList.of(new UserInfo(0, "name", 0)));
         mDeviceConfigProxy = new DeviceConfigProxyFake();
+        mUiEventLogger = new UiEventLoggerFake();
 
         mTestableLooper = TestableLooper.get(this);
         mLocationController = new LocationControllerImpl(mContext,
@@ -82,7 +90,9 @@
                 mock(BroadcastDispatcher.class),
                 mock(BootCompleteCache.class),
                 mUserTracker,
-                mContext.getPackageManager());
+                mContext.getPackageManager(),
+                mUiEventLogger,
+                mSecureSettings);
 
         mTestableLooper.processAllMessages();
     }
@@ -160,6 +170,10 @@
         mTestableLooper.processAllMessages();
 
         verify(callback, times(1)).onLocationActiveChanged(anyBoolean());
+        assertThat(mUiEventLogger.numLogs()).isEqualTo(1);
+        assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+                LocationControllerImpl.LocationIndicatorEvent.LOCATION_INDICATOR_MONITOR_HIGH_POWER
+                        .getId());
     }
 
     @Test
@@ -194,7 +208,7 @@
     }
 
     @Test
-    public void testCallbackNotified_additionalOps_shouldShowSystem() {
+    public void testCallbackNotified_additionalOps_shouldNotShowSystem() {
         LocationChangeCallback callback = mock(LocationChangeCallback.class);
         mLocationController.addCallback(callback);
         mDeviceConfigProxy.setProperty(
@@ -219,7 +233,9 @@
 
 
     @Test
-    public void testCallbackNotified_additionalOps_shouldNotShowSystem() {
+    public void testCallbackNotified_additionalOps_shouldShowSystem() {
+        when(mSecureSettings.getInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0))
+                .thenReturn(1);
         LocationChangeCallback callback = mock(LocationChangeCallback.class);
         mLocationController.addCallback(callback);
         mDeviceConfigProxy.setProperty(
@@ -251,6 +267,66 @@
         mTestableLooper.processAllMessages();
 
         verify(callback, times(1)).onLocationActiveChanged(false);
+
+        when(mSecureSettings.getInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0))
+                .thenReturn(0);
+        mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
+                "com.google.android.gms", true);
+
+        mTestableLooper.processAllMessages();
+
+        // onLocationActive(true) was not called again because the setting is disabled.
+        verify(callback, times(1)).onLocationActiveChanged(true);
+    }
+
+    @Test
+    public void testCallbackNotified_verifyMetrics() {
+        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()),
+                        new AppOpItem(AppOpsManager.OP_COARSE_LOCATION, 0,
+                                "com.google.android.googlequicksearchbox",
+                                System.currentTimeMillis()),
+                        new AppOpItem(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0,
+                                "com.google.android.apps.maps",
+                                System.currentTimeMillis())));
+        mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
+                "com.google.android.gms", true);
+
+        mTestableLooper.processAllMessages();
+
+        verify(callback, times(1)).onLocationActiveChanged(true);
+        assertThat(mUiEventLogger.numLogs()).isEqualTo(3);
+        assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+                LocationControllerImpl.LocationIndicatorEvent.LOCATION_INDICATOR_MONITOR_HIGH_POWER
+                        .getId());
+        // Even though the system access wasn't shown due to the device settings, ensure it was
+        // still logged.
+        assertThat(mUiEventLogger.eventId(1)).isEqualTo(
+                LocationControllerImpl.LocationIndicatorEvent.LOCATION_INDICATOR_SYSTEM_APP
+                        .getId());
+        assertThat(mUiEventLogger.eventId(2)).isEqualTo(
+                LocationControllerImpl.LocationIndicatorEvent.LOCATION_INDICATOR_NON_SYSTEM_APP
+                        .getId());
+        mUiEventLogger.getLogs().clear();
+
+        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);
+        assertThat(mUiEventLogger.numLogs()).isEqualTo(0);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 2f2e536..6593823 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -32,6 +32,7 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.common.ShellExecutor;
@@ -79,6 +80,7 @@
     @Mock ProtoTracer mProtoTracer;
     @Mock ShellCommandHandler mShellCommandHandler;
     @Mock CompatUI mCompatUI;
+    @Mock UserInfoController mUserInfoController;
     @Mock ShellExecutor mSysUiMainExecutor;
     @Mock DragAndDrop mDragAndDrop;
 
@@ -92,7 +94,7 @@
                 Optional.of(mDragAndDrop),
                 mCommandQueue, mConfigurationController, mKeyguardUpdateMonitor,
                 mNavigationModeController, mScreenLifecycle, mSysUiState, mProtoTracer,
-                mWakefulnessLifecycle, mSysUiMainExecutor);
+                mWakefulnessLifecycle, mUserInfoController, mSysUiMainExecutor);
     }
 
     @Test
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 196c6aa..e89dda9 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -362,5 +362,11 @@
     // Notify the user that some accessibility service has view and control permissions.
     // package: android
     NOTE_A11Y_VIEW_AND_CONTROL_ACCESS = 1005;
+
+    // Notify the user an abusive background app has been detected.
+    // Package: android
+    // Note: this is a base ID, multiple notifications will be posted for each
+    // abusive apps, with notification ID based off this ID.
+    NOTE_ABUSIVE_BG_APPS_BASE = 0xc1b2508; // 203105544
   }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0f35456..51b49ed 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -516,6 +516,8 @@
                 .append(String.valueOf(mMagnificationCapabilities));
         pw.append(", audioDescriptionByDefaultEnabled=")
                 .append(String.valueOf(mIsAudioDescriptionByDefaultRequested));
+        pw.append(", magnificationFollowTypingEnabled=")
+                .append(String.valueOf(mMagnificationFollowTypingEnabled));
         pw.append("}");
         pw.println();
         pw.append("     shortcut key:{");
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index e39b979..fe97a46 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -769,6 +769,10 @@
         mMagnificationFollowTypingEnabled = enabled;
     }
 
+    boolean isMagnificationFollowTypingEnabled() {
+        return mMagnificationFollowTypingEnabled;
+    }
+
     /**
      * Remove the display magnification with given id.
      *
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index 77e3ee5..9eb77b4 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -365,13 +365,18 @@
         mController.getFullScreenMagnificationController().unregister(displayId);
     }
 
-    /** Dumps {@link MagnificationConfig} and magnification region of magnifiers on the displays. */
+    /**
+     * Dumps magnification configuration {@link MagnificationConfig} and state for each
+     * {@link Display}
+     */
     public void dump(final PrintWriter pw, ArrayList<Display> displaysList) {
         for (int i = 0; i < displaysList.size(); i++) {
             final int displayId = displaysList.get(i).getDisplayId();
+
             final MagnificationConfig config = getMagnificationConfig(displayId);
             pw.println("Magnifier on display#" + displayId);
             pw.append("    " + config).println();
+
             final Region region = new Region();
             getCurrentMagnificationRegion(displayId, region, true);
             if (!region.isEmpty()) {
@@ -379,6 +384,8 @@
             }
             pw.append("    IdOfLastServiceToMagnify="
                     + getIdOfLastServiceToMagnify(config.getMode(), displayId)).println();
+
+            dumpTrackingTypingFocusEnabledState(pw, displayId, config.getMode());
         }
     }
 
@@ -389,4 +396,13 @@
                 : mController.getWindowMagnificationMgr().getIdOfLastServiceToMagnify(
                         displayId);
     }
+
+    private void dumpTrackingTypingFocusEnabledState(final PrintWriter pw, int displayId,
+            int mode) {
+        if (mode == MAGNIFICATION_MODE_WINDOW) {
+            pw.append("    TrackingTypingFocusEnabled="  + mController
+                            .getWindowMagnificationMgr().isTrackingTypingFocusEnabled(displayId))
+                    .println();
+        }
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 2fbefa4..278f3f9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -318,6 +318,10 @@
         mMagnificationFollowTypingEnabled = enabled;
     }
 
+    boolean isMagnificationFollowTypingEnabled() {
+        return mMagnificationFollowTypingEnabled;
+    }
+
     /**
      * Get the ID of the last service that changed the magnification config.
      *
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 78d9095..2168fb1 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -89,6 +89,7 @@
 import android.util.AtomicFile;
 import android.util.AttributeSet;
 import android.util.IntArray;
+import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Pair;
 import android.util.Slog;
@@ -1935,6 +1936,14 @@
     private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
         long requestId = UPDATE_COUNTER.incrementAndGet();
         if (widget != null) {
+            if (widget.trackingUpdate) {
+                // This is the first update, end the trace
+                widget.trackingUpdate = false;
+                Log.i(TAG, "Widget update received " + widget.toString());
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                        "appwidget update-intent " + widget.provider.id.toString(),
+                        widget.appWidgetId);
+            }
             widget.updateSequenceNos.put(ID_VIEWS_UPDATE, requestId);
         }
         if (widget == null || widget.provider == null || widget.provider.zombie
@@ -2011,6 +2020,15 @@
     private void scheduleNotifyAppWidgetRemovedLocked(Widget widget) {
         long requestId = UPDATE_COUNTER.incrementAndGet();
         if (widget != null) {
+            if (widget.trackingUpdate) {
+                // Widget is being removed without any update, end the trace
+                widget.trackingUpdate = false;
+                Log.i(TAG, "Widget removed " + widget.toString());
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                        "appwidget update-intent " + widget.provider.id.toString(),
+                        widget.appWidgetId);
+            }
+
             widget.updateSequenceNos.clear();
         }
         if (widget == null || widget.provider == null || widget.provider.zombie
@@ -2724,6 +2742,13 @@
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                             "appwidget init " + provider.id.componentName.getPackageName());
                     sendEnableIntentLocked(provider);
+                    provider.widgets.forEach(widget -> {
+                        widget.trackingUpdate = true;
+                        Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                                "appwidget update-intent " + provider.id.toString(),
+                                widget.appWidgetId);
+                        Log.i(TAG, "Widget update scheduled on unlock " + widget.toString());
+                    });
                     int[] appWidgetIds = getWidgetIds(provider.widgets);
                     sendUpdateIntentLocked(provider, appWidgetIds);
                     registerForBroadcastsLocked(provider, appWidgetIds);
@@ -4249,6 +4274,7 @@
         Host host;
         // Map of request type to updateSequenceNo.
         SparseLongArray updateSequenceNos = new SparseLongArray(2);
+        boolean trackingUpdate = false;
 
         @Override
         public String toString() {
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index ae39d7e..6c56e2f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -27,6 +27,7 @@
 import android.hardware.input.VirtualMouseScrollEvent;
 import android.hardware.input.VirtualTouchEvent;
 import android.os.IBinder;
+import android.os.IInputConstants;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Slog;
@@ -126,6 +127,7 @@
             final InputManagerInternal inputManagerInternal =
                     LocalServices.getService(InputManagerInternal.class);
             inputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+            inputManagerInternal.setPointerAcceleration(1);
             mActivePointerDisplayId = displayId;
         }
         try {
@@ -210,6 +212,8 @@
         final InputManagerInternal inputManagerInternal =
                 LocalServices.getService(InputManagerInternal.class);
         inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+        inputManagerInternal.setPointerAcceleration(
+                IInputConstants.DEFAULT_POINTER_ACCELERATION);
         mActivePointerDisplayId = Display.INVALID_DISPLAY;
     }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 5c8fb2e..95b9e58 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -45,10 +45,12 @@
 import android.hardware.input.VirtualTouchEvent;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -59,6 +61,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Map;
 import java.util.Set;
 
 
@@ -78,6 +81,7 @@
     private final OnDeviceCloseListener mListener;
     private final IBinder mAppToken;
     private final VirtualDeviceParams mParams;
+    private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
     private final IVirtualDeviceActivityListener mActivityListener;
 
     private ActivityListener createListenerAdapter(int displayId) {
@@ -206,6 +210,16 @@
 
     @Override // Binder call
     public void close() {
+        synchronized (mVirtualDeviceLock) {
+            if (!mPerDisplayWakelocks.isEmpty()) {
+                mPerDisplayWakelocks.forEach((displayId, wakeLock) -> {
+                    Slog.w(TAG, "VirtualDisplay " + displayId + " owned by UID " + mOwnerUid
+                            + " was not properly released");
+                    wakeLock.release();
+                });
+                mPerDisplayWakelocks.clear();
+            }
+        }
         mListener.onClose(mAssociationInfo.getId());
         mAppToken.unlinkToDeath(this, 0);
         mInputController.close();
@@ -383,22 +397,48 @@
     }
 
     DisplayWindowPolicyController onVirtualDisplayCreatedLocked(int displayId) {
-        if (mVirtualDisplayIds.contains(displayId)) {
-            throw new IllegalStateException(
-                    "Virtual device already have a virtual display with ID " + displayId);
+        synchronized (mVirtualDeviceLock) {
+            if (mVirtualDisplayIds.contains(displayId)) {
+                throw new IllegalStateException(
+                        "Virtual device already have a virtual display with ID " + displayId);
+            }
+            mVirtualDisplayIds.add(displayId);
+
+            // Since we're being called in the middle of the display being created, we post a
+            // task to grab the wakelock instead of doing it synchronously here, to avoid
+            // reentrancy  problems.
+            mContext.getMainThreadHandler().post(() -> addWakeLockForDisplay(displayId));
+
+            LocalServices.getService(
+                    InputManagerInternal.class).setDisplayEligibilityForPointerCapture(displayId,
+                    false);
+            final GenericWindowPolicyController dwpc =
+                    new GenericWindowPolicyController(FLAG_SECURE,
+                            SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                            getAllowedUserHandles(),
+                            mParams.getAllowedActivities(),
+                            mParams.getBlockedActivities(),
+                            createListenerAdapter(displayId));
+            mWindowPolicyControllers.put(displayId, dwpc);
+            return dwpc;
         }
-        mVirtualDisplayIds.add(displayId);
-        LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture(
-                displayId, false);
-        final GenericWindowPolicyController dwpc =
-                new GenericWindowPolicyController(FLAG_SECURE,
-                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
-                        getAllowedUserHandles(),
-                        mParams.getAllowedActivities(),
-                        mParams.getBlockedActivities(),
-                        createListenerAdapter(displayId));
-        mWindowPolicyControllers.put(displayId, dwpc);
-        return dwpc;
+    }
+
+    void addWakeLockForDisplay(int displayId) {
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplayIds.contains(displayId)
+                    || mPerDisplayWakelocks.containsKey(displayId)) {
+                Slog.e(TAG, "Not creating wakelock for displayId " + displayId);
+                return;
+            }
+            PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+            PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
+                    PowerManager.SCREEN_BRIGHT_WAKE_LOCK
+                            | PowerManager.ACQUIRE_CAUSES_WAKEUP,
+                    TAG + ":" + displayId, displayId);
+            wakeLock.acquire();
+            mPerDisplayWakelocks.put(displayId, wakeLock);
+        }
     }
 
     private ArraySet<UserHandle> getAllowedUserHandles() {
@@ -420,14 +460,22 @@
     }
 
     void onVirtualDisplayRemovedLocked(int displayId) {
-        if (!mVirtualDisplayIds.contains(displayId)) {
-            throw new IllegalStateException(
-                    "Virtual device doesn't have a virtual display with ID " + displayId);
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplayIds.contains(displayId)) {
+                throw new IllegalStateException(
+                        "Virtual device doesn't have a virtual display with ID " + displayId);
+            }
+            PowerManager.WakeLock wakeLock = mPerDisplayWakelocks.get(displayId);
+            if (wakeLock != null) {
+                wakeLock.release();
+                mPerDisplayWakelocks.remove(displayId);
+            }
+            mVirtualDisplayIds.remove(displayId);
+            LocalServices.getService(
+                    InputManagerInternal.class).setDisplayEligibilityForPointerCapture(
+                    displayId, true);
+            mWindowPolicyControllers.remove(displayId);
         }
-        mVirtualDisplayIds.remove(displayId);
-        LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture(
-                displayId, true);
-        mWindowPolicyControllers.remove(displayId);
     }
 
     int getOwnerUid() {
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index e996eb4..d49cc11 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -20,6 +20,7 @@
 import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 
 import java.util.Collection;
+import java.util.List;
 
 /**
  * Battery stats local system service interface. This is used to pass internal data out of
@@ -42,6 +43,17 @@
     public abstract SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes();
 
     /**
+     * Returns BatteryUsageStats, which contains power attribution data on a per-subsystem
+     * and per-UID basis.
+     *
+     * <p>
+     * Note: This is a slow running method and should be called from non-blocking threads only.
+     * </p>
+     */
+    public abstract List<BatteryUsageStats> getBatteryUsageStats(
+            List<BatteryUsageStatsQuery> queries);
+
+    /**
      * Inform battery stats how many deferred jobs existed when the app got launched and how
      * long ago was the last job execution for the app.
      * @param uid the uid of the app.
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 5d48d78..2f8dea7 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -323,13 +323,20 @@
         if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.chargerAcOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0
+                && mHealthInfo.chargerAcOnline) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.chargerUsbOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0
+                && mHealthInfo.chargerUsbOnline) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.chargerWirelessOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0
+                && mHealthInfo.chargerWirelessOnline) {
+            return true;
+        }
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_DOCK) != 0
+                && mHealthInfo.chargerDockOnline) {
             return true;
         }
         return false;
@@ -442,6 +449,8 @@
             return BatteryManager.BATTERY_PLUGGED_USB;
         } else if (healthInfo.chargerWirelessOnline) {
             return BatteryManager.BATTERY_PLUGGED_WIRELESS;
+        } else if (healthInfo.chargerDockOnline) {
+            return BatteryManager.BATTERY_PLUGGED_DOCK;
         } else {
             return BATTERY_PLUGGED_NONE;
         }
@@ -1118,6 +1127,8 @@
                 batteryPluggedValue = OsProtoEnums.BATTERY_PLUGGED_USB;
             } else if (mHealthInfo.chargerWirelessOnline) {
                 batteryPluggedValue = OsProtoEnums.BATTERY_PLUGGED_WIRELESS;
+            } else if (mHealthInfo.chargerDockOnline) {
+                batteryPluggedValue = OsProtoEnums.BATTERY_PLUGGED_DOCK;
             }
             proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue);
             proto.write(
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 3951680..39ac5ef 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -20,12 +20,12 @@
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
 import static android.Manifest.permission.SHUTDOWN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
 import static android.net.INetd.FIREWALL_ALLOWLIST;
-import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
 import static android.net.INetd.FIREWALL_CHAIN_NONE;
-import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
-import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
-import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
 import static android.net.INetd.FIREWALL_DENYLIST;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
@@ -34,16 +34,13 @@
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_UID;
-import static android.net.NetworkStats.TAG_NONE;
-import static android.net.TrafficStats.UID_TETHERING;
 
 import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.INetdUnsolicitedEventListener;
 import android.net.INetworkManagementEventObserver;
@@ -57,7 +54,6 @@
 import android.net.NetworkStack;
 import android.net.NetworkStats;
 import android.net.RouteInfo;
-import android.net.TetherStatsParcel;
 import android.net.UidRangeParcel;
 import android.net.util.NetdService;
 import android.os.BatteryStats;
@@ -1158,19 +1154,12 @@
             }
 
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "inetd bandwidth");
+            final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
             try {
                 if (allowlist) {
-                    if (enable) {
-                        mNetdService.bandwidthAddNiceApp(uid);
-                    } else {
-                        mNetdService.bandwidthRemoveNiceApp(uid);
-                    }
+                    cm.updateMeteredNetworkAllowList(uid, enable);
                 } else {
-                    if (enable) {
-                        mNetdService.bandwidthAddNaughtyApp(uid);
-                    } else {
-                        mNetdService.bandwidthRemoveNaughtyApp(uid);
-                    }
+                    cm.updateMeteredNetworkDenyList(uid, enable);
                 }
                 synchronized (mRulesLock) {
                     if (enable) {
@@ -1179,7 +1168,7 @@
                         quotaList.delete(uid);
                     }
                 }
-            } catch (RemoteException | ServiceSpecificException e) {
+            } catch (RuntimeException e) {
                 throw new IllegalStateException(e);
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
@@ -1292,40 +1281,9 @@
     private class NetdTetheringStatsProvider extends ITetheringStatsProvider.Stub {
         @Override
         public NetworkStats getTetherStats(int how) {
-            // We only need to return per-UID stats. Per-device stats are already counted by
-            // interface counters.
-            if (how != STATS_PER_UID) {
-                return new NetworkStats(SystemClock.elapsedRealtime(), 0);
-            }
-
-            final TetherStatsParcel[] tetherStatsVec;
-            try {
-                tetherStatsVec = mNetdService.tetherGetStats();
-            } catch (RemoteException | ServiceSpecificException e) {
-                throw new IllegalStateException("problem parsing tethering stats: ", e);
-            }
-
-            final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(),
-                tetherStatsVec.length);
-            final NetworkStats.Entry entry = new NetworkStats.Entry();
-
-            for (TetherStatsParcel tetherStats : tetherStatsVec) {
-                try {
-                    entry.iface = tetherStats.iface;
-                    entry.uid = UID_TETHERING;
-                    entry.set = SET_DEFAULT;
-                    entry.tag = TAG_NONE;
-                    entry.rxBytes   = tetherStats.rxBytes;
-                    entry.rxPackets = tetherStats.rxPackets;
-                    entry.txBytes   = tetherStats.txBytes;
-                    entry.txPackets = tetherStats.txPackets;
-                    stats.combineValues(entry);
-                } catch (ArrayIndexOutOfBoundsException e) {
-                    throw new IllegalStateException("invalid tethering stats " + e);
-                }
-            }
-
-            return stats;
+            // Remove the implementation of NetdTetheringStatsProvider#getTetherStats
+            // since all callers are migrated to use INetd#tetherGetStats directly.
+            throw new UnsupportedOperationException();
         }
 
         @Override
@@ -1336,20 +1294,9 @@
 
     @Override
     public NetworkStats getNetworkStatsTethering(int how) {
-        NetworkStack.checkNetworkStackPermission(mContext);
-
-        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
-        synchronized (mTetheringStatsProviders) {
-            for (ITetheringStatsProvider provider: mTetheringStatsProviders.keySet()) {
-                try {
-                    stats.combineAllValues(provider.getTetherStats(how));
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Problem reading tethering stats from " +
-                            mTetheringStatsProviders.get(provider) + ": " + e);
-                }
-            }
-        }
-        return stats;
+        // Remove the implementation of getNetworkStatsTethering since all callers are migrated
+        // to use INetd#tetherGetStats directly.
+        throw new UnsupportedOperationException();
     }
 
     @Override
@@ -1464,9 +1411,10 @@
                 throw new IllegalArgumentException("Bad child chain: " + chainName);
             }
 
+            final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
             try {
-                mNetdService.firewallEnableChildChain(chain, enable);
-            } catch (RemoteException | ServiceSpecificException e) {
+                cm.setFirewallChainEnabled(chain, enable);
+            } catch (RuntimeException e) {
                 throw new IllegalStateException(e);
             }
 
@@ -1538,25 +1486,10 @@
                     updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT);
                 }
             }
+            final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
             try {
-                switch (chain) {
-                    case FIREWALL_CHAIN_DOZABLE:
-                        mNetdService.firewallReplaceUidChain("fw_dozable", true, uids);
-                        break;
-                    case FIREWALL_CHAIN_STANDBY:
-                        mNetdService.firewallReplaceUidChain("fw_standby", false, uids);
-                        break;
-                    case FIREWALL_CHAIN_POWERSAVE:
-                        mNetdService.firewallReplaceUidChain("fw_powersave", true, uids);
-                        break;
-                    case FIREWALL_CHAIN_RESTRICTED:
-                        mNetdService.firewallReplaceUidChain("fw_restricted", true, uids);
-                        break;
-                    case FIREWALL_CHAIN_NONE:
-                    default:
-                        Slog.d(TAG, "setFirewallUidRules() called on invalid chain: " + chain);
-                }
-            } catch (RemoteException e) {
+                cm.replaceFirewallChain(chain, uids);
+            } catch (RuntimeException e) {
                 Slog.w(TAG, "Error flushing firewall chain " + chain, e);
             }
         }
@@ -1572,10 +1505,10 @@
 
     private void setFirewallUidRuleLocked(int chain, int uid, int rule) {
         if (updateFirewallUidRuleLocked(chain, uid, rule)) {
-            final int ruleType = getFirewallRuleType(chain, rule);
+            final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
             try {
-                mNetdService.firewallSetUidRule(chain, uid, ruleType);
-            } catch (RemoteException | ServiceSpecificException e) {
+                cm.updateFirewallRule(chain, uid, isFirewallRuleAllow(chain, rule));
+            } catch (RuntimeException e) {
                 throw new IllegalStateException(e);
             }
         }
@@ -1645,12 +1578,12 @@
         }
     }
 
-    private int getFirewallRuleType(int chain, int rule) {
+    // There are only two type of firewall rule: FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY.
+    private boolean isFirewallRuleAllow(int chain, int rule) {
         if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
-            return getFirewallType(chain) == FIREWALL_ALLOWLIST
-                    ? INetd.FIREWALL_RULE_DENY : INetd.FIREWALL_RULE_ALLOW;
+            return getFirewallType(chain) == FIREWALL_DENYLIST;
         }
-        return rule;
+        return rule == INetd.FIREWALL_RULE_ALLOW;
     }
 
     private void enforceSystemUid() {
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 9923274..ed545a6 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -227,6 +227,76 @@
     }
 
     /**
+     * Class representing the types of "onUser" events that we are being informed about as having
+     * finished.
+     *
+     * @hide
+     */
+    public static final class UserCompletedEventType {
+        /**
+         * Flag representing the {@link #onUserStarting} event.
+         * @hide
+         */
+        public static final int EVENT_TYPE_USER_STARTING = 1 << 0;
+        /**
+         * Flag representing the {@link #onUserUnlocked} event.
+         * @hide
+         */
+        public static final int EVENT_TYPE_USER_UNLOCKED = 1 << 1;
+        /**
+         * Flag representing the {@link #onUserSwitching} event.
+         * @hide
+         */
+        public static final int EVENT_TYPE_USER_SWITCHING = 1 << 2;
+
+        /**
+         * @hide
+         */
+        @IntDef(flag = true, prefix = "EVENT_TYPE_USER_", value = {
+                EVENT_TYPE_USER_STARTING,
+                EVENT_TYPE_USER_UNLOCKED,
+                EVENT_TYPE_USER_SWITCHING
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface EventTypesFlag {
+        }
+
+        private @EventTypesFlag int mEventType;
+
+        /** @hide */
+        UserCompletedEventType(@EventTypesFlag int eventType) {
+            mEventType = eventType;
+        }
+
+        /** Returns whether one of the events is {@link #onUserStarting}. */
+        public boolean includesOnUserStarting() {
+            return (mEventType & EVENT_TYPE_USER_STARTING) != 0;
+        }
+
+        /** Returns whether one of the events is {@link #onUserUnlocked}. */
+        public boolean includesOnUserUnlocked() {
+            return (mEventType & EVENT_TYPE_USER_UNLOCKED) != 0;
+        }
+
+        /** Returns whether one of the events is {@link #onUserSwitching}. */
+        public boolean includesOnUserSwitching() {
+            return (mEventType & EVENT_TYPE_USER_SWITCHING) != 0;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("{");
+            // List each in reverse order (to line up with binary better).
+            if (includesOnUserSwitching()) sb.append("|Switching");
+            if (includesOnUserUnlocked()) sb.append("|Unlocked");
+            if (includesOnUserStarting()) sb.append("|Starting");
+            if (sb.length() > 1) sb.append("|");
+            sb.append("}");
+            return sb.toString();
+        }
+    }
+
+    /**
      * Initializes the system service.
      * <p>
      * Subclasses must define a single argument constructor that accepts the context
@@ -407,6 +477,33 @@
     }
 
     /**
+     * Called some time <i>after</i> an onUser... event has completed, for the events delineated in
+     * {@link UserCompletedEventType}. May include more than one event.
+     *
+     * <p>
+     * This can be useful for processing tasks that must run after such an event but are non-urgent.
+     *
+     * There are no strict guarantees about how long after the event this will be called, only that
+     * it will be called if applicable. There is no guarantee about the order in which each service
+     * is informed, and these calls may be made in parallel using a thread pool.
+     *
+     * <p>Note that if the event is no longer applicable (for example, we switched to user 10, but
+     * before this method was called, we switched to user 11), the event will not be included in the
+     * {@code eventType} (i.e. user 10 won't mention the switch - even though it happened, it is no
+     * longer applicable).
+     *
+     * <p>This method is only called when the service {@link #isUserSupported(TargetUser) supports}
+     * this user.
+     *
+     * @param user target user completing the event (e.g. user being switched to)
+     * @param eventType the types of onUser event applicable (e.g. user starting and being unlocked)
+     *
+     * @hide
+     */
+    public void onUserCompletedEvent(@NonNull TargetUser user, UserCompletedEventType eventType) {
+    }
+
+    /**
      * Publish the service so it is accessible to other services and apps.
      *
      * @param name the name of the new service
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index e7f4de2..0bc3fcc 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -36,6 +36,7 @@
 import com.android.internal.os.SystemServerClassLoaderFactory;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemService.TargetUser;
+import com.android.server.SystemService.UserCompletedEventType;
 import com.android.server.am.EventLogTags;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.TimingsTraceAndSlog;
@@ -73,6 +74,7 @@
     private static final String USER_SWITCHING = "Switch"; // Logged as onSwitchUser
     private static final String USER_STOPPING = "Stop"; // Logged as onStopUser
     private static final String USER_STOPPED = "Cleanup"; // Logged as onCleanupUser
+    private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onCompletedEventUser
 
     // Whether to use multiple threads to run user lifecycle phases in parallel.
     private static boolean sUseLifecycleThreadPool = true;
@@ -404,6 +406,26 @@
         }
     }
 
+    /**
+     * Called some time <i>after</i> an onUser... event has completed, for the events delineated in
+     * {@link UserCompletedEventType}.
+     *
+     * @param eventFlags the events that completed, per {@link UserCompletedEventType}, or 0.
+     * @see SystemService#onUserCompletedEvent
+     */
+    public void onUserCompletedEvent(@UserIdInt int userId,
+            @UserCompletedEventType.EventTypesFlag int eventFlags) {
+        EventLog.writeEvent(EventLogTags.SSM_USER_COMPLETED_EVENT, userId, eventFlags);
+        if (eventFlags == 0) {
+            return;
+        }
+        onUser(TimingsTraceAndSlog.newAsyncLog(),
+                USER_COMPLETED_EVENT,
+                /* prevUser= */ null,
+                getTargetUser(userId),
+                new UserCompletedEventType(eventFlags));
+    }
+
     private void onUser(@NonNull String onWhat, @UserIdInt int userId) {
         onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, /* prevUser= */ null,
                 getTargetUser(userId));
@@ -411,19 +433,23 @@
 
     private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
             @Nullable TargetUser prevUser, @NonNull TargetUser curUser) {
+        onUser(t, onWhat, prevUser, curUser, /* completedEventType=*/ null);
+    }
+
+    private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
+            @Nullable TargetUser prevUser, @NonNull TargetUser curUser,
+            @Nullable UserCompletedEventType completedEventType) {
         final int curUserId = curUser.getUserIdentifier();
         // NOTE: do not change label below, or it might break performance tests that rely on it.
         t.traceBegin("ssm." + onWhat + "User-" + curUserId);
         Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId
                 + (prevUser != null ? " (from " + prevUser + ")" : ""));
-        final int serviceLen = mServices.size();
-        // Limit the lifecycle parallelization to all users other than the system user
-        // and only for the user start lifecycle phase for now.
-        final boolean useThreadPool = sUseLifecycleThreadPool
-                && curUserId != UserHandle.USER_SYSTEM
-                && onWhat.equals(USER_STARTING);
+
+        final boolean useThreadPool = useThreadPool(curUserId, onWhat);
         final ExecutorService threadPool =
                 useThreadPool ? Executors.newFixedThreadPool(mNumUserPoolThreads) : null;
+
+        final int serviceLen = mServices.size();
         for (int i = 0; i < serviceLen; i++) {
             final SystemService service = mServices.get(i);
             final String serviceName = service.getClass().getName();
@@ -446,8 +472,7 @@
                 }
                 continue;
             }
-            // Only submit this service to the thread pool if it's in the "other" category.
-            final boolean submitToThreadPool = useThreadPool && i >= sOtherServicesStartIndex;
+            final boolean submitToThreadPool = useThreadPool && useThreadPoolForService(onWhat, i);
             if (!submitToThreadPool) {
                 t.traceBegin("ssm.on" + onWhat + "User-" + curUserId + "_" + serviceName);
             }
@@ -459,7 +484,7 @@
                         break;
                     case USER_STARTING:
                         if (submitToThreadPool) {
-                            threadPool.submit(getOnStartUserRunnable(t, service, curUser));
+                            threadPool.submit(getOnUserStartingRunnable(t, service, curUser));
                         } else {
                             service.onUserStarting(curUser);
                         }
@@ -476,6 +501,10 @@
                     case USER_STOPPED:
                         service.onUserStopped(curUser);
                         break;
+                    case USER_COMPLETED_EVENT:
+                        threadPool.submit(getOnUserCompletedEventRunnable(
+                                t, service, serviceName, curUser, completedEventType));
+                        break;
                     default:
                         throw new IllegalArgumentException(onWhat + " what?");
                 }
@@ -498,9 +527,11 @@
             } catch (InterruptedException e) {
                 Slog.wtf(TAG, "User lifecycle thread pool was interrupted while awaiting completion"
                         + " of " + onWhat + " of user " + curUser, e);
-                Slog.e(TAG, "Couldn't terminate, disabling thread pool. "
-                        + "Please capture a bug report.");
-                sUseLifecycleThreadPool = false;
+                if (!onWhat.equals(USER_COMPLETED_EVENT)) {
+                    Slog.e(TAG, "Couldn't terminate, disabling thread pool. "
+                            + "Please capture a bug report.");
+                    sUseLifecycleThreadPool = false;
+                }
             }
             if (!terminated) {
                 Slog.wtf(TAG, "User lifecycle thread pool was not terminated.");
@@ -509,7 +540,38 @@
         t.traceEnd(); // main entry
     }
 
-    private Runnable getOnStartUserRunnable(TimingsTraceAndSlog oldTrace, SystemService service,
+    /**
+     * Whether the given onWhat should use a thread pool.
+     * IMPORTANT: changing the logic to return true won't necessarily make it multi-threaded.
+     *            There needs to be a corresponding logic change in onUser() to actually submit
+     *            to a threadPool for the given onWhat.
+     */
+    private boolean useThreadPool(int userId, @NonNull String onWhat) {
+        switch (onWhat) {
+            case USER_STARTING:
+                // Limit the lifecycle parallelization to all users other than the system user
+                // and only for the user start lifecycle phase for now.
+                return sUseLifecycleThreadPool && userId != UserHandle.USER_SYSTEM;
+            case USER_COMPLETED_EVENT:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private boolean useThreadPoolForService(@NonNull String onWhat, int serviceIndex) {
+        switch (onWhat) {
+            case USER_STARTING:
+                // Only submit this service to the thread pool if it's in the "other" category.
+                return serviceIndex >= sOtherServicesStartIndex;
+            case USER_COMPLETED_EVENT:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private Runnable getOnUserStartingRunnable(TimingsTraceAndSlog oldTrace, SystemService service,
             TargetUser curUser) {
         return () -> {
             final TimingsTraceAndSlog t = new TimingsTraceAndSlog(oldTrace);
@@ -531,6 +593,22 @@
         };
     }
 
+    private Runnable getOnUserCompletedEventRunnable(TimingsTraceAndSlog oldTrace,
+            SystemService service, String serviceName, TargetUser curUser,
+            UserCompletedEventType eventType) {
+        return () -> {
+            final TimingsTraceAndSlog t = new TimingsTraceAndSlog(oldTrace);
+            final int curUserId = curUser.getUserIdentifier();
+            t.traceBegin("ssm.on" + USER_COMPLETED_EVENT + "User-" + curUserId
+                    + "_" + eventType + "_" + serviceName);
+            long time = SystemClock.elapsedRealtime();
+            service.onUserCompletedEvent(curUser, eventType);
+            warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
+                    "on" + USER_COMPLETED_EVENT + "User-" + curUserId);
+            t.traceEnd();
+        };
+    }
+
     /** Sets the safe mode flag for services to query. */
     void setSafeMode(boolean safeMode) {
         mSafeMode = safeMode;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 8887108..7bd982c 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2975,9 +2975,21 @@
             Binder.restoreCallingIdentity(origId);
         }
 
+        notifyBindingServiceEventLocked(callerApp, callingPackage);
+
         return 1;
     }
 
+    @GuardedBy("mAm")
+    private void notifyBindingServiceEventLocked(ProcessRecord callerApp, String callingPackage) {
+        final ApplicationInfo ai = callerApp.info;
+        final String callerPackage = ai != null ? ai.packageName : callingPackage;
+        if (callerPackage != null) {
+            mAm.mHandler.obtainMessage(ActivityManagerService.DISPATCH_BINDING_SERVICE_EVENT,
+                    callerApp.uid, 0, callerPackage).sendToTarget();
+        }
+    }
+
     private void maybeLogBindCrossProfileService(
             int userId, String callingPackage, int callingUid) {
         if (UserHandle.isCore(callingUid)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b1b4c44..f134a70 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -153,8 +153,12 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.PendingIntentInfo;
 import android.app.ActivityManager.ProcessCapability;
+import android.app.ActivityManager.RestrictionLevel;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.BindServiceEventListener;
+import android.app.ActivityManagerInternal.BroadcastEventListener;
+import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.ActivityThread;
 import android.app.AnrController;
@@ -186,7 +190,6 @@
 import android.app.PendingIntent;
 import android.app.ProcessMemoryState;
 import android.app.ProfilerInfo;
-import android.app.PropertyInvalidatedCache;
 import android.app.SyncNotedAppOp;
 import android.app.WaitResult;
 import android.app.backup.BackupManager.OperationType;
@@ -233,11 +236,9 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ProviderInfoList;
 import android.content.pm.ResolveInfo;
-import com.android.server.pm.pkg.SELinuxUtil;
 import android.content.pm.ServiceInfo;
 import android.content.pm.TestUtilityService;
 import android.content.pm.UserInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -396,6 +397,8 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.pkg.SELinuxUtil;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.uri.GrantUri;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriGrantsManagerInternal;
@@ -438,6 +441,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -1373,6 +1377,25 @@
             = new ProcessMap<ArrayList<ProcessRecord>>();
 
     /**
+     * The list of foreground service state change listeners.
+     */
+    @GuardedBy("this")
+    final ArrayList<ForegroundServiceStateListener> mForegroundServiceStateListeners =
+            new ArrayList<>();
+
+    /**
+     * The list of broadcast event listeners.
+     */
+    final CopyOnWriteArrayList<BroadcastEventListener> mBroadcastEventListeners =
+            new CopyOnWriteArrayList<>();
+
+    /**
+     * The list of bind service event listeners.
+     */
+    final CopyOnWriteArrayList<BindServiceEventListener> mBindServiceEventListeners =
+            new CopyOnWriteArrayList<>();
+
+    /**
      * Set if the systemServer made a call to enterSafeMode.
      */
     @GuardedBy("this")
@@ -1456,6 +1479,8 @@
 
     final UidObserverController mUidObserverController;
 
+    final AppRestrictionController mAppRestrictionController;
+
     private final class AppDeathRecipient implements IBinder.DeathRecipient {
         final ProcessRecord mApp;
         final int mPid;
@@ -1512,6 +1537,8 @@
     static final int KILL_APP_ZYGOTE_MSG = 71;
     static final int BINDER_HEAVYHITTER_AUTOSAMPLER_TIMEOUT_MSG = 72;
     static final int WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG = 73;
+    static final int DISPATCH_SENDING_BROADCAST_EVENT = 74;
+    static final int DISPATCH_BINDING_SERVICE_EVENT = 75;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -1829,6 +1856,14 @@
                         ((ContentProviderRecord) msg.obj).onProviderPublishStatusLocked(false);
                     }
                 } break;
+                case DISPATCH_SENDING_BROADCAST_EVENT: {
+                    mBroadcastEventListeners.forEach(l ->
+                            l.onSendingBroadcast((String) msg.obj, msg.arg1));
+                } break;
+                case DISPATCH_BINDING_SERVICE_EVENT: {
+                    mBindServiceEventListeners.forEach(l ->
+                            l.onBindingService((String) msg.obj, msg.arg1));
+                } break;
             }
         }
     }
@@ -2269,6 +2304,7 @@
         mPendingIntentController = hasHandlerThread
                 ? new PendingIntentController(handlerThread.getLooper(), mUserController,
                         mConstants) : null;
+        mAppRestrictionController = new AppRestrictionController(mContext, this);
         mProcStartHandlerThread = null;
         mProcStartHandler = null;
         mHiddenApiBlacklist = null;
@@ -2378,6 +2414,8 @@
         mPendingIntentController = new PendingIntentController(
                 mHandlerThread.getLooper(), mUserController, mConstants);
 
+        mAppRestrictionController = new AppRestrictionController(mContext, this);
+
         mUseFifoUiScheduling = SystemProperties.getInt("sys.use_fifo_ui", 0) != 0;
 
         mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
@@ -2871,51 +2909,13 @@
         return mode == AppOpsManager.MODE_ALLOWED;
     }
 
-    /**
-     * 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");
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "getPackageProcessState");
+        }
+
         final int[] procState = {PROCESS_STATE_NONEXISTENT};
         synchronized (mProcLock) {
             mProcessList.forEachLruProcessesLOSP(false, proc -> {
@@ -6976,7 +6976,11 @@
 
     @Override
     public int getUidProcessState(int uid, String callingPackage) {
-        enforceUsageStatsPermission(callingPackage, "getUidProcessState");
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "getUidProcessState");
+        }
+
         synchronized (mProcLock) {
             return mProcessList.getUidProcStateLOSP(uid);
         }
@@ -6984,7 +6988,11 @@
 
     @Override
     public @ProcessCapability int getUidProcessCapabilities(int uid, String callingPackage) {
-        enforceUsageStatsPermission(callingPackage, "getUidProcessCapabilities");
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "getUidProcessState");
+        }
+
         synchronized (mProcLock) {
             return mProcessList.getUidProcessCapabilityLOSP(uid);
         }
@@ -6993,7 +7001,10 @@
     @Override
     public void registerUidObserver(IUidObserver observer, int which, int cutpoint,
             String callingPackage) {
-        enforceUsageStatsPermission(callingPackage, "registerUidObserver");
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "registerUidObserver");
+        }
         mUidObserverController.register(observer, which, cutpoint, callingPackage,
                 Binder.getCallingUid());
     }
@@ -7005,7 +7016,10 @@
 
     @Override
     public boolean isUidActive(int uid, String callingPackage) {
-        enforceUsageStatsPermission(callingPackage, "isUidActive");
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "isUidActive");
+        }
         synchronized (mProcLock) {
             if (isUidActiveLOSP(uid)) {
                 return true;
@@ -7746,6 +7760,7 @@
             mUserController.onSystemReady();
             mAppOpsService.systemReady();
             mProcessList.onSystemReady();
+            mAppRestrictionController.onSystemReady();
             mSystemReady = true;
             t.traceEnd();
         }
@@ -9029,6 +9044,10 @@
             }
             mComponentAliasResolver.dump(pw);
         }
+        if (dumpAll) {
+            pw.println("-------------------------------------------------------------------------------");
+            mAppRestrictionController.dump(pw, "");
+        }
     }
 
     /**
@@ -14831,8 +14850,16 @@
     final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
             int fgServiceTypes, boolean oomAdj) {
         final ProcessServiceRecord psr = proc.mServices;
-        if (isForeground != psr.hasForegroundServices()
+        final boolean foregroundStateChanged = isForeground != psr.hasForegroundServices();
+        if (foregroundStateChanged
                 || psr.getForegroundServiceTypes() != fgServiceTypes) {
+            if (foregroundStateChanged) {
+                // Notify internal listeners.
+                for (int i = mForegroundServiceStateListeners.size() - 1; i >= 0; i--) {
+                    mForegroundServiceStateListeners.get(i).onForegroundServiceStateChanged(
+                            proc.info.packageName, proc.info.uid, proc.getPid(), isForeground);
+                }
+            }
             psr.setHasForegroundServices(isForeground, fgServiceTypes);
             ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
                     proc.info.uid);
@@ -15831,6 +15858,7 @@
                 synchronized (mProcLock) {
                     mDeviceIdleAllowlist = allAppids;
                     mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
+                    mAppRestrictionController.setDeviceIdleAllowlist(allAppids, exceptIdleAppids);
                 }
             }
         }
@@ -16818,6 +16846,47 @@
         public void setStopUserOnSwitch(int value) {
             ActivityManagerService.this.setStopUserOnSwitch(value);
         }
+
+        @Override
+        public @RestrictionLevel int getRestrictionLevel(int uid) {
+            return mAppRestrictionController.getRestrictionLevel(uid);
+        }
+
+        @Override
+        public @RestrictionLevel int getRestrictionLevel(String pkg, @UserIdInt int userId) {
+            return mAppRestrictionController.getRestrictionLevel(pkg, userId);
+        }
+
+        @Override
+        public boolean isBgAutoRestrictedBucketFeatureFlagEnabled() {
+            return mAppRestrictionController.isBgAutoRestrictedBucketFeatureFlagEnabled();
+        }
+
+        @Override
+        public void addAppBackgroundRestrictionListener(
+                @NonNull ActivityManagerInternal.AppBackgroundRestrictionListener listener) {
+            mAppRestrictionController.addAppBackgroundRestrictionListener(listener);
+        }
+
+        @Override
+        public void addForegroundServiceStateListener(
+                @NonNull ForegroundServiceStateListener listener) {
+            synchronized (ActivityManagerService.this) {
+                mForegroundServiceStateListeners.add(listener);
+            }
+        }
+
+        @Override
+        public void addBroadcastEventListener(@NonNull BroadcastEventListener listener) {
+            // It's a CopyOnWriteArrayList, so no lock is needed.
+            mBroadcastEventListeners.add(listener);
+        }
+
+        @Override
+        public void addBindServiceEventListener(@NonNull BindServiceEventListener listener) {
+            // It's a CopyOnWriteArrayList, so no lock is needed.
+            mBindServiceEventListeners.add(listener);
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
@@ -16985,6 +17054,14 @@
         }
     }
 
+    @Override
+    @ReasonCode
+    public int getBackgroundRestrictionExemptionReason(int uid) {
+        enforceCallingPermission(android.Manifest.permission.DEVICE_POWER,
+                "getBackgroundRestrictionExemptionReason()");
+        return mAppRestrictionController.getBackgroundRestrictionExemptionReason(uid);
+    }
+
     /**
      * Force the settings cache to be loaded
      */
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index c062365..e352384 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -98,6 +98,7 @@
 import android.util.DisplayMetrics;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
+import android.window.SplashScreen;
 
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.util.HexDump;
@@ -178,6 +179,7 @@
     private boolean mIsLockTask;
     private boolean mAsync;
     private BroadcastOptions mBroadcastOptions;
+    private boolean mShowSplashScreen;
 
     final boolean mDumping;
 
@@ -336,6 +338,8 @@
                     return runGetIsolatedProcesses(pw);
                 case "set-stop-user-on-switch":
                     return runSetStopUserOnSwitch(pw);
+                case "set-bg-abusive-uids":
+                    return runSetBgAbusiveUids(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -428,6 +432,8 @@
                     mBroadcastOptions.setBackgroundActivityStartsAllowed(true);
                 } else if (opt.equals("--async")) {
                     mAsync = true;
+                } else if (opt.equals("--splashscreen-show-icon")) {
+                    mShowSplashScreen = true;
                 } else {
                     return false;
                 }
@@ -566,6 +572,12 @@
                 }
                 options.setLockTaskEnabled(true);
             }
+            if (mShowSplashScreen) {
+                if (options == null) {
+                    options = ActivityOptions.makeBasic();
+                }
+                options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+            }
             if (mWaitOption) {
                 result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent,
                         mimeType, null, null, 0, mStartFlags, profilerInfo,
@@ -3215,6 +3227,43 @@
         return 0;
     }
 
+    // TODO(b/203105544) STOPSHIP - For debugging only, to be removed before shipping.
+    private int runSetBgAbusiveUids(PrintWriter pw) throws RemoteException {
+        final String arg = getNextArg();
+        final AppBatteryTracker batteryTracker =
+                mInternal.mAppRestrictionController.getAppStateTracker(AppBatteryTracker.class);
+        if (batteryTracker == null) {
+            getErrPrintWriter().println("Unable to get bg battery tracker");
+            return -1;
+        }
+        if (arg == null) {
+            batteryTracker.mDebugUidPercentages.clear();
+            return 0;
+        }
+        String[] pairs = arg.split(",");
+        int[] uids = new int[pairs.length];
+        double[] values = new double[pairs.length];
+        try {
+            for (int i = 0; i < pairs.length; i++) {
+                String[] pair = pairs[i].split("=");
+                if (pair.length != 2) {
+                    getErrPrintWriter().println("Malformed input");
+                    return -1;
+                }
+                uids[i] = Integer.parseInt(pair[0]);
+                values[i] = Double.parseDouble(pair[1]);
+            }
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Malformed input");
+            return -1;
+        }
+        batteryTracker.mDebugUidPercentages.clear();
+        for (int i = 0; i < pairs.length; i++) {
+            batteryTracker.mDebugUidPercentages.put(uids[i], values[i]);
+        }
+        return 0;
+    }
+
     private Resources getResources(PrintWriter pw) throws RemoteException {
         // system resources does not contain all the device configuration, construct it manually.
         Configuration config = mInterface.getConfiguration();
@@ -3301,6 +3350,7 @@
             pw.println("      --windowingMode <WINDOWING_MODE>: The windowing mode to launch the activity into.");
             pw.println("      --activityType <ACTIVITY_TYPE>: The activity type to launch the activity as.");
             pw.println("      --display <DISPLAY_ID>: The display to launch the activity into.");
+            pw.println("      --splashscreen-icon: Show the splash screen icon on launch.");
             pw.println("  start-service [--user <USER_ID> | current] <INTENT>");
             pw.println("      Start a Service.  Options are:");
             pw.println("      --user <USER_ID> | current: Specify which user to run as; if not");
@@ -3551,6 +3601,8 @@
             pw.println("         Sets whether the current user (and its profiles) should be stopped"
                     + " when switching to a different user.");
             pw.println("         Without arguments, it resets to the value defined by platform.");
+            pw.println("  set-bg-abusive-uids [uid=percentage][,uid=percentage...]");
+            pw.println("         Force setting the battery usage of the given UID.");
             pw.println();
             Intent.printIntentArgsHelp(pw, "");
         }
diff --git a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
new file mode 100644
index 0000000..75de3a1
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateDurationsTracker.EVENT_NUM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy;
+import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates;
+import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
+import com.android.server.am.BaseAppStateDurationsTracker.EventListener;
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+import com.android.server.am.BaseAppStateTracker.Injector;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * A helper class to track the current drains that should be excluded from the current drain
+ * accounting, examples are media playback, location sharing, etc.
+ *
+ * <p>
+ * Note: as the {@link AppBatteryTracker#getUidBatteryUsage} could return the battery usage data
+ * from most recent polling due to throttling, the battery usage of a certain event here
+ * would NOT be the exactly same amount that it actually costs.
+ * </p>
+ */
+final class AppBatteryExemptionTracker
+        extends BaseAppStateDurationsTracker<AppBatteryExemptionPolicy, UidBatteryStates>
+        implements BaseAppStateEvents.Factory<UidBatteryStates>, EventListener {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AppBatteryExemptionTracker" : TAG_AM;
+
+    private static final boolean DEBUG_BACKGROUND_BATTERY_EXEMPTION_TRACKER = false;
+
+    // As it's a UID-based tracker, anywhere which requires a package name, use this default name.
+    private static final String DEFAULT_NAME = "";
+
+    AppBatteryExemptionTracker(Context context, AppRestrictionController controller) {
+        this(context, controller, null, null);
+    }
+
+    AppBatteryExemptionTracker(Context context, AppRestrictionController controller,
+            Constructor<? extends Injector<AppBatteryExemptionPolicy>> injector,
+            Object outerContext) {
+        super(context, controller, injector, outerContext);
+        mInjector.setPolicy(new AppBatteryExemptionPolicy(mInjector, this));
+    }
+
+    @Override
+    void onSystemReady() {
+        super.onSystemReady();
+        mAppRestrictionController.forEachTracker(tracker -> {
+            if (tracker instanceof BaseAppStateDurationsTracker) {
+                ((BaseAppStateDurationsTracker) tracker).registerEventListener(this);
+            }
+        });
+    }
+
+    @Override
+    public UidBatteryStates createAppStateEvents(int uid, String packageName) {
+        return new UidBatteryStates(uid, TAG, mInjector.getPolicy());
+    }
+
+    @Override
+    public UidBatteryStates createAppStateEvents(UidBatteryStates other) {
+        return new UidBatteryStates(other);
+    }
+
+    @Override
+    public void onNewEvent(int uid, String packageName, boolean start, long now, int eventType) {
+        if (!mInjector.getPolicy().isEnabled()) {
+            return;
+        }
+        final double batteryUsage = mAppRestrictionController.getUidBatteryUsage(uid);
+        synchronized (mLock) {
+            UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME);
+            if (pkg == null) {
+                pkg = createAppStateEvents(uid, DEFAULT_NAME);
+                mPkgEvents.put(uid, DEFAULT_NAME, pkg);
+            }
+            pkg.addEvent(start, now, batteryUsage, eventType);
+        }
+    }
+
+    private void onTrackerEnabled(boolean enabled) {
+        if (!enabled) {
+            synchronized (mLock) {
+                mPkgEvents.clear();
+            }
+        }
+    }
+
+    /**
+     * @return The to-be-exempted battery usage of the given UID in the given duration; it could
+     *         be considered as "exempted" due to various use cases, i.e. media playback.
+     */
+    double getUidBatteryExemptedUsageSince(int uid, long since, long now) {
+        if (!mInjector.getPolicy().isEnabled()) {
+            return 0.0d;
+        }
+        Pair<Double, Double> result;
+        synchronized (mLock) {
+            final UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME);
+            if (pkg == null) {
+                return 0.0d;
+            }
+            result = pkg.getBatteryUsageSince(since, now);
+        }
+        if (result.second > 0.0d) {
+            // We have an open event (just start, no stop), get the battery usage till now.
+            final double batteryUsage = mAppRestrictionController.getUidBatteryUsage(uid);
+            return result.first + batteryUsage - result.second;
+        }
+        return result.first;
+    }
+
+    static final class UidBatteryStates extends BaseAppStateDurations<UidStateEventWithBattery> {
+        UidBatteryStates(int uid, @NonNull String tag,
+                @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+            super(uid, DEFAULT_NAME, EVENT_NUM, tag, maxTrackingDurationConfig);
+        }
+
+        UidBatteryStates(@NonNull UidBatteryStates other) {
+            super(other);
+        }
+
+        /**
+         * @param start {@code true} if it's a start event.
+         * @param now   The timestamp when this event occurred.
+         * @param batteryUsage The background current drain since the system boots.
+         * @param eventType One of EVENT_TYPE_* defined in the class BaseAppStateDurationsTracker.
+         */
+        void addEvent(boolean start, long now, double batteryUsage, int eventType) {
+            if (start) {
+                addEvent(start, new UidStateEventWithBattery(start, now, batteryUsage, null),
+                        eventType);
+            } else {
+                final UidStateEventWithBattery last = getLastEvent(eventType);
+                if (last == null || !last.isStart()) {
+                    if (DEBUG_BACKGROUND_BATTERY_EXEMPTION_TRACKER) {
+                        Slog.wtf(TAG, "Unexpected stop event " + eventType);
+                    }
+                    return;
+                }
+                addEvent(start, new UidStateEventWithBattery(start, now,
+                        batteryUsage - last.getBatteryUsage(), last), eventType);
+            }
+        }
+
+        UidStateEventWithBattery getLastEvent(int eventType) {
+            return mEvents[eventType] != null ? mEvents[eventType].peekLast() : null;
+        }
+
+        /**
+         * @return The pair of bg battery usage of given duration; the first value in the pair
+         *         is the aggregated battery usage of all event pairs in this duration; while
+         *         the second value is the battery usage since the system boots, if there is
+         *         an open event(just start, no stop) at the end of the duration.
+         */
+        Pair<Double, Double> getBatteryUsageSince(long since, long now, int eventType) {
+            return getBatteryUsageSince(since, now, mEvents[eventType]);
+        }
+
+        private Pair<Double, Double> getBatteryUsageSince(long since, long now,
+                LinkedList<UidStateEventWithBattery> events) {
+            if (events == null || events.size() == 0) {
+                return Pair.create(0.0d, 0.0d);
+            }
+            double batteryUsage = 0.0d;
+            UidStateEventWithBattery lastEvent = null;
+            for (UidStateEventWithBattery event : events) {
+                lastEvent = event;
+                if (event.getTimestamp() < since || event.isStart()) {
+                    continue;
+                }
+                batteryUsage += event.getBatteryUsage(since, Math.min(now, event.getTimestamp()));
+                if (now <= event.getTimestamp()) {
+                    break;
+                }
+            }
+            return Pair.create(batteryUsage, lastEvent.isStart() ? lastEvent.getBatteryUsage() : 0);
+        }
+
+        /**
+         * @return The aggregated battery usage amongst all the event types we're tracking.
+         */
+        Pair<Double, Double> getBatteryUsageSince(long since, long now) {
+            LinkedList<UidStateEventWithBattery> result = new LinkedList<>();
+            for (int i = 0; i < mEvents.length; i++) {
+                result = add(result, mEvents[i]);
+            }
+            return getBatteryUsageSince(since, now, result);
+        }
+
+        /**
+         * Merge the two given duration table and return the result.
+         */
+        @VisibleForTesting
+        @Override
+        LinkedList<UidStateEventWithBattery> add(LinkedList<UidStateEventWithBattery> durations,
+                LinkedList<UidStateEventWithBattery> otherDurations) {
+            if (otherDurations == null || otherDurations.size() == 0) {
+                return durations;
+            }
+            if (durations == null || durations.size() == 0) {
+                return (LinkedList<UidStateEventWithBattery>) otherDurations.clone();
+            }
+            final Iterator<UidStateEventWithBattery> itl = durations.iterator();
+            final Iterator<UidStateEventWithBattery> itr = otherDurations.iterator();
+            UidStateEventWithBattery l = itl.next(), r = itr.next();
+            LinkedList<UidStateEventWithBattery> dest = new LinkedList<>();
+            boolean actl = false, actr = false, overlapping = false;
+            double batteryUsage = 0.0d;
+            long recentActTs = 0, overlappingDuration = 0;
+            for (long lts = l.getTimestamp(), rts = r.getTimestamp();
+                    lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) {
+                final boolean actCur = actl || actr;
+                final UidStateEventWithBattery earliest;
+                if (lts == rts) {
+                    earliest = l;
+                    // we'll deal with the double counting problem later.
+                    batteryUsage += actl ? l.getBatteryUsage() : 0.0d;
+                    batteryUsage += actr ? r.getBatteryUsage() : 0.0d;
+                    overlappingDuration += overlapping && (actl || actr)
+                            ? (lts - recentActTs) : 0;
+                    actl = !actl;
+                    actr = !actr;
+                    lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+                    rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+                } else if (lts < rts) {
+                    earliest = l;
+                    batteryUsage += actl ? l.getBatteryUsage() : 0.0d;
+                    overlappingDuration += overlapping && actl ? (lts - recentActTs) : 0;
+                    actl = !actl;
+                    lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+                } else {
+                    earliest = r;
+                    batteryUsage += actr ? r.getBatteryUsage() : 0.0d;
+                    overlappingDuration += overlapping && actr ? (rts - recentActTs) : 0;
+                    actr = !actr;
+                    rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+                }
+                overlapping = actl && actr;
+                if (actl || actr) {
+                    recentActTs = earliest.getTimestamp();
+                }
+                if (actCur != (actl || actr)) {
+                    final UidStateEventWithBattery event =
+                            (UidStateEventWithBattery) earliest.clone();
+                    if (actCur) {
+                        // It's an stop/end event, update the start timestamp and batteryUsage.
+                        final UidStateEventWithBattery lastEvent = dest.peekLast();
+                        final long startTs = lastEvent.getTimestamp();
+                        final long duration = event.getTimestamp() - startTs;
+                        final long durationWithOverlapping = duration + overlappingDuration;
+                        // Get the proportional batteryUsage.
+                        if (durationWithOverlapping != 0) {
+                            batteryUsage *= duration * 1.0d / durationWithOverlapping;
+                        } else {
+                            batteryUsage = 0.0d;
+                        }
+                        event.update(lastEvent, batteryUsage);
+                        batteryUsage = 0.0d;
+                        overlappingDuration = 0;
+                    }
+                    dest.add(event);
+                }
+            }
+            return dest;
+        }
+    }
+
+    private void trimDurations() {
+        final long now = SystemClock.elapsedRealtime();
+        trim(Math.max(0, now - mInjector.getPolicy().getMaxTrackingDuration()));
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix) {
+        // We're dumping the data in AppBatteryTracker actually, so just dump the policy here.
+        mInjector.getPolicy().dump(pw, prefix);
+    }
+
+    /**
+     * A basic event marking a certain event, i.e., a FGS start/stop;
+     * it'll record the background battery usage data over the start/stop.
+     */
+    static final class UidStateEventWithBattery extends BaseTimeEvent {
+        /**
+         * Whether or not this is a start event.
+         */
+        private boolean mIsStart;
+
+        /**
+         * The known background battery usage; it will be the total bg battery usage since
+         * the system boots if the {@link #mIsStart} is true, but will be the delta of the bg
+         * battery usage since the start event if the {@link #mIsStart} is false.
+         */
+        private double mBatteryUsage;
+
+        /**
+         * The peer event of this pair (a pair of start/stop events).
+         */
+        private @Nullable UidStateEventWithBattery mPeer;
+
+        UidStateEventWithBattery(boolean isStart, long now, double batteryUsage,
+                @Nullable UidStateEventWithBattery peer) {
+            super(now);
+            mIsStart = isStart;
+            mBatteryUsage = batteryUsage;
+            mPeer = peer;
+            if (peer != null) {
+                peer.mPeer = this;
+            }
+        }
+
+        UidStateEventWithBattery(UidStateEventWithBattery other) {
+            super(other);
+            mIsStart = other.mIsStart;
+            mBatteryUsage = other.mBatteryUsage;
+            // Don't copy the peer object though.
+        }
+
+        @Override
+        void trimTo(long timestamp) {
+            // We don't move the stop event.
+            if (!mIsStart || timestamp < mTimestamp) {
+                return;
+            }
+            if (mPeer != null) {
+                // Reduce the bg battery usage proportionally.
+                final double batteryUsage = mPeer.getBatteryUsage();
+                mPeer.mBatteryUsage = mPeer.getBatteryUsage(timestamp, mPeer.mTimestamp);
+                // Update the battery data of the start event too.
+                mBatteryUsage += batteryUsage - mPeer.mBatteryUsage;
+            }
+            mTimestamp = timestamp;
+        }
+
+        void update(@NonNull UidStateEventWithBattery peer, double batteryUsage) {
+            mPeer = peer;
+            peer.mPeer = this;
+            mBatteryUsage = batteryUsage;
+        }
+
+        boolean isStart() {
+            return mIsStart;
+        }
+
+        double getBatteryUsage(long start, long end) {
+            if (mIsStart || start >= mTimestamp || end <= start) {
+                return 0.0d;
+            }
+            start = Math.max(start, mPeer.mTimestamp);
+            end = Math.min(end, mTimestamp);
+            final long totalDur = mTimestamp - mPeer.mTimestamp;
+            final long inputDur = end - start;
+            return totalDur != 0 ? mBatteryUsage * (1.0d * inputDur) / totalDur : 0.0d;
+        }
+
+        double getBatteryUsage() {
+            return mBatteryUsage;
+        }
+
+        @Override
+        public Object clone() {
+            return new UidStateEventWithBattery(this);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null) {
+                return false;
+            }
+            if (other.getClass() != UidStateEventWithBattery.class) {
+                return false;
+            }
+            final UidStateEventWithBattery otherEvent = (UidStateEventWithBattery) other;
+            return otherEvent.mIsStart == mIsStart
+                    && otherEvent.mTimestamp == mTimestamp
+                    && Double.compare(otherEvent.mBatteryUsage, mBatteryUsage) == 0;
+        }
+
+        @Override
+        public int hashCode() {
+            return (Boolean.hashCode(mIsStart) * 31
+                    + Long.hashCode(mTimestamp)) * 31
+                    + Double.hashCode(mBatteryUsage);
+        }
+    }
+
+    static final class AppBatteryExemptionPolicy
+            extends BaseAppStateEventsPolicy<AppBatteryExemptionTracker> {
+        /**
+         * Whether or not we should enable the exemption of certain battery drains.
+         */
+        static final String KEY_BG_BATTERY_EXEMPTION_ENABLED =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "battery_exemption_enabled";
+
+        /**
+         * Default value to {@link #mTrackerEnabled}.
+         */
+        static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = true;
+
+        AppBatteryExemptionPolicy(@NonNull Injector injector,
+                @NonNull AppBatteryExemptionTracker tracker) {
+            super(injector, tracker,
+                    KEY_BG_BATTERY_EXEMPTION_ENABLED, DEFAULT_BG_BATTERY_EXEMPTION_ENABLED,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+        }
+
+        @Override
+        public void onMaxTrackingDurationChanged(long maxDuration) {
+            mTracker.mBgHandler.post(mTracker::trimDurations);
+        }
+
+        @Override
+        public void onTrackerEnabled(boolean enabled) {
+            mTracker.onTrackerEnabled(enabled);
+        }
+
+        @Override
+        void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix);
+            pw.println("APP BATTERY EXEMPTION TRACKER POLICY SETTINGS:");
+            final String indent = "  ";
+            prefix = indent + prefix;
+            super.dump(pw, prefix);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
new file mode 100644
index 0000000..8a21a0f
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -0,0 +1,1178 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManager.isLowRamDeviceStatic;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.util.TimeUtils.formatTime;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
+import static com.android.server.am.BaseAppStateTracker.ONE_MINUTE;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager.RestrictionLevel;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.os.BatteryConsumer;
+import android.os.BatteryStatsInternal;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.PowerExemptionManager;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UidBatteryConsumer;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseDoubleArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
+import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
+import com.android.server.am.BaseAppStateTracker.Injector;
+import com.android.server.pm.UserManagerInternal;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The battery usage tracker for apps, currently we are focusing on background + FGS battery here.
+ */
+final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy>
+        implements UidBatteryUsageProvider {
+    static final String TAG = TAG_WITH_CLASS_NAME ? "AppBatteryTracker" : TAG_AM;
+
+    static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER = false;
+
+    // As we don't support realtime per-UID battery usage stats yet, we're polling the stats
+    // in a regular time basis.
+    private final long mBatteryUsageStatsPollingIntervalMs;
+
+    // The timestamp when this system_server was started.
+    private long mBootTimestamp;
+
+    static final long BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_LONG = 30 * ONE_MINUTE; // 30 mins
+    static final long BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG = 2_000L; // 2s
+
+    private final long mBatteryUsageStatsPollingMinIntervalMs;
+
+    /**
+     * The battery stats query is expensive, so we'd throttle the query.
+     */
+    static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_LONG = 5 * ONE_MINUTE; // 5 mins
+    static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG = 2_000L; // 2s
+
+    static final BatteryConsumer.Dimensions BATT_DIMEN_FG =
+            new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_FOREGROUND);
+    static final BatteryConsumer.Dimensions BATT_DIMEN_BG =
+            new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_BACKGROUND);
+    static final BatteryConsumer.Dimensions BATT_DIMEN_FGS =
+            new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_FOREGROUND_SERVICE);
+
+    private final Runnable mBgBatteryUsageStatsPolling = this::updateBatteryUsageStatsAndCheck;
+    private final Runnable mBgBatteryUsageStatsCheck = this::checkBatteryUsageStats;
+
+    /**
+     * This tracks the user ids which are or were active during the last polling window,
+     * the index is the user id, and the value is if it's still running or not by now.
+     */
+    @GuardedBy("mLock")
+    private final SparseBooleanArray mActiveUserIdStates = new SparseBooleanArray();
+
+    /**
+     * When was the last battery usage sampled.
+     */
+    @GuardedBy("mLock")
+    private long mLastBatteryUsageSamplingTs;
+
+    /**
+     * Whether or not there is an ongoing battery stats update.
+     */
+    @GuardedBy("mLock")
+    private boolean mBatteryUsageStatsUpdatePending;
+
+    /**
+     * The current known battery usage data for each UID, since the system boots.
+     */
+    @GuardedBy("mLock")
+    private final SparseDoubleArray mUidBatteryUsage = new SparseDoubleArray();
+
+    /**
+     * The battery usage for each UID, in the rolling window of the past.
+     */
+    @GuardedBy("mLock")
+    private final SparseDoubleArray mUidBatteryUsageInWindow = new SparseDoubleArray();
+
+    /**
+     * The uid battery usage stats data from our last query, it does not include snapshot data.
+     */
+    // No lock is needed.
+    private final SparseDoubleArray mLastUidBatteryUsage = new SparseDoubleArray();
+
+    // No lock is needed.
+    private final SparseDoubleArray mTmpUidBatteryUsage = new SparseDoubleArray();
+
+    // No lock is needed.
+    private final SparseDoubleArray mTmpUidBatteryUsage2 = new SparseDoubleArray();
+
+    // No lock is needed.
+    private final ArraySet<UserHandle> mTmpUserIds = new ArraySet<>();
+
+    /**
+     * The start timestamp of the battery usage stats result from our last query.
+     */
+    // No lock is needed.
+    private long mLastUidBatteryUsageStartTs;
+
+    // For debug only.
+    final SparseDoubleArray mDebugUidPercentages = new SparseDoubleArray();
+
+    AppBatteryTracker(Context context, AppRestrictionController controller) {
+        this(context, controller, null, null);
+    }
+
+    AppBatteryTracker(Context context, AppRestrictionController controller,
+            Constructor<? extends Injector<AppBatteryPolicy>> injector,
+            Object outerContext) {
+        super(context, controller, injector, outerContext);
+        if (injector == null) {
+            mBatteryUsageStatsPollingIntervalMs = DEBUG_BACKGROUND_BATTERY_TRACKER
+                    ? BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG
+                    : BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_LONG;
+            mBatteryUsageStatsPollingMinIntervalMs = DEBUG_BACKGROUND_BATTERY_TRACKER
+                    ? BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG
+                    : BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_LONG;
+        } else {
+            mBatteryUsageStatsPollingIntervalMs = BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG;
+            mBatteryUsageStatsPollingMinIntervalMs =
+                    BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG;
+        }
+        mInjector.setPolicy(new AppBatteryPolicy(mInjector, this));
+    }
+
+    @Override
+    void onSystemReady() {
+        super.onSystemReady();
+        final UserManagerInternal um = mInjector.getUserManagerInternal();
+        final int[] userIds = um.getUserIds();
+        for (int userId : userIds) {
+            if (um.isUserRunning(userId)) {
+                synchronized (mLock) {
+                    mActiveUserIdStates.put(userId, true);
+                }
+            }
+        }
+        mBootTimestamp = mInjector.currentTimeMillis();
+        scheduleBatteryUsageStatsUpdateIfNecessary(mBatteryUsageStatsPollingIntervalMs);
+    }
+
+    private void scheduleBatteryUsageStatsUpdateIfNecessary(long delay) {
+        if (mInjector.getPolicy().isEnabled()) {
+            synchronized (mLock) {
+                if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsPolling)) {
+                    mBgHandler.postDelayed(mBgBatteryUsageStatsPolling, delay);
+                }
+            }
+        }
+    }
+
+    @Override
+    void onUserStarted(final @UserIdInt int userId) {
+        synchronized (mLock) {
+            mActiveUserIdStates.put(userId, true);
+        }
+    }
+
+    @Override
+    void onUserStopped(final @UserIdInt int userId) {
+        synchronized (mLock) {
+            mActiveUserIdStates.put(userId, false);
+        }
+    }
+
+    @Override
+    void onUserRemoved(final @UserIdInt int userId) {
+        synchronized (mLock) {
+            mActiveUserIdStates.delete(userId);
+            for (int i = mUidBatteryUsage.size() - 1; i >= 0; i--) {
+                if (UserHandle.getUserId(mUidBatteryUsage.keyAt(i)) == userId) {
+                    mUidBatteryUsage.removeAt(i);
+                }
+            }
+            for (int i = mUidBatteryUsageInWindow.size() - 1; i >= 0; i--) {
+                if (UserHandle.getUserId(mUidBatteryUsageInWindow.keyAt(i)) == userId) {
+                    mUidBatteryUsageInWindow.removeAt(i);
+                }
+            }
+        }
+    }
+
+    @Override
+    void onUidRemoved(final int uid) {
+        synchronized (mLock) {
+            mUidBatteryUsage.delete(uid);
+            mUidBatteryUsageInWindow.delete(uid);
+        }
+    }
+
+    @Override
+    void onUserInteractionStarted(String packageName, int uid) {
+        mInjector.getPolicy().onUserInteractionStarted(packageName, uid);
+    }
+
+    @Override
+    void onBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
+        mInjector.getPolicy().onBackgroundRestrictionChanged(uid, pkgName, restricted);
+    }
+
+    /**
+     * @return The total battery usage of the given UID since the system boots.
+     *
+     * <p>
+     * Note: as there are throttling in polling the battery usage stats by
+     * the {@link #mBatteryUsageStatsPollingMinIntervalMs}, the returned data here
+     * could be either from the most recent polling, or the very fresh one - if the most recent
+     * polling is outdated, it'll trigger an immediate update.
+     * </p>
+     */
+    @Override
+    public double getUidBatteryUsage(int uid) {
+        final long now = mInjector.currentTimeMillis();
+        final boolean updated = updateBatteryUsageStatsIfNecessary(now, false);
+        synchronized (mLock) {
+            if (updated) {
+                // We just got fresh data, schedule a check right a way.
+                mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling);
+                if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsCheck)) {
+                    mBgHandler.post(mBgBatteryUsageStatsCheck);
+                }
+            }
+            return mUidBatteryUsage.get(uid, 0.0d);
+        }
+    }
+
+    private void updateBatteryUsageStatsAndCheck() {
+        final long now = mInjector.currentTimeMillis();
+        if (updateBatteryUsageStatsIfNecessary(now, false)) {
+            checkBatteryUsageStats();
+        } else {
+            // We didn't do the battery stats update above, schedule a check later.
+            scheduleBatteryUsageStatsUpdateIfNecessary(
+                    mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs - now);
+        }
+    }
+
+    private void checkBatteryUsageStats() {
+        final long now = SystemClock.elapsedRealtime();
+        final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+        try {
+            final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
+            final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
+            for (int i = 0, size = uidConsumers.size(); i < size; i++) {
+                final int uid = uidConsumers.keyAt(i);
+                final double actualUsage = uidConsumers.valueAt(i);
+                final double exemptedUsage = mAppRestrictionController
+                        .getUidBatteryExemptedUsageSince(uid, since, now);
+                // It's possible the exemptedUsage could be larger than actualUsage,
+                // as the former one is an approximate value.
+                final double bgUsage = Math.max(0.0d, actualUsage - exemptedUsage);
+                final double percentage = bgPolicy.getPercentage(uid, bgUsage);
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                    Slog.i(TAG, String.format(
+                            "UID %d: %.3f mAh (or %4.2f%%) %.3f %.3f over the past %s",
+                            uid, bgUsage, percentage, exemptedUsage, actualUsage,
+                            TimeUtils.formatDuration(bgPolicy.mBgCurrentDrainWindowMs)));
+                }
+                bgPolicy.handleUidBatteryUsage(uid, percentage);
+            }
+            // For debugging only.
+            for (int i = 0, size = mDebugUidPercentages.size(); i < size; i++) {
+                bgPolicy.handleUidBatteryUsage(mDebugUidPercentages.keyAt(i),
+                        mDebugUidPercentages.valueAt(i));
+            }
+        } finally {
+            scheduleBatteryUsageStatsUpdateIfNecessary(mBatteryUsageStatsPollingIntervalMs);
+        }
+    }
+
+    /**
+     * Update the battery usage stats data, if it's allowed to do so.
+     *
+     * @return {@code true} if the battery stats is up to date.
+     */
+    private boolean updateBatteryUsageStatsIfNecessary(long now, boolean forceUpdate) {
+        boolean needUpdate = false;
+        boolean updated = false;
+        synchronized (mLock) {
+            if (mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs < now
+                    || forceUpdate) {
+                // The data we have is outdated.
+                if (mBatteryUsageStatsUpdatePending) {
+                    // An update is ongoing in parallel, just wait for it.
+                    try {
+                        mLock.wait();
+                    } catch (InterruptedException e) {
+                    }
+                } else {
+                    mBatteryUsageStatsUpdatePending = true;
+                    needUpdate = true;
+                }
+                updated = true;
+            } else {
+                // The data is still fresh, no need to update it.
+                return false;
+            }
+        }
+        if (needUpdate) {
+            // We don't want to query the battery usage stats with mLock held.
+            updateBatteryUsageStatsOnce(now);
+            synchronized (mLock) {
+                mLastBatteryUsageSamplingTs = now;
+                mBatteryUsageStatsUpdatePending = false;
+                mLock.notifyAll();
+            }
+        }
+        return updated;
+    }
+
+    private void updateBatteryUsageStatsOnce(long now) {
+        final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+        final ArraySet<UserHandle> userIds = mTmpUserIds;
+        final SparseDoubleArray buf = mTmpUidBatteryUsage;
+        final BatteryStatsInternal batteryStatsInternal = mInjector.getBatteryStatsInternal();
+        final long windowSize = Math.min(now - mBootTimestamp, bgPolicy.mBgCurrentDrainWindowMs);
+
+        buf.clear();
+        userIds.clear();
+        synchronized (mLock) {
+            for (int i = mActiveUserIdStates.size() - 1; i >= 0; i--) {
+                userIds.add(UserHandle.of(mActiveUserIdStates.keyAt(i)));
+                if (!mActiveUserIdStates.valueAt(i)) {
+                    mActiveUserIdStates.removeAt(i);
+                }
+            }
+        }
+
+        if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+            Slog.i(TAG, "updateBatteryUsageStatsOnce");
+        }
+
+        // Query the current battery usage stats.
+        BatteryUsageStatsQuery.Builder builder = new BatteryUsageStatsQuery.Builder()
+                .includeProcessStateData()
+                .setMaxStatsAgeMs(0);
+        final BatteryUsageStats stats = updateBatteryUsageStatsOnceInternal(
+                buf, builder, userIds, batteryStatsInternal);
+        final long curStart = stats != null ? stats.getStatsStartTimestamp() : 0L;
+        long curDuration = now - curStart;
+        boolean needUpdateUidBatteryUsageInWindow = true;
+
+        if (curDuration >= windowSize) {
+            // If we do have long enough data for the window, save it.
+            copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+            needUpdateUidBatteryUsageInWindow = false;
+        }
+
+        // Save the current data, which includes the battery usage since last snapshot.
+        mTmpUidBatteryUsage2.clear();
+        copyUidBatteryUsage(buf, mTmpUidBatteryUsage2);
+
+        final long lastUidBatteryUsageStartTs = mLastUidBatteryUsageStartTs;
+        mLastUidBatteryUsageStartTs = curStart;
+        if (curStart > lastUidBatteryUsageStartTs && lastUidBatteryUsageStartTs > 0) {
+            // The battery usage stats committed data since our last query,
+            // let's query the snapshots to get the data since last start.
+            builder = new BatteryUsageStatsQuery.Builder()
+                    .includeProcessStateData()
+                    .aggregateSnapshots(lastUidBatteryUsageStartTs, curStart);
+            updateBatteryUsageStatsOnceInternal(buf, builder, userIds, batteryStatsInternal);
+            curDuration += curStart - lastUidBatteryUsageStartTs;
+        }
+        if (needUpdateUidBatteryUsageInWindow && curDuration > windowSize) {
+            // If we do have long enough data for the window, save it.
+            copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+            needUpdateUidBatteryUsageInWindow = false;
+        }
+
+        // Add the delta into the global records.
+        for (int i = 0, size = buf.size(); i < size; i++) {
+            final int uid = buf.keyAt(i);
+            final int index = mUidBatteryUsage.indexOfKey(uid);
+            final double delta = Math.max(0.0d,
+                    buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d));
+            final double before;
+            if (index >= 0) {
+                before = mUidBatteryUsage.valueAt(index);
+                mUidBatteryUsage.setValueAt(index, before + delta);
+            } else {
+                before = 0.0d;
+                mUidBatteryUsage.put(uid, delta);
+            }
+            if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                final double actualDelta = buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d);
+                String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before
+                        + ", after=" + mUidBatteryUsage.get(uid, 0.0d) + ", delta=" + actualDelta
+                        + ", last=" + mLastUidBatteryUsage.get(uid, 0.0d)
+                        + ", curStart=" + curStart
+                        + ", lastLastStart=" + lastUidBatteryUsageStartTs
+                        + ", thisLastStart=" + mLastUidBatteryUsageStartTs;
+                if (actualDelta < 0.0d) {
+                    // Something is wrong, the battery usage shouldn't be negative.
+                    Slog.e(TAG, msg);
+                } else {
+                    Slog.i(TAG, msg);
+                }
+            }
+        }
+        // Now update the mLastUidBatteryUsage with the data we just saved above.
+        copyUidBatteryUsage(mTmpUidBatteryUsage2, mLastUidBatteryUsage);
+        mTmpUidBatteryUsage2.clear();
+
+        if (needUpdateUidBatteryUsageInWindow) {
+            // No sufficient data for the full window still, query snapshots again.
+            builder = new BatteryUsageStatsQuery.Builder()
+                    .includeProcessStateData()
+                    .aggregateSnapshots(now - windowSize, lastUidBatteryUsageStartTs);
+            updateBatteryUsageStatsOnceInternal(buf, builder, userIds, batteryStatsInternal);
+            copyUidBatteryUsage(buf, mUidBatteryUsageInWindow);
+        }
+    }
+
+    private static BatteryUsageStats updateBatteryUsageStatsOnceInternal(
+            SparseDoubleArray buf, BatteryUsageStatsQuery.Builder builder,
+            ArraySet<UserHandle> userIds, BatteryStatsInternal batteryStatsInternal) {
+        for (int i = 0, size = userIds.size(); i < size; i++) {
+            builder.addUser(userIds.valueAt(i));
+        }
+        final List<BatteryUsageStats> statsList = batteryStatsInternal
+                .getBatteryUsageStats(Arrays.asList(builder.build()));
+        if (ArrayUtils.isEmpty(statsList)) {
+            // Shouldn't happen unless in test.
+            return null;
+        }
+        final BatteryUsageStats stats = statsList.get(0);
+        final List<UidBatteryConsumer> uidConsumers = stats.getUidBatteryConsumers();
+        if (uidConsumers != null) {
+            for (UidBatteryConsumer uidConsumer : uidConsumers) {
+                // TODO: b/200326767 - as we are not supporting per proc state attribution yet,
+                // we couldn't distinguish between a real FGS vs. a bound FGS proc state.
+                final int uid = uidConsumer.getUid();
+                final double bgUsage = getBgUsage(uidConsumer);
+                int index = buf.indexOfKey(uid);
+                if (index < 0) {
+                    buf.put(uid, bgUsage);
+                } else {
+                    buf.setValueAt(index, buf.valueAt(index) + bgUsage);
+                }
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                    Slog.i(TAG, "updateBatteryUsageStatsOnceInternal uid=" + uid
+                            + ", bgUsage=" + bgUsage
+                            + ", start=" + stats.getStatsStartTimestamp()
+                            + ", end=" + stats.getStatsEndTimestamp());
+                }
+            }
+        }
+        return stats;
+    }
+
+    private static void copyUidBatteryUsage(SparseDoubleArray source, SparseDoubleArray dest) {
+        dest.clear();
+        for (int i = source.size() - 1; i >= 0; i--) {
+            dest.put(source.keyAt(i), source.valueAt(i));
+        }
+    }
+
+    private static void copyUidBatteryUsage(SparseDoubleArray source, SparseDoubleArray dest,
+            double scale) {
+        dest.clear();
+        for (int i = source.size() - 1; i >= 0; i--) {
+            dest.put(source.keyAt(i), source.valueAt(i) * scale);
+        }
+    }
+
+    private static double getBgUsage(final UidBatteryConsumer uidConsumer) {
+        return getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_BG)
+                + getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_FGS);
+    }
+
+    private static double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer,
+            final BatteryConsumer.Dimensions dimens) {
+        try {
+            return uidConsumer.getConsumedPower(dimens);
+        } catch (IllegalArgumentException e) {
+            return 0.0d;
+        }
+    }
+
+    private void onCurrentDrainMonitorEnabled(boolean enabled) {
+        if (enabled) {
+            if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsPolling)) {
+                mBgHandler.postDelayed(mBgBatteryUsageStatsPolling,
+                        mBatteryUsageStatsPollingIntervalMs);
+            }
+        } else {
+            mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling);
+            synchronized (mLock) {
+                if (mBatteryUsageStatsUpdatePending) {
+                    // An update is ongoing in parallel, just wait for it.
+                    try {
+                        mLock.wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+                mUidBatteryUsage.clear();
+                mUidBatteryUsageInWindow.clear();
+                mLastUidBatteryUsage.clear();
+                mLastUidBatteryUsageStartTs = mLastBatteryUsageSamplingTs = 0L;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void reset() {
+        synchronized (mLock) {
+            mUidBatteryUsage.clear();
+            mUidBatteryUsageInWindow.clear();
+            mLastUidBatteryUsage.clear();
+            mLastUidBatteryUsageStartTs = mLastBatteryUsageSamplingTs = 0L;
+        }
+        mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling);
+        updateBatteryUsageStatsAndCheck();
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.println("APP BATTERY STATE TRACKER:");
+        updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
+        final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
+        pw.print("  " + prefix);
+        pw.print("Boot=");
+        TimeUtils.dumpTime(pw, mBootTimestamp);
+        pw.print("  Last battery usage start=");
+        TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs);
+        pw.println();
+        pw.print("  " + prefix);
+        pw.print("Battery usage over last ");
+        final String newPrefix = "    " + prefix;
+        final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+        final long now = SystemClock.elapsedRealtime();
+        final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
+        pw.println(TimeUtils.formatDuration(now - since));
+        if (uidConsumers.size() == 0) {
+            pw.print(newPrefix);
+            pw.println("(none)");
+        } else {
+            for (int i = 0, size = uidConsumers.size(); i < size; i++) {
+                final int uid = uidConsumers.keyAt(i);
+                final double bgUsage = uidConsumers.valueAt(i);
+                final double exemptedUsage = mAppRestrictionController
+                        .getUidBatteryExemptedUsageSince(uid, since, now);
+                final double reportedUsage = Math.max(0.0d, bgUsage - exemptedUsage);
+                pw.format("%s%s: [%s] %.3f mAh (%4.2f%%) | %.3f mAh (%4.2f%%) | "
+                        + "%.3f mAh (%4.2f%%) | %.3f mAh\n",
+                        newPrefix, UserHandle.formatUid(uid),
+                        PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)),
+                        bgUsage , bgPolicy.getPercentage(uid, bgUsage),
+                        exemptedUsage, bgPolicy.getPercentage(-1, exemptedUsage),
+                        reportedUsage, bgPolicy.getPercentage(-1, reportedUsage),
+                        mUidBatteryUsage.get(uid, 0.0d));
+            }
+        }
+        super.dump(pw, prefix);
+    }
+
+    static final class AppBatteryPolicy extends BaseAppStatePolicy<AppBatteryTracker> {
+        /**
+         * Whether or not we should enable the monitoring on background current drains.
+         */
+        static final String KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_monitor_enabled";
+
+        /**
+         * The threshold of the background current drain (in percentage) to the restricted
+         * standby bucket. In conjunction with the {@link #KEY_BG_CURRENT_DRAIN_WINDOW},
+         * the app could be moved to more restricted standby bucket when its background current
+         * drain rate is over this limit.
+         */
+        static final String KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_threshold_to_restricted_bucket";
+
+        /**
+         * The threshold of the background current drain (in percentage) to the background
+         * restricted level. In conjunction with the {@link #KEY_BG_CURRENT_DRAIN_WINDOW},
+         * the app could be moved to more restricted level when its background current
+         * drain rate is over this limit.
+         */
+        static final String KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_threshold_to_bg_restricted";
+
+        /**
+         * The background current drain window size. In conjunction with the
+         * {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET}, the app could be moved to
+         * more restrictive bucket when its background current drain rate is over this limit.
+         */
+        static final String KEY_BG_CURRENT_DRAIN_WINDOW =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_window";
+
+        /**
+         * Similar to {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET}, but a higher
+         * value for the legitimate cases with higher background current drain.
+         */
+        static final String KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX
+                + "current_drain_high_threshold_to_restricted_bucket";
+
+        /**
+         * Similar to {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED}, but a higher value
+         * for the legitimate cases with higher background current drain.
+         */
+        static final String KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_high_threshold_to_bg_restricted";
+
+        /**
+         * The threshold of minimal time of hosting a foreground service with type "mediaPlayback"
+         * or a media session, over the given window, so it'd subject towards the higher
+         * background current drain threshold as defined in
+         * {@link #mBgCurrentDrainBgRestrictedHighThreshold}.
+         */
+        static final String KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_media_playback_min_duration";
+
+        /**
+         * Similar to {@link #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION} but for foreground
+         * service with type "location".
+         */
+        static final String KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_location_min_duration";
+
+        /**
+         * Whether or not we should enable the different threshold based on the durations of
+         * certain event type.
+         */
+        static final String KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX
+                + "current_drain_event_duration_based_threshold_enabled";
+
+        /**
+         * Default value to {@link #mTrackerEnabled}.
+         */
+        static final boolean DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED = true;
+
+        /**
+         * Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of
+         * the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
+         */
+        static final float DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD =
+                isLowRamDeviceStatic() ? 4.0f : 2.0f;
+
+        /**
+         * Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of
+         * the {@link #mBgCurrentDrainBgRestrictedThreshold}.
+         */
+        static final float DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD =
+                isLowRamDeviceStatic() ? 8.0f : 4.0f;
+
+        /**
+         * Default value to {@link #mBgCurrentDrainWindowMs}.
+         */
+        static final long DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS = ONE_DAY;
+
+        /**
+         * Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
+         * the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
+         */
+        static final float DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD =
+                isLowRamDeviceStatic() ? 60.0f : 30.0f;
+
+        /**
+         * Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
+         * the {@link #mBgCurrentDrainBgRestrictedThreshold}.
+         */
+        static final float DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD =
+                isLowRamDeviceStatic() ? 60.0f : 30.0f;
+
+        /**
+         * Default value to {@link #mBgCurrentDrainMediaPlaybackMinDuration}.
+         */
+        static final long DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION = 30 * ONE_MINUTE;
+
+        /**
+         * Default value to {@link #mBgCurrentDrainLocationMinDuration}.
+         */
+        static final long DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION = 30 * ONE_MINUTE;
+
+        /**
+         * Default value to {@link #mBgCurrentDrainEventDurationBasedThresholdEnabled}.
+         */
+        static final boolean DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED =
+                false;
+
+        /**
+         * The index to {@link #mBgCurrentDrainRestrictedBucketThreshold}
+         * and {@link #mBgCurrentDrainBgRestrictedThreshold}.
+         */
+        static final int INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD = 0;
+        static final int INDEX_HIGH_CURRENT_DRAIN_THRESHOLD = 1;
+
+        /**
+         * @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET.
+         * @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET.
+         */
+        volatile float[] mBgCurrentDrainRestrictedBucketThreshold = {
+                DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD,
+                DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD,
+        };
+
+        /**
+         * @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED.
+         * @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED.
+         */
+        volatile float[] mBgCurrentDrainBgRestrictedThreshold = {
+                DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD,
+                DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD,
+        };
+
+        /**
+         * @see #KEY_BG_CURRENT_DRAIN_WINDOW.
+         */
+        volatile long mBgCurrentDrainWindowMs = DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS;
+
+        /**
+         * @see #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION.
+         */
+        volatile long mBgCurrentDrainMediaPlaybackMinDuration =
+                DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION;
+
+        /**
+         * @see #KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION.
+         */
+        volatile long mBgCurrentDrainLocationMinDuration =
+                DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION;
+
+        /**
+         * @see #KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED.
+         */
+        volatile boolean mBgCurrentDrainEventDurationBasedThresholdEnabled;
+
+        /**
+         * The capacity of the battery when fully charged in mAh.
+         */
+        private int mBatteryFullChargeMah;
+
+        /**
+         * List of the packages with significant background battery usage, key is the UID of
+         * the package and value is an array of the timestamps when the UID is found guilty and
+         * should be moved to the next level of restriction.
+         */
+        @GuardedBy("mLock")
+        private final SparseArray<long[]> mHighBgBatteryPackages = new SparseArray<>();
+
+        @NonNull
+        private final Object mLock;
+
+        private static final int TIME_STAMP_INDEX_RESTRICTED_BUCKET = 0;
+        private static final int TIME_STAMP_INDEX_BG_RESTRICTED = 1;
+        private static final int TIME_STAMP_INDEX_LAST = 2;
+
+        AppBatteryPolicy(@NonNull Injector injector, @NonNull AppBatteryTracker tracker) {
+            super(injector, tracker, KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
+                    DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+            mLock = tracker.mLock;
+        }
+
+        @Override
+        public void onPropertiesChanged(String name) {
+            switch (name) {
+                case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET:
+                case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED:
+                case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET:
+                case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED:
+                    updateCurrentDrainThreshold();
+                    break;
+                case KEY_BG_CURRENT_DRAIN_WINDOW:
+                    updateCurrentDrainWindow();
+                    break;
+                case KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION:
+                    updateCurrentDrainMediaPlaybackMinDuration();
+                    break;
+                case KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION:
+                    updateCurrentDrainLocationMinDuration();
+                    break;
+                case KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED:
+                    updateCurrentDrainEventDurationBasedThresholdEnabled();
+                    break;
+                default:
+                    super.onPropertiesChanged(name);
+                    break;
+            }
+        }
+
+        void updateTrackerEnabled() {
+            if (mBatteryFullChargeMah > 0) {
+                super.updateTrackerEnabled();
+            } else {
+                mTrackerEnabled = false;
+                onTrackerEnabled(false);
+            }
+        }
+
+        public void onTrackerEnabled(boolean enabled) {
+            mTracker.onCurrentDrainMonitorEnabled(enabled);
+        }
+
+        private void updateCurrentDrainThreshold() {
+            mBgCurrentDrainRestrictedBucketThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] =
+                    DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
+                    DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD);
+            mBgCurrentDrainRestrictedBucketThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] =
+                    DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET,
+                    DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD);
+            mBgCurrentDrainBgRestrictedThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] =
+                    DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
+                    DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+            mBgCurrentDrainBgRestrictedThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] =
+                    DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED,
+                    DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD);
+        }
+
+        private void updateCurrentDrainWindow() {
+            mBgCurrentDrainWindowMs = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_WINDOW,
+                    mBgCurrentDrainWindowMs != DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS
+                    ? mBgCurrentDrainWindowMs : DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+        }
+
+        private void updateCurrentDrainMediaPlaybackMinDuration() {
+            mBgCurrentDrainMediaPlaybackMinDuration = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION,
+                    DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
+        }
+
+        private void updateCurrentDrainLocationMinDuration() {
+            mBgCurrentDrainLocationMinDuration = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION,
+                    DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION);
+        }
+
+        private void updateCurrentDrainEventDurationBasedThresholdEnabled() {
+            mBgCurrentDrainEventDurationBasedThresholdEnabled = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED,
+                    DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
+        }
+
+        @Override
+        public void onSystemReady() {
+            mBatteryFullChargeMah =
+                    mInjector.getBatteryManagerInternal().getBatteryFullCharge() / 1000;
+            super.onSystemReady();
+            updateCurrentDrainThreshold();
+            updateCurrentDrainWindow();
+            updateCurrentDrainMediaPlaybackMinDuration();
+            updateCurrentDrainLocationMinDuration();
+            updateCurrentDrainEventDurationBasedThresholdEnabled();
+        }
+
+        @Override
+        public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) {
+            synchronized (mLock) {
+                final int index = mHighBgBatteryPackages.indexOfKey(uid);
+                if (index < 0) {
+                    // Not found, return adaptive as the default one.
+                    return RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+                }
+                final long[] ts = mHighBgBatteryPackages.valueAt(index);
+                return ts[TIME_STAMP_INDEX_BG_RESTRICTED] > 0
+                        ? RESTRICTION_LEVEL_BACKGROUND_RESTRICTED
+                        : RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+            }
+        }
+
+        double getBgUsage(final UidBatteryConsumer uidConsumer) {
+            return getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_BG)
+                    + getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_FGS);
+        }
+
+        double getPercentage(final int uid, final double usage) {
+            final double actualPercentage = usage / mBatteryFullChargeMah * 100;
+            return DEBUG_BACKGROUND_BATTERY_TRACKER
+                    ? mTracker.mDebugUidPercentages.get(uid, actualPercentage) : actualPercentage;
+        }
+
+        void handleUidBatteryUsage(final int uid, final double percentage) {
+            final @ReasonCode int reason = shouldExemptUid(uid);
+            if (reason != REASON_DENIED) {
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                    Slog.i(TAG, "Exempting battery usage in " + UserHandle.formatUid(uid)
+                            + " " + PowerExemptionManager.reasonCodeToString(reason));
+                }
+                return;
+            }
+            boolean notifyController = false;
+            boolean excessive = false;
+            synchronized (mLock) {
+                final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel(uid);
+                if (curLevel >= RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+                    // We're already in the background restricted level, nothing more we could do.
+                    return;
+                }
+                final long now = SystemClock.elapsedRealtime();
+                final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now,
+                        mBgCurrentDrainWindowMs);
+                final int index = mHighBgBatteryPackages.indexOfKey(uid);
+                if (index < 0) {
+                    if (percentage >= mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
+                        // New findings to us, track it and let the controller know.
+                        final long[] ts = new long[TIME_STAMP_INDEX_LAST];
+                        ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
+                        mHighBgBatteryPackages.put(uid, ts);
+                        notifyController = excessive = true;
+                    }
+                } else {
+                    final long[] ts = mHighBgBatteryPackages.valueAt(index);
+                    if (percentage < mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
+                        // it's actually back to normal, but we don't untrack it until
+                        // explicit user interactions.
+                        notifyController = true;
+                    } else {
+                        excessive = true;
+                        if (percentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]) {
+                            // If we're in the restricted standby bucket but still seeing high
+                            // current drains, tell the controller again.
+                            if (curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET
+                                    && ts[TIME_STAMP_INDEX_BG_RESTRICTED] == 0) {
+                                if (now > ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]
+                                        + mBgCurrentDrainWindowMs) {
+                                    ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
+                                    notifyController = true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (excessive) {
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                    Slog.i(TAG, "Excessive background current drain " + uid
+                            + String.format(" %.2f%%", percentage) + " over "
+                            + TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
+                }
+                if (notifyController) {
+                    mTracker.mAppRestrictionController.refreshAppRestrictionLevelForUid(
+                            uid, REASON_MAIN_FORCED_BY_SYSTEM,
+                            REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE, true);
+                }
+            } else {
+                if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+                    Slog.i(TAG, "Background current drain backs to normal " + uid
+                            + String.format(" %.2f%%", percentage) + " over "
+                            + TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
+                }
+                // For now, we're not lifting the restrictions if the bg current drain backs to
+                // normal util an explicit user interaction.
+            }
+        }
+
+        private int getCurrentDrainThresholdIndex(int uid, long now, long window) {
+            return (hasMediaPlayback(uid, now, window) || hasLocation(uid, now, window))
+                    ? INDEX_HIGH_CURRENT_DRAIN_THRESHOLD
+                    : INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD;
+        }
+
+        private boolean hasMediaPlayback(int uid, long now, long window) {
+            return mBgCurrentDrainEventDurationBasedThresholdEnabled
+                    && mTracker.mAppRestrictionController.getCompositeMediaPlaybackDurations(
+                            uid, now, window) >= mBgCurrentDrainMediaPlaybackMinDuration;
+        }
+
+        private boolean hasLocation(int uid, long now, long window) {
+            final AppRestrictionController controller = mTracker.mAppRestrictionController;
+            if (mInjector.getPermissionManagerServiceInternal().checkUidPermission(
+                    uid, ACCESS_BACKGROUND_LOCATION) == PERMISSION_GRANTED) {
+                return true;
+            }
+            if (!mBgCurrentDrainEventDurationBasedThresholdEnabled) {
+                return false;
+            }
+            final long since = Math.max(0, now - window);
+            final long locationDuration = controller.getForegroundServiceTotalDurationsSince(
+                    uid, since, now, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
+            return locationDuration >= mBgCurrentDrainLocationMinDuration;
+        }
+
+        void onUserInteractionStarted(String packageName, int uid) {
+            boolean changed = false;
+            synchronized (mLock) {
+                final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel(
+                        uid, packageName);
+                if (curLevel == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+                    // It's a sticky state, user interaction won't change it, still track it.
+                } else {
+                    // Remove the given UID from our tracking list, as user interacted with it.
+                    final int index = mHighBgBatteryPackages.indexOfKey(uid);
+                    if (index >= 0) {
+                        mHighBgBatteryPackages.removeAt(index);
+                        changed = true;
+                    }
+                }
+            }
+            if (changed) {
+                // Request to refresh the app restriction level.
+                mTracker.mAppRestrictionController.refreshAppRestrictionLevelForUid(uid,
+                        REASON_MAIN_USAGE, REASON_SUB_USAGE_USER_INTERACTION, true);
+            }
+        }
+
+        void onBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
+            if (restricted) {
+                return;
+            }
+            synchronized (mLock) {
+                // User has explicitly removed it from background restricted level,
+                // clear the timestamp of the background-restricted
+                final long[] ts = mHighBgBatteryPackages.get(uid);
+                if (ts != null) {
+                    ts[TIME_STAMP_INDEX_BG_RESTRICTED] = 0;
+                }
+            }
+        }
+
+        private double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer,
+                final BatteryConsumer.Dimensions dimens) {
+            try {
+                return uidConsumer.getConsumedPower(dimens);
+            } catch (IllegalArgumentException e) {
+                return 0.0d;
+            }
+        }
+
+        @VisibleForTesting
+        void reset() {
+            mHighBgBatteryPackages.clear();
+            mTracker.reset();
+        }
+
+        @Override
+        void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix);
+            pw.println("APP BATTERY TRACKER POLICY SETTINGS:");
+            final String indent = "  ";
+            prefix = indent + prefix;
+            super.dump(pw, prefix);
+            if (isEnabled()) {
+                pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET);
+                pw.print('=');
+                pw.println(mBgCurrentDrainRestrictedBucketThreshold[
+                        INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD]);
+                pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET);
+                pw.print('=');
+                pw.println(mBgCurrentDrainRestrictedBucketThreshold[
+                        INDEX_HIGH_CURRENT_DRAIN_THRESHOLD]);
+                pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED);
+                pw.print('=');
+                pw.println(mBgCurrentDrainBgRestrictedThreshold[
+                        INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD]);
+                pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED);
+                pw.print('=');
+                pw.println(mBgCurrentDrainBgRestrictedThreshold[
+                        INDEX_HIGH_CURRENT_DRAIN_THRESHOLD]);
+                pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_WINDOW);
+                pw.print('=');
+                pw.println(mBgCurrentDrainWindowMs);
+                pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
+                pw.print('=');
+                pw.println(mBgCurrentDrainMediaPlaybackMinDuration);
+                pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION);
+                pw.print('=');
+                pw.println(mBgCurrentDrainLocationMinDuration);
+                pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
+                pw.print('=');
+                pw.println(mBgCurrentDrainEventDurationBasedThresholdEnabled);
+
+                pw.print(prefix);
+                pw.println("Excessive current drain detected:");
+                synchronized (mLock) {
+                    final int size = mHighBgBatteryPackages.size();
+                    prefix = indent + prefix;
+                    if (size > 0) {
+                        final long now = SystemClock.elapsedRealtime();
+                        for (int i = 0; i < size; i++) {
+                            final int uid = mHighBgBatteryPackages.keyAt(i);
+                            final long[] ts = mHighBgBatteryPackages.valueAt(i);
+                            final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now,
+                                    mBgCurrentDrainWindowMs);
+                            pw.format("%s%s: (threshold=%4.2f%%/%4.2f%%) %s/%s\n",
+                                    prefix,
+                                    UserHandle.formatUid(uid),
+                                    mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex],
+                                    mBgCurrentDrainBgRestrictedThreshold[thresholdIndex],
+                                    ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] == 0 ? "0"
+                                        : formatTime(ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET], now),
+                                    ts[TIME_STAMP_INDEX_BG_RESTRICTED] == 0 ? "0"
+                                        : formatTime(ts[TIME_STAMP_INDEX_BG_RESTRICTED], now));
+                        }
+                    } else {
+                        pw.print(prefix);
+                        pw.println("(none)");
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/AppBindServiceEventsTracker.java b/services/core/java/com/android/server/am/AppBindServiceEventsTracker.java
new file mode 100644
index 0000000..9e3cae6
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppBindServiceEventsTracker.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
+
+import android.annotation.NonNull;
+import android.app.ActivityManagerInternal.BindServiceEventListener;
+import android.content.Context;
+
+import com.android.server.am.AppBindServiceEventsTracker.AppBindServiceEventsPolicy;
+import com.android.server.am.BaseAppStateTimeSlotEventsTracker.SimpleAppStateTimeslotEvents;
+import com.android.server.am.BaseAppStateTracker.Injector;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+
+final class AppBindServiceEventsTracker extends BaseAppStateTimeSlotEventsTracker
+        <AppBindServiceEventsPolicy, SimpleAppStateTimeslotEvents>
+        implements BindServiceEventListener {
+
+    static final String TAG = TAG_WITH_CLASS_NAME ? "AppBindServiceEventsTracker" : TAG_AM;
+
+    static final boolean DEBUG_APP_STATE_BIND_SERVICE_EVENT_TRACKER = false;
+
+    AppBindServiceEventsTracker(Context context, AppRestrictionController controller) {
+        this(context, controller, null, null);
+    }
+
+    AppBindServiceEventsTracker(Context context, AppRestrictionController controller,
+            Constructor<? extends Injector<AppBindServiceEventsPolicy>> injector,
+            Object outerContext) {
+        super(context, controller, injector, outerContext);
+        mInjector.setPolicy(new AppBindServiceEventsPolicy(mInjector, this));
+    }
+
+    @Override
+    public void onBindingService(String packageName, int uid) {
+        if (mInjector.getPolicy().isEnabled()) {
+            onNewEvent(packageName, uid);
+        }
+    }
+
+    @Override
+    void onSystemReady() {
+        super.onSystemReady();
+        mInjector.getActivityManagerInternal().addBindServiceEventListener(this);
+    }
+
+    @Override
+    public SimpleAppStateTimeslotEvents createAppStateEvents(int uid, String packageName) {
+        return new SimpleAppStateTimeslotEvents(uid, packageName,
+                mInjector.getPolicy().getTimeSlotSize(), TAG, mInjector.getPolicy());
+    }
+
+    @Override
+    public SimpleAppStateTimeslotEvents createAppStateEvents(SimpleAppStateTimeslotEvents other) {
+        return new SimpleAppStateTimeslotEvents(other);
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.println("APP BIND SERVICE EVENT TRACKER:");
+        super.dump(pw, "  " + prefix);
+    }
+
+    static final class AppBindServiceEventsPolicy
+            extends BaseAppStateTimeSlotEventsPolicy<AppBindServiceEventsTracker> {
+        /**
+         * Whether or not we should enable the monitoring on abusive service bindings requests.
+         */
+        static final String KEY_BG_BIND_SVC_MONITOR_ENABLED =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "bind_svc_monitor_enabled";
+
+        /**
+         * The size of the sliding window in which the number of service binding requests is checked
+         * against the threshold.
+         */
+        static final String KEY_BG_BIND_SVC_WINDOW =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "bind_svc_window";
+
+        /**
+         * The threshold at where the number of service binding requests are considered as
+         * "excessive" within the given window.
+         */
+        static final String KEY_BG_EX_BIND_SVC_THRESHOLD =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "ex_bind_svc_threshold";
+
+        /**
+         * Default value to {@link #mTrackerEnabled}.
+         */
+        static final boolean DEFAULT_BG_BIND_SVC_MONITOR_ENABLED = true;
+
+        /**
+         * Default value to {@link #mMaxTrackingDuration}.
+         */
+        static final long DEFAULT_BG_BIND_SVC_WINDOW = ONE_DAY;
+
+        /**
+         * Default value to {@link #mNumOfEventsThreshold}.
+         */
+        static final int DEFAULT_BG_EX_BIND_SVC_THRESHOLD = 10_000;
+
+        AppBindServiceEventsPolicy(@NonNull Injector injector,
+                @NonNull AppBindServiceEventsTracker tracker) {
+            super(injector, tracker,
+                    KEY_BG_BIND_SVC_MONITOR_ENABLED, DEFAULT_BG_BIND_SVC_MONITOR_ENABLED,
+                    KEY_BG_BIND_SVC_WINDOW, DEFAULT_BG_BIND_SVC_WINDOW,
+                    KEY_BG_EX_BIND_SVC_THRESHOLD, DEFAULT_BG_EX_BIND_SVC_THRESHOLD);
+        }
+
+        @Override
+        String getEventName() {
+            return "bindservice";
+        }
+
+        @Override
+        void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix);
+            pw.println("APP BIND SERVICE EVENT TRACKER POLICY SETTINGS:");
+            super.dump(pw, "  " + prefix);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/AppBroadcastEventsTracker.java b/services/core/java/com/android/server/am/AppBroadcastEventsTracker.java
new file mode 100644
index 0000000..cafae40
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppBroadcastEventsTracker.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
+
+import android.annotation.NonNull;
+import android.app.ActivityManagerInternal.BroadcastEventListener;
+import android.content.Context;
+
+import com.android.server.am.AppBroadcastEventsTracker.AppBroadcastEventsPolicy;
+import com.android.server.am.BaseAppStateTimeSlotEventsTracker.SimpleAppStateTimeslotEvents;
+import com.android.server.am.BaseAppStateTracker.Injector;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+
+final class AppBroadcastEventsTracker extends BaseAppStateTimeSlotEventsTracker
+        <AppBroadcastEventsPolicy, SimpleAppStateTimeslotEvents> implements BroadcastEventListener {
+
+    static final String TAG = TAG_WITH_CLASS_NAME ? "AppBroadcastEventsTracker" : TAG_AM;
+
+    static final boolean DEBUG_APP_STATE_BROADCAST_EVENT_TRACKER = false;
+
+    AppBroadcastEventsTracker(Context context, AppRestrictionController controller) {
+        this(context, controller, null, null);
+    }
+
+    AppBroadcastEventsTracker(Context context, AppRestrictionController controller,
+            Constructor<? extends Injector<AppBroadcastEventsPolicy>> injector,
+            Object outerContext) {
+        super(context, controller, injector, outerContext);
+        mInjector.setPolicy(new AppBroadcastEventsPolicy(mInjector, this));
+    }
+
+    @Override
+    public void onSendingBroadcast(String packageName, int uid) {
+        if (mInjector.getPolicy().isEnabled()) {
+            onNewEvent(packageName, uid);
+        }
+    }
+
+    @Override
+    void onSystemReady() {
+        super.onSystemReady();
+        mInjector.getActivityManagerInternal().addBroadcastEventListener(this);
+    }
+
+    @Override
+    public SimpleAppStateTimeslotEvents createAppStateEvents(int uid, String packageName) {
+        return new SimpleAppStateTimeslotEvents(uid, packageName,
+                mInjector.getPolicy().getTimeSlotSize(), TAG, mInjector.getPolicy());
+    }
+
+    @Override
+    public SimpleAppStateTimeslotEvents createAppStateEvents(SimpleAppStateTimeslotEvents other) {
+        return new SimpleAppStateTimeslotEvents(other);
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.println("APP BROADCAST EVENT TRACKER:");
+        super.dump(pw, "  " + prefix);
+    }
+
+    static final class AppBroadcastEventsPolicy
+            extends BaseAppStateTimeSlotEventsPolicy<AppBroadcastEventsTracker> {
+        /**
+         * Whether or not we should enable the monitoring on abusive broadcasts.
+         */
+        static final String KEY_BG_BROADCAST_MONITOR_ENABLED =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "broadcast_monitor_enabled";
+
+        /**
+         * The size of the sliding window in which the number of broadcasts is checked
+         * against the threshold.
+         */
+        static final String KEY_BG_BROADCAST_WINDOW =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "broadcast_window";
+
+        /**
+         * The threshold at where the number of broadcasts are considered as "excessive"
+         * within the given window.
+         */
+        static final String KEY_BG_EX_BROADCAST_THRESHOLD =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "ex_broadcast_threshold";
+
+        /**
+         * Default value to {@link #mTrackerEnabled}.
+         */
+        static final boolean DEFAULT_BG_BROADCAST_MONITOR_ENABLED = true;
+
+        /**
+         * Default value to {@link #mMaxTrackingDuration}.
+         */
+        static final long DEFAULT_BG_BROADCAST_WINDOW = ONE_DAY;
+
+        /**
+         * Default value to {@link #mNumOfEventsThreshold}.
+         */
+        static final int DEFAULT_BG_EX_BROADCAST_THRESHOLD = 10_000;
+
+        AppBroadcastEventsPolicy(@NonNull Injector injector,
+                @NonNull AppBroadcastEventsTracker tracker) {
+            super(injector, tracker,
+                    KEY_BG_BROADCAST_MONITOR_ENABLED, DEFAULT_BG_BROADCAST_MONITOR_ENABLED,
+                    KEY_BG_BROADCAST_WINDOW, DEFAULT_BG_BROADCAST_WINDOW,
+                    KEY_BG_EX_BROADCAST_THRESHOLD, DEFAULT_BG_EX_BROADCAST_THRESHOLD);
+        }
+
+        @Override
+        String getEventName() {
+            return "broadcast";
+        }
+
+        @Override
+        void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix);
+            pw.println("APP BROADCAST EVENT TRACKER POLICY SETTINGS:");
+            super.dump(pw, "  " + prefix);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
new file mode 100644
index 0000000..9c775b3
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -0,0 +1,780 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+import static android.content.pm.ServiceInfo.NUM_OF_FOREGROUND_SERVICE_TYPES;
+import static android.content.pm.ServiceInfo.foregroundServiceTypeToLabel;
+import static android.os.PowerExemptionManager.REASON_DENIED;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
+import static com.android.server.am.BaseAppStateTracker.ONE_HOUR;
+
+import android.annotation.NonNull;
+import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
+import android.app.IProcessObserver;
+import android.content.Context;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.AppFGSTracker.AppFGSPolicy;
+import com.android.server.am.AppFGSTracker.PackageDurations;
+import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+import com.android.server.am.BaseAppStateTracker.Injector;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.LinkedList;
+
+/**
+ * The tracker for monitoring abusive (long-running) FGS.
+ */
+final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, PackageDurations>
+        implements ForegroundServiceStateListener {
+    static final String TAG = TAG_WITH_CLASS_NAME ? "AppFGSTracker" : TAG_AM;
+
+    static final boolean DEBUG_BACKGROUND_FGS_TRACKER = false;
+
+    private final MyHandler mHandler;
+
+    // Unlocked since it's only accessed in single thread.
+    private final ArrayMap<PackageDurations, Long> mTmpPkgDurations = new ArrayMap<>();
+
+    final IProcessObserver.Stub mProcessObserver = new IProcessObserver.Stub() {
+        @Override
+        public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
+        }
+
+        @Override
+        public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
+            final String packageName = mAppRestrictionController.getPackageName(pid);
+            if (packageName != null) {
+                mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_CHANGED,
+                        uid, serviceTypes, packageName).sendToTarget();
+            }
+        }
+
+        @Override
+        public void onProcessDied(int pid, int uid) {
+        }
+    };
+
+    @Override
+    public void onForegroundServiceStateChanged(String packageName,
+            int uid, int pid, boolean started) {
+        mHandler.obtainMessage(started ? MyHandler.MSG_FOREGROUND_SERVICES_STARTED
+                : MyHandler.MSG_FOREGROUND_SERVICES_STOPPED, pid, uid, packageName).sendToTarget();
+    }
+
+    private static class MyHandler extends Handler {
+        static final int MSG_FOREGROUND_SERVICES_STARTED = 0;
+        static final int MSG_FOREGROUND_SERVICES_STOPPED = 1;
+        static final int MSG_FOREGROUND_SERVICES_CHANGED = 2;
+        static final int MSG_CHECK_LONG_RUNNING_FGS = 3;
+
+        private final AppFGSTracker mTracker;
+
+        MyHandler(AppFGSTracker tracker) {
+            super(tracker.mBgHandler.getLooper());
+            mTracker = tracker;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_FOREGROUND_SERVICES_STARTED:
+                    mTracker.handleForegroundServicesChanged(
+                            (String) msg.obj, msg.arg1, msg.arg2, true);
+                    break;
+                case MSG_FOREGROUND_SERVICES_STOPPED:
+                    mTracker.handleForegroundServicesChanged(
+                            (String) msg.obj, msg.arg1, msg.arg2, false);
+                    break;
+                case MSG_FOREGROUND_SERVICES_CHANGED:
+                    mTracker.handleForegroundServicesChanged(
+                            (String) msg.obj, msg.arg1, msg.arg2);
+                    break;
+                case MSG_CHECK_LONG_RUNNING_FGS:
+                    mTracker.checkLongRunningFgs();
+                    break;
+            }
+        }
+    }
+
+    AppFGSTracker(Context context, AppRestrictionController controller) {
+        this(context, controller, null, null);
+    }
+
+    AppFGSTracker(Context context, AppRestrictionController controller,
+            Constructor<? extends Injector<AppFGSPolicy>> injector, Object outerContext) {
+        super(context, controller, injector, outerContext);
+        mHandler = new MyHandler(this);
+        mInjector.setPolicy(new AppFGSPolicy(mInjector, this));
+    }
+
+    @Override
+    void onSystemReady() {
+        super.onSystemReady();
+        mInjector.getActivityManagerInternal().addForegroundServiceStateListener(this);
+        mInjector.getActivityManagerInternal().registerProcessObserver(mProcessObserver);
+    }
+
+    @VisibleForTesting
+    @Override
+    void reset() {
+        mHandler.removeMessages(MyHandler.MSG_CHECK_LONG_RUNNING_FGS);
+        super.reset();
+    }
+
+    @Override
+    public PackageDurations createAppStateEvents(int uid, String packageName) {
+        return new PackageDurations(uid, packageName, mInjector.getPolicy(), this);
+    }
+
+    @Override
+    public PackageDurations createAppStateEvents(PackageDurations other) {
+        return new PackageDurations(other);
+    }
+
+    private void handleForegroundServicesChanged(String packageName, int pid, int uid,
+            boolean started) {
+        if (!mInjector.getPolicy().isEnabled()) {
+            return;
+        }
+        final long now = SystemClock.elapsedRealtime();
+        boolean longRunningFGSGone = false;
+        final int exemptReason = mInjector.getPolicy().shouldExemptUid(uid);
+        if (DEBUG_BACKGROUND_FGS_TRACKER) {
+            Slog.i(TAG, (started ? "Starting" : "Stopping") + " fgs in "
+                    + packageName + "/" + UserHandle.formatUid(uid)
+                    + " exemptReason=" + exemptReason);
+        }
+        synchronized (mLock) {
+            PackageDurations pkg = mPkgEvents.get(uid, packageName);
+            if (pkg == null) {
+                pkg = createAppStateEvents(uid, packageName);
+                mPkgEvents.put(uid, packageName, pkg);
+            }
+            final boolean wasLongRunning = pkg.isLongRunning();
+            pkg.addEvent(started, now);
+            longRunningFGSGone = wasLongRunning && !pkg.hasForegroundServices();
+            if (longRunningFGSGone) {
+                pkg.setIsLongRunning(false);
+            }
+            pkg.mExemptReason = exemptReason;
+            // Reschedule the checks.
+            scheduleDurationCheckLocked(now);
+        }
+        if (longRunningFGSGone) {
+            // The long-running FGS is gone, cancel the notification.
+            mInjector.getPolicy().onLongRunningFgsGone(packageName, uid);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void scheduleDurationCheckLocked(long now) {
+        // Look for the active FGS with longest running time till now.
+        final SparseArray<ArrayMap<String, PackageDurations>> map = mPkgEvents.getMap();
+        long longest = -1;
+        for (int i = map.size() - 1; i >= 0; i--) {
+            final ArrayMap<String, PackageDurations> val = map.valueAt(i);
+            for (int j = val.size() - 1; j >= 0; j--) {
+                final PackageDurations pkg = val.valueAt(j);
+                if (!pkg.hasForegroundServices() || pkg.isLongRunning()) {
+                    // No FGS or it's a known long-running FGS, ignore it.
+                    continue;
+                }
+                longest = Math.max(getTotalDurations(pkg, now), longest);
+            }
+        }
+        // Schedule a check in the future.
+        mHandler.removeMessages(MyHandler.MSG_CHECK_LONG_RUNNING_FGS);
+        if (longest >= 0) {
+            // We'd add the "service start foreground timeout", as the apps are allowed
+            // to call startForeground() within that timeout after the FGS being started.
+            final long future = mInjector.getServiceStartForegroundTimeout()
+                    + Math.max(0, mInjector.getPolicy().getFgsLongRunningThreshold() - longest);
+            if (DEBUG_BACKGROUND_FGS_TRACKER) {
+                Slog.i(TAG, "Scheduling a FGS duration check at "
+                        + TimeUtils.formatDuration(future));
+            }
+            mHandler.sendEmptyMessageDelayed(MyHandler.MSG_CHECK_LONG_RUNNING_FGS, future);
+        } else if (DEBUG_BACKGROUND_FGS_TRACKER) {
+            Slog.i(TAG, "Not scheduling FGS duration check");
+        }
+    }
+
+    private void checkLongRunningFgs() {
+        final AppFGSPolicy policy = mInjector.getPolicy();
+        final ArrayMap<PackageDurations, Long> pkgWithLongFgs = mTmpPkgDurations;
+        final long now = SystemClock.elapsedRealtime();
+        final long threshold = policy.getFgsLongRunningThreshold();
+        final long windowSize = policy.getFgsLongRunningWindowSize();
+        final long trimTo = Math.max(0, now - windowSize);
+
+        synchronized (mLock) {
+            final SparseArray<ArrayMap<String, PackageDurations>> map = mPkgEvents.getMap();
+            for (int i = map.size() - 1; i >= 0; i--) {
+                final ArrayMap<String, PackageDurations> val = map.valueAt(i);
+                for (int j = val.size() - 1; j >= 0; j--) {
+                    final PackageDurations pkg = val.valueAt(j);
+                    if (pkg.hasForegroundServices() && !pkg.isLongRunning()) {
+                        final long totalDuration = getTotalDurations(pkg, now);
+                        if (totalDuration >= threshold) {
+                            pkgWithLongFgs.put(pkg, totalDuration);
+                            pkg.setIsLongRunning(true);
+                            if (DEBUG_BACKGROUND_FGS_TRACKER) {
+                                Slog.i(TAG, pkg.mPackageName
+                                        + "/" + UserHandle.formatUid(pkg.mUid)
+                                        + " has FGS running for "
+                                        + TimeUtils.formatDuration(totalDuration)
+                                        + " over " + TimeUtils.formatDuration(windowSize));
+                            }
+                        }
+                    }
+                }
+            }
+            // Trim the duration list, we don't need to keep track of all old records.
+            trim(trimTo);
+        }
+
+        final int size = pkgWithLongFgs.size();
+        if (size > 0) {
+            // Sort it by the durations.
+            final Integer[] indices = new Integer[size];
+            for (int i = 0; i < size; i++) {
+                indices[i] = i;
+            }
+            Arrays.sort(indices, (a, b) -> Long.compare(
+                    pkgWithLongFgs.valueAt(a), pkgWithLongFgs.valueAt(b)));
+            // Notify it in the order of from longest to shortest durations.
+            for (int i = size - 1; i >= 0; i--) {
+                final PackageDurations pkg = pkgWithLongFgs.keyAt(indices[i]);
+                policy.onLongRunningFgs(pkg.mPackageName, pkg.mUid, pkg.mExemptReason);
+            }
+            pkgWithLongFgs.clear();
+        }
+
+        synchronized (mLock) {
+            scheduleDurationCheckLocked(now);
+        }
+    }
+
+    private void handleForegroundServicesChanged(String packageName, int uid, int serviceTypes) {
+        if (!mInjector.getPolicy().isEnabled()) {
+            return;
+        }
+        final int exemptReason = mInjector.getPolicy().shouldExemptUid(uid);
+        final long now = SystemClock.elapsedRealtime();
+        if (DEBUG_BACKGROUND_FGS_TRACKER) {
+            Slog.i(TAG, "Updating fgs type for " + packageName + "/" + UserHandle.formatUid(uid)
+                    + " to " + Integer.toHexString(serviceTypes)
+                    + " exemptReason=" + exemptReason);
+        }
+        synchronized (mLock) {
+            PackageDurations pkg = mPkgEvents.get(uid, packageName);
+            if (pkg == null) {
+                pkg = new PackageDurations(uid, packageName, mInjector.getPolicy(), this);
+                mPkgEvents.put(uid, packageName, pkg);
+            }
+            pkg.setForegroundServiceType(serviceTypes, now);
+            pkg.mExemptReason = exemptReason;
+        }
+    }
+
+    private void onBgFgsMonitorEnabled(boolean enabled) {
+        if (enabled) {
+            synchronized (mLock) {
+                scheduleDurationCheckLocked(SystemClock.elapsedRealtime());
+            }
+        } else {
+            mHandler.removeMessages(MyHandler.MSG_CHECK_LONG_RUNNING_FGS);
+            synchronized (mLock) {
+                mPkgEvents.clear();
+            }
+        }
+    }
+
+    private void onBgFgsLongRunningThresholdChanged() {
+        synchronized (mLock) {
+            if (mInjector.getPolicy().isEnabled()) {
+                scheduleDurationCheckLocked(SystemClock.elapsedRealtime());
+            }
+        }
+    }
+
+    static int foregroundServiceTypeToIndex(@ForegroundServiceType int serviceType) {
+        return serviceType == FOREGROUND_SERVICE_TYPE_NONE ? 0
+                : Integer.numberOfTrailingZeros(serviceType) + 1;
+    }
+
+    static @ForegroundServiceType int indexToForegroundServiceType(int index) {
+        return index == PackageDurations.DEFAULT_INDEX
+                ? FOREGROUND_SERVICE_TYPE_NONE : (1 << (index - 1));
+    }
+
+    long getTotalDurations(PackageDurations pkg, long now) {
+        return getTotalDurations(pkg.mPackageName, pkg.mUid, now,
+                foregroundServiceTypeToIndex(FOREGROUND_SERVICE_TYPE_NONE));
+    }
+
+    boolean hasForegroundServices(String packageName, int uid) {
+        synchronized (mLock) {
+            final PackageDurations pkg = mPkgEvents.get(uid, packageName);
+            return pkg != null && pkg.hasForegroundServices();
+        }
+    }
+
+    boolean hasForegroundServices(int uid) {
+        synchronized (mLock) {
+            final SparseArray<ArrayMap<String, PackageDurations>> map = mPkgEvents.getMap();
+            final ArrayMap<String, PackageDurations> pkgs = map.get(uid);
+            if (pkgs != null) {
+                for (int i = pkgs.size() - 1; i >= 0; i--) {
+                    final PackageDurations pkg = pkgs.valueAt(i);
+                    if (pkg.hasForegroundServices()) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.println("APP FOREGROUND SERVICE TRACKER:");
+        super.dump(pw, "  " + prefix);
+    }
+
+    /**
+     * Tracks the durations with active FGS for a given package.
+     */
+    static class PackageDurations extends BaseAppStateDurations<BaseTimeEvent> {
+        private final AppFGSTracker mTracker;
+
+        /**
+         * Whether or not this package is considered as having long-running FGS.
+         */
+        private boolean mIsLongRunning;
+
+        /**
+         * The current foreground service types, should be a combination of the values in
+         * {@link android.content.pm.ServiceInfo.ForegroundServiceType}.
+         */
+        private int mForegroundServiceTypes;
+
+        /**
+         * The index to the duration list array, where it holds the overall FGS stats of this
+         * package.
+         */
+        static final int DEFAULT_INDEX = foregroundServiceTypeToIndex(FOREGROUND_SERVICE_TYPE_NONE);
+
+        PackageDurations(int uid, String packageName,
+                MaxTrackingDurationConfig maxTrackingDurationConfig, AppFGSTracker tracker) {
+            super(uid, packageName, NUM_OF_FOREGROUND_SERVICE_TYPES + 1, TAG,
+                    maxTrackingDurationConfig);
+            mEvents[DEFAULT_INDEX] = new LinkedList<>();
+            mTracker = tracker;
+        }
+
+        PackageDurations(@NonNull PackageDurations other) {
+            super(other);
+            mIsLongRunning = other.mIsLongRunning;
+            mForegroundServiceTypes = other.mForegroundServiceTypes;
+            mTracker = other.mTracker;
+        }
+
+        /**
+         * Add a foreground service start/stop event.
+         */
+        void addEvent(boolean startFgs, long now) {
+            addEvent(startFgs, new BaseTimeEvent(now), DEFAULT_INDEX);
+            if (!startFgs && !hasForegroundServices()) {
+                mIsLongRunning = false;
+            }
+
+            if (!startFgs && mForegroundServiceTypes != FOREGROUND_SERVICE_TYPE_NONE) {
+                // Save the stop time per service type.
+                for (int i = 1; i < mEvents.length; i++) {
+                    if (mEvents[i] == null) {
+                        continue;
+                    }
+                    if (isActive(i)) {
+                        mEvents[i].add(new BaseTimeEvent(now));
+                        notifyListenersOnEventIfNecessary(false, now,
+                                indexToForegroundServiceType(i));
+                    }
+                }
+                mForegroundServiceTypes = FOREGROUND_SERVICE_TYPE_NONE;
+            }
+        }
+
+        /**
+         * Called on the service type changes via the {@link android.app.Service#startForeground}.
+         */
+        void setForegroundServiceType(int serviceTypes, long now) {
+            if (serviceTypes == mForegroundServiceTypes || !hasForegroundServices()) {
+                // Nothing to do.
+                return;
+            }
+            int changes = serviceTypes ^ mForegroundServiceTypes;
+            for (int serviceType = Integer.highestOneBit(changes); serviceType != 0;) {
+                final int i = foregroundServiceTypeToIndex(serviceType);
+                if ((serviceTypes & serviceType) != 0) {
+                    // Start this type.
+                    if (mEvents[i] == null) {
+                        mEvents[i] = new LinkedList<>();
+                    }
+                    if (!isActive(i)) {
+                        mEvents[i].add(new BaseTimeEvent(now));
+                        notifyListenersOnEventIfNecessary(true, now, serviceType);
+                    }
+                } else {
+                    // Stop this type.
+                    if (mEvents[i] != null && isActive(i)) {
+                        mEvents[i].add(new BaseTimeEvent(now));
+                        notifyListenersOnEventIfNecessary(false, now, serviceType);
+                    }
+                }
+                changes &= ~serviceType;
+                serviceType = Integer.highestOneBit(changes);
+            }
+            mForegroundServiceTypes = serviceTypes;
+        }
+
+        private void notifyListenersOnEventIfNecessary(boolean start, long now,
+                @ForegroundServiceType int serviceType) {
+            int eventType;
+            switch (serviceType) {
+                case FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK:
+                    eventType = BaseAppStateDurationsTracker.EVENT_TYPE_FGS_MEDIA_PLAYBACK;
+                    break;
+                case FOREGROUND_SERVICE_TYPE_LOCATION:
+                    eventType = BaseAppStateDurationsTracker.EVENT_TYPE_FGS_LOCATION;
+                    break;
+                default:
+                    return;
+            }
+            mTracker.notifyListenersOnEvent(mUid, mPackageName, start, now, eventType);
+        }
+
+        void setIsLongRunning(boolean isLongRunning) {
+            mIsLongRunning = isLongRunning;
+        }
+
+        boolean isLongRunning() {
+            return mIsLongRunning;
+        }
+
+        boolean hasForegroundServices() {
+            return isActive(DEFAULT_INDEX);
+        }
+
+        @Override
+        String formatEventTypeLabel(int index) {
+            if (index == DEFAULT_INDEX) {
+                return "Overall foreground services: ";
+            } else {
+                return foregroundServiceTypeToLabel(indexToForegroundServiceType(index)) + ": ";
+            }
+        }
+    }
+
+    static final class AppFGSPolicy extends BaseAppStateEventsPolicy<AppFGSTracker> {
+        /**
+         * Whether or not we should enable the monitoring on abusive FGS.
+         */
+        static final String KEY_BG_FGS_MONITOR_ENABLED =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "fgs_monitor_enabled";
+
+        /**
+         * The size of the sliding window in which the accumulated FGS durations are checked
+         * against the threshold.
+         */
+        static final String KEY_BG_FGS_LONG_RUNNING_WINDOW =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "fgs_long_running_window";
+
+        /**
+         * The threshold at where the accumulated FGS durations are considered as "long-running"
+         * within the given window.
+         */
+        static final String KEY_BG_FGS_LONG_RUNNING_THRESHOLD =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "fgs_long_running_threshold";
+
+        /**
+         * If a package has run FGS with "mediaPlayback" over this threshold, it won't be considered
+         * as a long-running FGS.
+         */
+        static final String KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "fgs_media_playback_threshold";
+
+        /**
+         * If a package has run FGS with "location" over this threshold, it won't be considered
+         * as a long-running FGS.
+         */
+        static final String KEY_BG_FGS_LOCATION_THRESHOLD =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "fgs_location_threshold";
+
+        /**
+         * Default value to {@link #mTrackerEnabled}.
+         */
+        static final boolean DEFAULT_BG_FGS_MONITOR_ENABLED = true;
+
+        /**
+         * Default value to {@link #mMaxTrackingDuration}.
+         */
+        static final long DEFAULT_BG_FGS_LONG_RUNNING_WINDOW = ONE_DAY;
+
+        /**
+         * Default value to {@link #mBgFgsLongRunningThresholdMs}.
+         */
+        static final long DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD = 20 * ONE_HOUR;
+
+        /**
+         * Default value to {@link #mBgFgsMediaPlaybackThresholdMs}.
+         */
+        static final long DEFAULT_BG_FGS_MEDIA_PLAYBACK_THRESHOLD = 4 * ONE_HOUR;
+
+        /**
+         * Default value to {@link #mBgFgsLocationThresholdMs}.
+         */
+        static final long DEFAULT_BG_FGS_LOCATION_THRESHOLD = 4 * ONE_HOUR;
+
+        /**
+         * @see #KEY_BG_FGS_LONG_RUNNING_THRESHOLD.
+         */
+        private volatile long mBgFgsLongRunningThresholdMs = DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD;
+
+        /**
+         * @see #KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD.
+         */
+        private volatile long mBgFgsMediaPlaybackThresholdMs =
+                DEFAULT_BG_FGS_MEDIA_PLAYBACK_THRESHOLD;
+
+        /**
+         * @see #KEY_BG_FGS_LOCATION_THRESHOLD.
+         */
+        private volatile long mBgFgsLocationThresholdMs = DEFAULT_BG_FGS_LOCATION_THRESHOLD;
+
+        AppFGSPolicy(@NonNull Injector injector, @NonNull AppFGSTracker tracker) {
+            super(injector, tracker, KEY_BG_FGS_MONITOR_ENABLED, DEFAULT_BG_FGS_MONITOR_ENABLED,
+                    KEY_BG_FGS_LONG_RUNNING_WINDOW, DEFAULT_BG_FGS_LONG_RUNNING_WINDOW);
+        }
+
+        @Override
+        public void onSystemReady() {
+            super.onSystemReady();
+            updateBgFgsLongRunningThreshold();
+            updateBgFgsMediaPlaybackThreshold();
+            updateBgFgsLocationThreshold();
+        }
+
+        @Override
+        public void onPropertiesChanged(String name) {
+            switch (name) {
+                case KEY_BG_FGS_LONG_RUNNING_THRESHOLD:
+                    updateBgFgsLongRunningThreshold();
+                    break;
+                case KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD:
+                    updateBgFgsMediaPlaybackThreshold();
+                    break;
+                case KEY_BG_FGS_LOCATION_THRESHOLD:
+                    updateBgFgsLocationThreshold();
+                    break;
+                default:
+                    super.onPropertiesChanged(name);
+                    break;
+            }
+        }
+
+        @Override
+        public void onTrackerEnabled(boolean enabled) {
+            mTracker.onBgFgsMonitorEnabled(enabled);
+        }
+
+        @Override
+        public void onMaxTrackingDurationChanged(long maxDuration) {
+            mTracker.onBgFgsLongRunningThresholdChanged();
+        }
+
+        private void updateBgFgsLongRunningThreshold() {
+            final long threshold = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_FGS_LONG_RUNNING_THRESHOLD,
+                    DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD);
+            if (threshold != mBgFgsLongRunningThresholdMs) {
+                mBgFgsLongRunningThresholdMs = threshold;
+                mTracker.onBgFgsLongRunningThresholdChanged();
+            }
+        }
+
+        private void updateBgFgsMediaPlaybackThreshold() {
+            mBgFgsMediaPlaybackThresholdMs = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD,
+                    DEFAULT_BG_FGS_MEDIA_PLAYBACK_THRESHOLD);
+        }
+
+        private void updateBgFgsLocationThreshold() {
+            mBgFgsLocationThresholdMs = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_FGS_LOCATION_THRESHOLD,
+                    DEFAULT_BG_FGS_LOCATION_THRESHOLD);
+        }
+
+        long getFgsLongRunningThreshold() {
+            return mBgFgsLongRunningThresholdMs;
+        }
+
+        long getFgsLongRunningWindowSize() {
+            return getMaxTrackingDuration();
+        }
+
+        long getFGSMediaPlaybackThreshold() {
+            return mBgFgsMediaPlaybackThresholdMs;
+        }
+
+        long getLocationFGSThreshold() {
+            return mBgFgsLocationThresholdMs;
+        }
+
+        void onLongRunningFgs(String packageName, int uid, @ReasonCode int exemptReason) {
+            if (exemptReason != REASON_DENIED) {
+                return;
+            }
+            final long now = SystemClock.elapsedRealtime();
+            final long window = getFgsLongRunningWindowSize();
+            final long since = Math.max(0, now - window);
+            if (shouldExemptMediaPlaybackFGS(packageName, uid, now, window)) {
+                return;
+            }
+            if (shouldExemptLocationFGS(packageName, uid, now, since)) {
+                return;
+            }
+            if (hasBackgroundLocationPermission(packageName, uid)) {
+                // This package has background location permission, ignore it.
+                return;
+            }
+            mTracker.mAppRestrictionController.postLongRunningFgsIfNecessary(packageName, uid);
+        }
+
+        boolean shouldExemptMediaPlaybackFGS(String packageName, int uid, long now, long window) {
+            final long mediaPlaybackMs = mTracker.mAppRestrictionController
+                    .getCompositeMediaPlaybackDurations(packageName, uid, now, window);
+            if (mediaPlaybackMs > 0 && mediaPlaybackMs >= getFGSMediaPlaybackThreshold()) {
+                if (DEBUG_BACKGROUND_FGS_TRACKER) {
+                    Slog.i(TAG, "Ignoring long-running FGS in " + packageName + "/"
+                            + UserHandle.formatUid(uid) + " media playback for "
+                            + TimeUtils.formatDuration(mediaPlaybackMs));
+                }
+                return true;
+            }
+            return false;
+        }
+
+        boolean shouldExemptLocationFGS(String packageName, int uid, long now, long since) {
+            final long locationMs = mTracker.mAppRestrictionController
+                    .getForegroundServiceTotalDurationsSince(packageName, uid, since, now,
+                            FOREGROUND_SERVICE_TYPE_LOCATION);
+            if (locationMs > 0 && locationMs >= getLocationFGSThreshold()) {
+                if (DEBUG_BACKGROUND_FGS_TRACKER) {
+                    Slog.i(TAG, "Ignoring long-running FGS in " + packageName + "/"
+                            + UserHandle.formatUid(uid) + " location for "
+                            + TimeUtils.formatDuration(locationMs));
+                }
+                return true;
+            }
+            return false;
+        }
+
+        boolean hasBackgroundLocationPermission(String packageName, int uid) {
+            if (mInjector.getPermissionManagerServiceInternal().checkPermission(
+                    packageName, ACCESS_BACKGROUND_LOCATION, UserHandle.getUserId(uid))
+                    == PERMISSION_GRANTED) {
+                if (DEBUG_BACKGROUND_FGS_TRACKER) {
+                    Slog.i(TAG, "Ignoring bg-location FGS in " + packageName + "/"
+                            + UserHandle.formatUid(uid));
+                }
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        String getExemptionReasonString(String packageName, int uid, @ReasonCode int reason) {
+            if (reason != REASON_DENIED) {
+                return super.getExemptionReasonString(packageName, uid, reason);
+            }
+            final long now = SystemClock.elapsedRealtime();
+            final long window = getFgsLongRunningWindowSize();
+            final long since = Math.max(0, now - getFgsLongRunningWindowSize());
+            return "{mediaPlayback=" + shouldExemptMediaPlaybackFGS(packageName, uid, now, window)
+                    + ", location=" + shouldExemptLocationFGS(packageName, uid, now, since)
+                    + ", bgLocationPerm=" + hasBackgroundLocationPermission(packageName, uid) + "}";
+        }
+
+        void onLongRunningFgsGone(String packageName, int uid) {
+            mTracker.mAppRestrictionController
+                    .cancelLongRunningFGSNotificationIfNecessary(packageName, uid);
+        }
+
+        @Override
+        void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix);
+            pw.println("APP FOREGROUND SERVICE TRACKER POLICY SETTINGS:");
+            final String indent = "  ";
+            prefix = indent + prefix;
+            super.dump(pw, prefix);
+            if (isEnabled()) {
+                pw.print(prefix);
+                pw.print(KEY_BG_FGS_LONG_RUNNING_THRESHOLD);
+                pw.print('=');
+                pw.println(mBgFgsLongRunningThresholdMs);
+                pw.print(prefix);
+                pw.print(KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD);
+                pw.print('=');
+                pw.println(mBgFgsMediaPlaybackThresholdMs);
+                pw.print(prefix);
+                pw.print(KEY_BG_FGS_LOCATION_THRESHOLD);
+                pw.print('=');
+                pw.println(mBgFgsLocationThresholdMs);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/AppMediaSessionTracker.java b/services/core/java/com/android/server/am/AppMediaSessionTracker.java
new file mode 100644
index 0000000..3914f6f
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppMediaSessionTracker.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.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateDurationsTracker.EVENT_TYPE_MEDIA_SESSION;
+import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
+import android.os.HandlerExecutor;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.app.ProcessMap;
+import com.android.server.am.AppMediaSessionTracker.AppMediaSessionPolicy;
+import com.android.server.am.BaseAppStateDurationsTracker.SimplePackageDurations;
+import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.List;
+
+/**
+ * The tracker for monitoring the active media sessions of apps.
+ */
+final class AppMediaSessionTracker
+        extends BaseAppStateDurationsTracker<AppMediaSessionPolicy, SimplePackageDurations> {
+    static final String TAG = TAG_WITH_CLASS_NAME ? "AppMediaSessionTracker" : TAG_AM;
+
+    static final boolean DEBUG_MEDIA_SESSION_TRACKER = false;
+
+    private final HandlerExecutor mHandlerExecutor;
+    private final OnActiveSessionsChangedListener mSessionsChangedListener =
+            this::handleMediaSessionChanged;
+
+    // Unlocked since it's only accessed in single thread.
+    private final ProcessMap<Boolean> mTmpMediaControllers = new ProcessMap<>();
+
+    AppMediaSessionTracker(Context context, AppRestrictionController controller) {
+        this(context, controller, null, null);
+    }
+
+    AppMediaSessionTracker(Context context, AppRestrictionController controller,
+            Constructor<? extends Injector<AppMediaSessionPolicy>> injector, Object outerContext) {
+        super(context, controller, injector, outerContext);
+        mHandlerExecutor = new HandlerExecutor(mBgHandler);
+        mInjector.setPolicy(new AppMediaSessionPolicy(mInjector, this));
+    }
+
+    @Override
+    public SimplePackageDurations createAppStateEvents(int uid, String packageName) {
+        return new SimplePackageDurations(uid, packageName, mInjector.getPolicy());
+    }
+
+    @Override
+    public SimplePackageDurations createAppStateEvents(SimplePackageDurations other) {
+        return new SimplePackageDurations(other);
+    }
+
+    private void onBgMediaSessionMonitorEnabled(boolean enabled) {
+        if (enabled) {
+            mInjector.getMediaSessionManager().addOnActiveSessionsChangedListener(
+                    null, UserHandle.ALL, mHandlerExecutor, mSessionsChangedListener);
+        } else {
+            mInjector.getMediaSessionManager().removeOnActiveSessionsChangedListener(
+                    mSessionsChangedListener);
+        }
+    }
+
+    private void handleMediaSessionChanged(List<MediaController> controllers) {
+        if (controllers != null) {
+            synchronized (mLock) {
+                final long now = SystemClock.elapsedRealtime();
+                for (MediaController controller : controllers) {
+                    final String packageName = controller.getPackageName();
+                    final int uid = controller.getSessionToken().getUid();
+                    SimplePackageDurations pkg = mPkgEvents.get(uid, packageName);
+                    if (pkg == null) {
+                        pkg = createAppStateEvents(uid, packageName);
+                        mPkgEvents.put(uid, packageName, pkg);
+                    }
+                    if (!pkg.isActive()) {
+                        pkg.addEvent(true, now);
+                        notifyListenersOnEvent(pkg.mUid, pkg.mPackageName, true, now,
+                                EVENT_TYPE_MEDIA_SESSION);
+                    }
+                    // Mark it as active, so we could filter out inactive ones below.
+                    mTmpMediaControllers.put(packageName, uid, Boolean.TRUE);
+
+                    if (DEBUG_MEDIA_SESSION_TRACKER) {
+                        Slog.i(TAG, "Active media session from " + packageName + "/"
+                                + UserHandle.formatUid(uid));
+                    }
+                }
+
+                // Iterate the duration list and stop those inactive ones.
+                final SparseArray<ArrayMap<String, SimplePackageDurations>> map =
+                        mPkgEvents.getMap();
+                for (int i = map.size() - 1; i >= 0; i--) {
+                    final ArrayMap<String, SimplePackageDurations> val = map.valueAt(i);
+                    for (int j = val.size() - 1; j >= 0; j--) {
+                        final SimplePackageDurations pkg = val.valueAt(j);
+                        if (pkg.isActive()
+                                && mTmpMediaControllers.get(pkg.mPackageName, pkg.mUid) == null) {
+                            // This package has removed its controller, issue a stop event.
+                            pkg.addEvent(false, now);
+                            notifyListenersOnEvent(pkg.mUid, pkg.mPackageName, false, now,
+                                    EVENT_TYPE_MEDIA_SESSION);
+                        }
+                    }
+                }
+            }
+            mTmpMediaControllers.clear();
+        } else {
+            synchronized (mLock) {
+                // Issue stop event to all active trackers.
+                final SparseArray<ArrayMap<String, SimplePackageDurations>> map =
+                        mPkgEvents.getMap();
+                final long now = SystemClock.elapsedRealtime();
+                for (int i = map.size() - 1; i >= 0; i--) {
+                    final ArrayMap<String, SimplePackageDurations> val = map.valueAt(i);
+                    for (int j = val.size() - 1; j >= 0; j--) {
+                        final SimplePackageDurations pkg = val.valueAt(j);
+                        if (pkg.isActive()) {
+                            pkg.addEvent(false, now);
+                            notifyListenersOnEvent(pkg.mUid, pkg.mPackageName, false, now,
+                                    EVENT_TYPE_MEDIA_SESSION);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void trimDurations() {
+        final long now = SystemClock.elapsedRealtime();
+        trim(Math.max(0, now - mInjector.getPolicy().getMaxTrackingDuration()));
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.println("APP MEDIA SESSION TRACKER:");
+        super.dump(pw, "  " + prefix);
+    }
+
+    static final class AppMediaSessionPolicy
+            extends BaseAppStateEventsPolicy<AppMediaSessionTracker> {
+        /**
+         * Whether or not we should enable the monitoring on media sessions.
+         */
+        static final String KEY_BG_MEADIA_SESSION_MONITOR_ENABLED =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "media_session_monitor_enabled";
+
+        /**
+         * The maximum duration we'd keep tracking, events earlier than that will be discarded.
+         */
+        static final String KEY_BG_MEDIA_SESSION_MONITOR_MAX_TRACKING_DURATION =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "media_session_monitor_max_tracking_duration";
+
+        /**
+         * Default value to {@link #mTrackerEnabled}.
+         */
+        static final boolean DEFAULT_BG_MEDIA_SESSION_MONITOR_ENABLED = true;
+
+        /**
+         * Default value to {@link #mBgMediaSessionMonitorMaxTrackingDurationMs}.
+         */
+        static final long DEFAULT_BG_MEDIA_SESSION_MONITOR_MAX_TRACKING_DURATION =
+                4 * ONE_DAY;
+
+        AppMediaSessionPolicy(@NonNull Injector injector, @NonNull AppMediaSessionTracker tracker) {
+            super(injector, tracker,
+                    KEY_BG_MEADIA_SESSION_MONITOR_ENABLED,
+                    DEFAULT_BG_MEDIA_SESSION_MONITOR_ENABLED,
+                    KEY_BG_MEDIA_SESSION_MONITOR_MAX_TRACKING_DURATION,
+                    DEFAULT_BG_MEDIA_SESSION_MONITOR_MAX_TRACKING_DURATION);
+        }
+
+        @Override
+        public void onTrackerEnabled(boolean enabled) {
+            mTracker.onBgMediaSessionMonitorEnabled(enabled);
+        }
+
+        @Override
+        public void onMaxTrackingDurationChanged(long maxDuration) {
+            mTracker.mBgHandler.post(mTracker::trimDurations);
+        }
+
+        @Override
+        String getExemptionReasonString(String packageName, int uid, @ReasonCode int reason) {
+            // This tracker is a helper class for other trackers, we don't track exemptions here.
+            return "n/a";
+        }
+
+        @Override
+        void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix);
+            pw.println("APP MEDIA SESSION TRACKER POLICY SETTINGS:");
+            super.dump(pw, "  " + prefix);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
new file mode 100644
index 0000000..bd63a24
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -0,0 +1,2068 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
+import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
+import static android.app.ActivityManager.UID_OBSERVER_GONE;
+import static android.app.ActivityManager.UID_OBSERVER_IDLE;
+import static android.app.ActivityManager.UID_OBSERVER_PROCSTATE;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_UNDEFINED;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_USER_FLAG_INTERACTION;
+import static android.app.usage.UsageStatsManager.REASON_SUB_MASK;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_UPDATE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+import static android.app.usage.UsageStatsManager.reasonToString;
+import static android.content.Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
+import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
+import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.os.PowerExemptionManager.REASON_DEVICE_DEMO_MODE;
+import static android.os.PowerExemptionManager.REASON_DEVICE_OWNER;
+import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
+import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
+import static android.os.PowerExemptionManager.REASON_PROFILE_OWNER;
+import static android.os.PowerExemptionManager.REASON_ROLE_DIALER;
+import static android.os.PowerExemptionManager.REASON_ROLE_EMERGENCY;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppFGSTracker.foregroundServiceTypeToIndex;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RestrictionLevel;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
+import android.app.ActivityThread;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.app.usage.AppStandbyInfo;
+import android.app.usage.UsageStatsManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ServiceInfo;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
+import android.database.ContentObserver;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertiesChangedListener;
+import android.provider.DeviceConfig.Properties;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseArrayMap;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.function.TriConsumer;
+import com.android.server.AppStateTracker;
+import com.android.server.LocalServices;
+import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.usage.AppStandbyInternal;
+import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
+
+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.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.function.Consumer;
+
+/**
+ * This class tracks various state of the apps and mutates their restriction levels accordingly.
+ */
+public final class AppRestrictionController {
+    static final String TAG = TAG_WITH_CLASS_NAME ? "AppRestrictionController" : TAG_AM;
+    static final boolean DEBUG_BG_RESTRICTION_CONTROLLER = false;
+
+    /**
+     * The prefix for the sub-namespace of our device configs under
+     * the {@link android.provider.DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}.
+     */
+    static final String DEVICE_CONFIG_SUBNAMESPACE_PREFIX = "bg_";
+
+    static final int STOCK_PM_FLAGS = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE
+            | MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+
+    /**
+     * Whether or not to show the foreground service manager on tapping notifications.
+     */
+    private static final boolean ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER = false;
+
+    private final Context mContext;
+    private final HandlerThread mBgHandlerThread;
+    private final BgHandler mBgHandler;
+    private final HandlerExecutor mBgExecutor;
+
+    // No lock is needed, as it's immutable after initialization in constructor.
+    private final ArrayList<BaseAppStateTracker> mAppStateTrackers = new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private final RestrictionSettings mRestrictionSettings = new RestrictionSettings();
+
+    private final CopyOnWriteArraySet<AppBackgroundRestrictionListener> mRestrictionListeners =
+            new CopyOnWriteArraySet<>();
+
+    /**
+     * A mapping between the UID/Pkg and its pending work which should be triggered on inactive;
+     * an active UID/pkg pair should have an entry here, although its pending work could be null.
+     */
+    @GuardedBy("mLock")
+    private final SparseArrayMap<String, Runnable> mActiveUids = new SparseArrayMap<>();
+
+    // No lock is needed as it's accessed in bg handler thread only.
+    private final ArrayList<Runnable> mTmpRunnables = new ArrayList<>();
+
+    /**
+     * Power-save allowlisted app-ids (not including except-idle-allowlisted ones).
+     */
+    private int[] mDeviceIdleAllowlist = new int[0]; // No lock is needed.
+
+    /**
+     * Power-save allowlisted app-ids (including except-idle-allowlisted ones).
+     */
+    private int[] mDeviceIdleExceptIdleAllowlist = new int[0]; // No lock is needed.
+
+    private final Object mLock = new Object();
+    private final Injector mInjector;
+    private final NotificationHelper mNotificationHelper;
+
+    private final OnRoleHoldersChangedListener mRoleHolderChangedListener =
+            this::onRoleHoldersChanged;
+
+    /**
+     * The key is the UID, the value is the list of the roles it holds.
+     */
+    @GuardedBy("mLock")
+    private final SparseArray<ArrayList<String>> mUidRolesMapping = new SparseArray<>();
+
+    /**
+     * Cache the package name and information about if it's a system module.
+     */
+    @GuardedBy("mLock")
+    private final HashMap<String, Boolean> mSystemModulesCache = new HashMap<>();
+
+    final ActivityManagerService mActivityManagerService;
+
+    /**
+     * The restriction levels that each package is on, the levels here are defined in
+     * {@link android.app.ActivityManager.RESTRICTION_LEVEL_*}.
+     */
+    final class RestrictionSettings {
+        @GuardedBy("mLock")
+        final SparseArrayMap<String, PkgSettings> mRestrictionLevels = new SparseArrayMap();
+
+        final class PkgSettings {
+            private final String mPackageName;
+            private final int mUid;
+
+            private @RestrictionLevel int mCurrentRestrictionLevel;
+            private @RestrictionLevel int mLastRestrictionLevel;
+            private @ElapsedRealtimeLong long mLevelChangeTimeElapsed;
+            private int mReason;
+
+            private @ElapsedRealtimeLong long[] mLastNotificationShownTimeElapsed;
+            private int[] mNotificationId;
+
+            PkgSettings(String packageName, int uid) {
+                mPackageName = packageName;
+                mUid = uid;
+                mCurrentRestrictionLevel = mLastRestrictionLevel = RESTRICTION_LEVEL_UNKNOWN;
+            }
+
+            @RestrictionLevel int update(@RestrictionLevel int level, int reason, int subReason) {
+                if (level != mCurrentRestrictionLevel) {
+                    mLastRestrictionLevel = mCurrentRestrictionLevel;
+                    mCurrentRestrictionLevel = level;
+                    mLevelChangeTimeElapsed = SystemClock.elapsedRealtime();
+                    mReason = (REASON_MAIN_MASK & reason) | (REASON_SUB_MASK & subReason);
+                    mBgHandler.obtainMessage(BgHandler.MSG_APP_RESTRICTION_LEVEL_CHANGED,
+                            mUid, level, mPackageName).sendToTarget();
+                }
+                return mLastRestrictionLevel;
+            }
+
+            @Override
+            public String toString() {
+                final StringBuilder sb = new StringBuilder(128);
+                sb.append("RestrictionLevel{");
+                sb.append(Integer.toHexString(System.identityHashCode(this)));
+                sb.append(':');
+                sb.append(mPackageName);
+                sb.append('/');
+                sb.append(UserHandle.formatUid(mUid));
+                sb.append('}');
+                sb.append(' ');
+                sb.append(ActivityManager.restrictionLevelToName(mCurrentRestrictionLevel));
+                sb.append('(');
+                sb.append(reasonToString(mReason));
+                sb.append(')');
+                return sb.toString();
+            }
+
+            void dump(PrintWriter pw, @ElapsedRealtimeLong long nowElapsed) {
+                pw.print(toString());
+                if (mLastRestrictionLevel != RESTRICTION_LEVEL_UNKNOWN) {
+                    pw.print('/');
+                    pw.print(ActivityManager.restrictionLevelToName(mLastRestrictionLevel));
+                }
+                pw.print(" levelChange=");
+                TimeUtils.formatDuration(mLevelChangeTimeElapsed - nowElapsed, pw);
+                if (mLastNotificationShownTimeElapsed != null) {
+                    for (int i = 0; i < mLastNotificationShownTimeElapsed.length; i++) {
+                        if (mLastNotificationShownTimeElapsed[i] > 0) {
+                            pw.print(" lastNoti(");
+                            pw.print(mNotificationHelper.notificationTypeToString(i));
+                            pw.print(")=");
+                            TimeUtils.formatDuration(
+                                    mLastNotificationShownTimeElapsed[i] - nowElapsed, pw);
+                        }
+                    }
+                }
+            }
+
+            String getPackageName() {
+                return mPackageName;
+            }
+
+            int getUid() {
+                return mUid;
+            }
+
+            @RestrictionLevel int getCurrentRestrictionLevel() {
+                return mCurrentRestrictionLevel;
+            }
+
+            @RestrictionLevel int getLastRestrictionLevel() {
+                return mLastRestrictionLevel;
+            }
+
+            int getReason() {
+                return mReason;
+            }
+
+            @ElapsedRealtimeLong long getLastNotificationTime(
+                    @NotificationHelper.NotificationType int notificationType) {
+                if (mLastNotificationShownTimeElapsed == null) {
+                    return 0;
+                }
+                return mLastNotificationShownTimeElapsed[notificationType];
+            }
+
+            void setLastNotificationTime(@NotificationHelper.NotificationType int notificationType,
+                    @ElapsedRealtimeLong long timestamp) {
+                if (mLastNotificationShownTimeElapsed == null) {
+                    mLastNotificationShownTimeElapsed =
+                            new long[NotificationHelper.NOTIFICATION_TYPE_LAST];
+                }
+                mLastNotificationShownTimeElapsed[notificationType] = timestamp;
+            }
+
+            int getNotificationId(@NotificationHelper.NotificationType int notificationType) {
+                if (mNotificationId == null) {
+                    return 0;
+                }
+                return mNotificationId[notificationType];
+            }
+
+            void setNotificationId(@NotificationHelper.NotificationType int notificationType,
+                    int notificationId) {
+                if (mNotificationId == null) {
+                    mNotificationId = new int[NotificationHelper.NOTIFICATION_TYPE_LAST];
+                }
+                mNotificationId[notificationType] = notificationId;
+            }
+        }
+
+        /**
+         * Update the restriction level.
+         *
+         * @return The previous restriction level.
+         */
+        @RestrictionLevel int update(String packageName, int uid, @RestrictionLevel int level,
+                int reason, int subReason) {
+            synchronized (mLock) {
+                PkgSettings settings = getRestrictionSettingsLocked(uid, packageName);
+                if (settings == null) {
+                    settings = new PkgSettings(packageName, uid);
+                    mRestrictionLevels.add(uid, packageName, settings);
+                }
+                return settings.update(level, reason, subReason);
+            }
+        }
+
+        /**
+         * @return The reason of why it's in this level.
+         */
+        int getReason(String packageName, int uid) {
+            synchronized (mLock) {
+                final PkgSettings settings = mRestrictionLevels.get(uid, packageName);
+                return settings != null ? settings.getReason()
+                        : (REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_UNDEFINED);
+            }
+        }
+
+        @RestrictionLevel int getRestrictionLevel(int uid) {
+            synchronized (mLock) {
+                final int uidKeyIndex = mRestrictionLevels.indexOfKey(uid);
+                if (uidKeyIndex < 0) {
+                    return RESTRICTION_LEVEL_UNKNOWN;
+                }
+                final int numPackages = mRestrictionLevels.numElementsForKeyAt(uidKeyIndex);
+                if (numPackages == 0) {
+                    return RESTRICTION_LEVEL_UNKNOWN;
+                }
+                @RestrictionLevel int level = RESTRICTION_LEVEL_UNKNOWN;
+                for (int i = 0; i < numPackages; i++) {
+                    final PkgSettings setting = mRestrictionLevels.valueAt(uidKeyIndex, i);
+                    if (setting != null) {
+                        final @RestrictionLevel int l = setting.getCurrentRestrictionLevel();
+                        level = (level == RESTRICTION_LEVEL_UNKNOWN) ? l : Math.min(level, l);
+                    }
+                }
+                return level;
+            }
+        }
+
+        @RestrictionLevel int getRestrictionLevel(int uid, String packageName) {
+            synchronized (mLock) {
+                final PkgSettings settings = getRestrictionSettingsLocked(uid, packageName);
+                return settings == null
+                        ? getRestrictionLevel(uid) : settings.getCurrentRestrictionLevel();
+            }
+        }
+
+        @RestrictionLevel int getRestrictionLevel(String packageName, @UserIdInt int userId) {
+            final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
+            final int uid = pm.getPackageUid(packageName, STOCK_PM_FLAGS, userId);
+            return getRestrictionLevel(uid, packageName);
+        }
+
+        private @RestrictionLevel int getLastRestrictionLevel(int uid, String packageName) {
+            synchronized (mLock) {
+                final PkgSettings settings = mRestrictionLevels.get(uid, packageName);
+                return settings == null
+                        ? RESTRICTION_LEVEL_UNKNOWN : settings.mLastRestrictionLevel;
+            }
+        }
+
+        @GuardedBy("mLock")
+        void forEachPackageInUidLocked(int uid,
+                @NonNull TriConsumer<String, Integer, Integer> consumer) {
+            final int uidKeyIndex = mRestrictionLevels.indexOfKey(uid);
+            if (uidKeyIndex < 0) {
+                return;
+            }
+            final int numPackages = mRestrictionLevels.numElementsForKeyAt(uidKeyIndex);
+            for (int i = 0; i < numPackages; i++) {
+                final PkgSettings settings = mRestrictionLevels.valueAt(uidKeyIndex, i);
+                consumer.accept(mRestrictionLevels.keyAt(uidKeyIndex, i),
+                        settings.getCurrentRestrictionLevel(), settings.getReason());
+            }
+        }
+
+        @GuardedBy("mLock")
+        void forEachUidLocked(@NonNull Consumer<Integer> consumer) {
+            for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
+                consumer.accept(mRestrictionLevels.keyAt(i));
+            }
+        }
+
+        @GuardedBy("mLock")
+        PkgSettings getRestrictionSettingsLocked(int uid, String packageName) {
+            return mRestrictionLevels.get(uid, packageName);
+        }
+
+        void removeUser(@UserIdInt int userId) {
+            synchronized (mLock) {
+                for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
+                    final int uid = mRestrictionLevels.keyAt(i);
+                    if (UserHandle.getUserId(uid) != userId) {
+                        continue;
+                    }
+                    mRestrictionLevels.deleteAt(i);
+                }
+            }
+        }
+
+        void removePackage(String pkgName, int uid) {
+            synchronized (mLock) {
+                mRestrictionLevels.delete(uid, pkgName);
+            }
+        }
+
+        void removeUid(int uid) {
+            synchronized (mLock) {
+                mRestrictionLevels.delete(uid);
+            }
+        }
+
+        @VisibleForTesting
+        void reset() {
+            synchronized (mLock) {
+                mRestrictionLevels.clear();
+            }
+        }
+
+        @GuardedBy("mLock")
+        void dumpLocked(PrintWriter pw, String prefix) {
+            final ArrayList<PkgSettings> settings = new ArrayList<>();
+            mRestrictionLevels.forEach(setting -> settings.add(setting));
+            Collections.sort(settings, Comparator.comparingInt(PkgSettings::getUid));
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            for (int i = 0, size = settings.size(); i < size; i++) {
+                pw.print(prefix);
+                pw.print('#');
+                pw.print(i);
+                pw.print(' ');
+                settings.get(i).dump(pw, nowElapsed);
+                pw.println();
+            }
+        }
+    }
+
+    final class ConstantsObserver extends ContentObserver implements
+            OnPropertiesChangedListener {
+        /**
+         * Whether or not to set the app to restricted standby bucket automatically
+         * when it's background-restricted.
+         */
+        static final String KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "auto_restricted_bucket_on_bg_restricted";
+
+        /**
+         * The minimal interval in ms before posting a notification again on abusive behaviors
+         * of a certain package.
+         */
+        static final String KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "abusive_notification_minimal_interval";
+
+        static final boolean DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION = true;
+        static final long DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS = 24 * 60 * 60 * 1000;
+
+        volatile boolean mBgAutoRestrictedBucket;
+
+        volatile boolean mRestrictedBucketEnabled;
+
+        volatile long mBgNotificationMinIntervalMs;
+
+        ConstantsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onPropertiesChanged(Properties properties) {
+            for (String name : properties.getKeyset()) {
+                if (name == null || !name.startsWith(DEVICE_CONFIG_SUBNAMESPACE_PREFIX)) {
+                    return;
+                }
+                switch (name) {
+                    case KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION:
+                        updateBgAutoRestrictedBucketChanged();
+                        break;
+                    case KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL:
+                        updateBgAbusiveNotificationMinimalInterval();
+                        break;
+                }
+                AppRestrictionController.this.onPropertiesChanged(name);
+            }
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            updateSettings();
+        }
+
+        public void start() {
+            final ContentResolver cr = mContext.getContentResolver();
+            cr.registerContentObserver(Global.getUriFor(Global.ENABLE_RESTRICTED_BUCKET),
+                    false, this);
+            updateSettings();
+            updateDeviceConfig();
+        }
+
+        void updateSettings() {
+            mRestrictedBucketEnabled = isRestrictedBucketEnabled();
+        }
+
+        private boolean isRestrictedBucketEnabled() {
+            return Global.getInt(mContext.getContentResolver(),
+                    Global.ENABLE_RESTRICTED_BUCKET,
+                    Global.DEFAULT_ENABLE_RESTRICTED_BUCKET) == 1;
+        }
+
+        void updateDeviceConfig() {
+            updateBgAutoRestrictedBucketChanged();
+            updateBgAbusiveNotificationMinimalInterval();
+        }
+
+        private void updateBgAutoRestrictedBucketChanged() {
+            boolean oldValue = mBgAutoRestrictedBucket;
+            mBgAutoRestrictedBucket = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION,
+                    DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION);
+            if (oldValue != mBgAutoRestrictedBucket) {
+                dispatchAutoRestrictedBucketFeatureFlagChanged(mBgAutoRestrictedBucket);
+            }
+        }
+
+        private void updateBgAbusiveNotificationMinimalInterval() {
+            mBgNotificationMinIntervalMs = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL,
+                    DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS);
+        }
+    }
+
+    private final ConstantsObserver mConstantsObserver;
+
+    private final AppStateTracker.BackgroundRestrictedAppListener mBackgroundRestrictionListener =
+            new AppStateTracker.BackgroundRestrictedAppListener() {
+                @Override
+                public void updateBackgroundRestrictedForUidPackage(int uid, String packageName,
+                        boolean restricted) {
+                    mBgHandler.obtainMessage(BgHandler.MSG_BACKGROUND_RESTRICTION_CHANGED,
+                            uid, restricted ? 1 : 0, packageName).sendToTarget();
+                }
+            };
+
+    private final AppIdleStateChangeListener mAppIdleStateChangeListener =
+            new AppIdleStateChangeListener() {
+                @Override
+                public void onAppIdleStateChanged(String packageName, @UserIdInt int userId,
+                        boolean idle, int bucket, int reason) {
+                    mBgHandler.obtainMessage(BgHandler.MSG_APP_STANDBY_BUCKET_CHANGED,
+                            userId, bucket, packageName).sendToTarget();
+                }
+
+                @Override
+                public void onUserInteractionStarted(String packageName, @UserIdInt int userId) {
+                    mBgHandler.obtainMessage(BgHandler.MSG_USER_INTERACTION_STARTED,
+                            userId, 0, packageName).sendToTarget();
+                }
+            };
+
+    private final IUidObserver mUidObserver =
+            new IUidObserver.Stub() {
+                @Override
+                public void onUidStateChanged(int uid, int procState, long procStateSeq,
+                        int capability) {
+                    mBgHandler.obtainMessage(BgHandler.MSG_UID_PROC_STATE_CHANGED, uid, procState)
+                            .sendToTarget();
+                }
+
+                @Override
+                public void onUidIdle(int uid, boolean disabled) {
+                    mBgHandler.obtainMessage(BgHandler.MSG_UID_IDLE, uid, disabled ? 1 : 0)
+                            .sendToTarget();
+                }
+
+                @Override
+                public void onUidGone(int uid, boolean disabled) {
+                    mBgHandler.obtainMessage(BgHandler.MSG_UID_GONE, uid, disabled ? 1 : 0)
+                            .sendToTarget();
+                }
+
+                @Override
+                public void onUidActive(int uid) {
+                    mBgHandler.obtainMessage(BgHandler.MSG_UID_ACTIVE, uid, 0).sendToTarget();
+                }
+
+                @Override
+                public void onUidCachedChanged(int uid, boolean cached) {
+                }
+            };
+
+    /**
+     * Register the background restriction listener callback.
+     */
+    public void addAppBackgroundRestrictionListener(
+            @NonNull AppBackgroundRestrictionListener listener) {
+        mRestrictionListeners.add(listener);
+    }
+
+    AppRestrictionController(final Context context, final ActivityManagerService service) {
+        this(new Injector(context), service);
+    }
+
+    AppRestrictionController(final Injector injector, final ActivityManagerService service) {
+        mInjector = injector;
+        mContext = injector.getContext();
+        mActivityManagerService = service;
+        mBgHandlerThread = new HandlerThread("bgres-controller");
+        mBgHandlerThread.start();
+        mBgHandler = new BgHandler(mBgHandlerThread.getLooper(), injector);
+        mBgExecutor = new HandlerExecutor(mBgHandler);
+        mConstantsObserver = new ConstantsObserver(mBgHandler);
+        mNotificationHelper = new NotificationHelper(this);
+        injector.initAppStateTrackers(this);
+    }
+
+    void onSystemReady() {
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                ActivityThread.currentApplication().getMainExecutor(), mConstantsObserver);
+        mConstantsObserver.start();
+        initRestrictionStates();
+        initSystemModuleNames();
+        registerForUidObservers();
+        registerForSystemBroadcasts();
+        mNotificationHelper.onSystemReady();
+        mInjector.getAppStateTracker().addBackgroundRestrictedAppListener(
+                mBackgroundRestrictionListener);
+        mInjector.getAppStandbyInternal().addListener(mAppIdleStateChangeListener);
+        mInjector.getRoleManager().addOnRoleHoldersChangedListenerAsUser(mBgExecutor,
+                mRoleHolderChangedListener, UserHandle.ALL);
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i).onSystemReady();
+        }
+    }
+
+    @VisibleForTesting
+    void resetRestrictionSettings() {
+        mRestrictionSettings.reset();
+        initRestrictionStates();
+    }
+
+    private void initRestrictionStates() {
+        final int[] allUsers = mInjector.getUserManagerInternal().getUserIds();
+        for (int userId : allUsers) {
+            refreshAppRestrictionLevelForUser(userId, REASON_MAIN_FORCED_BY_USER,
+                    REASON_SUB_FORCED_USER_FLAG_INTERACTION);
+        }
+    }
+
+    private void initSystemModuleNames() {
+        final PackageManager pm = mInjector.getPackageManager();
+        final List<ModuleInfo> moduleInfos = pm.getInstalledModules(0 /* flags */);
+        if (moduleInfos == null) {
+            return;
+        }
+        synchronized (mLock) {
+            for (ModuleInfo info : moduleInfos) {
+                mSystemModulesCache.put(info.getPackageName(), Boolean.TRUE);
+            }
+        }
+    }
+
+    private boolean isSystemModule(String packageName) {
+        synchronized (mLock) {
+            final Boolean val = mSystemModulesCache.get(packageName);
+            if (val != null) {
+                return val.booleanValue();
+            }
+        }
+
+        // Slow path: check if the package is listed among the system modules.
+        final PackageManager pm = mInjector.getPackageManager();
+        boolean isSystemModule = false;
+        try {
+            isSystemModule = pm.getModuleInfo(packageName, 0 /* flags */) != null;
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+
+        if (!isSystemModule) {
+            try {
+                final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
+                // Check if the package is contained in an APEX. There is no public API to properly
+                // check whether a given APK package comes from an APEX registered as module.
+                // Therefore we conservatively assume that any package scanned from an /apex path is
+                // a system package.
+                isSystemModule = pkg != null && pkg.applicationInfo.sourceDir.startsWith(
+                        Environment.getApexDirectory().getAbsolutePath());
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+        }
+        // Update the cache.
+        synchronized (mLock) {
+            mSystemModulesCache.put(packageName, isSystemModule);
+        }
+        return isSystemModule;
+    }
+
+    private void registerForUidObservers() {
+        try {
+            mInjector.getIActivityManager().registerUidObserver(mUidObserver,
+                    UID_OBSERVER_ACTIVE | UID_OBSERVER_GONE | UID_OBSERVER_IDLE
+                    | UID_OBSERVER_PROCSTATE, PROCESS_STATE_FOREGROUND_SERVICE, "android");
+        } catch (RemoteException e) {
+            // Intra-process call, it won't happen.
+        }
+    }
+
+    /**
+     * Called when initializing a user.
+     */
+    private void refreshAppRestrictionLevelForUser(@UserIdInt int userId, int reason,
+            int subReason) {
+        final List<AppStandbyInfo> appStandbyInfos = mInjector.getAppStandbyInternal()
+                .getAppStandbyBuckets(userId);
+        if (ArrayUtils.isEmpty(appStandbyInfos)) {
+            return;
+        }
+
+        if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+            Slog.i(TAG, "Refreshing restriction levels of user " + userId);
+        }
+        final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
+        for (AppStandbyInfo info: appStandbyInfos) {
+            final int uid = pm.getPackageUid(info.mPackageName, STOCK_PM_FLAGS, userId);
+            if (uid < 0) {
+                // Shouldn't happen.
+                Slog.e(TAG, "Unable to find " + info.mPackageName + "/u" + userId);
+                continue;
+            }
+            final @RestrictionLevel int level = calcAppRestrictionLevel(
+                    userId, uid, info.mPackageName, info.mStandbyBucket, false, false);
+            applyRestrictionLevel(info.mPackageName, uid, level,
+                    info.mStandbyBucket, true, reason, subReason);
+        }
+    }
+
+    void refreshAppRestrictionLevelForUid(int uid, int reason, int subReason,
+            boolean allowRequestBgRestricted) {
+        final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
+        if (ArrayUtils.isEmpty(packages)) {
+            return;
+        }
+        final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+        final int userId = UserHandle.getUserId(uid);
+        final long now = SystemClock.elapsedRealtime();
+        for (String pkg: packages) {
+            final int curBucket = appStandbyInternal.getAppStandbyBucket(pkg, userId, now, false);
+            final @RestrictionLevel int level = calcAppRestrictionLevel(userId, uid, pkg,
+                    curBucket, allowRequestBgRestricted, true);
+            if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+                Slog.i(TAG, "Proposed restriction level of " + pkg + "/"
+                        + UserHandle.formatUid(uid) + ": "
+                        + ActivityManager.restrictionLevelToName(level));
+            }
+            applyRestrictionLevel(pkg, uid, level, curBucket, true, reason, subReason);
+        }
+    }
+
+    private @RestrictionLevel int calcAppRestrictionLevel(@UserIdInt int userId, int uid,
+            String packageName, @UsageStatsManager.StandbyBuckets int standbyBucket,
+            boolean allowRequestBgRestricted, boolean calcTrackers) {
+        if (mInjector.getAppHibernationInternal().isHibernatingForUser(packageName, userId)) {
+            return RESTRICTION_LEVEL_HIBERNATION;
+        }
+        @RestrictionLevel int level;
+        switch (standbyBucket) {
+            case STANDBY_BUCKET_EXEMPTED:
+                level = RESTRICTION_LEVEL_EXEMPTED;
+                break;
+            case STANDBY_BUCKET_NEVER:
+                level = RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+                break;
+            case STANDBY_BUCKET_ACTIVE:
+            case STANDBY_BUCKET_WORKING_SET:
+            case STANDBY_BUCKET_FREQUENT:
+            case STANDBY_BUCKET_RARE:
+            case STANDBY_BUCKET_RESTRICTED:
+            default:
+                if (mInjector.getAppStateTracker()
+                        .isAppBackgroundRestricted(uid, packageName)) {
+                    return RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+                }
+                level = mConstantsObserver.mRestrictedBucketEnabled
+                        && standbyBucket == STANDBY_BUCKET_RESTRICTED
+                        ? RESTRICTION_LEVEL_RESTRICTED_BUCKET
+                        : RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+                if (calcTrackers) {
+                    @RestrictionLevel int l = calcAppRestrictionLevelFromTackers(uid, packageName);
+                    if (l == RESTRICTION_LEVEL_EXEMPTED) {
+                        return RESTRICTION_LEVEL_EXEMPTED;
+                    }
+                    level = Math.max(l, level);
+                    if (level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+                        // This level can't be entered without user consent
+                        if (allowRequestBgRestricted) {
+                            mBgHandler.obtainMessage(BgHandler.MSG_REQUEST_BG_RESTRICTED,
+                                    uid, 0, packageName).sendToTarget();
+                        }
+                        // Lower the level.
+                        level = RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+                    }
+                }
+                break;
+        }
+        return level;
+    }
+
+    /**
+     * Ask each of the trackers for their proposed restriction levels for the given uid/package,
+     * and return the most restrictive level.
+     *
+     * <p>Note, it's different from the {@link #getRestrictionLevel} where it returns the least
+     * restrictive level. We're returning the most restrictive level here because each tracker
+     * monitors certain dimensions of the app, the abusive behaviors could be detected in one or
+     * more of these dimensions, but not necessarily all of them. </p>
+     */
+    private @RestrictionLevel int calcAppRestrictionLevelFromTackers(int uid, String packageName) {
+        @RestrictionLevel int level = RESTRICTION_LEVEL_UNKNOWN;
+        final boolean isRestrictedBucketEnabled = mConstantsObserver.mRestrictedBucketEnabled;
+        for (int i = mAppStateTrackers.size() - 1; i >= 0; i--) {
+            @RestrictionLevel int l = mAppStateTrackers.get(i).getPolicy()
+                    .getProposedRestrictionLevel(packageName, uid);
+            if (!isRestrictedBucketEnabled && l == RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+                l = RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+            }
+            level = Math.max(level, l);
+        }
+        return level;
+    }
+
+    /**
+     * Get the restriction level of the given UID, if it hosts multiple packages,
+     * return least restricted one (or if any of them is exempted).
+     */
+    @RestrictionLevel int getRestrictionLevel(int uid) {
+        return mRestrictionSettings.getRestrictionLevel(uid);
+    }
+
+    /**
+     * Get the restriction level of the given UID and package.
+     */
+    @RestrictionLevel int getRestrictionLevel(int uid, String packageName) {
+        return mRestrictionSettings.getRestrictionLevel(uid, packageName);
+    }
+
+    /**
+     * Get the restriction level of the given package in given user id.
+     */
+    @RestrictionLevel int getRestrictionLevel(String packageName, @UserIdInt int userId) {
+        return mRestrictionSettings.getRestrictionLevel(packageName, userId);
+    }
+
+    /**
+     * @return The total foreground service durations for the given package/uid with given
+     * foreground service type, or the total durations regardless the type if the given type is 0.
+     */
+    long getForegroundServiceTotalDurations(String packageName, int uid, long now,
+            @ForegroundServiceType int serviceType) {
+        return mInjector.getAppFGSTracker().getTotalDurations(packageName, uid, now,
+                foregroundServiceTypeToIndex(serviceType));
+    }
+
+    /**
+     * @return The total foreground service durations for the given uid with given
+     * foreground service type, or the total durations regardless the type if the given type is 0.
+     */
+    long getForegroundServiceTotalDurations(int uid, long now,
+            @ForegroundServiceType int serviceType) {
+        return mInjector.getAppFGSTracker().getTotalDurations(uid, now,
+                foregroundServiceTypeToIndex(serviceType));
+    }
+
+    /**
+     * @return The foreground service durations since given timestamp for the given package/uid
+     * with given foreground service type, or the total durations regardless the type if the given
+     * type is 0.
+     */
+    long getForegroundServiceTotalDurationsSince(String packageName, int uid, long since, long now,
+            @ForegroundServiceType int serviceType) {
+        return mInjector.getAppFGSTracker().getTotalDurationsSince(packageName, uid, since, now,
+                foregroundServiceTypeToIndex(serviceType));
+    }
+
+    /**
+     * @return The foreground service durations since given timestamp for the given uid with given
+     * foreground service type, or the total durations regardless the type if the given type is 0.
+     */
+    long getForegroundServiceTotalDurationsSince(int uid, long since, long now,
+            @ForegroundServiceType int serviceType) {
+        return mInjector.getAppFGSTracker().getTotalDurationsSince(uid, since, now,
+                foregroundServiceTypeToIndex(serviceType));
+    }
+
+    /**
+     * @return The total durations for the given package/uid with active media session.
+     */
+    long getMediaSessionTotalDurations(String packageName, int uid, long now) {
+        return mInjector.getAppMediaSessionTracker().getTotalDurations(packageName, uid, now);
+    }
+
+    /**
+     * @return The total durations for the given uid with active media session.
+     */
+    long getMediaSessionTotalDurations(int uid, long now) {
+        return mInjector.getAppMediaSessionTracker().getTotalDurations(uid, now);
+    }
+
+    /**
+     * @return The durations since given timestamp for the given package/uid with
+     * active media session.
+     */
+    long getMediaSessionTotalDurationsSince(String packageName, int uid, long since, long now) {
+        return mInjector.getAppMediaSessionTracker().getTotalDurationsSince(packageName, uid, since,
+                now);
+    }
+
+    /**
+     * @return The durations since given timestamp for the given uid with active media session.
+     */
+    long getMediaSessionTotalDurationsSince(int uid, long since, long now) {
+        return mInjector.getAppMediaSessionTracker().getTotalDurationsSince(uid, since, now);
+    }
+
+    /**
+     * @return The durations over the given window, where the given package/uid has either
+     * foreground services with type "mediaPlayback" running, or active media session running.
+     */
+    long getCompositeMediaPlaybackDurations(String packageName, int uid, long now, long window) {
+        final long since = Math.max(0, now - window);
+        final long mediaPlaybackDuration = Math.max(
+                getMediaSessionTotalDurationsSince(packageName, uid, since, now),
+                getForegroundServiceTotalDurationsSince(packageName, uid, since, now,
+                        ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK));
+        return mediaPlaybackDuration;
+    }
+
+    /**
+     * @return The durations over the given window, where the given uid has either foreground
+     * services with type "mediaPlayback" running, or active media session running.
+     */
+    long getCompositeMediaPlaybackDurations(int uid, long now, long window) {
+        final long since = Math.max(0, now - window);
+        final long mediaPlaybackDuration = Math.max(
+                getMediaSessionTotalDurationsSince(uid, since, now),
+                getForegroundServiceTotalDurationsSince(uid, since, now,
+                        ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK));
+        return mediaPlaybackDuration;
+    }
+
+    /**
+     * @return If the given package/uid has an active foreground service running.
+     */
+    boolean hasForegroundServices(String packageName, int uid) {
+        return mInjector.getAppFGSTracker().hasForegroundServices(packageName, uid);
+    }
+
+    /**
+     * @return If the given uid has an active foreground service running.
+     */
+    boolean hasForegroundServices(int uid) {
+        return mInjector.getAppFGSTracker().hasForegroundServices(uid);
+    }
+
+    /**
+     * @return The to-be-exempted battery usage of the given UID in the given duration; it could
+     *         be considered as "exempted" due to various use cases, i.e. media playback.
+     */
+    double getUidBatteryExemptedUsageSince(int uid, long since, long now) {
+        return mInjector.getAppBatteryExemptionTracker()
+                .getUidBatteryExemptedUsageSince(uid, since, now);
+    }
+
+    /**
+     * @return The total battery usage of the given UID since the system boots.
+     */
+    double getUidBatteryUsage(int uid) {
+        return mInjector.getUidBatteryUsageProvider().getUidBatteryUsage(uid);
+    }
+
+    interface UidBatteryUsageProvider {
+        /**
+         * @return The total battery usage of the given UID since the system boots.
+         */
+        double getUidBatteryUsage(int uid);
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.println("BACKGROUND RESTRICTION LEVEL SETTINGS");
+        prefix = "  " + prefix;
+        synchronized (mLock) {
+            mRestrictionSettings.dumpLocked(pw, prefix);
+        }
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            pw.println();
+            mAppStateTrackers.get(i).dump(pw, prefix);
+        }
+    }
+
+    private void applyRestrictionLevel(String pkgName, int uid, @RestrictionLevel int level,
+            int curBucket, boolean allowUpdateBucket, int reason, int subReason) {
+        int curLevel;
+        int prevReason;
+        synchronized (mLock) {
+            curLevel = getRestrictionLevel(uid, pkgName);
+            if (curLevel == level) {
+                // Nothing to do.
+                return;
+            }
+            if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+                Slog.i(TAG, "Updating the restriction level of " + pkgName + "/"
+                        + UserHandle.formatUid(uid) + " from "
+                        + ActivityManager.restrictionLevelToName(curLevel) + " to "
+                        + ActivityManager.restrictionLevelToName(level)
+                        + " reason=" + reason + ", subReason=" + subReason);
+            }
+
+            prevReason = mRestrictionSettings.getReason(pkgName, uid);
+            mRestrictionSettings.update(pkgName, uid, level, reason, subReason);
+        }
+
+        if (!allowUpdateBucket || curBucket == STANDBY_BUCKET_EXEMPTED) {
+            return;
+        }
+        final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+        if (level >= RESTRICTION_LEVEL_RESTRICTED_BUCKET
+                && curLevel < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+            if (!mConstantsObserver.mRestrictedBucketEnabled
+                    || !mConstantsObserver.mBgAutoRestrictedBucket) {
+                return;
+            }
+            // Moving the app standby bucket to restricted in the meanwhile.
+            if (DEBUG_BG_RESTRICTION_CONTROLLER
+                    && level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+                Slog.i(TAG, pkgName + "/" + UserHandle.formatUid(uid)
+                        + " is bg-restricted, moving to restricted standby bucket");
+            }
+            if (curBucket != STANDBY_BUCKET_RESTRICTED) {
+                // restrict the app if it hasn't done so.
+                boolean doIt = true;
+                synchronized (mLock) {
+                    final int index = mActiveUids.indexOfKey(uid, pkgName);
+                    if (index >= 0) {
+                        // It's currently active, enqueue it.
+                        mActiveUids.add(uid, pkgName, () -> appStandbyInternal.restrictApp(
+                                pkgName, UserHandle.getUserId(uid), reason, subReason));
+                        doIt = false;
+                    }
+                }
+                if (doIt) {
+                    appStandbyInternal.restrictApp(pkgName, UserHandle.getUserId(uid),
+                            reason, subReason);
+                }
+            }
+        } else if (curLevel >= RESTRICTION_LEVEL_RESTRICTED_BUCKET
+                && level < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+            // Moved out of the background-restricted state.
+            if (curBucket != STANDBY_BUCKET_RARE) {
+                synchronized (mLock) {
+                    final int index = mActiveUids.indexOfKey(uid, pkgName);
+                    if (index >= 0) {
+                        mActiveUids.add(uid, pkgName, null);
+                    }
+                }
+                appStandbyInternal.maybeUnrestrictApp(pkgName, UserHandle.getUserId(uid),
+                        prevReason & REASON_MAIN_MASK, prevReason & REASON_SUB_MASK,
+                        reason, subReason);
+            }
+        }
+    }
+
+    private void handleBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
+        // Firstly, notify the trackers.
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i)
+                    .onBackgroundRestrictionChanged(uid, pkgName, restricted);
+        }
+
+        final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+        final int userId = UserHandle.getUserId(uid);
+        final long now = SystemClock.elapsedRealtime();
+        final int curBucket = appStandbyInternal.getAppStandbyBucket(pkgName, userId, now, false);
+        if (restricted) {
+            // The app could fall into the background restricted with user consent only,
+            // so set the reason to it.
+            applyRestrictionLevel(pkgName, uid, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
+                    curBucket, true, REASON_MAIN_FORCED_BY_USER,
+                    REASON_SUB_FORCED_USER_FLAG_INTERACTION);
+            mBgHandler.obtainMessage(BgHandler.MSG_CANCEL_REQUEST_BG_RESTRICTED, uid, 0, pkgName)
+                    .sendToTarget();
+        } else {
+            // Moved out of the background-restricted state, we'd need to check if it should
+            // stay in the restricted standby bucket.
+            final @RestrictionLevel int lastLevel =
+                    mRestrictionSettings.getLastRestrictionLevel(uid, pkgName);
+            final int tentativeBucket = curBucket == STANDBY_BUCKET_EXEMPTED
+                    ? STANDBY_BUCKET_EXEMPTED
+                    : (lastLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET
+                            ? STANDBY_BUCKET_RESTRICTED : STANDBY_BUCKET_RARE);
+            final @RestrictionLevel int level = calcAppRestrictionLevel(
+                    UserHandle.getUserId(uid), uid, pkgName, tentativeBucket, false, true);
+
+            applyRestrictionLevel(pkgName, uid, level, curBucket, true,
+                    REASON_MAIN_USAGE, REASON_SUB_USAGE_USER_INTERACTION);
+        }
+    }
+
+    private void dispatchAppRestrictionLevelChanges(int uid, String pkgName,
+            @RestrictionLevel int newLevel) {
+        mRestrictionListeners.forEach(
+                l -> l.onRestrictionLevelChanged(uid, pkgName, newLevel));
+    }
+
+    private void dispatchAutoRestrictedBucketFeatureFlagChanged(boolean newValue) {
+        final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+        final ArrayList<Runnable> pendingTasks = new ArrayList<>();
+        synchronized (mLock) {
+            mRestrictionSettings.forEachUidLocked(uid -> {
+                mRestrictionSettings.forEachPackageInUidLocked(uid, (pkgName, level, reason) -> {
+                    if (level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+                        pendingTasks.add(newValue
+                                ? () -> appStandbyInternal.restrictApp(pkgName,
+                                UserHandle.getUserId(uid), reason & REASON_MAIN_MASK,
+                                reason & REASON_SUB_MASK)
+                                : () -> appStandbyInternal.maybeUnrestrictApp(pkgName,
+                                UserHandle.getUserId(uid), reason & REASON_MAIN_MASK,
+                                reason & REASON_SUB_MASK, REASON_MAIN_USAGE,
+                                REASON_SUB_USAGE_SYSTEM_UPDATE));
+                    }
+                });
+            });
+        }
+        for (int i = 0; i < pendingTasks.size(); i++) {
+            pendingTasks.get(i).run();
+        }
+        mRestrictionListeners.forEach(
+                l -> l.onAutoRestrictedBucketFeatureFlagChanged(newValue));
+    }
+
+    private void handleAppStandbyBucketChanged(int bucket, String packageName,
+            @UserIdInt int userId) {
+        final int uid = mInjector.getPackageManagerInternal().getPackageUid(
+                packageName, STOCK_PM_FLAGS, userId);
+        final @RestrictionLevel int level = calcAppRestrictionLevel(
+                userId, uid, packageName, bucket, false, false);
+        applyRestrictionLevel(packageName, uid, level, bucket, false,
+                REASON_MAIN_DEFAULT, REASON_SUB_DEFAULT_UNDEFINED);
+    }
+
+    void handleRequestBgRestricted(String packageName, int uid) {
+        if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+            Slog.i(TAG, "Requesting background restricted " + packageName + " "
+                    + UserHandle.formatUid(uid));
+        }
+        mNotificationHelper.postRequestBgRestrictedIfNecessary(packageName, uid);
+    }
+
+    void handleCancelRequestBgRestricted(String packageName, int uid) {
+        if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+            Slog.i(TAG, "Cancelling requesting background restricted " + packageName + " "
+                    + UserHandle.formatUid(uid));
+        }
+        mNotificationHelper.cancelRequestBgRestrictedIfNecessary(packageName, uid);
+    }
+
+    void handleUidProcStateChanged(int uid, int procState) {
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i).onUidProcStateChanged(uid, procState);
+        }
+    }
+
+    void handleUidGone(int uid) {
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i).onUidGone(uid);
+        }
+    }
+
+    static class NotificationHelper {
+        static final String PACKAGE_SCHEME = "package";
+        static final String GROUP_KEY = "com.android.app.abusive_bg_apps";
+
+        static final int SUMMARY_NOTIFICATION_ID = SystemMessage.NOTE_ABUSIVE_BG_APPS_BASE;
+
+        static final int NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN = 0;
+        static final int NOTIFICATION_TYPE_LONG_RUNNING_FGS = 1;
+        static final int NOTIFICATION_TYPE_LAST = 2;
+
+        @IntDef(prefix = { "NOTIFICATION_TYPE_"}, value = {
+            NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN,
+            NOTIFICATION_TYPE_LONG_RUNNING_FGS,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        static @interface NotificationType{}
+
+        static final String[] NOTIFICATION_TYPE_STRINGS = {
+            "Abusive current drain",
+            "Long-running FGS",
+        };
+
+        static final String ACTION_FGS_MANAGER_TRAMPOLINE =
+                "com.android.server.am.ACTION_FGS_MANAGER_TRAMPOLINE";
+
+        static String notificationTypeToString(@NotificationType int notificationType) {
+            return NOTIFICATION_TYPE_STRINGS[notificationType];
+        }
+
+        private final AppRestrictionController mBgController;
+        private final NotificationManager mNotificationManager;
+        private final Injector mInjector;
+        private final Object mLock;
+        private final Context mContext;
+
+        private final BroadcastReceiver mActionButtonReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final String action = intent.getAction();
+                switch (intent.getAction()) {
+                    case ACTION_FGS_MANAGER_TRAMPOLINE:
+                        final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                        final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+                        cancelRequestBgRestrictedIfNecessary(packageName, uid);
+                        final Intent newIntent = new Intent(ACTION_SHOW_FOREGROUND_SERVICE_MANAGER);
+                        newIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                        mContext.sendBroadcastAsUser(newIntent,
+                                UserHandle.of(UserHandle.getUserId(uid)));
+                        break;
+                }
+            }
+        };
+
+        @GuardedBy("mLock")
+        private int mNotificationIDStepper = SUMMARY_NOTIFICATION_ID + 1;
+
+        NotificationHelper(AppRestrictionController controller) {
+            mBgController = controller;
+            mInjector = controller.mInjector;
+            mNotificationManager = mInjector.getNotificationManager();
+            mLock = controller.mLock;
+            mContext = mInjector.getContext();
+        }
+
+        void onSystemReady() {
+            mContext.registerReceiverForAllUsers(mActionButtonReceiver,
+                    new IntentFilter(ACTION_FGS_MANAGER_TRAMPOLINE),
+                    MANAGE_ACTIVITY_TASKS, mBgController.mBgHandler);
+        }
+
+        void postRequestBgRestrictedIfNecessary(String packageName, int uid) {
+            final Intent intent = new Intent(Settings.ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL);
+            intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null));
+
+            final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(mContext, 0,
+                    intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, null,
+                    UserHandle.of(UserHandle.getUserId(uid)));
+            Notification.Action[] actions = null;
+            if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER
+                    && mBgController.hasForegroundServices(packageName, uid)) {
+                final Intent trampoline = new Intent(ACTION_FGS_MANAGER_TRAMPOLINE);
+                trampoline.setPackage("android");
+                trampoline.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+                trampoline.putExtra(Intent.EXTRA_UID, uid);
+                final PendingIntent fgsMgrTrampoline = PendingIntent.getBroadcastAsUser(
+                        mContext, 0, trampoline,
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+                        UserHandle.CURRENT);
+                actions = new Notification.Action[] {
+                    new Notification.Action.Builder(null,
+                            mContext.getString(
+                            com.android.internal.R.string.notification_action_check_bg_apps),
+                            fgsMgrTrampoline)
+                            .build()
+                };
+            }
+            postNotificationIfNecessary(NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN,
+                    com.android.internal.R.string.notification_title_abusive_bg_apps,
+                    com.android.internal.R.string.notification_content_abusive_bg_apps,
+                    pendingIntent, packageName, uid, actions);
+        }
+
+        void postLongRunningFgsIfNecessary(String packageName, int uid) {
+            PendingIntent pendingIntent;
+            if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER) {
+                final Intent intent = new Intent(ACTION_SHOW_FOREGROUND_SERVICE_MANAGER);
+                intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0,
+                        intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+                        UserHandle.of(UserHandle.getUserId(uid)));
+            } else {
+                final Intent intent = new Intent(Settings.ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL);
+                intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null));
+                pendingIntent = PendingIntent.getActivityAsUser(mContext, 0,
+                        intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+                        null, UserHandle.of(UserHandle.getUserId(uid)));
+            }
+
+            postNotificationIfNecessary(NOTIFICATION_TYPE_LONG_RUNNING_FGS,
+                    com.android.internal.R.string.notification_title_abusive_bg_apps,
+                    com.android.internal.R.string.notification_content_long_running_fgs,
+                    pendingIntent, packageName, uid, null);
+        }
+
+        int getNotificationIdIfNecessary(@NotificationType int notificationType,
+                String packageName, int uid) {
+            synchronized (mLock) {
+                final RestrictionSettings.PkgSettings settings = mBgController.mRestrictionSettings
+                        .getRestrictionSettingsLocked(uid, packageName);
+                if (settings == null) {
+                    return 0;
+                }
+
+                final long now = SystemClock.elapsedRealtime();
+                final long lastNotificationShownTimeElapsed =
+                        settings.getLastNotificationTime(notificationType);
+                if (lastNotificationShownTimeElapsed != 0 && (lastNotificationShownTimeElapsed
+                        + mBgController.mConstantsObserver.mBgNotificationMinIntervalMs > now)) {
+                    if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+                        Slog.i(TAG, "Not showing notification as last notification was shown "
+                                + TimeUtils.formatDuration(now - lastNotificationShownTimeElapsed)
+                                + " ago");
+                    }
+                    return 0;
+                }
+                settings.setLastNotificationTime(notificationType, now);
+                int notificationId = settings.getNotificationId(notificationType);
+                if (notificationId <= 0) {
+                    notificationId = mNotificationIDStepper++;
+                    settings.setNotificationId(notificationType, notificationId);
+                }
+                if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+                    Slog.i(TAG, "Showing notification for " + packageName
+                            + "/" + UserHandle.formatUid(uid)
+                            + ", id=" + notificationId
+                            + ", now=" + now
+                            + ", lastShown=" + lastNotificationShownTimeElapsed);
+                }
+                return notificationId;
+            }
+        }
+
+        void postNotificationIfNecessary(@NotificationType int notificationType, int titleRes,
+                int messageRes, PendingIntent pendingIntent, String packageName, int uid,
+                @Nullable Notification.Action[] actions) {
+            int notificationId = getNotificationIdIfNecessary(notificationType, packageName, uid);
+            if (notificationId <= 0) {
+                return;
+            }
+
+            final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
+            final PackageManager pm = mInjector.getPackageManager();
+            final ApplicationInfo ai = pmi.getApplicationInfo(packageName, STOCK_PM_FLAGS,
+                    SYSTEM_UID, UserHandle.getUserId(uid));
+            final String title = mContext.getString(titleRes);
+            final String message = mContext.getString(messageRes,
+                    ai != null ? pm.getText(packageName, ai.labelRes, ai) : packageName);
+            final Icon icon = ai != null ? Icon.createWithResource(packageName, ai.icon) : null;
+
+            postNotification(notificationId, packageName, uid, title, message, icon, pendingIntent,
+                    actions);
+        }
+
+        void postNotification(int notificationId, String packageName, int uid, String title,
+                String message, Icon icon, PendingIntent pendingIntent,
+                @Nullable Notification.Action[] actions) {
+            final UserHandle targetUser = UserHandle.of(UserHandle.getUserId(uid));
+            postSummaryNotification(targetUser);
+
+            final Notification.Builder notificationBuilder = new Notification.Builder(mContext,
+                    ABUSIVE_BACKGROUND_APPS)
+                    .setAutoCancel(true)
+                    .setGroup(GROUP_KEY)
+                    .setWhen(System.currentTimeMillis())
+                    .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
+                    .setColor(mContext.getColor(
+                            com.android.internal.R.color.system_notification_accent_color))
+                    .setContentTitle(title)
+                    .setContentText(message)
+                    .setContentIntent(pendingIntent);
+            if (icon != null) {
+                notificationBuilder.setLargeIcon(icon);
+            }
+            if (actions != null) {
+                for (Notification.Action action : actions) {
+                    notificationBuilder.addAction(action);
+                }
+            }
+
+            final Notification notification = notificationBuilder.build();
+            // Remember the package name for testing.
+            notification.extras.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
+
+            mNotificationManager.notifyAsUser(null, notificationId, notification, targetUser);
+        }
+
+        private void postSummaryNotification(@NonNull UserHandle targetUser) {
+            final Notification summary = new Notification.Builder(mContext,
+                    ABUSIVE_BACKGROUND_APPS)
+                    .setGroup(GROUP_KEY)
+                    .setGroupSummary(true)
+                    .setStyle(new Notification.BigTextStyle())
+                    .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
+                    .setColor(mContext.getColor(
+                            com.android.internal.R.color.system_notification_accent_color))
+                    .build();
+            mNotificationManager.notifyAsUser(null, SUMMARY_NOTIFICATION_ID, summary, targetUser);
+        }
+
+        void cancelRequestBgRestrictedIfNecessary(String packageName, int uid) {
+            synchronized (mLock) {
+                final RestrictionSettings.PkgSettings settings = mBgController.mRestrictionSettings
+                        .getRestrictionSettingsLocked(uid, packageName);
+                if (settings != null) {
+                    final int notificationId =
+                            settings.getNotificationId(NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN);
+                    if (notificationId > 0) {
+                        mNotificationManager.cancel(notificationId);
+                    }
+                }
+            }
+        }
+
+        void cancelLongRunningFGSNotificationIfNecessary(String packageName, int uid) {
+            synchronized (mLock) {
+                final RestrictionSettings.PkgSettings settings = mBgController.mRestrictionSettings
+                        .getRestrictionSettingsLocked(uid, packageName);
+                if (settings != null) {
+                    final int notificationId =
+                            settings.getNotificationId(NOTIFICATION_TYPE_LONG_RUNNING_FGS);
+                    if (notificationId > 0) {
+                        mNotificationManager.cancel(notificationId);
+                    }
+                }
+            }
+        }
+    }
+
+    void handleUidInactive(int uid, boolean disabled) {
+        final ArrayList<Runnable> pendingTasks = mTmpRunnables;
+        synchronized (mLock) {
+            final int index = mActiveUids.indexOfKey(uid);
+            if (index < 0) {
+                return;
+            }
+            final int numPackages = mActiveUids.numElementsForKeyAt(index);
+            for (int i = 0; i < numPackages; i++) {
+                final Runnable pendingTask = mActiveUids.valueAt(index, i);
+                if (pendingTask != null) {
+                    pendingTasks.add(pendingTask);
+                }
+            }
+            mActiveUids.deleteAt(index);
+        }
+        for (int i = 0, size = pendingTasks.size(); i < size; i++) {
+            pendingTasks.get(i).run();
+        }
+        pendingTasks.clear();
+    }
+
+    void handleUidActive(int uid) {
+        synchronized (mLock) {
+            final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+            final int userId = UserHandle.getUserId(uid);
+            mRestrictionSettings.forEachPackageInUidLocked(uid, (pkgName, level, reason) -> {
+                if (level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+                    mActiveUids.add(uid, pkgName, () -> appStandbyInternal.restrictApp(pkgName,
+                            userId, reason & REASON_MAIN_MASK, reason & REASON_SUB_MASK));
+                } else {
+                    mActiveUids.add(uid, pkgName, null);
+                }
+            });
+        }
+    }
+
+    boolean isOnDeviceIdleAllowlist(int uid, boolean allowExceptIdle) {
+        final int appId = UserHandle.getAppId(uid);
+
+        final int[] allowlist = allowExceptIdle
+                ? mDeviceIdleExceptIdleAllowlist
+                : mDeviceIdleAllowlist;
+
+        return Arrays.binarySearch(allowlist, appId) >= 0;
+    }
+
+    void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids) {
+        mDeviceIdleAllowlist = allAppids;
+        mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
+    }
+
+    /**
+     * @return The reason code of whether or not the given UID should be exempted from background
+     * restrictions here.
+     *
+     * <p>
+     * Note: Call it with caution as it'll try to acquire locks in other services.
+     * </p>
+     */
+    @ReasonCode
+    int getBackgroundRestrictionExemptionReason(int uid) {
+        if (UserHandle.isCore(uid)) {
+            return REASON_SYSTEM_UID;
+        }
+        if (isOnDeviceIdleAllowlist(uid, false)) {
+            return REASON_ALLOWLISTED_PACKAGE;
+        }
+        final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
+        if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
+            return REASON_COMPANION_DEVICE_MANAGER;
+        }
+        if (UserManager.isDeviceInDemoMode(mContext)) {
+            return REASON_DEVICE_DEMO_MODE;
+        }
+        if (am.isDeviceOwner(uid)) {
+            return REASON_DEVICE_OWNER;
+        }
+        if (am.isProfileOwner(uid)) {
+            return REASON_PROFILE_OWNER;
+        }
+        final int uidProcState = am.getUidProcessState(uid);
+        if (uidProcState <= PROCESS_STATE_PERSISTENT) {
+            return REASON_PROC_STATE_PERSISTENT;
+        } else if (uidProcState <= PROCESS_STATE_PERSISTENT_UI) {
+            return REASON_PROC_STATE_PERSISTENT_UI;
+        }
+        final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
+        if (packages != null) {
+            final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
+            for (String pkg : packages) {
+                if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
+                        uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+                    return REASON_OP_ACTIVATE_VPN;
+                } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
+                        uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+                    return REASON_OP_ACTIVATE_PLATFORM_VPN;
+                } else if (isSystemModule(pkg)) {
+                    return REASON_SYSTEM_MODULE;
+                }
+            }
+        }
+        if (isRoleHeldByUid(RoleManager.ROLE_DIALER, uid)) {
+            return REASON_ROLE_DIALER;
+        }
+        if (isRoleHeldByUid(RoleManager.ROLE_EMERGENCY, uid)) {
+            return REASON_ROLE_EMERGENCY;
+        }
+        return REASON_DENIED;
+    }
+
+    private boolean isRoleHeldByUid(@NonNull String roleName, int uid) {
+        synchronized (mLock) {
+            final ArrayList<String> roles = mUidRolesMapping.get(uid);
+            return roles != null && roles.indexOf(roleName) >= 0;
+        }
+    }
+
+    private void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+        final List<String> rolePkgs = mInjector.getRoleManager().getRoleHoldersAsUser(
+                roleName, user);
+        final ArraySet<Integer> roleUids = new ArraySet<>();
+        final int userId = user.getIdentifier();
+        if (rolePkgs != null) {
+            final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
+            for (String pkg: rolePkgs) {
+                roleUids.add(pm.getPackageUid(pkg, STOCK_PM_FLAGS, userId));
+            }
+        }
+        synchronized (mLock) {
+            for (int i = mUidRolesMapping.size() - 1; i >= 0; i--) {
+                final int uid = mUidRolesMapping.keyAt(i);
+                if (UserHandle.getUserId(uid) != userId) {
+                    continue;
+                }
+                final ArrayList<String> roles = mUidRolesMapping.valueAt(i);
+                final int index = roles.indexOf(roleName);
+                final boolean isRole = roleUids.contains(uid);
+                if (index >= 0) {
+                    if (!isRole) { // Not holding this role anymore, remove it.
+                        roles.remove(index);
+                        if (roles.isEmpty()) {
+                            mUidRolesMapping.removeAt(i);
+                        }
+                    }
+                } else if (isRole) { // Got this new role, add it.
+                    roles.add(roleName);
+                    roleUids.remove(uid);
+                }
+            }
+            for (int i = roleUids.size() - 1; i >= 0; i--) { // Take care of the leftovers.
+                final ArrayList<String> roles = new ArrayList<>();
+                roles.add(roleName);
+                mUidRolesMapping.put(roleUids.valueAt(i), roles);
+            }
+        }
+    }
+
+    /**
+     * @return The background handler of this controller.
+     */
+    Handler getBackgroundHandler() {
+        return mBgHandler;
+    }
+
+    /**
+     * @return The background handler thread of this controller.
+     */
+    @VisibleForTesting
+    HandlerThread getBackgroundHandlerThread() {
+        return mBgHandlerThread;
+    }
+
+    /**
+     * @return The global lock of this controller.
+     */
+    Object getLock() {
+        return mLock;
+    }
+
+    @VisibleForTesting
+    void addAppStateTracker(@NonNull BaseAppStateTracker tracker) {
+        mAppStateTrackers.add(tracker);
+    }
+
+    /**
+     * @return The tracker instance of the given class.
+     */
+    <T extends BaseAppStateTracker> T getAppStateTracker(Class<T> trackerClass) {
+        for (BaseAppStateTracker tracker : mAppStateTrackers) {
+            if (trackerClass.isAssignableFrom(tracker.getClass())) {
+                return (T) tracker;
+            }
+        }
+        return null;
+    }
+
+    void postLongRunningFgsIfNecessary(String packageName, int uid) {
+        mNotificationHelper.postLongRunningFgsIfNecessary(packageName, uid);
+    }
+
+    void cancelLongRunningFGSNotificationIfNecessary(String packageName, int uid) {
+        mNotificationHelper.cancelLongRunningFGSNotificationIfNecessary(packageName, uid);
+    }
+
+    String getPackageName(int pid) {
+        return mInjector.getPackageName(pid);
+    }
+
+    static class BgHandler extends Handler {
+        static final int MSG_BACKGROUND_RESTRICTION_CHANGED = 0;
+        static final int MSG_APP_RESTRICTION_LEVEL_CHANGED = 1;
+        static final int MSG_APP_STANDBY_BUCKET_CHANGED = 2;
+        static final int MSG_USER_INTERACTION_STARTED = 3;
+        static final int MSG_REQUEST_BG_RESTRICTED = 4;
+        static final int MSG_UID_IDLE = 5;
+        static final int MSG_UID_ACTIVE = 6;
+        static final int MSG_UID_GONE = 7;
+        static final int MSG_UID_PROC_STATE_CHANGED = 8;
+        static final int MSG_CANCEL_REQUEST_BG_RESTRICTED = 9;
+
+        private final Injector mInjector;
+
+        BgHandler(Looper looper, Injector injector) {
+            super(looper);
+            mInjector = injector;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final AppRestrictionController c = mInjector
+                    .getAppRestrictionController();
+            switch (msg.what) {
+                case MSG_BACKGROUND_RESTRICTION_CHANGED: {
+                    c.handleBackgroundRestrictionChanged(msg.arg1, (String) msg.obj, msg.arg2 == 1);
+                } break;
+                case MSG_APP_RESTRICTION_LEVEL_CHANGED: {
+                    c.dispatchAppRestrictionLevelChanges(msg.arg1, (String) msg.obj, msg.arg2);
+                } break;
+                case MSG_APP_STANDBY_BUCKET_CHANGED: {
+                    c.handleAppStandbyBucketChanged(msg.arg2, (String) msg.obj, msg.arg1);
+                } break;
+                case MSG_USER_INTERACTION_STARTED: {
+                    c.onUserInteractionStarted((String) msg.obj, msg.arg1);
+                } break;
+                case MSG_REQUEST_BG_RESTRICTED: {
+                    c.handleRequestBgRestricted((String) msg.obj, msg.arg1);
+                } break;
+                case MSG_UID_IDLE: {
+                    c.handleUidInactive(msg.arg1, msg.arg2 == 1);
+                } break;
+                case MSG_UID_ACTIVE: {
+                    c.handleUidActive(msg.arg1);
+                } break;
+                case MSG_CANCEL_REQUEST_BG_RESTRICTED: {
+                    c.handleCancelRequestBgRestricted((String) msg.obj, msg.arg1);
+                } break;
+                case MSG_UID_PROC_STATE_CHANGED: {
+                    c.handleUidProcStateChanged(msg.arg1, msg.arg2);
+                } break;
+                case MSG_UID_GONE: {
+                    // It also means this UID is inactive now.
+                    c.handleUidInactive(msg.arg1, msg.arg2 == 1);
+                    c.handleUidGone(msg.arg1);
+                } break;
+            }
+        }
+    }
+
+    static class Injector {
+        private final Context mContext;
+        private ActivityManagerInternal mActivityManagerInternal;
+        private AppRestrictionController mAppRestrictionController;
+        private AppOpsManager mAppOpsManager;
+        private AppStandbyInternal mAppStandbyInternal;
+        private AppStateTracker mAppStateTracker;
+        private AppHibernationManagerInternal mAppHibernationInternal;
+        private IActivityManager mIActivityManager;
+        private UserManagerInternal mUserManagerInternal;
+        private PackageManagerInternal mPackageManagerInternal;
+        private NotificationManager mNotificationManager;
+        private RoleManager mRoleManager;
+        private AppBatteryTracker mAppBatteryTracker;
+        private AppBatteryExemptionTracker mAppBatteryExemptionTracker;
+        private AppFGSTracker mAppFGSTracker;
+        private AppMediaSessionTracker mAppMediaSessionTracker;
+
+        Injector(Context context) {
+            mContext = context;
+        }
+
+        Context getContext() {
+            return mContext;
+        }
+
+        void initAppStateTrackers(AppRestrictionController controller) {
+            mAppRestrictionController = controller;
+            mAppBatteryTracker = new AppBatteryTracker(mContext, controller);
+            mAppBatteryExemptionTracker = new AppBatteryExemptionTracker(mContext, controller);
+            mAppFGSTracker = new AppFGSTracker(mContext, controller);
+            mAppMediaSessionTracker = new AppMediaSessionTracker(mContext, controller);
+            controller.mAppStateTrackers.add(mAppBatteryTracker);
+            controller.mAppStateTrackers.add(mAppBatteryExemptionTracker);
+            controller.mAppStateTrackers.add(mAppFGSTracker);
+            controller.mAppStateTrackers.add(mAppMediaSessionTracker);
+            controller.mAppStateTrackers.add(new AppBroadcastEventsTracker(mContext, controller));
+            controller.mAppStateTrackers.add(new AppBindServiceEventsTracker(mContext, controller));
+        }
+
+        ActivityManagerInternal getActivityManagerInternal() {
+            if (mActivityManagerInternal == null) {
+                mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+            }
+            return mActivityManagerInternal;
+        }
+
+        AppRestrictionController getAppRestrictionController() {
+            return mAppRestrictionController;
+        }
+
+        AppOpsManager getAppOpsManager() {
+            if (mAppOpsManager == null) {
+                mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
+            }
+            return mAppOpsManager;
+        }
+
+        AppStandbyInternal getAppStandbyInternal() {
+            if (mAppStandbyInternal == null) {
+                mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
+            }
+            return mAppStandbyInternal;
+        }
+
+        AppHibernationManagerInternal getAppHibernationInternal() {
+            if (mAppHibernationInternal == null) {
+                mAppHibernationInternal = LocalServices.getService(
+                        AppHibernationManagerInternal.class);
+            }
+            return mAppHibernationInternal;
+        }
+
+        AppStateTracker getAppStateTracker() {
+            if (mAppStateTracker == null) {
+                mAppStateTracker = LocalServices.getService(AppStateTracker.class);
+            }
+            return mAppStateTracker;
+        }
+
+        IActivityManager getIActivityManager() {
+            return ActivityManager.getService();
+        }
+
+        UserManagerInternal getUserManagerInternal() {
+            if (mUserManagerInternal == null) {
+                mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+            }
+            return mUserManagerInternal;
+        }
+
+        PackageManagerInternal getPackageManagerInternal() {
+            if (mPackageManagerInternal == null) {
+                mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+            }
+            return mPackageManagerInternal;
+        }
+
+        PackageManager getPackageManager() {
+            return getContext().getPackageManager();
+        }
+
+        NotificationManager getNotificationManager() {
+            if (mNotificationManager == null) {
+                mNotificationManager = getContext().getSystemService(NotificationManager.class);
+            }
+            return mNotificationManager;
+        }
+
+        RoleManager getRoleManager() {
+            if (mRoleManager == null) {
+                mRoleManager = getContext().getSystemService(RoleManager.class);
+            }
+            return mRoleManager;
+        }
+
+        AppFGSTracker getAppFGSTracker() {
+            return mAppFGSTracker;
+        }
+
+        AppMediaSessionTracker getAppMediaSessionTracker() {
+            return mAppMediaSessionTracker;
+        }
+
+        ActivityManagerService getActivityManagerService() {
+            return mAppRestrictionController.mActivityManagerService;
+        }
+
+        UidBatteryUsageProvider getUidBatteryUsageProvider() {
+            return mAppBatteryTracker;
+        }
+
+        AppBatteryExemptionTracker getAppBatteryExemptionTracker() {
+            return mAppBatteryExemptionTracker;
+        }
+
+        String getPackageName(int pid) {
+            final ActivityManagerService am = getActivityManagerService();
+            final ProcessRecord app;
+            synchronized (am.mPidsSelfLocked) {
+                app = am.mPidsSelfLocked.get(pid);
+                if (app != null) {
+                    final ApplicationInfo ai = app.info;
+                    if (ai != null) {
+                        return ai.packageName;
+                    }
+                }
+            }
+            return null;
+        }
+    }
+
+    private void registerForSystemBroadcasts() {
+        final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final String action = intent.getAction();
+                switch (intent.getAction()) {
+                    case Intent.ACTION_PACKAGE_ADDED: {
+                        if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                            final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                            if (uid >= 0) {
+                                onUidAdded(uid);
+                            }
+                        }
+                    } break;
+                    case Intent.ACTION_PACKAGE_FULLY_REMOVED: {
+                        final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                        final Uri data = intent.getData();
+                        String ssp;
+                        if (uid >= 0 && data != null
+                                && (ssp = data.getSchemeSpecificPart()) != null) {
+                            onPackageRemoved(ssp, uid);
+                        }
+                    } break;
+                    case Intent.ACTION_UID_REMOVED: {
+                        if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                            final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                            if (uid >= 0) {
+                                onUidRemoved(uid);
+                            }
+                        }
+                    } break;
+                    case Intent.ACTION_USER_ADDED: {
+                        final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                        if (userId >= 0) {
+                            onUserAdded(userId);
+                        }
+                    } break;
+                    case Intent.ACTION_USER_STARTED: {
+                        final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                        if (userId >= 0) {
+                            onUserStarted(userId);
+                        }
+                    } break;
+                    case Intent.ACTION_USER_STOPPED: {
+                        final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                        if (userId >= 0) {
+                            onUserStopped(userId);
+                        }
+                    } break;
+                    case Intent.ACTION_USER_REMOVED: {
+                        final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                        if (userId >= 0) {
+                            onUserRemoved(userId);
+                        }
+                    } break;
+                }
+            }
+        };
+        final IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+        packageFilter.addDataScheme("package");
+        mContext.registerReceiverForAllUsers(broadcastReceiver, packageFilter, null, mBgHandler);
+        final IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_ADDED);
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        userFilter.addAction(Intent.ACTION_UID_REMOVED);
+        mContext.registerReceiverForAllUsers(broadcastReceiver, userFilter, null, mBgHandler);
+    }
+
+    void forEachTracker(Consumer<BaseAppStateTracker> sink) {
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            sink.accept(mAppStateTrackers.get(i));
+        }
+    }
+
+    private void onUserAdded(@UserIdInt int userId) {
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i).onUserAdded(userId);
+        }
+    }
+
+    private void onUserStarted(@UserIdInt int userId) {
+        refreshAppRestrictionLevelForUser(userId, REASON_MAIN_FORCED_BY_USER,
+                REASON_SUB_FORCED_USER_FLAG_INTERACTION);
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i).onUserStarted(userId);
+        }
+    }
+
+    private void onUserStopped(@UserIdInt int userId) {
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i).onUserStopped(userId);
+        }
+    }
+
+    private void onUserRemoved(@UserIdInt int userId) {
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i).onUserRemoved(userId);
+        }
+        mRestrictionSettings.removeUser(userId);
+    }
+
+    private void onUidAdded(int uid) {
+        refreshAppRestrictionLevelForUid(uid, REASON_MAIN_FORCED_BY_SYSTEM,
+                REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED, false);
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i).onUidAdded(uid);
+        }
+    }
+
+    private void onPackageRemoved(String pkgName, int uid) {
+        mRestrictionSettings.removePackage(pkgName, uid);
+    }
+
+    private void onUidRemoved(int uid) {
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i).onUidRemoved(uid);
+        }
+        mRestrictionSettings.removeUid(uid);
+    }
+
+    boolean isBgAutoRestrictedBucketFeatureFlagEnabled() {
+        return mConstantsObserver.mBgAutoRestrictedBucket;
+    }
+
+    private void onPropertiesChanged(String name) {
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i).onPropertiesChanged(name);
+        }
+    }
+
+    private void onUserInteractionStarted(String packageName, @UserIdInt int userId) {
+        final int uid = mInjector.getPackageManagerInternal()
+                .getPackageUid(packageName, STOCK_PM_FLAGS, userId);
+        for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+            mAppStateTrackers.get(i).onUserInteractionStarted(packageName, uid);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateDurations.java b/services/core/java/com/android/server/am/BaseAppStateDurations.java
new file mode 100644
index 0000000..4d3b4dd
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateDurations.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import android.annotation.NonNull;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * A helper class to track the accumulated durations of certain events; supports tracking event
+ * start/stop, trim.
+ */
+abstract class BaseAppStateDurations<T extends BaseTimeEvent> extends BaseAppStateTimeEvents<T> {
+    static final boolean DEBUG_BASE_APP_STATE_DURATIONS = false;
+
+    BaseAppStateDurations(int uid, @NonNull String packageName, int numOfEventTypes,
+            @NonNull String tag, @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+        super(uid, packageName, numOfEventTypes, tag, maxTrackingDurationConfig);
+    }
+
+    BaseAppStateDurations(@NonNull BaseAppStateDurations other) {
+        super(other);
+    }
+
+    /**
+     * Add a start/stop event.
+     */
+    void addEvent(boolean start, @NonNull T event, int index) {
+        if (mEvents[index] == null) {
+            mEvents[index] = new LinkedList<>();
+        }
+        final LinkedList<T> events = mEvents[index];
+        final int size = events.size();
+        final boolean active = isActive(index);
+
+        if (DEBUG_BASE_APP_STATE_DURATIONS && !start && !active) {
+            Slog.wtf(mTag, "Under-counted start event");
+            return;
+        }
+        if (start != active) {
+            // Only record the event time if it's not the same state as now
+            events.add(event);
+        }
+        trimEvents(getEarliest(event.getTimestamp()), index);
+    }
+
+    @Override
+    void trimEvents(long earliest, int index) {
+        trimEvents(earliest, mEvents[index]);
+    }
+
+    void trimEvents(long earliest, LinkedList<T> events) {
+        if (events == null) {
+            return;
+        }
+        while (events.size() > 1) {
+            final T current = events.peek();
+            if (current.getTimestamp() >= earliest) {
+                return; // All we have are newer than the given timestamp.
+            }
+            // Check the timestamp of stop event.
+            if (events.get(1).getTimestamp() > earliest) {
+                // Trim the duration by moving the start time.
+                events.get(0).trimTo(earliest);
+                return;
+            }
+            // Discard the 1st duration as it's older than the given timestamp.
+            events.pop();
+            events.pop();
+        }
+        if (events.size() == 1) {
+            // Trim the duration by moving the start time.
+            events.get(0).trimTo(Math.max(earliest, events.peek().getTimestamp()));
+        }
+    }
+
+    /**
+     * Merge the two given duration table and return the result.
+     */
+    @Override
+    LinkedList<T> add(LinkedList<T> durations, LinkedList<T> otherDurations) {
+        if (otherDurations == null || otherDurations.size() == 0) {
+            return durations;
+        }
+        if (durations == null || durations.size() == 0) {
+            return (LinkedList<T>) otherDurations.clone();
+        }
+        final Iterator<T> itl = durations.iterator();
+        final Iterator<T> itr = otherDurations.iterator();
+        T l = itl.next(), r = itr.next();
+        LinkedList<T> dest = new LinkedList<>();
+        boolean actl = false, actr = false;
+        for (long lts = l.getTimestamp(), rts = r.getTimestamp();
+                lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) {
+            final boolean actCur = actl || actr;
+            final T earliest;
+            if (lts == rts) {
+                earliest = l;
+                actl = !actl;
+                actr = !actr;
+                lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+                rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+            } else if (lts < rts) {
+                earliest = l;
+                actl = !actl;
+                lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+            } else {
+                earliest = r;
+                actr = !actr;
+                rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+            }
+            if (actCur != (actl || actr)) {
+                dest.add((T) earliest.clone());
+            }
+        }
+        return dest;
+    }
+
+    /**
+     * Subtract the other durations from the this duration table at given index
+     */
+    void subtract(BaseAppStateDurations otherDurations, int thisIndex, int otherIndex) {
+        if (mEvents.length <= thisIndex || mEvents[thisIndex] == null
+                || otherDurations.mEvents.length <= otherIndex
+                || otherDurations.mEvents[otherIndex] == null) {
+            if (DEBUG_BASE_APP_STATE_DURATIONS) {
+                Slog.wtf(mTag, "Incompatible event table this=" + this + ", other=" + otherDurations
+                        + ", thisIndex=" + thisIndex + ", otherIndex=" + otherIndex);
+            }
+            return;
+        }
+        mEvents[thisIndex] = subtract(mEvents[thisIndex], otherDurations.mEvents[otherIndex]);
+    }
+
+    /**
+     * Subtract the other durations at given index from the this duration table at all indexes.
+     */
+    void subtract(BaseAppStateDurations otherDurations, int otherIndex) {
+        if (otherDurations.mEvents.length <= otherIndex
+                || otherDurations.mEvents[otherIndex] == null) {
+            if (DEBUG_BASE_APP_STATE_DURATIONS) {
+                Slog.wtf(mTag, "Incompatible event table this=" + this + ", other=" + otherDurations
+                        + ", otherIndex=" + otherIndex);
+            }
+            return;
+        }
+        for (int i = 0; i < mEvents.length; i++) {
+            if (mEvents[i] != null) {
+                mEvents[i] = subtract(mEvents[i], otherDurations.mEvents[otherIndex]);
+            }
+        }
+    }
+
+    /**
+     * Subtract the other durations from the given duration table and return the new one.
+     */
+    LinkedList<T> subtract(LinkedList<T> durations, LinkedList<T> otherDurations) {
+        if (otherDurations == null || otherDurations.size() == 0
+                || durations == null || durations.size() == 0) {
+            return durations;
+        }
+        final Iterator<T> itl = durations.iterator();
+        final Iterator<T> itr = otherDurations.iterator();
+        T l = itl.next(), r = itr.next();
+        LinkedList<T> dest = new LinkedList<>();
+        boolean actl = false, actr = false;
+        for (long lts = l.getTimestamp(), rts = r.getTimestamp();
+                lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) {
+            final boolean actCur = actl && !actr;
+            final T earliest;
+            if (lts == rts) {
+                earliest = l;
+                actl = !actl;
+                actr = !actr;
+                lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+                rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+            } else if (lts < rts) {
+                earliest = l;
+                actl = !actl;
+                lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+            } else {
+                earliest = r;
+                actr = !actr;
+                rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+            }
+            if (actCur != (actl && !actr)) {
+                dest.add((T) earliest.clone());
+            }
+        }
+        return dest;
+    }
+
+    long getTotalDurations(long now, int index) {
+        return getTotalDurationsSince(getEarliest(0), now, index);
+    }
+
+    long getTotalDurationsSince(long since, long now, int index) {
+        final LinkedList<T> events = mEvents[index];
+        if (events == null || events.size() == 0) {
+            return 0L;
+        }
+        boolean active = true;
+        long last = 0;
+        long duration = 0;
+        for (T event : events) {
+            if (event.getTimestamp() < since || active) {
+                last = event.getTimestamp();
+            } else {
+                duration += Math.max(0, event.getTimestamp() - Math.max(last, since));
+            }
+            active = !active;
+        }
+        if ((events.size() & 1) == 1) {
+            duration += Math.max(0, now - Math.max(last, since));
+        }
+        return duration;
+    }
+
+    boolean isActive(int index) {
+        return mEvents[index] != null && (mEvents[index].size() & 1) == 1;
+    }
+
+    @Override
+    String formatEventSummary(long now, int index) {
+        return TimeUtils.formatDuration(getTotalDurations(now, index));
+    }
+
+    @Override
+    public String toString() {
+        return mPackageName + "/" + UserHandle.formatUid(mUid)
+                + " isActive[0]=" + isActive(0)
+                + " totalDurations[0]=" + getTotalDurations(SystemClock.elapsedRealtime(), 0);
+    }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java b/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java
new file mode 100644
index 0000000..cc89e84
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.BaseAppStateEvents.MaxTrackingDurationConfig;
+import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * Base class to track certain binary state event of apps.
+ */
+abstract class BaseAppStateDurationsTracker
+        <T extends BaseAppStateEventsPolicy, U extends BaseAppStateDurations>
+        extends BaseAppStateEventsTracker<T, U> {
+    static final boolean DEBUG_BASE_APP_STATE_DURATION_TRACKER = false;
+
+    static final int EVENT_TYPE_MEDIA_SESSION = 0;
+    static final int EVENT_TYPE_FGS_MEDIA_PLAYBACK = 1;
+    static final int EVENT_TYPE_FGS_LOCATION = 2;
+    static final int EVENT_NUM = 3;
+
+    final ArrayList<EventListener> mEventListeners = new ArrayList<>();
+
+    @GuardedBy("mLock")
+    final SparseArray<UidStateDurations> mUidStateDurations = new SparseArray<>();
+
+    interface EventListener {
+        void onNewEvent(int uid, String packageName, boolean start, long now, int eventType);
+    }
+
+    BaseAppStateDurationsTracker(Context context, AppRestrictionController controller,
+            Constructor<? extends Injector<T>> injector, Object outerContext) {
+        super(context, controller, injector, outerContext);
+    }
+
+    @Override
+    void onUidProcStateChanged(final int uid, final int procState) {
+        synchronized (mLock) {
+            if (mPkgEvents.getMap().indexOfKey(uid) < 0) {
+                // If we're not tracking its events, ignore its UID state changes.
+                return;
+            }
+            onUidProcStateChangedUncheckedLocked(uid, procState);
+            UidStateDurations uidStateDurations = mUidStateDurations.get(uid);
+            if (uidStateDurations == null) {
+                uidStateDurations = new UidStateDurations(uid, mInjector.getPolicy());
+                mUidStateDurations.put(uid, uidStateDurations);
+            }
+            uidStateDurations.addEvent(procState < PROCESS_STATE_FOREGROUND_SERVICE,
+                    SystemClock.elapsedRealtime());
+        }
+    }
+
+    @Override
+    void onUidGone(final int uid) {
+        onUidProcStateChanged(uid, PROCESS_STATE_NONEXISTENT);
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    void trimLocked(long earliest) {
+        super.trimLocked(earliest);
+        for (int i = mUidStateDurations.size() - 1; i >= 0; i--) {
+            final UidStateDurations u = mUidStateDurations.valueAt(i);
+            u.trim(earliest);
+            if (u.isEmpty()) {
+                mUidStateDurations.removeAt(i);
+            }
+        }
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    void onUntrackingUidLocked(int uid) {
+        mUidStateDurations.remove(uid);
+    }
+
+    void registerEventListener(@NonNull EventListener listener) {
+        synchronized (mLock) {
+            mEventListeners.add(listener);
+        }
+    }
+
+    void notifyListenersOnEvent(int uid, String packageName,
+            boolean start, long now, int eventType) {
+        synchronized (mLock) {
+            for (int i = 0, size = mEventListeners.size(); i < size; i++) {
+                mEventListeners.get(i).onNewEvent(uid, packageName, start, now, eventType);
+            }
+        }
+    }
+
+    long getTotalDurations(String packageName, int uid, long now, int index, boolean bgOnly) {
+        synchronized (mLock) {
+            final U durations = mPkgEvents.get(uid, packageName);
+            if (durations == null) {
+                return 0;
+            }
+            if (bgOnly) {
+                final UidStateDurations uidDurations = mUidStateDurations.get(uid);
+                if (uidDurations != null && !uidDurations.isEmpty()) {
+                    final U res = createAppStateEvents(durations);
+                    res.subtract(uidDurations, index, UidStateDurations.DEFAULT_INDEX);
+                    return res.getTotalDurations(now, index);
+                }
+            }
+            return durations.getTotalDurations(now, index);
+        }
+    }
+
+    long getTotalDurations(String packageName, int uid, long now, int index) {
+        return getTotalDurations(packageName, uid, now, index, true /* bgOnly */);
+    }
+
+    long getTotalDurations(String packageName, int uid, long now) {
+        return getTotalDurations(packageName, uid, now, SimplePackageDurations.DEFAULT_INDEX);
+    }
+
+    long getTotalDurations(int uid, long now, int index, boolean bgOnly) {
+        synchronized (mLock) {
+            final U durations = getUidEventsLocked(uid);
+            if (durations == null) {
+                return 0;
+            }
+            if (bgOnly) {
+                final UidStateDurations uidDurations = mUidStateDurations.get(uid);
+                if (uidDurations != null && !uidDurations.isEmpty()) {
+                    durations.subtract(uidDurations, index, UidStateDurations.DEFAULT_INDEX);
+                }
+            }
+            return durations.getTotalDurations(now, index);
+        }
+    }
+
+    long getTotalDurations(int uid, long now, int index) {
+        return getTotalDurations(uid, now, index, true /* bgOnly */);
+    }
+
+    long getTotalDurations(int uid, long now) {
+        return getTotalDurations(uid, now, SimplePackageDurations.DEFAULT_INDEX);
+    }
+
+    long getTotalDurationsSince(String packageName, int uid, long since, long now, int index,
+            boolean bgOnly) {
+        synchronized (mLock) {
+            final U durations = mPkgEvents.get(uid, packageName);
+            if (durations == null) {
+                return 0;
+            }
+            if (bgOnly) {
+                final UidStateDurations uidDurations = mUidStateDurations.get(uid);
+                if (uidDurations != null && !uidDurations.isEmpty()) {
+                    final U res = createAppStateEvents(durations);
+                    res.subtract(uidDurations, index, UidStateDurations.DEFAULT_INDEX);
+                    return res.getTotalDurationsSince(since, now, index);
+                }
+            }
+            return durations.getTotalDurationsSince(since, now, index);
+        }
+    }
+
+    long getTotalDurationsSince(String packageName, int uid, long since, long now, int index) {
+        return getTotalDurationsSince(packageName, uid, since, now, index, true /* bgOnly */);
+    }
+
+    long getTotalDurationsSince(String packageName, int uid, long since, long now) {
+        return getTotalDurationsSince(packageName, uid, since, now,
+                SimplePackageDurations.DEFAULT_INDEX);
+    }
+
+    long getTotalDurationsSince(int uid, long since, long now, int index, boolean bgOnly) {
+        synchronized (mLock) {
+            final U durations = getUidEventsLocked(uid);
+            if (durations == null) {
+                return 0;
+            }
+            if (bgOnly) {
+                final UidStateDurations uidDurations = mUidStateDurations.get(uid);
+                if (uidDurations != null && !uidDurations.isEmpty()) {
+                    durations.subtract(uidDurations, index, UidStateDurations.DEFAULT_INDEX);
+                }
+            }
+            return durations.getTotalDurationsSince(since, now, index);
+        }
+    }
+
+    long getTotalDurationsSince(int uid, long since, long now, int index) {
+        return getTotalDurationsSince(uid, since, now, index, true /* bgOnly */);
+    }
+
+    long getTotalDurationsSince(int uid, long since, long now) {
+        return getTotalDurationsSince(uid, since, now, SimplePackageDurations.DEFAULT_INDEX);
+    }
+
+    @VisibleForTesting
+    @Override
+    void reset() {
+        super.reset();
+        synchronized (mLock) {
+            mUidStateDurations.clear();
+        }
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    void dumpEventLocked(PrintWriter pw, String prefix, U events, long now) {
+        final UidStateDurations uidDurations = mUidStateDurations.get(events.mUid);
+        pw.print("  " + prefix);
+        pw.println("(bg only)");
+        if (uidDurations == null || uidDurations.isEmpty()) {
+            events.dump(pw, "    " + prefix, now);
+            return;
+        }
+        final U bgEvents = createAppStateEvents(events);
+        bgEvents.subtract(uidDurations, SimplePackageDurations.DEFAULT_INDEX);
+        bgEvents.dump(pw, "    " + prefix, now);
+        pw.print("  " + prefix);
+        pw.println("(fg + bg)");
+        events.dump(pw, "    " + prefix, now);
+    }
+
+    /**
+     * Simple duration table, with only one track of durations.
+     */
+    static class SimplePackageDurations extends BaseAppStateDurations<BaseTimeEvent> {
+        static final int DEFAULT_INDEX = 0;
+
+        SimplePackageDurations(int uid, String packageName,
+                MaxTrackingDurationConfig maxTrackingDurationConfig) {
+            super(uid, packageName, 1, TAG, maxTrackingDurationConfig);
+            mEvents[DEFAULT_INDEX] = new LinkedList<BaseTimeEvent>();
+        }
+
+        SimplePackageDurations(SimplePackageDurations other) {
+            super(other);
+        }
+
+        void addEvent(boolean active, long now) {
+            addEvent(active, new BaseTimeEvent(now), DEFAULT_INDEX);
+        }
+
+        long getTotalDurations(long now) {
+            return getTotalDurations(now, DEFAULT_INDEX);
+        }
+
+        long getTotalDurationsSince(long since, long now) {
+            return getTotalDurationsSince(since, now, DEFAULT_INDEX);
+        }
+
+        boolean isActive() {
+            return isActive(DEFAULT_INDEX);
+        }
+
+        @Override
+        String formatEventTypeLabel(int index) {
+            return "";
+        }
+    }
+
+    static class UidStateDurations extends SimplePackageDurations {
+        UidStateDurations(int uid, MaxTrackingDurationConfig maxTrackingDurationConfig) {
+            super(uid, "", maxTrackingDurationConfig);
+        }
+
+        UidStateDurations(UidStateDurations other) {
+            super(other);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateEvents.java b/services/core/java/com/android/server/am/BaseAppStateEvents.java
new file mode 100644
index 0000000..a754059
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateEvents.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static android.os.PowerExemptionManager.REASON_DENIED;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.LinkedList;
+
+/**
+ * A helper class to track the occurrences of certain events.
+ */
+abstract class BaseAppStateEvents<E> {
+    static final boolean DEBUG_BASE_APP_STATE_EVENTS = false;
+    final int mUid;
+    final @NonNull String mPackageName;
+    final @NonNull String mTag;
+    final @NonNull MaxTrackingDurationConfig mMaxTrackingDurationConfig;
+
+    /**
+     * The events we're tracking.
+     *
+     * <p>
+     * The meaning of the events is up to the derived classes, i.e., it could be a series of
+     * individual events, or a series of event pairs (i.e., start/stop event). The implementations
+     * of {@link #add}, {@link #trim} etc. in this class are based on the individual events.
+     * </p>
+     */
+    final LinkedList<E>[] mEvents;
+
+    /**
+     * In case the data we're tracking here is ignored, here is why.
+     */
+    @ReasonCode int mExemptReason = REASON_DENIED;
+
+    BaseAppStateEvents(int uid, @NonNull String packageName, int numOfEventTypes,
+            @NonNull String tag, @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+        mUid = uid;
+        mPackageName = packageName;
+        mTag = tag;
+        mMaxTrackingDurationConfig = maxTrackingDurationConfig;
+        mEvents = new LinkedList[numOfEventTypes];
+    }
+
+    BaseAppStateEvents(@NonNull BaseAppStateEvents other) {
+        mUid = other.mUid;
+        mPackageName = other.mPackageName;
+        mTag = other.mTag;
+        mMaxTrackingDurationConfig = other.mMaxTrackingDurationConfig;
+        mEvents = new LinkedList[other.mEvents.length];
+        for (int i = 0; i < mEvents.length; i++) {
+            if (other.mEvents[i] != null) {
+                mEvents[i] = new LinkedList<E>(other.mEvents[i]);
+            }
+        }
+    }
+
+    /**
+     * Add an individual event.
+     */
+    void addEvent(E event, long now, int index) {
+        if (mEvents[index] == null) {
+            mEvents[index] = new LinkedList<E>();
+        }
+        final LinkedList<E> events = mEvents[index];
+        events.add(event);
+        trimEvents(getEarliest(now), index);
+    }
+
+    /**
+     * Remove/trim earlier events with start time older than the given timestamp.
+     */
+    void trim(long earliest) {
+        for (int i = 0; i < mEvents.length; i++) {
+            trimEvents(earliest, i);
+        }
+    }
+
+    /**
+     * Remove/trim earlier events with start time older than the given timestamp.
+     */
+    abstract void trimEvents(long earliest, int index);
+
+    /**
+     * @return {@code true} if there is no events being tracked.
+     */
+    boolean isEmpty() {
+        for (int i = 0; i < mEvents.length; i++) {
+            if (mEvents[i] != null && !mEvents[i].isEmpty()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @return {@code true} if there is no events being tracked.
+     */
+    boolean isEmpty(int index) {
+        return mEvents[index] == null || mEvents[index].isEmpty();
+    }
+
+    /**
+     * Merge the events table from another instance.
+     */
+    void add(BaseAppStateEvents other) {
+        if (mEvents.length != other.mEvents.length) {
+            if (DEBUG_BASE_APP_STATE_EVENTS) {
+                Slog.wtf(mTag, "Incompatible event table this=" + this + ", other=" + other);
+            }
+            return;
+        }
+        for (int i = 0; i < mEvents.length; i++) {
+            mEvents[i] = add(mEvents[i], other.mEvents[i]);
+        }
+    }
+
+    @VisibleForTesting
+    LinkedList<E> getRawEvents(int index) {
+        return mEvents[index];
+    }
+
+    /**
+     * Merge the two given events table and return the result.
+     */
+    abstract LinkedList<E> add(LinkedList<E> events, LinkedList<E> otherEvents);
+
+    /**
+     * The number of events since the given time.
+     */
+    abstract int getTotalEventsSince(long since, long now, int index);
+
+    /**
+     * The total number of events we are tracking.
+     */
+    int getTotalEvents(long now, int index) {
+        return getTotalEventsSince(getEarliest(0), now, index);
+    }
+
+    /**
+     * @return The earliest possible time we're tracking with given timestamp.
+     */
+    long getEarliest(long now) {
+        return Math.max(0, now - mMaxTrackingDurationConfig.getMaxTrackingDuration());
+    }
+
+    void dump(PrintWriter pw, String prefix, @ElapsedRealtimeLong long nowElapsed) {
+        for (int i = 0; i < mEvents.length; i++) {
+            if (mEvents[i] == null) {
+                continue;
+            }
+            pw.print(prefix);
+            pw.print(formatEventTypeLabel(i));
+            pw.println(formatEventSummary(nowElapsed, i));
+        }
+    }
+
+    String formatEventSummary(long now, int index) {
+        return Integer.toString(getTotalEvents(now, index));
+    }
+
+    String formatEventTypeLabel(int index) {
+        return Integer.toString(index) + ":";
+    }
+
+    @Override
+    public String toString() {
+        return mPackageName + "/" + UserHandle.formatUid(mUid)
+                + " totalEvents[0]=" + formatEventSummary(SystemClock.elapsedRealtime(), 0);
+    }
+
+    interface Factory<T extends BaseAppStateEvents> {
+        T createAppStateEvents(int uid, String packageName);
+        T createAppStateEvents(T other);
+    }
+
+    interface MaxTrackingDurationConfig {
+        /**
+         * @return The mximum duration we'd keep tracking.
+         */
+        long getMaxTrackingDuration();
+    }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java b/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java
new file mode 100644
index 0000000..3e1bcae
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.PowerExemptionManager;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.BaseAppStateEvents.MaxTrackingDurationConfig;
+import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.LinkedList;
+
+/**
+ * Base class to track certain state event of apps.
+ */
+abstract class BaseAppStateEventsTracker
+        <T extends BaseAppStateEventsPolicy, U extends BaseAppStateEvents>
+        extends BaseAppStateTracker<T> implements BaseAppStateEvents.Factory<U> {
+    static final boolean DEBUG_BASE_APP_STATE_EVENTS_TRACKER = false;
+
+    @GuardedBy("mLock")
+    final UidProcessMap<U> mPkgEvents = new UidProcessMap<>();
+
+    @GuardedBy("mLock")
+    final ArraySet<Integer> mTopUids = new ArraySet<>();
+
+    BaseAppStateEventsTracker(Context context, AppRestrictionController controller,
+            Constructor<? extends Injector<T>> injector, Object outerContext) {
+        super(context, controller, injector, outerContext);
+    }
+
+    @VisibleForTesting
+    void reset() {
+        synchronized (mLock) {
+            mPkgEvents.clear();
+            mTopUids.clear();
+        }
+    }
+
+    @GuardedBy("mLock")
+    U getUidEventsLocked(int uid) {
+        U events = null;
+        final ArrayMap<String, U> map = mPkgEvents.getMap().get(uid);
+        if (map == null) {
+            return null;
+        }
+        for (int i = map.size() - 1; i >= 0; i--) {
+            final U event = map.valueAt(i);
+            if (event != null) {
+                if (events == null) {
+                    events = createAppStateEvents(uid, event.mPackageName);
+                }
+                events.add(event);
+            }
+        }
+        return events;
+    }
+
+    void trim(long earliest) {
+        synchronized (mLock) {
+            trimLocked(earliest);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void trimLocked(long earliest) {
+        final SparseArray<ArrayMap<String, U>> map = mPkgEvents.getMap();
+        for (int i = map.size() - 1; i >= 0; i--) {
+            final ArrayMap<String, U> val = map.valueAt(i);
+            for (int j = val.size() - 1; j >= 0; j--) {
+                final U v = val.valueAt(j);
+                v.trim(earliest);
+                if (v.isEmpty()) {
+                    val.removeAt(j);
+                }
+            }
+            if (val.size() == 0) {
+                map.removeAt(i);
+            }
+        }
+    }
+
+    boolean isUidOnTop(int uid) {
+        synchronized (mLock) {
+            return mTopUids.contains(uid);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void onUntrackingUidLocked(int uid) {
+    }
+
+    @Override
+    void onUidProcStateChanged(final int uid, final int procState) {
+        synchronized (mLock) {
+            if (mPkgEvents.getMap().indexOfKey(uid) < 0) {
+                // If we're not tracking its events, ignore its UID state changes.
+                return;
+            }
+            onUidProcStateChangedUncheckedLocked(uid, procState);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void onUidProcStateChangedUncheckedLocked(final int uid, final int procState) {
+        if (procState < ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+            mTopUids.add(uid);
+        } else {
+            mTopUids.remove(uid);
+        }
+    }
+
+    @Override
+    void onUidGone(final int uid) {
+        synchronized (mLock) {
+            mTopUids.remove(uid);
+        }
+    }
+
+    @Override
+    void onUidRemoved(final int uid) {
+        synchronized (mLock) {
+            mPkgEvents.getMap().remove(uid);
+            onUntrackingUidLocked(uid);
+        }
+    }
+
+    @Override
+    void onUserRemoved(final @UserIdInt int userId) {
+        synchronized (mLock) {
+            final SparseArray<ArrayMap<String, U>> map = mPkgEvents.getMap();
+            for (int i = map.size() - 1; i >= 0; i--) {
+                final int uid = map.keyAt(i);
+                if (UserHandle.getUserId(uid) == userId) {
+                    map.removeAt(i);
+                    onUntrackingUidLocked(uid);
+                }
+            }
+        }
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix) {
+        final T policy = mInjector.getPolicy();
+        synchronized (mLock) {
+            final long now = SystemClock.elapsedRealtime();
+            final SparseArray<ArrayMap<String, U>> map = mPkgEvents.getMap();
+            for (int i = map.size() - 1; i >= 0; i--) {
+                final int uid = map.keyAt(i);
+                final ArrayMap<String, U> val = map.valueAt(i);
+                for (int j = val.size() - 1; j >= 0; j--) {
+                    final String packageName = val.keyAt(j);
+                    final U events = val.valueAt(j);
+                    dumpEventHeaderLocked(pw, prefix, packageName, uid, events, policy);
+                    dumpEventLocked(pw, prefix, events, now);
+                }
+            }
+        }
+        policy.dump(pw, prefix);
+    }
+
+    @GuardedBy("mLock")
+    void dumpEventHeaderLocked(PrintWriter pw, String prefix, String packageName, int uid, U events,
+            T policy) {
+        pw.print(prefix);
+        pw.print("* ");
+        pw.print(packageName);
+        pw.print('/');
+        pw.print(UserHandle.formatUid(uid));
+        pw.print(" exemption=");
+        pw.println(policy.getExemptionReasonString(packageName, uid, events.mExemptReason));
+    }
+
+    @GuardedBy("mLock")
+    void dumpEventLocked(PrintWriter pw, String prefix, U events, long now) {
+        events.dump(pw, "  " + prefix, now);
+    }
+
+    abstract static class BaseAppStateEventsPolicy<V extends BaseAppStateEventsTracker>
+            extends BaseAppStatePolicy<V> implements MaxTrackingDurationConfig {
+        /**
+         * The key to the maximum duration we'd keep tracking, events earlier than that
+         * will be discarded.
+         */
+        final @NonNull String mKeyMaxTrackingDuration;
+
+        /**
+         * The default to the {@link #mMaxTrackingDuration}.
+         */
+        final long mDefaultMaxTrackingDuration;
+
+        /**
+         * The maximum duration we'd keep tracking, events earlier than that will be discarded.
+         */
+        volatile long mMaxTrackingDuration;
+
+        BaseAppStateEventsPolicy(@NonNull Injector<?> injector, @NonNull V tracker,
+                @NonNull String keyTrackerEnabled, boolean defaultTrackerEnabled,
+                @NonNull String keyMaxTrackingDuration, long defaultMaxTrackingDuration) {
+            super(injector, tracker, keyTrackerEnabled, defaultTrackerEnabled);
+            mKeyMaxTrackingDuration = keyMaxTrackingDuration;
+            mDefaultMaxTrackingDuration = defaultMaxTrackingDuration;
+        }
+
+        @Override
+        public void onPropertiesChanged(String name) {
+            if (mKeyMaxTrackingDuration.equals(name)) {
+                updateMaxTrackingDuration();
+            } else {
+                super.onPropertiesChanged(name);
+            }
+        }
+
+        @Override
+        public void onSystemReady() {
+            super.onSystemReady();
+            updateMaxTrackingDuration();
+        }
+
+        /**
+         * Called when the maximum duration we'd keep tracking has been changed.
+         */
+        public abstract void onMaxTrackingDurationChanged(long maxDuration);
+
+        void updateMaxTrackingDuration() {
+            long max = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    mKeyMaxTrackingDuration, mDefaultMaxTrackingDuration);
+            if (max != mMaxTrackingDuration) {
+                mMaxTrackingDuration = max;
+                onMaxTrackingDurationChanged(max);
+            }
+        }
+
+        @Override
+        public long getMaxTrackingDuration() {
+            return mMaxTrackingDuration;
+        }
+
+        String getExemptionReasonString(String packageName, int uid, @ReasonCode int reason) {
+            return PowerExemptionManager.reasonCodeToString(reason);
+        }
+
+        @Override
+        void dump(PrintWriter pw, String prefix) {
+            super.dump(pw, prefix);
+            if (isEnabled()) {
+                pw.print(prefix);
+                pw.print(mKeyMaxTrackingDuration);
+                pw.print('=');
+                pw.println(mMaxTrackingDuration);
+            }
+        }
+    }
+
+    /**
+     * Simple event table, with only one track of events.
+     */
+    static class SimplePackageEvents extends BaseAppStateTimeEvents {
+        static final int DEFAULT_INDEX = 0;
+
+        SimplePackageEvents(int uid, String packageName,
+                MaxTrackingDurationConfig maxTrackingDurationConfig) {
+            super(uid, packageName, 1, TAG, maxTrackingDurationConfig);
+            mEvents[DEFAULT_INDEX] = new LinkedList<Long>();
+        }
+
+        long getTotalEvents(long now) {
+            return getTotalEvents(now, DEFAULT_INDEX);
+        }
+
+        long getTotalEventsSince(long since, long now) {
+            return getTotalEventsSince(since, now, DEFAULT_INDEX);
+        }
+
+        @Override
+        String formatEventTypeLabel(int index) {
+            return "";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStatePolicy.java b/services/core/java/com/android/server/am/BaseAppStatePolicy.java
new file mode 100644
index 0000000..67318a7
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStatePolicy.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.app.ActivityManager.RestrictionLevel;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.provider.DeviceConfig;
+
+import com.android.server.am.BaseAppStateTracker.Injector;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class to track the policy for certain state of the app.
+ *
+ * @param <T> A class derived from BaseAppStateTracker.
+ */
+public abstract class BaseAppStatePolicy<T extends BaseAppStateTracker> {
+
+    protected final Injector<?> mInjector;
+    protected final T mTracker;
+
+    /**
+     * The key to the device config, on whether or not we should enable the tracker.
+     */
+    protected final @NonNull String mKeyTrackerEnabled;
+
+    /**
+     * The default settings on whether or not we should enable the tracker.
+     */
+    protected final boolean mDefaultTrackerEnabled;
+
+    /**
+     * Whether or not we should enable the tracker.
+     */
+    volatile boolean mTrackerEnabled;
+
+    BaseAppStatePolicy(@NonNull Injector<?> injector, @NonNull T tracker,
+            @NonNull String keyTrackerEnabled, boolean defaultTrackerEnabled) {
+        mInjector = injector;
+        mTracker = tracker;
+        mKeyTrackerEnabled = keyTrackerEnabled;
+        mDefaultTrackerEnabled = defaultTrackerEnabled;
+    }
+
+    void updateTrackerEnabled() {
+        final boolean enabled = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                mKeyTrackerEnabled, mDefaultTrackerEnabled);
+        if (enabled != mTrackerEnabled) {
+            mTrackerEnabled = enabled;
+            onTrackerEnabled(enabled);
+        }
+    }
+
+    /**
+     * Called when the tracker enable flag flips.
+     */
+    public abstract void onTrackerEnabled(boolean enabled);
+
+    /**
+     * Called when a device config property in the activity manager namespace
+     * has changed.
+     */
+    public void onPropertiesChanged(@NonNull String name) {
+        if (mKeyTrackerEnabled.equals(name)) {
+            updateTrackerEnabled();
+        }
+    }
+
+    /**
+     * @return The proposed background restriction policy for the given package/uid.
+     */
+    public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) {
+        return RESTRICTION_LEVEL_UNKNOWN;
+    }
+
+    /**
+     * Called when the system is ready to rock.
+     */
+    public void onSystemReady() {
+        updateTrackerEnabled();
+    }
+
+    /**
+     * @return If this tracker is enabled or not.
+     */
+    public boolean isEnabled() {
+        return mTrackerEnabled;
+    }
+
+    /**
+     * @return If the given UID should be exempted.
+     *
+     * <p>
+     * Note: Call it with caution as it'll try to acquire locks in other services.
+     * </p>
+     */
+    @CallSuper
+    @ReasonCode
+    public int shouldExemptUid(int uid) {
+        return mTracker.mAppRestrictionController.getBackgroundRestrictionExemptionReason(uid);
+    }
+
+    /**
+     * Dump to the given printer writer.
+     */
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.print(mKeyTrackerEnabled);
+        pw.print('=');
+        pw.println(mTrackerEnabled);
+    }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateTimeEvents.java b/services/core/java/com/android/server/am/BaseAppStateTimeEvents.java
new file mode 100644
index 0000000..1eccdf2
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateTimeEvents.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 com.android.server.am;
+
+import android.annotation.NonNull;
+
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * A helper class to track the timestamps of individual events.
+ */
+class BaseAppStateTimeEvents<T extends BaseTimeEvent> extends BaseAppStateEvents<T> {
+
+    BaseAppStateTimeEvents(int uid, @NonNull String packageName, int numOfEventTypes,
+            @NonNull String tag, @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+        super(uid, packageName, numOfEventTypes, tag, maxTrackingDurationConfig);
+    }
+
+    BaseAppStateTimeEvents(@NonNull BaseAppStateTimeEvents other) {
+        super(other);
+    }
+
+    @Override
+    LinkedList<T> add(LinkedList<T> durations, LinkedList<T> otherDurations) {
+        if (otherDurations == null || otherDurations.size() == 0) {
+            return durations;
+        }
+        if (durations == null || durations.size() == 0) {
+            return (LinkedList<T>) otherDurations.clone();
+        }
+        final Iterator<T> itl = durations.iterator();
+        final Iterator<T> itr = otherDurations.iterator();
+        T l = itl.next(), r = itr.next();
+        LinkedList<T> dest = new LinkedList<>();
+        for (long lts = l.getTimestamp(), rts = r.getTimestamp();
+                lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) {
+            if (lts == rts) {
+                dest.add((T) l.clone());
+                lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+                rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+            } else if (lts < rts) {
+                dest.add((T) l.clone());
+                lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+            } else {
+                dest.add((T) r.clone());
+                rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+            }
+        }
+        return dest;
+    }
+
+    @Override
+    int getTotalEventsSince(long since, long now, int index) {
+        final LinkedList<T> events = mEvents[index];
+        if (events == null || events.size() == 0) {
+            return 0;
+        }
+        int count = 0;
+        for (T event : events) {
+            if (event.getTimestamp() >= since) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    @Override
+    void trimEvents(long earliest, int index) {
+        final LinkedList<T> events = mEvents[index];
+        if (events == null) {
+            return;
+        }
+        while (events.size() > 0) {
+            final T current = events.peek();
+            if (current.getTimestamp() >= earliest) {
+                return; // All we have are newer than the given timestamp.
+            }
+            events.pop();
+        }
+    }
+
+    /**
+     * A data class encapsulate the individual event data.
+     */
+    static class BaseTimeEvent implements Cloneable {
+        /**
+         * The timestamp this event occurred at.
+         */
+        long mTimestamp;
+
+        BaseTimeEvent(long timestamp) {
+            mTimestamp = timestamp;
+        }
+
+        BaseTimeEvent(BaseTimeEvent other) {
+            mTimestamp = other.mTimestamp;
+        }
+
+        void trimTo(long timestamp) {
+            mTimestamp = timestamp;
+        }
+
+        long getTimestamp() {
+            return mTimestamp;
+        }
+
+        @Override
+        public Object clone() {
+            return new BaseTimeEvent(this);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null) {
+                return false;
+            }
+            if (other.getClass() != BaseTimeEvent.class) {
+                return false;
+            }
+            return ((BaseTimeEvent) other).mTimestamp == mTimestamp;
+        }
+
+        @Override
+        public int hashCode() {
+            return Long.hashCode(mTimestamp);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateTimeSlotEvents.java b/services/core/java/com/android/server/am/BaseAppStateTimeSlotEvents.java
new file mode 100644
index 0000000..0c43a33
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateTimeSlotEvents.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * Base class to track certain individual event of app states, it groups the events into time-based
+ * slots, thus we could only track the total number of events in a slot, eliminating
+ * the needs to track the timestamps for each individual event. This will be much more memory
+ * efficient for the case of massive amount of events.
+ */
+class BaseAppStateTimeSlotEvents extends BaseAppStateEvents<Integer> {
+
+    static final boolean DEBUG_BASE_APP_TIME_SLOT_EVENTS = false;
+
+    /**
+     * The size (in ms) of the timeslot, should be greater than 0 always.
+     */
+    final long mTimeSlotSize;
+
+    /**
+     * The start timestamp of current timeslot.
+     */
+    long[] mCurSlotStartTime;
+
+    BaseAppStateTimeSlotEvents(int uid, @NonNull String packageName, int numOfEventTypes,
+            long timeslotSize, @NonNull String tag,
+            @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+        super(uid, packageName, numOfEventTypes, tag, maxTrackingDurationConfig);
+        mTimeSlotSize = timeslotSize;
+        mCurSlotStartTime = new long[numOfEventTypes];
+    }
+
+    BaseAppStateTimeSlotEvents(@NonNull BaseAppStateTimeSlotEvents other) {
+        super(other);
+        mTimeSlotSize = other.mTimeSlotSize;
+        mCurSlotStartTime = new long[other.mCurSlotStartTime.length];
+        for (int i = 0; i < mCurSlotStartTime.length; i++) {
+            mCurSlotStartTime[i] = other.mCurSlotStartTime[i];
+        }
+    }
+
+    @Override
+    LinkedList<Integer> add(LinkedList<Integer> events, LinkedList<Integer> otherEvents) {
+        if (DEBUG_BASE_APP_TIME_SLOT_EVENTS) {
+            Slog.wtf(mTag, "Called into BaseAppStateTimeSlotEvents#add unexpected.");
+        }
+        // This function is invalid semantically here without the information of time-bases.
+        return null;
+    }
+
+    @Override
+    void add(BaseAppStateEvents otherObj) {
+        if (otherObj == null || !(otherObj instanceof BaseAppStateTimeSlotEvents)) {
+            return;
+        }
+        final BaseAppStateTimeSlotEvents other = (BaseAppStateTimeSlotEvents) otherObj;
+        if (mEvents.length != other.mEvents.length) {
+            if (DEBUG_BASE_APP_TIME_SLOT_EVENTS) {
+                Slog.wtf(mTag, "Incompatible event table this=" + this + ", other=" + other);
+            }
+            return;
+        }
+        for (int i = 0; i < mEvents.length; i++) {
+            final LinkedList<Integer> otherEvents = other.mEvents[i];
+            if (otherEvents == null || otherEvents.size() == 0) {
+                continue;
+            }
+            LinkedList<Integer> events = mEvents[i];
+            if (events == null || events.size() == 0) {
+                mEvents[i] = new LinkedList<Integer>(otherEvents);
+                mCurSlotStartTime[i] = other.mCurSlotStartTime[i];
+                continue;
+            }
+
+            final LinkedList<Integer> dest = new LinkedList<>();
+            final Iterator<Integer> itl = events.iterator();
+            final Iterator<Integer> itr = otherEvents.iterator();
+            final long maxl = mCurSlotStartTime[i];
+            final long maxr = other.mCurSlotStartTime[i];
+            final long minl = maxl - mTimeSlotSize * (events.size() - 1);
+            final long minr = maxr - mTimeSlotSize * (otherEvents.size() - 1);
+            final long latest = Math.max(maxl, maxr);
+            final long earliest = Math.min(minl, minr);
+            for (long start = earliest; start <= latest; start += mTimeSlotSize) {
+                dest.add((start >= minl && start <= maxl ? itl.next() : 0)
+                        + (start >= minr && start <= maxr ? itr.next() : 0));
+            }
+            mEvents[i] = dest;
+            if (maxl < maxr) {
+                mCurSlotStartTime[i] = other.mCurSlotStartTime[i];
+            }
+            trimEvents(getEarliest(mCurSlotStartTime[i]), i);
+        }
+    }
+
+    @Override
+    int getTotalEventsSince(long since, long now, int index) {
+        final LinkedList<Integer> events = mEvents[index];
+        if (events == null || events.size() == 0) {
+            return 0;
+        }
+        final long start = getSlotStartTime(since);
+        if (start > mCurSlotStartTime[index]) {
+            return 0;
+        }
+        final long end = Math.min(getSlotStartTime(now), mCurSlotStartTime[index]);
+        final Iterator<Integer> it = events.descendingIterator();
+        int count = 0;
+        for (long time = mCurSlotStartTime[index]; time >= start && it.hasNext();
+                time -= mTimeSlotSize) {
+            final int val = it.next();
+            if (time <= end) {
+                count += val;
+            }
+        }
+        return count;
+    }
+
+    void addEvent(long now, int index) {
+        final long slot = getSlotStartTime(now);
+        if (DEBUG_BASE_APP_TIME_SLOT_EVENTS) {
+            Slog.i(mTag, "Adding event to slot " + slot);
+        }
+        LinkedList<Integer> events = mEvents[index];
+        if (events == null) {
+            events = new LinkedList<Integer>();
+            mEvents[index] = events;
+        }
+        if (events.size() == 0) {
+            events.add(1);
+        } else {
+            for (long start = mCurSlotStartTime[index]; start < slot; start += mTimeSlotSize) {
+                events.add(0);
+            }
+            events.offerLast(events.pollLast() + 1);
+        }
+        mCurSlotStartTime[index] = slot;
+        trimEvents(getEarliest(now), index);
+    }
+
+    @Override
+    void trimEvents(long earliest, int index) {
+        final LinkedList<Integer> events = mEvents[index];
+        if (events == null || events.size() == 0) {
+            return;
+        }
+        final long slot = getSlotStartTime(earliest);
+        for (long time = mCurSlotStartTime[index] - mTimeSlotSize * (events.size() - 1);
+                time < slot && events.size() > 0; time += mTimeSlotSize) {
+            events.pop();
+        }
+    }
+
+    long getSlotStartTime(long timestamp) {
+        return timestamp - timestamp % mTimeSlotSize;
+    }
+
+    @VisibleForTesting
+    long getCurrentSlotStartTime(int index) {
+        return mCurSlotStartTime[index];
+    }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java b/services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java
new file mode 100644
index 0000000..2fbca1f
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
+import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_FGS;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_TOP;
+import static android.os.PowerExemptionManager.reasonCodeToString;
+
+import static com.android.server.am.BaseAppStateTracker.ONE_MINUTE;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager.RestrictionLevel;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ProcessMap;
+import com.android.server.am.BaseAppStateTimeSlotEventsTracker.BaseAppStateTimeSlotEventsPolicy;
+import com.android.server.am.BaseAppStateTimeSlotEventsTracker.SimpleAppStateTimeslotEvents;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+
+/**
+ * Base class to track {@link #BaseAppStateTimeSlotEvents}.
+ */
+abstract class BaseAppStateTimeSlotEventsTracker
+        <T extends BaseAppStateTimeSlotEventsPolicy, U extends SimpleAppStateTimeslotEvents>
+        extends BaseAppStateEventsTracker<T, U> {
+    static final String TAG = "BaseAppStateTimeSlotEventsTracker";
+
+    static final boolean DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER = false;
+
+    // Unlocked since it's only accessed in single thread.
+    private final ArrayMap<U, Integer> mTmpPkgs = new ArrayMap<>();
+
+    private H mHandler;
+
+    BaseAppStateTimeSlotEventsTracker(Context context, AppRestrictionController controller,
+            Constructor<? extends Injector<T>> injector, Object outerContext) {
+        super(context, controller, injector, outerContext);
+        mHandler = new H(this);
+    }
+
+    void onNewEvent(String packageName, int uid) {
+        mHandler.obtainMessage(H.MSG_NEW_EVENT, uid, 0, packageName).sendToTarget();
+    }
+
+    void handleNewEvent(String packageName, int uid) {
+        if (mInjector.getPolicy().shouldExempt(packageName, uid) != REASON_DENIED) {
+            return;
+        }
+        final long now = SystemClock.elapsedRealtime();
+        boolean notify = false;
+        int totalEvents;
+        synchronized (mLock) {
+            U pkgEvents = mPkgEvents.get(uid, packageName);
+            if (pkgEvents == null) {
+                pkgEvents = createAppStateEvents(uid, packageName);
+                mPkgEvents.put(uid, packageName, pkgEvents);
+            }
+            pkgEvents.addEvent(now, SimpleAppStateTimeslotEvents.DEFAULT_INDEX);
+            totalEvents = pkgEvents.getTotalEvents(now, SimpleAppStateTimeslotEvents.DEFAULT_INDEX);
+            notify = totalEvents >= mInjector.getPolicy().getNumOfEventsThreshold();
+        }
+        if (notify) {
+            mInjector.getPolicy().onExcessiveEvents(
+                    packageName, uid, totalEvents, now);
+        }
+    }
+
+    void onMonitorEnabled(boolean enabled) {
+        if (!enabled) {
+            synchronized (mLock) {
+                mPkgEvents.clear();
+            }
+        }
+    }
+
+    void onNumOfEventsThresholdChanged(int threshold) {
+        final long now = SystemClock.elapsedRealtime();
+        synchronized (mLock) {
+            SparseArray<ArrayMap<String, U>> pkgEvents = mPkgEvents.getMap();
+            for (int i = pkgEvents.size() - 1; i >= 0; i--) {
+                final ArrayMap<String, U> pkgs = pkgEvents.valueAt(i);
+                for (int j = pkgs.size() - 1; j >= 0; j--) {
+                    final U pkg = pkgs.valueAt(j);
+                    int totalEvents = pkg.getTotalEvents(now,
+                            SimpleAppStateTimeslotEvents.DEFAULT_INDEX);
+                    if (totalEvents >= threshold) {
+                        mTmpPkgs.put(pkg, totalEvents);
+                    }
+                }
+            }
+        }
+        for (int i = mTmpPkgs.size() - 1; i >= 0; i--) {
+            final U pkg = mTmpPkgs.keyAt(i);
+            mInjector.getPolicy().onExcessiveEvents(
+                    pkg.mPackageName, pkg.mUid, mTmpPkgs.valueAt(i), now);
+        }
+        mTmpPkgs.clear();
+    }
+
+    private void trimEvents() {
+        final long now = SystemClock.elapsedRealtime();
+        trim(Math.max(0, now - mInjector.getPolicy().getMaxTrackingDuration()));
+    }
+
+    @Override
+    void onUserInteractionStarted(String packageName, int uid) {
+        mInjector.getPolicy().onUserInteractionStarted(packageName, uid);
+    }
+
+    static class H extends Handler {
+        static final int MSG_NEW_EVENT = 0;
+
+        final BaseAppStateTimeSlotEventsTracker mTracker;
+
+        H(BaseAppStateTimeSlotEventsTracker tracker) {
+            super(tracker.mBgHandler.getLooper());
+            mTracker = tracker;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_NEW_EVENT:
+                    mTracker.handleNewEvent((String) msg.obj, msg.arg1);
+                    break;
+            }
+        }
+    }
+
+    static class BaseAppStateTimeSlotEventsPolicy<E extends BaseAppStateTimeSlotEventsTracker>
+            extends BaseAppStateEventsPolicy<E> {
+
+        final String mKeyNumOfEventsThreshold;
+        final int mDefaultNumOfEventsThreshold;
+
+        @NonNull
+        private final Object mLock;
+
+        @GuardedBy("mLock")
+        private final ProcessMap<Long> mExcessiveEventPkgs = new ProcessMap<>();
+
+        long mTimeSlotSize = DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER
+                    ? SimpleAppStateTimeslotEvents.DEFAULT_TIME_SLOT_SIZE_DEBUG
+                    : SimpleAppStateTimeslotEvents.DEFAULT_TIME_SLOT_SIZE;
+
+        volatile int mNumOfEventsThreshold;
+
+        BaseAppStateTimeSlotEventsPolicy(@NonNull Injector injector, @NonNull E tracker,
+                @NonNull String keyTrackerEnabled, boolean defaultTrackerEnabled,
+                @NonNull String keyMaxTrackingDuration, long defaultMaxTrackingDuration,
+                @NonNull String keyNumOfEventsThreshold, int defaultNumOfEventsThreshold) {
+            super(injector, tracker, keyTrackerEnabled, defaultTrackerEnabled,
+                    keyMaxTrackingDuration, defaultMaxTrackingDuration);
+            mKeyNumOfEventsThreshold = keyNumOfEventsThreshold;
+            mDefaultNumOfEventsThreshold = defaultNumOfEventsThreshold;
+            mLock = tracker.mLock;
+        }
+
+        @Override
+        public void onSystemReady() {
+            super.onSystemReady();
+            updateNumOfEventsThreshold();
+        }
+
+        @Override
+        public void onPropertiesChanged(String name) {
+            if (mKeyNumOfEventsThreshold.equals(name)) {
+                updateNumOfEventsThreshold();
+            } else {
+                super.onPropertiesChanged(name);
+            }
+        }
+
+        @Override
+        public void onTrackerEnabled(boolean enabled) {
+            mTracker.onMonitorEnabled(enabled);
+        }
+
+        @Override
+        public void onMaxTrackingDurationChanged(long maxDuration) {
+            mTracker.mBgHandler.post(mTracker::trimEvents);
+        }
+
+        private void updateNumOfEventsThreshold() {
+            final int threshold = DeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    mKeyNumOfEventsThreshold,
+                    mDefaultNumOfEventsThreshold);
+            if (threshold != mNumOfEventsThreshold) {
+                mNumOfEventsThreshold = threshold;
+                mTracker.onNumOfEventsThresholdChanged(threshold);
+            }
+        }
+
+        int getNumOfEventsThreshold() {
+            return mNumOfEventsThreshold;
+        }
+
+        long getTimeSlotSize() {
+            return mTimeSlotSize;
+        }
+
+        @VisibleForTesting
+        void setTimeSlotSize(long size) {
+            mTimeSlotSize = size;
+        }
+
+        String getEventName() {
+            return "event";
+        }
+
+        void onExcessiveEvents(String packageName, int uid, int numOfEvents, long now) {
+            boolean notifyController = false;
+            synchronized (mLock) {
+                Long ts = mExcessiveEventPkgs.get(packageName, uid);
+                if (ts == null) {
+                    if (DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER) {
+                        Slog.i(TAG, "Excessive amount of " + getEventName() + " from "
+                                + packageName + "/" + UserHandle.formatUid(uid) + ": " + numOfEvents
+                                + " over " + TimeUtils.formatDuration(getMaxTrackingDuration()));
+                    }
+                    mExcessiveEventPkgs.put(packageName, uid, now);
+                    notifyController = true;
+                }
+            }
+            if (notifyController) {
+                mTracker.mAppRestrictionController.refreshAppRestrictionLevelForUid(
+                        uid, REASON_MAIN_FORCED_BY_SYSTEM,
+                        REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE, true);
+            }
+        }
+
+        /**
+         * Whether or not we should ignore the incoming event.
+         */
+        @ReasonCode int shouldExempt(String packageName, int uid) {
+            if (mTracker.isUidOnTop(uid)) {
+                if (DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER) {
+                    Slog.i(TAG, "Ignoring event from " + packageName + "/"
+                            + UserHandle.formatUid(uid) + ": top");
+                }
+                return REASON_PROC_STATE_TOP;
+            }
+            if (mTracker.mAppRestrictionController.hasForegroundServices(packageName, uid)) {
+                if (DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER) {
+                    Slog.i(TAG, "Ignoring event " + packageName + "/"
+                            + UserHandle.formatUid(uid) + ": has active FGS");
+                }
+                return REASON_PROC_STATE_FGS;
+            }
+            final @ReasonCode int reason = shouldExemptUid(uid);
+            if (reason != REASON_DENIED) {
+                if (DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER) {
+                    Slog.i(TAG, "Ignoring event " + packageName + "/" + UserHandle.formatUid(uid)
+                            + ": " + reasonCodeToString(reason));
+                }
+                return reason;
+            }
+            return REASON_DENIED;
+        }
+
+        @Override
+        public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) {
+            synchronized (mLock) {
+                return mExcessiveEventPkgs.get(packageName, uid) == null
+                        ? RESTRICTION_LEVEL_ADAPTIVE_BUCKET
+                        : RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+            }
+        }
+
+        void onUserInteractionStarted(String packageName, int uid) {
+            boolean notifyController = false;
+            synchronized (mLock) {
+                notifyController = mExcessiveEventPkgs.remove(packageName, uid) != null;
+            }
+            mTracker.mAppRestrictionController.refreshAppRestrictionLevelForUid(uid,
+                    REASON_MAIN_USAGE, REASON_SUB_USAGE_USER_INTERACTION, true);
+        }
+
+        @Override
+        void dump(PrintWriter pw, String prefix) {
+            super.dump(pw, prefix);
+            if (isEnabled()) {
+                pw.print(prefix);
+                pw.print(mKeyNumOfEventsThreshold);
+                pw.print('=');
+                pw.println(mDefaultNumOfEventsThreshold);
+            }
+            pw.print(prefix);
+            pw.print("event_time_slot_size=");
+            pw.println(getTimeSlotSize());
+        }
+    }
+
+    /**
+     * A simple time-slot based event table, with only one track of events.
+     */
+    static class SimpleAppStateTimeslotEvents extends BaseAppStateTimeSlotEvents {
+        static final int DEFAULT_INDEX = 0;
+        static final long DEFAULT_TIME_SLOT_SIZE = 15 * ONE_MINUTE;
+        static final long DEFAULT_TIME_SLOT_SIZE_DEBUG = ONE_MINUTE;
+
+        SimpleAppStateTimeslotEvents(int uid, @NonNull String packageName,
+                long timeslotSize, @NonNull String tag,
+                @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+            super(uid, packageName, 1, timeslotSize, tag, maxTrackingDurationConfig);
+        }
+
+        SimpleAppStateTimeslotEvents(SimpleAppStateTimeslotEvents other) {
+            super(other);
+        }
+
+        @Override
+        String formatEventTypeLabel(int index) {
+            return "";
+        }
+
+        @Override
+        String formatEventSummary(long now, int index) {
+            if (mEvents[DEFAULT_INDEX] == null || mEvents[DEFAULT_INDEX].size() == 0) {
+                return "(none)";
+            }
+            final int total = getTotalEvents(now, DEFAULT_INDEX);
+            return "total=" + total + ", latest="
+                    + getTotalEventsSince(mCurSlotStartTime[DEFAULT_INDEX], now, DEFAULT_INDEX)
+                    + "(slot=" + TimeUtils.formatTime(mCurSlotStartTime[DEFAULT_INDEX], now) + ")";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateTracker.java b/services/core/java/com/android/server/am/BaseAppStateTracker.java
new file mode 100644
index 0000000..2846f6c
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateTracker.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static com.android.server.am.ActiveServices.SERVICE_START_FOREGROUND_TIMEOUT;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.session.MediaSessionManager;
+import android.os.BatteryManagerInternal;
+import android.os.BatteryStatsInternal;
+import android.os.Handler;
+import android.util.Slog;
+
+import com.android.server.DeviceIdleInternal;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+
+/**
+ * Base class to track certain state of the app, could be used to determine the restriction level.
+ *
+ * @param <T> A class derived from BaseAppStatePolicy.
+ */
+public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
+    protected static final String TAG = TAG_WITH_CLASS_NAME ? "BaseAppStatePolicy" : TAG_AM;
+
+    static final long ONE_MINUTE = 60 * 1_000L;
+    static final long ONE_HOUR = 60 * ONE_MINUTE;
+    static final long ONE_DAY = 24 * ONE_HOUR;
+
+    protected final AppRestrictionController mAppRestrictionController;
+    protected final Injector<T> mInjector;
+    protected final Context mContext;
+    protected final Handler mBgHandler;
+    protected final Object mLock;
+
+    BaseAppStateTracker(Context context, AppRestrictionController controller,
+            @Nullable Constructor<? extends Injector<T>> injector, Object outerContext) {
+        mContext = context;
+        mAppRestrictionController = controller;
+        mBgHandler = controller.getBackgroundHandler();
+        mLock = controller.getLock();
+        if (injector == null) {
+            mInjector = new Injector<>();
+        } else {
+            Injector<T> localInjector = null;
+            try {
+                localInjector = injector.newInstance(outerContext);
+            } catch (Exception e) {
+                Slog.w(TAG, "Unable to instantiate " + injector, e);
+            }
+            mInjector = (localInjector == null) ? new Injector<>() : localInjector;
+        }
+    }
+
+    /**
+     * Return the policy holder of this tracker.
+     */
+    T getPolicy() {
+        return mInjector.getPolicy();
+    }
+
+    /**
+     * Called when the system is ready to rock.
+     */
+    void onSystemReady() {
+        mInjector.onSystemReady();
+    }
+
+    /**
+     * Called when a user with the given uid is added.
+     */
+    void onUidAdded(final int uid) {
+    }
+
+    /**
+     * Called when a user with the given uid is removed.
+     */
+    void onUidRemoved(final int uid) {
+    }
+
+    /**
+     * Called when a user with the given userId is added.
+     */
+    void onUserAdded(final @UserIdInt int userId) {
+    }
+
+    /**
+     * Called when a user with the given userId is started.
+     */
+    void onUserStarted(final @UserIdInt int userId) {
+    }
+
+    /**
+     * Called when a user with the given userId is stopped.
+     */
+    void onUserStopped(final @UserIdInt int userId) {
+    }
+
+    /**
+     * Called when a user with the given userId is removed.
+     */
+    void onUserRemoved(final @UserIdInt int userId) {
+    }
+
+    /**
+     * Called when a device config property in the activity manager namespace
+     * has changed.
+     */
+    void onPropertiesChanged(@NonNull String name) {
+        getPolicy().onPropertiesChanged(name);
+    }
+
+    /**
+     * Called when an app has transitioned into an active state due to user interaction.
+     */
+    void onUserInteractionStarted(String packageName, int uid) {
+    }
+
+    /**
+     * Called when the background restriction settings of the given app is changed.
+     */
+    void onBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
+    }
+
+    /**
+     * Called when the process state of the given UID has been changed.
+     *
+     * <p>Note: as of now, for simplification, we're tracking the TOP state changes only.</p>
+     */
+    void onUidProcStateChanged(int uid, int procState) {
+    }
+
+    /**
+     * Called when all the processes in the given UID have died.
+     */
+    void onUidGone(int uid) {
+    }
+
+    /**
+     * Dump to the given printer writer.
+     */
+    void dump(PrintWriter pw, String prefix) {
+        mInjector.getPolicy().dump(pw, "  " + prefix);
+    }
+
+    static class Injector<T extends BaseAppStatePolicy> {
+        T mAppStatePolicy;
+
+        ActivityManagerInternal mActivityManagerInternal;
+        BatteryManagerInternal mBatteryManagerInternal;
+        BatteryStatsInternal mBatteryStatsInternal;
+        DeviceIdleInternal mDeviceIdleInternal;
+        UserManagerInternal mUserManagerInternal;
+        PackageManager mPackageManager;
+        PermissionManagerServiceInternal mPermissionManagerServiceInternal;
+        AppOpsManager mAppOpsManager;
+        MediaSessionManager mMediaSessionManager;
+        RoleManager mRoleManager;
+
+        void setPolicy(T policy) {
+            mAppStatePolicy = policy;
+        }
+
+        void onSystemReady() {
+            mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+            mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
+            mBatteryStatsInternal = LocalServices.getService(BatteryStatsInternal.class);
+            mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
+            mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+            mPermissionManagerServiceInternal = LocalServices.getService(
+                    PermissionManagerServiceInternal.class);
+            final Context context = mAppStatePolicy.mTracker.mContext;
+            mPackageManager = context.getPackageManager();
+            mAppOpsManager = context.getSystemService(AppOpsManager.class);
+            mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
+            mRoleManager = context.getSystemService(RoleManager.class);
+
+            getPolicy().onSystemReady();
+        }
+
+        ActivityManagerInternal getActivityManagerInternal() {
+            return mActivityManagerInternal;
+        }
+
+        BatteryManagerInternal getBatteryManagerInternal() {
+            return mBatteryManagerInternal;
+        }
+
+        BatteryStatsInternal getBatteryStatsInternal() {
+            return mBatteryStatsInternal;
+        }
+
+        T getPolicy() {
+            return mAppStatePolicy;
+        }
+
+        DeviceIdleInternal getDeviceIdleInternal() {
+            return mDeviceIdleInternal;
+        }
+
+        UserManagerInternal getUserManagerInternal() {
+            return mUserManagerInternal;
+        }
+
+        /**
+         * Equivalent to {@link java.lang.System#currentTimeMillis}.
+         */
+        long currentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        PackageManager getPackageManager() {
+            return mPackageManager;
+        }
+
+        PermissionManagerServiceInternal getPermissionManagerServiceInternal() {
+            return mPermissionManagerServiceInternal;
+        }
+
+        AppOpsManager getAppOpsManager() {
+            return mAppOpsManager;
+        }
+
+        MediaSessionManager getMediaSessionManager() {
+            return mMediaSessionManager;
+        }
+
+        long getServiceStartForegroundTimeout() {
+            return SERVICE_START_FOREGROUND_TIMEOUT;
+        }
+
+        RoleManager getRoleManager() {
+            return mRoleManager;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 0b92954..c8ad0e8 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -431,6 +431,11 @@
         }
 
         @Override
+        public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
+            return BatteryStatsService.this.getBatteryUsageStats(queries);
+        }
+
+        @Override
         public void noteJobsDeferred(int uid, int numDeferred, long sinceLast) {
             if (DBG) Slog.d(TAG, "Jobs deferred " + uid + ": " + numDeferred + " " + sinceLast);
             BatteryStatsService.this.noteJobsDeferred(uid, numDeferred, sinceLast);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index e36ea20..0c383eb 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 import static android.text.TextUtils.formatSimple;
@@ -28,6 +29,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
@@ -37,6 +39,7 @@
 import android.app.PendingIntent;
 import android.app.RemoteServiceException.CannotDeliverBroadcastException;
 import android.app.usage.UsageEvents.Event;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.IIntentReceiver;
@@ -44,6 +47,7 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
@@ -67,7 +71,9 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -316,7 +322,11 @@
         final ProcessReceiverRecord prr = app.mReceivers;
         prr.addCurReceiver(r);
         app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
-        mService.updateLruProcessLocked(app, false, null);
+        // Don't bump its LRU position if it's in the background restricted.
+        if (mService.mInternal.getRestrictionLevel(app.info.packageName, app.userId)
+                < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+            mService.updateLruProcessLocked(app, false, null);
+        }
         // Make sure the oom adj score is updated before delivering the broadcast.
         // Force an update, even if there are other pending requests, overall it still saves time,
         // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
@@ -324,6 +334,7 @@
         mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
 
         // Tell the application to launch this receiver.
+        maybeReportBroadcastDispatchedEventLocked(r);
         r.intent.setComponent(r.curComponent);
 
         boolean started = false;
@@ -916,6 +927,7 @@
                 r.receiverTime = SystemClock.uptimeMillis();
                 maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
                 maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
+                maybeReportBroadcastDispatchedEventLocked(r);
                 performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                         new Intent(r.intent), r.resultCode, r.resultData,
                         r.resultExtras, r.ordered, r.initialSticky, r.userId);
@@ -1830,9 +1842,48 @@
         mPendingBroadcastRecvIndex = recIdx;
     }
 
+
+    @Nullable
+    private String getTargetPackage(BroadcastRecord r) {
+        if (r.intent == null) {
+            return null;
+        }
+        if (r.intent.getPackage() != null) {
+            return r.intent.getPackage();
+        } else if (r.intent.getComponent() != null) {
+            return r.intent.getComponent().getPackageName();
+        }
+        return null;
+    }
+
+    private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r) {
+        final String targetPackage = getTargetPackage(r);
+        // Ignore non-explicit broadcasts
+        if (targetPackage == null) {
+            return;
+        }
+        // TODO (206518114): Only allow apps with ACCESS_PACKAGE_USAGE_STATS to set
+        // getIdForResponseEvent.
+        if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
+            return;
+        }
+        // TODO (206518114): Only report this event when the broadcast is dispatched while the app
+        // is in the background state.
+        getUsageStatsManagerInternal().reportBroadcastDispatched(
+                r.callingUid, targetPackage, UserHandle.of(r.userId),
+                r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime());
+    }
+
+    @NonNull
+    private UsageStatsManagerInternal getUsageStatsManagerInternal() {
+        final UsageStatsManagerInternal usageStatsManagerInternal =
+                LocalServices.getService(UsageStatsManagerInternal.class);
+        return usageStatsManagerInternal;
+    }
+
     private boolean noteOpForManifestReceiver(int appOp, BroadcastRecord r, ResolveInfo info,
             ComponentName component) {
-        if (info.activityInfo.attributionTags == null) {
+        if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) {
             return noteOpForManifestReceiverInner(appOp, r, info, component, null);
         } else {
             // Attribution tags provided, noteOp each tag
@@ -2021,6 +2072,13 @@
                 System.identityHashCode(original));
         }
 
+        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+        final String callerPackage = info != null ? info.packageName : original.callerPackage;
+        if (callerPackage != null) {
+            mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+                    original.callingUid, 0, callerPackage).sendToTarget();
+        }
+
         // Note sometimes (only for sticky broadcasts?) we reuse BroadcastRecords,
         // So don't change the incoming record directly.
         final BroadcastRecord historyRecord = original.maybeStripForHistory();
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 3c5b872..df792ee2 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -1046,6 +1046,16 @@
             userId = UserHandle.getCallingUserId();
         }
 
+        if (isAuthorityRedirectedForCloneProfile(authority)) {
+            UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
+            UserInfo userInfo = umInternal.getUserInfo(userId);
+
+            if (userInfo != null && userInfo.isCloneProfile()) {
+                userId = umInternal.getProfileParentId(userId);
+                checkUser = false;
+            }
+        }
+
         ProviderInfo cpi = null;
         try {
             cpi = AppGlobals.getPackageManager().resolveContentProvider(authority,
@@ -1055,17 +1065,6 @@
                             | PackageManager.MATCH_DIRECT_BOOT_AWARE
                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
                     userId);
-            if (cpi == null && isAuthorityRedirectedForCloneProfile(authority)) {
-                // This might be a provider that's running only in the system user that's
-                // also serving clone profiles
-                cpi = AppGlobals.getPackageManager().resolveContentProvider(authority,
-                        ActivityManagerService.STOCK_PM_FLAGS
-                                | PackageManager.GET_URI_PERMISSION_PATTERNS
-                                | PackageManager.MATCH_DISABLED_COMPONENTS
-                                | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                        UserHandle.USER_SYSTEM);
-            }
         } catch (RemoteException ignored) {
         }
         if (cpi == null) {
@@ -1073,16 +1072,6 @@
                     + "; expected to find a valid ContentProvider for this authority";
         }
 
-        if (isAuthorityRedirectedForCloneProfile(authority)) {
-            UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
-            UserInfo userInfo = umInternal.getUserInfo(userId);
-
-            if (userInfo != null && userInfo.isCloneProfile()) {
-                userId = umInternal.getProfileParentId(userId);
-                checkUser = false;
-            }
-        }
-
         final int callingPid = Binder.getCallingPid();
         ProcessRecord r;
         final String appName;
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 68f2e35..b250a0c 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -115,3 +115,4 @@
 30085 ssm_user_unlocked (userId|1|5)
 30086 ssm_user_stopping (userId|1|5)
 30087 ssm_user_stopped (userId|1|5)
+30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5)
diff --git a/services/core/java/com/android/server/am/UidProcessMap.java b/services/core/java/com/android/server/am/UidProcessMap.java
new file mode 100644
index 0000000..f708d37
--- /dev/null
+++ b/services/core/java/com/android/server/am/UidProcessMap.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+/**
+ * Utility class to track mappings between (UID, name) and E.
+ *
+ * @param <E> The type of the values in this map.
+ */
+public class UidProcessMap<E> {
+    final SparseArray<ArrayMap<String, E>> mMap = new SparseArray<>();
+
+    /**
+     * Retrieve a value from the map.
+     */
+    public E get(int uid, String name) {
+        final ArrayMap<String, E> names = mMap.get(uid);
+        if (names == null) {
+            return null;
+        }
+        return names.get(name);
+    }
+
+    /**
+     * Add a new value to the array map.
+     */
+    public E put(int uid, String name, E value) {
+        ArrayMap<String, E> names = mMap.get(uid);
+        if (names == null) {
+            names = new ArrayMap<String, E>(2);
+            mMap.put(uid, names);
+        }
+        names.put(name, value);
+        return value;
+    }
+
+    /**
+     * Remove an existing key (uid, name) from the array map.
+     */
+    public E remove(int uid, String name) {
+        final int index = mMap.indexOfKey(uid);
+        if (index < 0) {
+            return null;
+        }
+        final ArrayMap<String, E> names = mMap.valueAt(index);
+        if (names != null) {
+            final E old = names.remove(name);
+            if (names.isEmpty()) {
+                mMap.removeAt(index);
+            }
+            return old;
+        }
+        return null;
+    }
+
+    /**
+     * Return the underneath map.
+     */
+    public SparseArray<ArrayMap<String, E>> getMap() {
+        return mMap;
+    }
+
+    /**
+     * Return the number of items in this map.
+     */
+    public int size() {
+        return mMap.size();
+    }
+
+    /**
+     * Make the map empty. All storage is released.
+     */
+    public void clear() {
+        mMap.clear();
+    }
+
+    /**
+     * Perform a {@link #put} of all key/value pairs in other.
+     */
+    public void putAll(UidProcessMap<E> other) {
+        for (int i = other.mMap.size() - 1; i >= 0; i--) {
+            final int uid = other.mMap.keyAt(i);
+            final ArrayMap<String, E> names = mMap.get(uid);
+            if (names != null) {
+                names.putAll(other.mMap.valueAt(i));
+            } else {
+                mMap.put(uid, new ArrayMap<String, E>(other.mMap.valueAt(i)));
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 252584c..e4c0846 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -109,6 +109,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.SystemService.UserCompletedEventType;
 import com.android.server.SystemServiceManager;
 import com.android.server.am.UserState.KeyEvictedCallback;
 import com.android.server.pm.UserManagerInternal;
@@ -166,6 +167,7 @@
     static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 110;
     static final int START_USER_SWITCH_FG_MSG = 120;
     static final int COMPLETE_USER_SWITCH_MSG = 130;
+    static final int USER_COMPLETED_EVENT_MSG = 140;
 
     // Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if
     // the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not
@@ -184,6 +186,14 @@
     // when it never calls back.
     private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
 
+    /**
+     * Time after last scheduleOnUserCompletedEvent() call at which USER_COMPLETED_EVENT_MSG will be
+     * scheduled (although it may fire sooner instead).
+     * When it fires, {@link #reportOnUserCompletedEvent} will be processed.
+     */
+    // TODO(b/197344658): Increase to 10s or 15s once we have a switch-UX-is-done invocation too.
+    private static final int USER_COMPLETED_EVENT_DELAY_MS = 5 * 1000;
+
     // Used for statsd logging with UserLifecycleJourneyReported + UserLifecycleEventOccurred atoms
     private static final long INVALID_SESSION_ID = 0;
 
@@ -365,6 +375,13 @@
     private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>();
 
     /**
+     * Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet-
+     * unreported user-starting events have transpired for the given user.
+     */
+    @GuardedBy("mCompletedEventTypes")
+    private final SparseIntArray mCompletedEventTypes = new SparseIntArray();
+
+    /**
      * Sets on {@link #setInitialConfig(boolean, int, boolean)}, which is called by
      * {@code ActivityManager} when the system is started.
      *
@@ -2778,6 +2795,9 @@
 
                 mInjector.getSystemServiceManager().onUserStarting(
                         TimingsTraceAndSlog.newAsyncLog(), msg.arg1);
+                scheduleOnUserCompletedEvent(msg.arg1,
+                        UserCompletedEventType.EVENT_TYPE_USER_STARTING,
+                        USER_COMPLETED_EVENT_DELAY_MS);
 
                 logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
                         USER_LIFECYCLE_EVENT_STATE_FINISH);
@@ -2802,6 +2822,13 @@
                 break;
             case USER_UNLOCKED_MSG:
                 mInjector.getSystemServiceManager().onUserUnlocked(msg.arg1);
+                scheduleOnUserCompletedEvent(msg.arg1,
+                        UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED,
+                        // If it's the foreground user, we wait longer to let it fully load.
+                        // Else, there's nothing specific to wait for, so we basically just proceed.
+                        // (No need to acquire lock to read mCurrentUserId since it is volatile.)
+                        // TODO: Find something to wait for in the case of a profile.
+                        mCurrentUserId == msg.arg1 ? USER_COMPLETED_EVENT_DELAY_MS : 1000);
                 logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER,
                         USER_LIFECYCLE_EVENT_STATE_FINISH);
                 clearSessionId(msg.arg1);
@@ -2815,6 +2842,12 @@
                         Integer.toString(msg.arg1), msg.arg1);
 
                 mInjector.getSystemServiceManager().onUserSwitching(msg.arg2, msg.arg1);
+                scheduleOnUserCompletedEvent(msg.arg1,
+                        UserCompletedEventType.EVENT_TYPE_USER_SWITCHING,
+                        USER_COMPLETED_EVENT_DELAY_MS);
+                break;
+            case USER_COMPLETED_EVENT_MSG:
+                reportOnUserCompletedEvent((Integer) msg.obj);
                 break;
             case FOREGROUND_PROFILE_CHANGED_MSG:
                 dispatchForegroundProfileChanged(msg.arg1);
@@ -2847,6 +2880,71 @@
     }
 
     /**
+     * Schedules {@link SystemServiceManager#onUserCompletedEvent()} with the given
+     * {@link UserCompletedEventType} event, which will be combined with any other events for that
+     * user already scheduled.
+     * If it isn't rescheduled first, it will fire after a delayMs delay.
+     *
+     * @param eventType event type flags from {@link UserCompletedEventType} to append to the
+     *                  schedule. Use 0 to schedule the ssm call without modifying the event types.
+     */
+    // TODO(b/197344658): Also call scheduleOnUserCompletedEvent(userId, 0, 0) after switch UX done.
+    @VisibleForTesting
+    void scheduleOnUserCompletedEvent(
+            int userId, @UserCompletedEventType.EventTypesFlag int eventType, int delayMs) {
+
+        if (eventType != 0) {
+            synchronized (mCompletedEventTypes) {
+                mCompletedEventTypes.put(userId, mCompletedEventTypes.get(userId, 0) | eventType);
+            }
+        }
+
+        final Object msgObj = userId;
+        mHandler.removeEqualMessages(USER_COMPLETED_EVENT_MSG, msgObj);
+        mHandler.sendMessageDelayed(
+                mHandler.obtainMessage(USER_COMPLETED_EVENT_MSG, msgObj),
+                delayMs);
+    }
+
+    /**
+     * Calls {@link SystemServiceManager#onUserCompletedEvent()} for the given user, sending all the
+     * {@link UserCompletedEventType} events that have been scheduled for it if they are still
+     * applicable.
+     *
+     * Called on the mHandler thread.
+     */
+    @VisibleForTesting
+    void reportOnUserCompletedEvent(Integer userId) {
+        mHandler.removeEqualMessages(USER_COMPLETED_EVENT_MSG, userId);
+
+        int eventTypes;
+        synchronized (mCompletedEventTypes) {
+            eventTypes = mCompletedEventTypes.get(userId, 0);
+            mCompletedEventTypes.delete(userId);
+        }
+
+        // Now, remove any eventTypes that are no longer true.
+        int eligibleEventTypes = 0;
+        synchronized (mLock) {
+            final UserState uss = mStartedUsers.get(userId);
+            if (uss != null && uss.state != UserState.STATE_SHUTDOWN) {
+                eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_STARTING;
+            }
+            if (uss != null && uss.state == STATE_RUNNING_UNLOCKED) {
+                eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED;
+            }
+            if (userId == mCurrentUserId) {
+                eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_SWITCHING;
+            }
+        }
+        Slogf.i(TAG, "reportOnUserCompletedEvent(%d): stored=%s, eligible=%s", userId,
+                Integer.toBinaryString(eventTypes), Integer.toBinaryString(eligibleEventTypes));
+        eventTypes &= eligibleEventTypes;
+
+        mInjector.systemServiceManagerOnUserCompletedEvent(userId, eventTypes);
+    }
+
+    /**
      * statsd helper method for logging the start of a user journey via a UserLifecycleEventOccurred
      * atom given the originating and targeting users for the journey.
      */
@@ -3086,7 +3184,11 @@
         }
 
         void systemServiceManagerOnUserStopped(@UserIdInt int userId) {
-            mService.mSystemServiceManager.onUserStopped(userId);
+            getSystemServiceManager().onUserStopped(userId);
+        }
+
+        void systemServiceManagerOnUserCompletedEvent(@UserIdInt int userId, int eventTypes) {
+            getSystemServiceManager().onUserCompletedEvent(userId, eventTypes);
         }
 
         protected UserManagerService getUserManager() {
@@ -3113,7 +3215,7 @@
         }
 
         boolean isRuntimeRestarted() {
-            return mService.mSystemServiceManager.isRuntimeRestarted();
+            return getSystemServiceManager().isRuntimeRestarted();
         }
 
         SystemServiceManager getSystemServiceManager() {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index f5f7bb3..417ebcd 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -938,6 +938,19 @@
         }
     }
 
+    /**
+     * Remove frame rate override due to mode switch
+     */
+    private void resetFps(String packageName, @UserIdInt int userId) {
+        try {
+            final float fps = 0.0f;
+            final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
+            nativeSetOverrideFrameRate(uid, fps);
+        } catch (PackageManager.NameNotFoundException e) {
+            return;
+        }
+    }
+
     private void enableCompatScale(String packageName, long scaleId) {
         final long uid = Binder.clearCallingIdentity();
         try {
@@ -1030,6 +1043,7 @@
         if (gameMode == GameManager.GAME_MODE_STANDARD
                 || gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
             disableCompatScale(packageName);
+            resetFps(packageName, userId);
             return;
         }
         GamePackageConfiguration packageConfig = null;
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
index b4c43f6..73278e4 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.app;
 
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.Intent;
@@ -36,17 +37,19 @@
     private final Context mContext;
 
     GameServiceProviderInstanceFactoryImpl(@NonNull Context context) {
-        this.mContext = context;
+        mContext = context;
     }
 
     @NonNull
     @Override
-    public GameServiceProviderInstance create(@NonNull
-            GameServiceProviderConfiguration gameServiceProviderConfiguration) {
+    public GameServiceProviderInstance create(
+            @NonNull GameServiceProviderConfiguration gameServiceProviderConfiguration) {
         return new GameServiceProviderInstanceImpl(
                 gameServiceProviderConfiguration.getUserHandle(),
                 BackgroundThread.getExecutor(),
+                mContext,
                 new GameClassifierImpl(mContext.getPackageManager()),
+                ActivityManager.getService(),
                 ActivityTaskManager.getService(),
                 (WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE),
                 LocalServices.getService(WindowManagerInternal.class),
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 43c9839..8578de7 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -16,13 +16,18 @@
 
 package com.android.server.app;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
+import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.app.TaskStackListener;
 import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.os.RemoteException;
@@ -110,12 +115,23 @@
                                 gameScreenshotResultFuture);
                     });
                 }
+
+                @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+                public void restartGame(int taskId) {
+                    mContext.enforceCallingPermission(Manifest.permission.FORCE_STOP_PACKAGES,
+                            "restartGame()");
+                    mBackgroundExecutor.execute(() -> {
+                        GameServiceProviderInstanceImpl.this.restartGame(taskId);
+                    });
+                }
             };
 
     private final Object mLock = new Object();
     private final UserHandle mUserHandle;
     private final Executor mBackgroundExecutor;
+    private final Context mContext;
     private final GameClassifier mGameClassifier;
+    private final IActivityManager mActivityManager;
     private final IActivityTaskManager mActivityTaskManager;
     private final WindowManagerService mWindowManagerService;
     private final WindowManagerInternal mWindowManagerInternal;
@@ -131,7 +147,9 @@
     GameServiceProviderInstanceImpl(
             @NonNull UserHandle userHandle,
             @NonNull Executor backgroundExecutor,
+            @NonNull Context context,
             @NonNull GameClassifier gameClassifier,
+            @NonNull IActivityManager activityManager,
             @NonNull IActivityTaskManager activityTaskManager,
             @NonNull WindowManagerService windowManagerService,
             @NonNull WindowManagerInternal windowManagerInternal,
@@ -139,7 +157,9 @@
             @NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector) {
         mUserHandle = userHandle;
         mBackgroundExecutor = backgroundExecutor;
+        mContext = context;
         mGameClassifier = gameClassifier;
+        mActivityManager = activityManager;
         mActivityTaskManager = activityTaskManager;
         mWindowManagerService = windowManagerService;
         mWindowManagerInternal = windowManagerInternal;
@@ -310,6 +330,7 @@
                     + ") is not awaiting game session request. Ignoring.");
             return;
         }
+        mGameSessions.put(taskId, existingGameSessionRecord.withGameSessionRequested());
 
         GameSessionViewHostConfiguration gameSessionViewHostConfiguration =
                 createViewHostConfigurationForTask(taskId);
@@ -532,4 +553,27 @@
             }
         });
     }
+
+    private void restartGame(int taskId) {
+        String packageName;
+
+        synchronized (mLock) {
+            boolean isTaskAssociatedWithGameSession = mGameSessions.containsKey(taskId);
+            if (!isTaskAssociatedWithGameSession) {
+                return;
+            }
+
+            packageName = mGameSessions.get(taskId).getComponentName().getPackageName();
+        }
+
+        try {
+            mActivityManager.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        Intent launchIntent =
+                mContext.getPackageManager().getLaunchIntentForPackage(packageName);
+        mContext.startActivity(launchIntent);
+    }
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 565e295..65be5f0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -59,6 +59,7 @@
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 
@@ -1794,4 +1795,10 @@
         }
         return null;
     }
+
+    UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.getDeviceSensorUuid(device);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 2dd6bf5..8a62c34 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -48,7 +48,9 @@
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * Class to manage the inventory of all connected devices.
@@ -185,12 +187,20 @@
         final @NonNull String mDeviceName;
         final @NonNull String mDeviceAddress;
         int mDeviceCodecFormat;
+        final UUID mSensorUuid;
 
-        DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
+        DeviceInfo(int deviceType, String deviceName, String deviceAddress,
+                   int deviceCodecFormat, UUID sensorUuid) {
             mDeviceType = deviceType;
             mDeviceName = deviceName == null ? "" : deviceName;
             mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
             mDeviceCodecFormat = deviceCodecFormat;
+            mSensorUuid = sensorUuid;
+        }
+
+        DeviceInfo(int deviceType, String deviceName, String deviceAddress,
+                   int deviceCodecFormat) {
+            this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null);
         }
 
         @Override
@@ -199,7 +209,8 @@
                     + " (" + AudioSystem.getDeviceName(mDeviceType)
                     + ") name:" + mDeviceName
                     + " addr:" + mDeviceAddress
-                    + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
+                    + " codec: " + Integer.toHexString(mDeviceCodecFormat)
+                    + " sensorUuid: " + Objects.toString(mSensorUuid) + "]";
         }
 
         @NonNull String getKey() {
@@ -980,8 +991,13 @@
         // Reset A2DP suspend state each time a new sink is connected
         mAudioSystem.setParameters("A2dpSuspended=false");
 
+        // The convention for head tracking sensors associated with A2DP devices is to
+        // use a UUID derived from the MAC address as follows:
+        //   time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
+        UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(
+                new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
         final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
-                address, a2dpCodec);
+                address, a2dpCodec, sensorUuid);
         final String diKey = di.getKey();
         mConnectedDevices.put(diKey, di);
         // on a connection always overwrite the device seen by AudioPolicy, see comment above when
@@ -1360,9 +1376,6 @@
             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) {
@@ -1456,6 +1469,17 @@
         mDevRoleCapturePresetDispatchers.finishBroadcast();
     }
 
+    UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+        final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
+                device.getAddress());
+        synchronized (mDevicesLock) {
+            DeviceInfo di = mConnectedDevices.get(key);
+            if (di == null) {
+                return null;
+            }
+            return di.mSensorUuid;
+        }
+    }
     //----------------------------------------------------------
     // For tests only
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0ea936e..a853ac5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -190,6 +190,7 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -11017,6 +11018,10 @@
         return delayMillis;
     }
 
+    UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+        return mDeviceBroker.getDeviceSensorUuid(device);
+    }
+
     //======================
     // misc
     //======================
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 6ec9836..42fca9b 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -371,7 +371,7 @@
      * @return false if SCO isn't connected
      */
     /*package*/ synchronized boolean isBluetoothScoOn() {
-        if (mBluetoothHeadset == null) {
+        if (mBluetoothHeadset == null || mBluetoothHeadsetDevice == null) {
             return false;
         }
         return mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
@@ -505,7 +505,7 @@
         // Discard timeout message
         mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
         mBluetoothHeadset = headset;
-        setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
+        setBtScoActiveDevice(headset != null ? headset.getActiveDevice() : null);
         // Refresh SCO audio state
         checkScoAudioState();
         if (mScoAudioState != SCO_STATE_ACTIVATE_REQ
@@ -513,7 +513,7 @@
             return;
         }
         boolean status = false;
-        if (mBluetoothHeadsetDevice != null) {
+        if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
             switch (mScoAudioState) {
                 case SCO_STATE_ACTIVATE_REQ:
                     status = connectBluetoothScoAudioHelper(
@@ -552,6 +552,9 @@
     }
 
     private AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
+        if (btDevice == null) {
+            return new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
+        }
         String address = btDevice.getAddress();
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index e6789d5..106cbba 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -43,6 +43,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.UUID;
 
 /**
  * A helper class to manage Spatializer related functionality
@@ -168,6 +169,7 @@
             return;
         }
         mState = STATE_DISABLED_UNAVAILABLE;
+        mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES);
         // note at this point mSpat is still not instantiated
     }
 
@@ -204,6 +206,11 @@
         logd("onRoutingUpdated: can spatialize media 5.1:" + able
                 + " on device:" + ROUTING_DEVICES[0]);
         setDispatchAvailableState(able);
+
+        if (mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED
+                && mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_DISABLED) {
+            postInitSensors();
+        }
     }
 
     //------------------------------------------------------
@@ -909,12 +916,21 @@
                 }
             }
             // initialize sensor handles
-            // TODO-HT update to non-private sensor once head tracker sensor is defined
-            for (Sensor sensor : mSensorManager.getDynamicSensorList(
-                    Sensor.TYPE_DEVICE_PRIVATE_BASE)) {
-                if (sensor.getStringType().equals(HEADTRACKER_SENSOR)) {
-                    headHandle = sensor.getHandle();
-                    break;
+            UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]);
+            List<Sensor> sensors = new ArrayList<Sensor>(0);
+            sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER));
+            sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_DEVICE_PRIVATE_BASE));
+            for (Sensor sensor : sensors) {
+                if (sensor.getType() == Sensor.TYPE_HEAD_TRACKER
+                        || sensor.getStringType().equals(HEADTRACKER_SENSOR)) {
+                    UUID uuid = sensor.getUuid();
+                    if (uuid.equals(routingDeviceUuid)) {
+                        headHandle = sensor.getHandle();
+                        break;
+                    }
+                    if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
+                        headHandle = sensor.getHandle();
+                    }
                 }
             }
             Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
diff --git a/services/core/java/com/android/server/audio/UuidUtils.java b/services/core/java/com/android/server/audio/UuidUtils.java
new file mode 100644
index 0000000..2003619
--- /dev/null
+++ b/services/core/java/com/android/server/audio/UuidUtils.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.audio;
+
+import android.media.AudioDeviceAttributes;
+import android.media.AudioSystem;
+import android.util.Slog;
+
+import java.util.UUID;
+
+/**
+ *  UuidUtils class implements helper functions to handle unique identifiers
+ *  used to associate head tracking sensors to audio devices.
+ */
+class UuidUtils {
+    private static final String TAG = "AudioService.UuidUtils";
+
+    private static final long LSB_PREFIX_MASK = 0xFFFF000000000000L;
+    private static final long LSB_SUFFIX_MASK = 0x0000FFFFFFFFFFFFL;
+    // The sensor UUID for Bluetooth devices is defined as follows:
+    // - 8 most significant bytes: All 0s
+    // - 8 most significant bytes: Ascii B, Ascii T, Device MAC address on 6 bytes
+    private static final long LSB_PREFIX_BT = 0x4254000000000000L;
+
+    /**
+     * Special UUID for a head tracking sensor not associated with an audio device.
+     */
+    public static final UUID STANDALONE_UUID = new UUID(0, 0);
+
+    /**
+     *  Generate a headtracking UUID from AudioDeviceAttributes
+     */
+    public static UUID uuidFromAudioDeviceAttributes(AudioDeviceAttributes device) {
+        switch (device.getInternalType()) {
+            case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP:
+                String address = device.getAddress().replace(":", "");
+                if (address.length() != 12) {
+                    return null;
+                }
+                address = "0x" + address;
+                long lsb = LSB_PREFIX_BT;
+                try {
+                    lsb |= Long.decode(address).longValue();
+                } catch (NumberFormatException e) {
+                    return null;
+                }
+                if (AudioService.DEBUG_DEVICES) {
+                    Slog.i(TAG, "uuidFromAudioDeviceAttributes lsb: " + Long.toHexString(lsb));
+                }
+                return new UUID(0, lsb);
+            default:
+                // Handle other device types here
+                return null;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 86d72ba..8b8103e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -29,6 +29,8 @@
 import android.os.Vibrator;
 import android.util.Slog;
 
+import java.util.function.Supplier;
+
 /**
  * Abstract {@link HalClientMonitor} subclass that operations eligible/interested in acquisition
  * messages should extend.
@@ -52,12 +54,7 @@
     private boolean mShouldSendErrorToClient = true;
     private boolean mAlreadyCancelled;
 
-    /**
-     * Stops the HAL operation specific to the ClientMonitor subclass.
-     */
-    protected abstract void stopHalOperation();
-
-    public AcquisitionClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    public AcquisitionClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int cookie, int sensorId, boolean shouldVibrate,
             int statsModality, int statsAction, int statsClient) {
@@ -67,6 +64,11 @@
         mShouldVibrate = shouldVibrate;
     }
 
+    /**
+     * Stops the HAL operation specific to the ClientMonitor subclass.
+     */
+    protected abstract void stopHalOperation();
+
     @Override
     public void unableToStart() {
         try {
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 35a0f57..b715faf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -42,6 +42,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * A class to keep track of the authentication state for a given client.
@@ -89,23 +90,7 @@
     //  the state. We should think of a way to improve this in the future.
     protected @State int mState = STATE_NEW;
 
-    /**
-     * Handles lifecycle, e.g. {@link BiometricScheduler},
-     * {@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.
-     * @param authenticated
-     */
-    protected abstract void handleLifecycleAfterAuth(boolean authenticated);
-
-    /**
-     * @return true if a user was detected (i.e. face was found, fingerprint sensor was touched.
-     *         etc)
-     */
-    public abstract boolean wasUserDetected();
-
-    public AuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int targetUserId, long operationId, boolean restricted, @NonNull String owner,
             int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric,
@@ -474,6 +459,22 @@
         }
     }
 
+    /**
+     * Handles lifecycle, e.g. {@link BiometricScheduler},
+     * {@link com.android.server.biometrics.sensors.BaseClientMonitor.Callback} 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.
+     * @param authenticated
+     */
+    protected abstract void handleLifecycleAfterAuth(boolean authenticated);
+
+    /**
+     * @return true if a user was detected (i.e. face was found, fingerprint sensor was touched.
+     *         etc)
+     */
+    public abstract boolean wasUserDetected();
+
     public @State int getState() {
         return mState;
     }
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 3b7adc1..74f4931 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -29,6 +29,7 @@
 import com.android.server.biometrics.BiometricsProto;
 
 import java.util.Arrays;
+import java.util.function.Supplier;
 
 /**
  * A class to keep track of the enrollment state for a given client.
@@ -49,7 +50,7 @@
      */
     protected abstract boolean hasReachedEnrollmentLimit();
 
-    public EnrollClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    public EnrollClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
             int timeoutSec, int statsModality, int sensorId, boolean shouldVibrate) {
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 6fb6d08..9689418 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -25,11 +25,13 @@
 
 import com.android.server.biometrics.BiometricsProto;
 
+import java.util.function.Supplier;
+
 public abstract class GenerateChallengeClient<T> extends HalClientMonitor<T> {
 
     private static final String TAG = "GenerateChallengeClient";
 
-    public GenerateChallengeClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    public GenerateChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int userId, @NonNull String owner, int sensorId) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
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 c8830f8..66a1c6e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -22,35 +22,16 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 
+import java.util.function.Supplier;
+
 /**
  * Abstract {@link BaseClientMonitor} implementation that supports HAL operations.
  * @param <T> HAL template
  */
 public abstract class HalClientMonitor<T> extends BaseClientMonitor {
-    /**
-     * Interface that allows ClientMonitor subclasses to retrieve a fresh instance to the HAL.
-     */
-    public interface LazyDaemon<T> {
-        /**
-         * @return A fresh instance to the biometric HAL
-         */
-        T getDaemon();
-    }
-
-    /**
-     * Starts the HAL operation specific to the ClientMonitor subclass.
-     */
-    protected abstract void startHalOperation();
-
-    /**
-     * 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(ClientMonitorCallback)}.
-     */
-    public abstract void unableToStart();
-
+    
     @NonNull
-    protected final LazyDaemon<T> mLazyDaemon;
+    protected final Supplier<T> mLazyDaemon;
 
     /**
      * @param context    system_server context
@@ -65,7 +46,7 @@
      * @param statsAction   One of {@link BiometricsProtoEnums} ACTION_* constants
      * @param statsClient   One of {@link BiometricsProtoEnums} CLIENT_* constants
      */
-    public HalClientMonitor(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    public HalClientMonitor(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
             int statsClient) {
@@ -76,6 +57,18 @@
 
     @Nullable
     public T getFreshDaemon() {
-        return mLazyDaemon.getDaemon();
+        return mLazyDaemon.get();
     }
+
+    /**
+     * Starts the HAL operation specific to the ClientMonitor subclass.
+     */
+    protected abstract void startHalOperation();
+
+    /**
+     * 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(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 0636893..0e6d11e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -28,6 +28,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Wraps {@link InternalEnumerateClient} and {@link RemovalClient}. Keeps track of all the
@@ -99,14 +100,14 @@
     };
 
     protected abstract InternalEnumerateClient<T> getEnumerateClient(Context context,
-            LazyDaemon<T> lazyDaemon, IBinder token, int userId, String owner,
+            Supplier<T> lazyDaemon, IBinder token, int userId, String owner,
             List<S> enrolledList, BiometricUtils<S> utils, int sensorId);
 
     protected abstract RemovalClient<S, T> getRemovalClient(Context context,
-            LazyDaemon<T> lazyDaemon, IBinder token, int biometricId, int userId, String owner,
+            Supplier<T> lazyDaemon, IBinder token, int biometricId, int userId, String owner,
             BiometricUtils<S> utils, int sensorId, Map<Integer, Long> authenticatorIds);
 
-    protected InternalCleanupClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    protected InternalCleanupClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             int userId, @NonNull String owner, int sensorId, int statsModality,
             @NonNull List<S> enrolledList, @NonNull BiometricUtils<S> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
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 05ea19a..5f97f37 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -27,6 +27,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * Internal class to help clean up unknown templates in the HAL and Framework
@@ -43,7 +44,7 @@
     // List of templates to remove from the HAL
     private List<BiometricAuthenticator.Identifier> mUnknownHALTemplates = new ArrayList<>();
 
-    protected InternalEnumerateClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, int userId, @NonNull String owner,
             @NonNull List<? extends BiometricAuthenticator.Identifier> enrolledList,
             @NonNull BiometricUtils utils, int sensorId, int statsModality) {
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 ee6bb0f..697d77c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
@@ -27,6 +27,7 @@
 import com.android.server.biometrics.BiometricsProto;
 
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * ClientMonitor subclass for requesting authenticatorId invalidation. See
@@ -40,7 +41,7 @@
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
     @NonNull private final IInvalidationCallback mInvalidationCallback;
 
-    public InvalidationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    public InvalidationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             int userId, int sensorId, @NonNull Map<Integer, Long> authenticatorIds,
             @NonNull IInvalidationCallback callback) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId,
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 e79819b..a0cef94 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -27,6 +27,7 @@
 import com.android.server.biometrics.BiometricsProto;
 
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * A class to keep track of the remove state for a given client.
@@ -40,7 +41,7 @@
     private final Map<Integer, Long> mAuthenticatorIds;
     private final boolean mHasEnrollmentsBeforeStarting;
 
-    public RemovalClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    public RemovalClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils, int sensorId,
             @NonNull Map<Integer, Long> authenticatorIds, int statsModality) {
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 21a6ddf..7d83863 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -23,9 +23,11 @@
 
 import com.android.server.biometrics.BiometricsProto;
 
+import java.util.function.Supplier;
+
 public abstract class RevokeChallengeClient<T> extends HalClientMonitor<T> {
 
-    public RevokeChallengeClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    public RevokeChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, int userId, @NonNull String owner, int sensorId) {
         super(context, lazyDaemon, token, null /* listener */, userId, owner,
                 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
index 3d69326..1bc3248 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -25,6 +25,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
 
+import java.util.function.Supplier;
+
 /**
  * Abstract class for starting a new user.
  * @param <T> Interface to request a new user.
@@ -37,13 +39,13 @@
      * @param <U> New user object.
      */
     public interface UserStartedCallback<U> {
-        void onUserStarted(int newUserId, U newUser);
+        void onUserStarted(int newUserId, U newUser, int halInterfaceVersion);
     }
 
     @NonNull @VisibleForTesting
     protected final UserStartedCallback<U> mUserStartedCallback;
 
-    public StartUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    public StartUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
             @NonNull UserStartedCallback<U> callback) {
         super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
index 1f6e1e9..3eafbb8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -25,6 +25,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
 
+import java.util.function.Supplier;
+
 /**
  * Abstract class for stopping a user.
  * @param <T> Interface for stopping the user.
@@ -43,7 +45,7 @@
         getCallback().onClientFinished(this, true /* success */);
     }
 
-    public StopUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+    public StopUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
             @NonNull UserStoppedCallback callback) {
         super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
new file mode 100644
index 0000000..006667a
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.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.server.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.face.ISession;
+
+import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
+
+/**
+ * A holder for an AIDL {@link ISession} with additional metadata about the current user
+ * and the backend.
+ */
+public class AidlSession {
+
+    private final int mHalInterfaceVersion;
+    @NonNull
+    private final ISession mSession;
+    private final int mUserId;
+    @NonNull private final HalSessionCallback mHalSessionCallback;
+
+    public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
+            HalSessionCallback halSessionCallback) {
+        mHalInterfaceVersion = halInterfaceVersion;
+        mSession = session;
+        mUserId = userId;
+        mHalSessionCallback = halSessionCallback;
+    }
+
+    /** The underlying {@link ISession}. */
+    @NonNull public ISession getSession() {
+        return mSession;
+    }
+
+    /** The user id associated with the session. */
+    public int getUserId() {
+        return mUserId;
+    }
+
+    /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
+    HalSessionCallback getHalSessionCallback() {
+        return mHalSessionCallback;
+    }
+
+    /**
+     * If this backend implements the *WithContext methods for enroll, authenticate, and
+     * detectInteraction. These variants should always be called if they are available.
+     */
+    public boolean hasContextMethods() {
+        return mHalInterfaceVersion >= 2;
+    }
+}
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 8998269..9bd7476 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
@@ -165,7 +165,7 @@
         }
 
         mEnrollmentIds.add(nextRandomId);
-        mSensor.getSessionForUser(userId).mHalSessionCallback
+        mSensor.getSessionForUser(userId).getHalSessionCallback()
                 .onEnrollmentProgress(nextRandomId, 0 /* remaining */);
     }
 
@@ -181,7 +181,7 @@
             return;
         }
         final int fid = faces.get(0).getBiometricId();
-        mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationSucceeded(fid,
+        mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationSucceeded(fid,
                 HardwareAuthTokenUtils.toHardwareAuthToken(new byte[69]));
     }
 
@@ -189,7 +189,7 @@
     public void rejectAuthentication(int userId)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFailed();
+        mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
     }
 
     // TODO(b/178414967): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
@@ -205,7 +205,7 @@
 
         // TODO(b/178414967): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
         // This will need to call the correct callback once the onAcquired callback is removed.
-        mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFrame(
+        mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFrame(
                 authenticationFrame);
     }
 
@@ -213,7 +213,7 @@
     public void notifyError(int userId, int errorCode)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mSensor.getSessionForUser(userId).mHalSessionCallback.onError((byte) errorCode,
+        mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode,
                 0 /* vendorCode */);
     }
 
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 dc21a04f..c4e0502 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
@@ -27,8 +27,9 @@
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
 import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
 import android.hardware.face.FaceAuthenticationFrame;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
@@ -48,11 +49,13 @@
 import com.android.server.biometrics.sensors.face.UsageStats;
 
 import java.util.ArrayList;
+import java.util.function.Supplier;
 
 /**
  * Face-specific authentication client for the {@link IFace} AIDL HAL interface.
  */
-class FaceAuthenticationClient extends AuthenticationClient<ISession> implements LockoutConsumer {
+class FaceAuthenticationClient extends AuthenticationClient<AidlSession>
+        implements LockoutConsumer {
     private static final String TAG = "FaceAuthenticationClient";
 
     @NonNull private final UsageStats mUsageStats;
@@ -69,7 +72,7 @@
     @FaceManager.FaceAcquired private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
 
     FaceAuthenticationClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon,
+            @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
@@ -122,7 +125,7 @@
                         0 /* vendorCode */);
                 mCallback.onClientFinished(this, false /* success */);
             } else {
-                mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+                mCancellationSignal = doAuthenticate();
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting auth", e);
@@ -131,6 +134,22 @@
         }
     }
 
+    private ICancellationSignal doAuthenticate() throws RemoteException {
+        final AidlSession session = getFreshDaemon();
+
+        if (session.hasContextMethods()) {
+            final OperationContext context = new OperationContext();
+            // TODO: add reason, id, and isAoD
+            context.id = 0;
+            context.reason = OperationReason.UNKNOWN;
+            context.isAoD = false;
+            context.isCrypto = isCryptoOperation();
+            return session.getSession().authenticateWithContext(mOperationId, context);
+        } else {
+            return session.getSession().authenticate(mOperationId);
+        }
+    }
+
     @Override
     protected void stopHalOperation() {
         if (mCancellationSignal != null) {
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 72a20db07..3f3db43 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
@@ -23,7 +23,8 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -34,11 +35,13 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.DetectionConsumer;
 
+import java.util.function.Supplier;
+
 /**
  * Performs face detection without exposing any matching information (e.g. accept/reject have the
  * same haptic, lockout counter is not increased).
  */
-public class FaceDetectClient extends AcquisitionClient<ISession> implements DetectionConsumer {
+public class FaceDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer {
 
     private static final String TAG = "FaceDetectClient";
 
@@ -46,7 +49,7 @@
     @Nullable private ICancellationSignal mCancellationSignal;
     @Nullable private SensorPrivacyManager mSensorPrivacyManager;
 
-    public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+    FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId, boolean isStrongBiometric, int statsClient) {
@@ -87,13 +90,29 @@
         }
 
         try {
-            mCancellationSignal = getFreshDaemon().detectInteraction();
+            mCancellationSignal = doDetectInteraction();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting face detect", e);
             mCallback.onClientFinished(this, false /* success */);
         }
     }
 
+    private ICancellationSignal doDetectInteraction() throws RemoteException {
+        final AidlSession session = getFreshDaemon();
+
+        if (session.hasContextMethods()) {
+            final OperationContext context = new OperationContext();
+            // TODO: add reason, id, and isAoD
+            context.id = 0;
+            context.reason = OperationReason.UNKNOWN;
+            context.isAoD = false;
+            context.isCrypto = isCryptoOperation();
+            return session.getSession().detectInteractionWithContext(context);
+        } else {
+            return session.getSession().detectInteraction();
+        }
+    }
+
     @Override
     public void onInteractionDetected() {
         vibrateSuccess();
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 5c57dbb..8dc53b6 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
@@ -22,14 +22,16 @@
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
 import android.hardware.biometrics.face.EnrollmentType;
 import android.hardware.biometrics.face.Feature;
 import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
 import android.hardware.common.NativeHandle;
 import android.hardware.face.Face;
 import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.FaceManager;
+import android.hardware.keymaster.HardwareAuthToken;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -51,11 +53,12 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * Face-specific enroll client for the {@link IFace} AIDL HAL interface.
  */
-public class FaceEnrollClient extends EnrollClient<ISession> {
+public class FaceEnrollClient extends EnrollClient<AidlSession> {
 
     private static final String TAG = "FaceEnrollClient";
 
@@ -82,7 +85,7 @@
                 }
             };
 
-    FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+    FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
@@ -177,14 +180,12 @@
                 featureList.add(Feature.REQUIRE_DIVERSE_POSES);
             }
 
-            byte[] features = new byte[featureList.size()];
+            final byte[] features = new byte[featureList.size()];
             for (int i = 0; i < featureList.size(); i++) {
                 features[i] = featureList.get(i);
             }
 
-            mCancellationSignal = getFreshDaemon().enroll(
-                    HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken),
-                    EnrollmentType.DEFAULT, features, mHwPreviewHandle);
+            mCancellationSignal = doEnroll(features);
         } catch (RemoteException | IllegalArgumentException e) {
             Slog.e(TAG, "Exception when requesting enroll", e);
             onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
@@ -192,6 +193,26 @@
         }
     }
 
+    private ICancellationSignal doEnroll(byte[] features) throws RemoteException {
+        final AidlSession session = getFreshDaemon();
+        final HardwareAuthToken hat =
+                HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
+
+        if (session.hasContextMethods()) {
+            final OperationContext context = new OperationContext();
+            // TODO: add reason, id, and isAoD
+            context.id = 0;
+            context.reason = OperationReason.UNKNOWN;
+            context.isAoD = false;
+            context.isCrypto = isCryptoOperation();
+            return session.getSession().enrollWithContext(
+                    hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, context);
+        } else {
+            return session.getSession().enroll(hat, EnrollmentType.DEFAULT, features,
+                    mHwPreviewHandle);
+        }
+    }
+
     @Override
     protected void stopHalOperation() {
         if (mCancellationSignal != null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index 7cdeebb..bdad268 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -27,14 +26,16 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
+import java.util.function.Supplier;
+
 /**
  * Face-specific generateChallenge client for the {@link IFace} AIDL HAL interface.
  */
-public class FaceGenerateChallengeClient extends GenerateChallengeClient<ISession> {
+public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
     private static final String TAG = "FaceGenerateChallengeClient";
 
     FaceGenerateChallengeClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+            @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
             int sensorId) {
         super(context, lazyDaemon, token, listener, userId, owner, sensorId);
@@ -43,7 +44,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().generateChallenge();
+            getFreshDaemon().getSession().generateChallenge();
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to generateChallenge", e);
             mCallback.onClientFinished(this, false /* success */);
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 584b58c..2f3187b 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
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.face.ISession;
 import android.os.RemoteException;
 import android.util.Slog;
 
@@ -28,14 +27,16 @@
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.util.Map;
+import java.util.function.Supplier;
 
-class FaceGetAuthenticatorIdClient extends HalClientMonitor<ISession> {
+class FaceGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
 
     private static final String TAG = "FaceGetAuthenticatorIdClient";
 
     private final Map<Integer, Long> mAuthenticatorIds;
 
-    FaceGetAuthenticatorIdClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+    FaceGetAuthenticatorIdClient(@NonNull Context context,
+            @NonNull Supplier<AidlSession> lazyDaemon,
             int userId, @NonNull String opPackageName, int sensorId,
             Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, opPackageName,
@@ -57,7 +58,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().getAuthenticatorId();
+            getFreshDaemon().getSession().getAuthenticatorId();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
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 acf5720..79479be 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
@@ -22,7 +22,6 @@
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.provider.Settings;
@@ -36,17 +35,18 @@
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Face-specific get feature client for the {@link IFace} AIDL HAL interface.
  */
-public class FaceGetFeatureClient extends HalClientMonitor<ISession> implements ErrorConsumer {
+public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
 
     private static final String TAG = "FaceGetFeatureClient";
 
     private final int mUserId;
 
-    FaceGetFeatureClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+    FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
@@ -69,7 +69,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().getFeatures();
+            getFreshDaemon().getSession().getFeatures();
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to getFeature", e);
             mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index c6696aed..a2b0339 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
 import android.hardware.face.Face;
 import android.os.IBinder;
 
@@ -31,14 +30,15 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Face-specific internal cleanup client for the {@link IFace} AIDL HAL interface.
  */
-class FaceInternalCleanupClient extends InternalCleanupClient<Face, ISession> {
+class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> {
 
     FaceInternalCleanupClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, int userId, @NonNull String owner,
+            @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
             int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE,
@@ -46,16 +46,16 @@
     }
 
     @Override
-    protected InternalEnumerateClient<ISession> getEnumerateClient(Context context,
-            LazyDaemon<ISession> lazyDaemon, IBinder token, int userId, String owner,
+    protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context,
+            Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner,
             List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) {
         return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
                 enrolledList, utils, sensorId);
     }
 
     @Override
-    protected RemovalClient<Face, ISession> getRemovalClient(Context context,
-            LazyDaemon<ISession> lazyDaemon, IBinder token,
+    protected RemovalClient<Face, AidlSession> getRemovalClient(Context context,
+            Supplier<AidlSession> lazyDaemon, IBinder token,
             int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
             Map<Integer, Long> authenticatorIds) {
         // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
index 0ece884..88c9d3b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -30,15 +29,16 @@
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * Face-specific internal enumerate client for the {@link IFace} AIDL HAL interface.
  */
-class FaceInternalEnumerateClient extends InternalEnumerateClient<ISession> {
+class FaceInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
     private static final String TAG = "FaceInternalEnumerateClient";
 
     FaceInternalEnumerateClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, int userId,
+            @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId,
             @NonNull String owner, @NonNull List<Face> enrolledList,
             @NonNull BiometricUtils<Face> utils, int sensorId) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
@@ -48,7 +48,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().enumerateEnrollments();
+            getFreshDaemon().getSession().enumerateEnrollments();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enumerate", e);
             mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
index 405e2b2..04ea2cfc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.IInvalidationCallback;
-import android.hardware.biometrics.face.ISession;
 import android.hardware.face.Face;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -27,12 +26,13 @@
 import com.android.server.biometrics.sensors.InvalidationClient;
 
 import java.util.Map;
+import java.util.function.Supplier;
 
-public class FaceInvalidationClient extends InvalidationClient<Face, ISession> {
+public class FaceInvalidationClient extends InvalidationClient<Face, AidlSession> {
     private static final String TAG = "FaceInvalidationClient";
 
     public FaceInvalidationClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, int userId, int sensorId,
+            @NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId,
             @NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) {
         super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback);
     }
@@ -40,7 +40,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().invalidateAuthenticatorId();
+            getFreshDaemon().getSession().invalidateAuthenticatorId();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index ba678f3..130a05a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -31,16 +30,17 @@
 import com.android.server.biometrics.sensors.RemovalClient;
 
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Face-specific removal client for the {@link IFace} AIDL HAL interface.
  */
-class FaceRemovalClient extends RemovalClient<Face, ISession> {
+class FaceRemovalClient extends RemovalClient<Face, AidlSession> {
     private static final String TAG = "FaceRemovalClient";
 
     final int[] mBiometricIds;
 
-    FaceRemovalClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+    FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int[] biometricIds, int userId, @NonNull String owner,
             @NonNull BiometricUtils<Face> utils, int sensorId,
@@ -53,7 +53,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().removeEnrollments(mBiometricIds);
+            getFreshDaemon().getSession().removeEnrollments(mBiometricIds);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting remove", e);
             mCallback.onClientFinished(this, false /* success */);
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 fd44c5c..67bf3f5 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
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -34,12 +33,14 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
+import java.util.function.Supplier;
+
 /**
  * Face-specific resetLockout client for the {@link IFace} AIDL HAL interface.
  * Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is
  * cleared.
  */
-public class FaceResetLockoutClient extends HalClientMonitor<ISession> implements ErrorConsumer {
+public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
 
     private static final String TAG = "FaceResetLockoutClient";
 
@@ -48,7 +49,7 @@
     private final LockoutResetDispatcher mLockoutResetDispatcher;
 
     FaceResetLockoutClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, int userId, String owner, int sensorId,
+            @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
             @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
@@ -73,7 +74,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().resetLockout(mHardwareAuthToken);
+            getFreshDaemon().getSession().resetLockout(mHardwareAuthToken);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to reset lockout", e);
             mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
index 7a69c44..acd2e05 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
@@ -19,24 +19,25 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
+import java.util.function.Supplier;
+
 /**
  * Face-specific revokeChallenge client for the {@link IFace} AIDL HAL interface.
  */
-public class FaceRevokeChallengeClient extends RevokeChallengeClient<ISession> {
+public class FaceRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
 
     private static final String TAG = "FaceRevokeChallengeClient";
 
     private final long mChallenge;
 
     FaceRevokeChallengeClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+            @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             int userId, @NonNull String owner, int sensorId, long challenge) {
         super(context, lazyDaemon, token, userId, owner, sensorId);
         mChallenge = challenge;
@@ -45,7 +46,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().revokeChallenge(mChallenge);
+            getFreshDaemon().getSession().revokeChallenge(mChallenge);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to revokeChallenge", e);
             mCallback.onClientFinished(this, false /* success */);
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 ee6982a..9d535a2 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
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -33,10 +32,12 @@
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
+import java.util.function.Supplier;
+
 /**
  * Face-specific get feature client for the {@link IFace} AIDL HAL interface.
  */
-public class FaceSetFeatureClient extends HalClientMonitor<ISession> implements ErrorConsumer {
+public class FaceSetFeatureClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
 
     private static final String TAG = "FaceSetFeatureClient";
 
@@ -44,7 +45,7 @@
     private final boolean mEnabled;
     private final HardwareAuthToken mHardwareAuthToken;
 
-    FaceSetFeatureClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+    FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId, int feature, boolean enabled,
             byte[] hardwareAuthToken) {
@@ -74,8 +75,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon()
-                    .setFeature(mHardwareAuthToken,
+            getFreshDaemon().getSession().setFeature(mHardwareAuthToken,
                     AidlConversionUtils.convertFrameworkToAidlFeature(mFeature), mEnabled);
         } catch (RemoteException | IllegalArgumentException e) {
             Slog.e(TAG, "Unable to set feature: " + mFeature + " to enabled: " + mEnabled, e);
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 4a3da0d..f5a98ff 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
@@ -30,12 +30,15 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StartUserClient;
 
+import java.util.function.Supplier;
+
 public class FaceStartUserClient extends StartUserClient<IFace, ISession> {
     private static final String TAG = "FaceStartUserClient";
 
     @NonNull private final ISessionCallback mSessionCallback;
 
-    public FaceStartUserClient(@NonNull Context context, @NonNull LazyDaemon<IFace> lazyDaemon,
+    public FaceStartUserClient(@NonNull Context context,
+            @NonNull Supplier<IFace> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
             @NonNull ISessionCallback sessionCallback,
             @NonNull UserStartedCallback<ISession> callback) {
@@ -52,10 +55,12 @@
     @Override
     protected void startHalOperation() {
         try {
-            final ISession newSession = getFreshDaemon().createSession(getSensorId(),
+            final IFace hal = getFreshDaemon();
+            final int version = hal.getInterfaceVersion();
+            final ISession newSession = hal.createSession(getSensorId(),
                     getTargetUserId(), mSessionCallback);
             Binder.allowBlocking(newSession.asBinder());
-            mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
+            mUserStartedCallback.onUserStarted(getTargetUserId(), newSession, version);
             getCallback().onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
@@ -65,6 +70,5 @@
 
     @Override
     public void unableToStart() {
-
     }
 }
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 88b9235..48b4856 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
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.face.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -27,10 +26,12 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StopUserClient;
 
-public class FaceStopUserClient extends StopUserClient<ISession> {
+import java.util.function.Supplier;
+
+public class FaceStopUserClient extends StopUserClient<AidlSession> {
     private static final String TAG = "FaceStopUserClient";
 
-    public FaceStopUserClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+    public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
             @NonNull UserStoppedCallback callback) {
         super(context, lazyDaemon, token, userId, sensorId, callback);
@@ -45,7 +46,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().close();
+            getFreshDaemon().getSession().close();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             getCallback().onClientFinished(this, false /* success */);
@@ -54,6 +55,5 @@
 
     @Override
     public void unableToStart() {
-
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 206b8f0..33e6fa4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -53,7 +53,6 @@
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.Interruptable;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
@@ -67,6 +66,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Maintains the state of a single sensor within an instance of the {@link IFace} HAL.
@@ -84,24 +84,9 @@
     @NonNull private final UserAwareBiometricScheduler mScheduler;
     @NonNull private final LockoutCache mLockoutCache;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
-    @NonNull private final HalClientMonitor.LazyDaemon<ISession> mLazySession;
-    @Nullable private Session mCurrentSession;
 
-    static class Session {
-        @NonNull final HalSessionCallback mHalSessionCallback;
-        @NonNull private final String mTag;
-        @NonNull private final ISession mSession;
-        private final int mUserId;
-
-        Session(@NonNull String tag, @NonNull ISession session, int userId,
-                @NonNull HalSessionCallback halSessionCallback) {
-            mTag = tag;
-            mSession = session;
-            mUserId = userId;
-            mHalSessionCallback = halSessionCallback;
-            Slog.d(mTag, "New session created for user: " + userId);
-        }
-    }
+    @NonNull private final Supplier<AidlSession> mLazySession;
+    @Nullable private AidlSession mCurrentSession;
 
     static class HalSessionCallback extends ISessionCallback.Stub {
         /**
@@ -496,7 +481,7 @@
         mSensorProperties = sensorProperties;
         mScheduler = new UserAwareBiometricScheduler(tag,
                 BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
-                () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
+                () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
                 new UserAwareBiometricScheduler.UserSwitchCallback() {
                     @NonNull
                     @Override
@@ -508,21 +493,22 @@
                     @NonNull
                     @Override
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
-                        final HalSessionCallback.Callback callback = () -> {
-                            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-                            mCurrentSession = null;
-                        };
-
                         final int sensorId = mSensorProperties.sensorId;
 
                         final HalSessionCallback resultController = new HalSessionCallback(mContext,
                                 mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
-                                lockoutResetDispatcher, callback);
+                                lockoutResetDispatcher, () -> {
+                            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+                            mCurrentSession = null;
+                        });
 
                         final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
-                                (userIdStarted, newSession) -> {
-                                    mCurrentSession = new Session(mTag, newSession, userIdStarted,
-                                            resultController);
+                                (userIdStarted, newSession, halInterfaceVersion) -> {
+                                    Slog.d(mTag, "New session created for user: "
+                                            + userIdStarted + " with hal version: "
+                                            + halInterfaceVersion);
+                                    mCurrentSession = new AidlSession(halInterfaceVersion,
+                                            newSession, userIdStarted, resultController);
                                     if (FaceUtils.getLegacyInstance(sensorId)
                                             .isInvalidationInProgress(mContext, userIdStarted)) {
                                         Slog.w(mTag,
@@ -542,10 +528,10 @@
                 });
         mLockoutCache = new LockoutCache();
         mAuthenticatorIds = new HashMap<>();
-        mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
+        mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
     }
 
-    @NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
+    @NonNull Supplier<AidlSession> getLazySession() {
         return mLazySession;
     }
 
@@ -553,8 +539,8 @@
         return mSensorProperties;
     }
 
-    @Nullable Session getSessionForUser(int userId) {
-        if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
+    @Nullable AidlSession getSessionForUser(int userId) {
+        if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
             return mCurrentSession;
         } else {
             return null;
@@ -583,10 +569,10 @@
         if (enabled != mTestHalEnabled) {
             // The framework should retrieve a new session from the HAL.
             try {
-                if (mCurrentSession != null && mCurrentSession.mSession != null) {
+                if (mCurrentSession != null) {
                     // TODO(181984005): This should be scheduled instead of directly invoked
                     Slog.d(mTag, "Closing old session");
-                    mCurrentSession.mSession.close();
+                    mCurrentSession.getSession().close();
                 }
             } catch (RemoteException e) {
                 Slog.e(mTag, "RemoteException", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index 15d6a89..4fc2e22 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -207,6 +207,11 @@
             public ICancellationSignal detectInteractionWithContext(OperationContext context) {
                 return detectInteraction();
             }
+
+            @Override
+            public void onContextChanged(OperationContext context) {
+                Slog.w(TAG, "onContextChanged");
+            }
         };
     }
 }
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 9a52db1..586abe2 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
@@ -64,7 +64,6 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
@@ -89,6 +88,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
 
 /**
  * Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or its extended
@@ -111,7 +111,7 @@
     @NonNull private final Context mContext;
     @NonNull private final BiometricScheduler mScheduler;
     @NonNull private final Handler mHandler;
-    @NonNull private final HalClientMonitor.LazyDaemon<IBiometricsFace> mLazyDaemon;
+    @NonNull private final Supplier<IBiometricsFace> mLazyDaemon;
     @NonNull private final LockoutHalImpl mLockoutTracker;
     @NonNull private final UsageStats mUsageStats;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
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 1e0e799..9038435 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
@@ -41,6 +41,7 @@
 import com.android.server.biometrics.sensors.face.UsageStats;
 
 import java.util.ArrayList;
+import java.util.function.Supplier;
 
 /**
  * Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
@@ -61,7 +62,7 @@
     private SensorPrivacyManager mSensorPrivacyManager;
 
     FaceAuthenticationClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+            @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
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 8068e14..92f7253 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
@@ -40,6 +40,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.function.Supplier;
 
 /**
  * Face-specific enroll client supporting the {@link android.hardware.biometrics.face.V1_0} HIDL
@@ -53,7 +54,7 @@
     @NonNull private final int[] mEnrollIgnoreList;
     @NonNull private final int[] mEnrollIgnoreListVendor;
 
-    FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+    FaceEnrollClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
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 e29a192..b66ad60 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
@@ -31,6 +31,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * Face-specific generateChallenge client supporting the
@@ -48,7 +49,7 @@
     private Long mChallengeResult;
 
     FaceGenerateChallengeClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
+            @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
             int sensorId, long now) {
         super(context, lazyDaemon, token, listener, userId, owner, sensorId);
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 0a9d96d..1b387bf 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
@@ -32,6 +32,8 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
+import java.util.function.Supplier;
+
 /**
  * Face-specific getFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
  * HIDL interface.
@@ -44,7 +46,7 @@
     private final int mFaceId;
     private boolean mValue;
 
-    FaceGetFeatureClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+    FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId, int feature, int faceId) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
index 1e3b92d..93a2913 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
@@ -30,6 +30,7 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Face-specific internal cleanup client supporting the
@@ -38,7 +39,7 @@
 class FaceInternalCleanupClient extends InternalCleanupClient<Face, IBiometricsFace> {
 
     FaceInternalCleanupClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
+            @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
             int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE,
@@ -47,7 +48,7 @@
 
     @Override
     protected InternalEnumerateClient<IBiometricsFace> getEnumerateClient(Context context,
-            LazyDaemon<IBiometricsFace> lazyDaemon, IBinder token, int userId, String owner,
+            Supplier<IBiometricsFace> lazyDaemon, IBinder token, int userId, String owner,
             List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) {
         return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
                 enrolledList, utils, sensorId);
@@ -55,7 +56,7 @@
 
     @Override
     protected RemovalClient<Face, IBiometricsFace> getRemovalClient(Context context,
-            LazyDaemon<IBiometricsFace> lazyDaemon, IBinder token,
+            Supplier<IBiometricsFace> lazyDaemon, IBinder token,
             int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
             Map<Integer, Long> authenticatorIds) {
         // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
index f2a9afc..f1788de 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
@@ -29,6 +29,7 @@
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * Face-specific internal enumerate client supporting the
@@ -38,7 +39,7 @@
     private static final String TAG = "FaceInternalEnumerateClient";
 
     FaceInternalEnumerateClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token, int userId,
+            @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, int userId,
             @NonNull String owner, @NonNull List<Face> enrolledList,
             @NonNull BiometricUtils<Face> utils, int sensorId) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
index 3ae2011..cbc23e4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
@@ -30,6 +30,7 @@
 import com.android.server.biometrics.sensors.RemovalClient;
 
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Face-specific removal client supporting the {@link android.hardware.biometrics.face.V1_0}
@@ -40,7 +41,7 @@
 
     private final int mBiometricId;
 
-    FaceRemovalClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+    FaceRemovalClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
             int sensorId, @NonNull Map<Integer, Long> authenticatorIds) {
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 ee01c43..88e2318 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
@@ -28,6 +28,7 @@
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.util.ArrayList;
+import java.util.function.Supplier;
 
 /**
  * Face-specific resetLockout client supporting the {@link android.hardware.biometrics.face.V1_0}
@@ -40,7 +41,7 @@
     private final ArrayList<Byte> mHardwareAuthToken;
 
     FaceResetLockoutClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, int userId, String owner, int sensorId,
+            @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, String owner, int sensorId,
             @NonNull byte[] hardwareAuthToken) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
                 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
index 5ec7a98..ab8d161 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
@@ -25,6 +25,8 @@
 
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
+import java.util.function.Supplier;
+
 /**
  * Face-specific revokeChallenge client supporting the {@link android.hardware.biometrics.face.V1_0}
  * HIDL interface.
@@ -34,7 +36,7 @@
     private static final String TAG = "FaceRevokeChallengeClient";
 
     FaceRevokeChallengeClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
+            @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
             int userId, @NonNull String owner, int sensorId) {
         super(context, lazyDaemon, token, userId, owner, sensorId);
     }
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 ee28f7b..b2b52e7 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
@@ -31,6 +31,7 @@
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.util.ArrayList;
+import java.util.function.Supplier;
 
 /**
  * Face-specific setFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
@@ -45,7 +46,7 @@
     private final ArrayList<Byte> mHardwareAuthToken;
     private final int mFaceId;
 
-    FaceSetFeatureClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+    FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId, int feature, boolean enabled,
             byte[] hardwareAuthToken, int faceId) {
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 8ee8ce5..04b9327 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
@@ -30,6 +30,7 @@
 
 import java.io.File;
 import java.util.Map;
+import java.util.function.Supplier;
 
 public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace> {
     private static final String TAG = "FaceUpdateActiveUserClient";
@@ -39,7 +40,7 @@
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
 
     FaceUpdateActiveUserClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
+            @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
             int sensorId, boolean hasEnrolledBiometrics,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
new file mode 100644
index 0000000..727101a
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.fingerprint.ISession;
+
+import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
+
+/**
+ * A holder for an AIDL {@link ISession} with additional metadata about the current user
+ * and the backend.
+ */
+public class AidlSession {
+
+    private final int mHalInterfaceVersion;
+    @NonNull private final ISession mSession;
+    private final int mUserId;
+    @NonNull private final HalSessionCallback mHalSessionCallback;
+
+    public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
+            HalSessionCallback halSessionCallback) {
+        mHalInterfaceVersion = halInterfaceVersion;
+        mSession = session;
+        mUserId = userId;
+        mHalSessionCallback = halSessionCallback;
+    }
+
+    /** The underlying {@link ISession}. */
+    @NonNull public ISession getSession() {
+        return mSession;
+    }
+
+    /** The user id associated with the session. */
+    public int getUserId() {
+        return mUserId;
+    }
+
+    /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
+    HalSessionCallback getHalSessionCallback() {
+        return mHalSessionCallback;
+    }
+
+    /**
+     * If this backend implements the *WithContext methods for enroll, authenticate, and
+     * detectInteraction. These variants should always be called if they are available.
+     */
+    public boolean hasContextMethods() {
+        return mHalInterfaceVersion >= 2;
+    }
+}
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 b29fbb6..0528cd4 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
@@ -157,7 +157,7 @@
         }
 
         mEnrollmentIds.add(nextRandomId);
-        mSensor.getSessionForUser(userId).mHalSessionCallback
+        mSensor.getSessionForUser(userId).getHalSessionCallback()
                 .onEnrollmentProgress(nextRandomId, 0 /* remaining */);
     }
 
@@ -173,7 +173,7 @@
             return;
         }
         final int fid = fingerprints.get(0).getBiometricId();
-        mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationSucceeded(fid,
+        mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationSucceeded(fid,
                 HardwareAuthTokenUtils.toHardwareAuthToken(new byte[69]));
     }
 
@@ -181,14 +181,14 @@
     public void rejectAuthentication(int userId)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFailed();
+        mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
     }
 
     @Override
     public void notifyAcquired(int userId, int acquireInfo)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mSensor.getSessionForUser(userId).mHalSessionCallback
+        mSensor.getSessionForUser(userId).getHalSessionCallback()
                 .onAcquired((byte) acquireInfo, 0 /* vendorCode */);
     }
 
@@ -196,7 +196,7 @@
     public void notifyError(int userId, int errorCode)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mSensor.getSessionForUser(userId).mHalSessionCallback.onError((byte) errorCode,
+        mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode,
                 0 /* vendorCode */);
     }
 
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 f3d0121..2c1c80c 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
@@ -25,7 +25,9 @@
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -47,12 +49,13 @@
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 
 import java.util.ArrayList;
+import java.util.function.Supplier;
 
 /**
  * Fingerprint-specific authentication client supporting the
  * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
  */
-class FingerprintAuthenticationClient extends AuthenticationClient<ISession> implements
+class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> implements
         Udfps, LockoutConsumer {
     private static final String TAG = "FingerprintAuthenticationClient";
 
@@ -65,7 +68,7 @@
     private boolean mIsPointerDown;
 
     FingerprintAuthenticationClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon,
+            @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
@@ -158,7 +161,7 @@
         mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this);
 
         try {
-            mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+            mCancellationSignal = doAuthenticate();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
@@ -168,6 +171,22 @@
         }
     }
 
+    private ICancellationSignal doAuthenticate() throws RemoteException {
+        final AidlSession session = getFreshDaemon();
+
+        if (session.hasContextMethods()) {
+            final OperationContext context = new OperationContext();
+            // TODO: add reason, id, and isAoD
+            context.id = 0;
+            context.reason = OperationReason.UNKNOWN;
+            context.isAoD = false;
+            context.isCrypto = isCryptoOperation();
+            return session.getSession().authenticateWithContext(mOperationId, context);
+        } else {
+            return session.getSession().authenticate(mOperationId);
+        }
+    }
+
     @Override
     protected void stopHalOperation() {
         mSensorOverlays.hide(getSensorId());
@@ -191,7 +210,20 @@
             mIsPointerDown = true;
             mState = STATE_STARTED;
             mALSProbeCallback.getProbe().enable();
-            getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major);
+
+            final AidlSession session = getFreshDaemon();
+            if (session.hasContextMethods()) {
+                final PointerContext context = new PointerContext();
+                context.pointerId = 0;
+                context.x = x;
+                context.y = y;
+                context.minor = minor;
+                context.major = major;
+                context.isAoD = false; // TODO; get value
+                session.getSession().onPointerDownWithContext(context);
+            } else {
+                session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
+            }
 
             if (getListener() != null) {
                 getListener().onUdfpsPointerDown(getSensorId());
@@ -207,7 +239,15 @@
             mIsPointerDown = false;
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
             mALSProbeCallback.getProbe().disable();
-            getFreshDaemon().onPointerUp(0 /* pointerId */);
+
+            final AidlSession session = getFreshDaemon();
+            if (session.hasContextMethods()) {
+                final PointerContext context = new PointerContext();
+                context.pointerId = 0;
+                session.getSession().onPointerUpWithContext(context);
+            } else {
+                session.getSession().onPointerUp(0 /* pointerId */);
+            }
 
             if (getListener() != null) {
                 getListener().onUdfpsPointerUp(getSensorId());
@@ -225,7 +265,7 @@
     @Override
     public void onUiReady() {
         try {
-            getFreshDaemon().onUiReady();
+            getFreshDaemon().getSession().onUiReady();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
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 1f0482d..6645332 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
@@ -22,7 +22,8 @@
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -35,11 +36,13 @@
 import com.android.server.biometrics.sensors.DetectionConsumer;
 import com.android.server.biometrics.sensors.SensorOverlays;
 
+import java.util.function.Supplier;
+
 /**
  * Performs fingerprint detection without exposing any matching information (e.g. accept/reject
  * have the same haptic, lockout counter is not increased).
  */
-class FingerprintDetectClient extends AcquisitionClient<ISession> implements DetectionConsumer {
+class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer {
 
     private static final String TAG = "FingerprintDetectClient";
 
@@ -47,7 +50,7 @@
     @NonNull private final SensorOverlays mSensorOverlays;
     @Nullable private ICancellationSignal mCancellationSignal;
 
-    FingerprintDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+    FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId,
@@ -84,7 +87,7 @@
         mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
 
         try {
-            mCancellationSignal = getFreshDaemon().detectInteraction();
+            mCancellationSignal = doDetectInteraction();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting finger detect", e);
             mSensorOverlays.hide(getSensorId());
@@ -92,6 +95,22 @@
         }
     }
 
+    private ICancellationSignal doDetectInteraction() throws RemoteException {
+        final AidlSession session = getFreshDaemon();
+
+        if (session.hasContextMethods()) {
+            final OperationContext context = new OperationContext();
+            // TODO: add reason, id, and isAoD
+            context.id = 0;
+            context.reason = OperationReason.UNKNOWN;
+            context.isAoD = false;
+            context.isCrypto = isCryptoOperation();
+            return session.getSession().detectInteractionWithContext(context);
+        } else {
+            return session.getSession().detectInteraction();
+        }
+    }
+
     @Override
     public void onInteractionDetected() {
         vibrateSuccess();
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 169c3eb..d0c5bb8 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
@@ -24,12 +24,15 @@
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.hardware.keymaster.HardwareAuthToken;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -46,7 +49,9 @@
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 
-class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
+import java.util.function.Supplier;
+
+class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps {
 
     private static final String TAG = "FingerprintEnrollClient";
 
@@ -59,7 +64,7 @@
     private boolean mIsPointerDown;
 
     FingerprintEnrollClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId,
+            @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
@@ -156,8 +161,7 @@
 
         BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
         try {
-            mCancellationSignal = getFreshDaemon().enroll(
-                    HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken));
+            mCancellationSignal = doEnroll();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enroll", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
@@ -166,11 +170,42 @@
         }
     }
 
+    private ICancellationSignal doEnroll() throws RemoteException {
+        final AidlSession session = getFreshDaemon();
+        final HardwareAuthToken hat =
+                HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
+
+        if (session.hasContextMethods()) {
+            final OperationContext context = new OperationContext();
+            // TODO: add reason, id, and isAoD
+            context.id = 0;
+            context.reason = OperationReason.UNKNOWN;
+            context.isAoD = false;
+            context.isCrypto = isCryptoOperation();
+            return session.getSession().enrollWithContext(hat, context);
+        } else {
+            return session.getSession().enroll(hat);
+        }
+    }
+
     @Override
     public void onPointerDown(int x, int y, float minor, float major) {
         try {
             mIsPointerDown = true;
-            getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major);
+
+            final AidlSession session = getFreshDaemon();
+            if (session.hasContextMethods()) {
+                final PointerContext context = new PointerContext();
+                context.pointerId = 0;
+                context.x = x;
+                context.y = y;
+                context.minor = minor;
+                context.major = major;
+                context.isAoD = false;
+                session.getSession().onPointerDownWithContext(context);
+            } else {
+                session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to send pointer down", e);
         }
@@ -180,7 +215,15 @@
     public void onPointerUp() {
         try {
             mIsPointerDown = false;
-            getFreshDaemon().onPointerUp(0 /* pointerId */);
+
+            final AidlSession session = getFreshDaemon();
+            if (session.hasContextMethods()) {
+                final PointerContext context = new PointerContext();
+                context.pointerId = 0;
+                session.getSession().onPointerUpWithContext(context);
+            } else {
+                session.getSession().onPointerUp(0 /* pointerId */);
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to send pointer up", e);
         }
@@ -194,7 +237,7 @@
     @Override
     public void onUiReady() {
         try {
-            getFreshDaemon().onUiReady();
+            getFreshDaemon().getSession().onUiReady();
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to send UI ready", e);
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index 4f54f8a..04a7ca0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -27,14 +26,16 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
+import java.util.function.Supplier;
+
 /**
  * Fingerprint-specific generateChallenge client for the {@link IFingerprint} AIDL HAL interface.
  */
-class FingerprintGenerateChallengeClient extends GenerateChallengeClient<ISession> {
+class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
     private static final String TAG = "FingerprintGenerateChallengeClient";
 
     FingerprintGenerateChallengeClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon,
+            @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener,
             int userId, @NonNull String owner, int sensorId) {
@@ -44,7 +45,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().generateChallenge();
+            getFreshDaemon().getSession().generateChallenge();
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to generateChallenge", e);
             mCallback.onClientFinished(this, false /* success */);
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 52bd234..3a487fc 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
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.fingerprint.ISession;
 import android.os.RemoteException;
 import android.util.Slog;
 
@@ -28,15 +27,16 @@
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.util.Map;
+import java.util.function.Supplier;
 
-class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<ISession> {
+class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
 
     private static final String TAG = "FingerprintGetAuthenticatorIdClient";
 
     private final Map<Integer, Long> mAuthenticatorIds;
 
     FingerprintGetAuthenticatorIdClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, int userId, @NonNull String owner,
+            @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
             int sensorId, Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
                 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FINGERPRINT,
@@ -57,7 +57,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().getAuthenticatorId();
+            getFreshDaemon().getSession().getAuthenticatorId();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 0de3f4f..0ecad72 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 
@@ -31,15 +30,16 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Fingerprint-specific internal cleanup client supporting the
  * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
  */
-class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, ISession> {
+class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> {
 
     FingerprintInternalCleanupClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, int userId, @NonNull String owner,
+            @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
             int sensorId, @NonNull List<Fingerprint> enrolledList,
             @NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, userId, owner, sensorId,
@@ -47,16 +47,16 @@
     }
 
     @Override
-    protected InternalEnumerateClient<ISession> getEnumerateClient(Context context,
-            LazyDaemon<ISession> lazyDaemon, IBinder token, int userId, String owner,
+    protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context,
+            Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner,
             List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId) {
         return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
                 enrolledList, utils, sensorId);
     }
 
     @Override
-    protected RemovalClient<Fingerprint, ISession> getRemovalClient(Context context,
-            LazyDaemon<ISession> lazyDaemon, IBinder token, int biometricId, int userId,
+    protected RemovalClient<Fingerprint, AidlSession> getRemovalClient(Context context,
+            Supplier<AidlSession> lazyDaemon, IBinder token, int biometricId, int userId,
             String owner, BiometricUtils<Fingerprint> utils, int sensorId,
             Map<Integer, Long> authenticatorIds) {
         return new FingerprintRemovalClient(context, lazyDaemon, token,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
index e20544a..06ba6d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -29,16 +28,17 @@
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * Fingerprint-specific internal client supporting the
  * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
  */
-class FingerprintInternalEnumerateClient extends InternalEnumerateClient<ISession> {
+class FingerprintInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
     private static final String TAG = "FingerprintInternalEnumerateClient";
 
     protected FingerprintInternalEnumerateClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, int userId,
+            @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId,
             @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
             @NonNull BiometricUtils<Fingerprint> utils, int sensorId) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
@@ -48,7 +48,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().enumerateEnrollments();
+            getFreshDaemon().getSession().enumerateEnrollments();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enumerate", e);
             mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
index 6cd2ef1..1ee32e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.IInvalidationCallback;
-import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -27,12 +26,13 @@
 import com.android.server.biometrics.sensors.InvalidationClient;
 
 import java.util.Map;
+import java.util.function.Supplier;
 
-public class FingerprintInvalidationClient extends InvalidationClient<Fingerprint, ISession> {
+public class FingerprintInvalidationClient extends InvalidationClient<Fingerprint, AidlSession> {
     private static final String TAG = "FingerprintInvalidationClient";
 
     public FingerprintInvalidationClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, int userId, int sensorId,
+            @NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId,
             @NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) {
         super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback);
     }
@@ -40,7 +40,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().invalidateAuthenticatorId();
+            getFreshDaemon().getSession().invalidateAuthenticatorId();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index 9a9d6ab..fbc1dc0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -31,18 +30,19 @@
 import com.android.server.biometrics.sensors.RemovalClient;
 
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Fingerprint-specific removal client supporting the
  * {@link android.hardware.biometrics.fingerprint.IFingerprint} interface.
  */
-class FingerprintRemovalClient extends RemovalClient<Fingerprint, ISession> {
+class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> {
     private static final String TAG = "FingerprintRemovalClient";
 
     private final int[] mBiometricIds;
 
     FingerprintRemovalClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+            @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
             @NonNull Map<Integer, Long> authenticatorIds) {
@@ -54,7 +54,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().removeEnrollments(mBiometricIds);
+            getFreshDaemon().getSession().removeEnrollments(mBiometricIds);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting remove", e);
             mCallback.onClientFinished(this, false /* success */);
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 ee8d170..0e64dab 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
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -34,12 +33,14 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
+import java.util.function.Supplier;
+
 /**
  * Fingerprint-specific resetLockout client for the {@link IFingerprint} AIDL HAL interface.
  * Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is
  * cleared.
  */
-class FingerprintResetLockoutClient extends HalClientMonitor<ISession> implements ErrorConsumer {
+class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
 
     private static final String TAG = "FingerprintResetLockoutClient";
 
@@ -48,7 +49,7 @@
     private final LockoutResetDispatcher mLockoutResetDispatcher;
 
     FingerprintResetLockoutClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, int userId, String owner, int sensorId,
+            @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
             @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
@@ -73,7 +74,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().resetLockout(mHardwareAuthToken);
+            getFreshDaemon().getSession().resetLockout(mHardwareAuthToken);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to reset lockout", e);
             mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
index 9e6f1bc..fd93867 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -19,24 +19,25 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
+import java.util.function.Supplier;
+
 /**
  * Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface.
  */
-class FingerprintRevokeChallengeClient extends RevokeChallengeClient<ISession> {
+class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
 
     private static final String TAG = "FingerpirntRevokeChallengeClient";
 
     private final long mChallenge;
 
     FingerprintRevokeChallengeClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+            @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             int userId, @NonNull String owner, int sensorId, long challenge) {
         super(context, lazyDaemon, token, userId, owner, sensorId);
         mChallenge = challenge;
@@ -45,7 +46,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().revokeChallenge(mChallenge);
+            getFreshDaemon().getSession().revokeChallenge(mChallenge);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to revokeChallenge", e);
             mCallback.onClientFinished(this, false /* success */);
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 9f11df6..9dc06e1 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
@@ -30,13 +30,15 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StartUserClient;
 
+import java.util.function.Supplier;
+
 public class FingerprintStartUserClient extends StartUserClient<IFingerprint, ISession> {
     private static final String TAG = "FingerprintStartUserClient";
 
     @NonNull private final ISessionCallback mSessionCallback;
 
     public FingerprintStartUserClient(@NonNull Context context,
-            @NonNull LazyDaemon<IFingerprint> lazyDaemon,
+            @NonNull Supplier<IFingerprint> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
             @NonNull ISessionCallback sessionCallback,
             @NonNull UserStartedCallback<ISession> callback) {
@@ -53,10 +55,12 @@
     @Override
     protected void startHalOperation() {
         try {
-            final ISession newSession = getFreshDaemon().createSession(getSensorId(),
+            final IFingerprint hal = getFreshDaemon();
+            final int version = hal.getInterfaceVersion();
+            final ISession newSession = hal.createSession(getSensorId(),
                     getTargetUserId(), mSessionCallback);
             Binder.allowBlocking(newSession.asBinder());
-            mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
+            mUserStartedCallback.onUserStarted(getTargetUserId(), newSession, version);
             getCallback().onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
@@ -66,6 +70,5 @@
 
     @Override
     public void unableToStart() {
-
     }
 }
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 9d38145..fac17f2c 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
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.fingerprint.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -27,11 +26,13 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StopUserClient;
 
-public class FingerprintStopUserClient extends StopUserClient<ISession> {
+import java.util.function.Supplier;
+
+public class FingerprintStopUserClient extends StopUserClient<AidlSession> {
     private static final String TAG = "FingerprintStopUserClient";
 
     public FingerprintStopUserClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @Nullable IBinder token, int userId,
+            @NonNull Supplier<AidlSession> lazyDaemon, @Nullable IBinder token, int userId,
             int sensorId, @NonNull UserStoppedCallback callback) {
         super(context, lazyDaemon, token, userId, sensorId, callback);
     }
@@ -45,7 +46,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().close();
+            getFreshDaemon().getSession().close();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             getCallback().onClientFinished(this, false /* success */);
@@ -54,6 +55,5 @@
 
     @Override
     public void unableToStart() {
-
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 59e4b58..2276232 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -51,7 +51,6 @@
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -66,6 +65,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Maintains the state of a single sensor within an instance of the
@@ -86,24 +86,8 @@
     @NonNull private final LockoutCache mLockoutCache;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
 
-    @Nullable private Session mCurrentSession;
-    @NonNull private final HalClientMonitor.LazyDaemon<ISession> mLazySession;
-
-    static class Session {
-        @NonNull private final String mTag;
-        @NonNull private final ISession mSession;
-        private final int mUserId;
-        @NonNull final HalSessionCallback mHalSessionCallback;
-
-        Session(@NonNull String tag, @NonNull ISession session, int userId,
-                @NonNull HalSessionCallback halSessionCallback) {
-            mTag = tag;
-            mSession = session;
-            mUserId = userId;
-            mHalSessionCallback = halSessionCallback;
-            Slog.d(mTag, "New session created for user: " + userId);
-        }
-    }
+    @Nullable private AidlSession mCurrentSession;
+    @NonNull private final Supplier<AidlSession> mLazySession;
 
     static class HalSessionCallback extends ISessionCallback.Stub {
 
@@ -452,7 +436,7 @@
         mScheduler = new UserAwareBiometricScheduler(tag,
                 BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
                 gestureAvailabilityDispatcher,
-                () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
+                () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
                 new UserAwareBiometricScheduler.UserSwitchCallback() {
                     @NonNull
                     @Override
@@ -464,20 +448,21 @@
                     @NonNull
                     @Override
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
-                        final HalSessionCallback.Callback callback = () -> {
-                            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-                            mCurrentSession = null;
-                        };
-
                         final int sensorId = mSensorProperties.sensorId;
 
                         final HalSessionCallback resultController = new HalSessionCallback(mContext,
                                 mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
-                                lockoutResetDispatcher, callback);
+                                lockoutResetDispatcher, () -> {
+                            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+                            mCurrentSession = null;
+                        });
 
                         final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
-                                (userIdStarted, newSession) -> {
-                                    mCurrentSession = new Session(mTag,
+                                (userIdStarted, newSession, halInterfaceVersion) -> {
+                                    Slog.d(mTag, "New session created for user: "
+                                            + userIdStarted + " with hal version: "
+                                            + halInterfaceVersion);
+                                    mCurrentSession = new AidlSession(halInterfaceVersion,
                                             newSession, userIdStarted, resultController);
                                     if (FingerprintUtils.getInstance(sensorId)
                                             .isInvalidationInProgress(mContext, userIdStarted)) {
@@ -497,10 +482,10 @@
                     }
                 });
         mAuthenticatorIds = new HashMap<>();
-        mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
+        mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
     }
 
-    @NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
+    @NonNull Supplier<AidlSession> getLazySession() {
         return mLazySession;
     }
 
@@ -508,8 +493,8 @@
         return mSensorProperties;
     }
 
-    @Nullable Session getSessionForUser(int userId) {
-        if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
+    @Nullable AidlSession getSessionForUser(int userId) {
+        if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
             return mCurrentSession;
         } else {
             return null;
@@ -539,10 +524,10 @@
         if (enabled != mTestHalEnabled) {
             // The framework should retrieve a new session from the HAL.
             try {
-                if (mCurrentSession != null && mCurrentSession.mSession != null) {
+                if (mCurrentSession != null) {
                     // TODO(181984005): This should be scheduled instead of directly invoked
                     Slog.d(mTag, "Closing old session");
-                    mCurrentSession.mSession.close();
+                    mCurrentSession.getSession().close();
                 }
             } catch (RemoteException e) {
                 Slog.e(mTag, "RemoteException", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index 1eb153c..452c972 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -212,6 +212,11 @@
             public void onPointerUpWithContext(PointerContext context) {
                 onPointerUp(context.pointerId);
             }
+
+            @Override
+            public void onContextChanged(OperationContext context) {
+                Slog.w(TAG, "onContextChanged");
+            }
         };
     }
 }
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 f160dff..29d460f 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
@@ -67,7 +67,6 @@
 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;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
@@ -90,6 +89,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
 
 /**
  * Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
@@ -111,7 +111,7 @@
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final LockoutFrameworkImpl mLockoutTracker;
     private final BiometricTaskStackListener mTaskStackListener;
-    private final HalClientMonitor.LazyDaemon<IBiometricsFingerprint> mLazyDaemon;
+    private final Supplier<IBiometricsFingerprint> mLazyDaemon;
     private final Map<Integer, Long> mAuthenticatorIds;
 
     @Nullable private IBiometricsFingerprint mDaemon;
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 87d47c1..589bfcf 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
@@ -45,6 +45,7 @@
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 
 import java.util.ArrayList;
+import java.util.function.Supplier;
 
 /**
  * Fingerprint-specific authentication client supporting the
@@ -64,7 +65,7 @@
     private boolean mIsPointerDown;
 
     FingerprintAuthenticationClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
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 9137212..8848746 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
@@ -40,6 +40,7 @@
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 
 import java.util.ArrayList;
+import java.util.function.Supplier;
 
 /**
  * Performs fingerprint detection without exposing any matching information (e.g. accept/reject
@@ -55,7 +56,7 @@
     private boolean mIsPointerDown;
 
     public FingerprintDetectClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
             int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController,
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 82b046d..c69deac 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
@@ -41,6 +41,8 @@
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 
+import java.util.function.Supplier;
+
 /**
  * Fingerprint-specific enroll client supporting the
  * {@link android.hardware.biometrics.fingerprint.V2_1} and
@@ -56,7 +58,7 @@
     private boolean mIsPointerDown;
 
     FingerprintEnrollClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
index db2f045..591f542 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
@@ -26,6 +26,8 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
+import java.util.function.Supplier;
+
 /**
  * Fingerprint-specific generateChallenge/preEnroll client supporting the
  * {@link android.hardware.biometrics.fingerprint.V2_1} and
@@ -37,7 +39,7 @@
     private static final String TAG = "FingerprintGenerateChallengeClient";
 
     FingerprintGenerateChallengeClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
             int sensorId) {
         super(context, lazyDaemon, token, listener, userId, owner, sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
index a42a8ae..403602b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
@@ -30,6 +30,7 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Fingerprint-specific internal cleanup client supporting the
@@ -40,7 +41,7 @@
         extends InternalCleanupClient<Fingerprint, IBiometricsFingerprint> {
 
     FingerprintInternalCleanupClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
             @NonNull String owner, int sensorId, @NonNull List<Fingerprint> enrolledList,
             @NonNull BiometricUtils<Fingerprint> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
@@ -50,7 +51,7 @@
 
     @Override
     protected InternalEnumerateClient<IBiometricsFingerprint> getEnumerateClient(
-            Context context, LazyDaemon<IBiometricsFingerprint> lazyDaemon, IBinder token,
+            Context context, Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
             int userId, String owner, List<Fingerprint> enrolledList,
             BiometricUtils<Fingerprint> utils, int sensorId) {
         return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
@@ -59,7 +60,7 @@
 
     @Override
     protected RemovalClient<Fingerprint, IBiometricsFingerprint> getRemovalClient(Context context,
-            LazyDaemon<IBiometricsFingerprint> lazyDaemon, IBinder token,
+            Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
             int biometricId, int userId, String owner, BiometricUtils<Fingerprint> utils,
             int sensorId, Map<Integer, Long> authenticatorIds) {
         // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
index 7117cf3..def8ed0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
@@ -29,6 +29,7 @@
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * Fingerprint-specific internal enumerate client supporting the
@@ -39,7 +40,7 @@
     private static final String TAG = "FingerprintInternalEnumerateClient";
 
     FingerprintInternalEnumerateClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             int userId, @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
             @NonNull BiometricUtils<Fingerprint> utils, int sensorId) {
         super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
index 2f360f3..77c201c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
@@ -30,6 +30,7 @@
 import com.android.server.biometrics.sensors.RemovalClient;
 
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Fingerprint-specific removal client supporting the
@@ -42,7 +43,7 @@
     private final int mBiometricId;
 
     FingerprintRemovalClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
             @NonNull Map<Integer, Long> authenticatorIds) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
index b6b29b3..0180a46 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
@@ -25,6 +25,8 @@
 
 import com.android.server.biometrics.sensors.RevokeChallengeClient;
 
+import java.util.function.Supplier;
+
 /**
  * Fingerprint-specific revokeChallenge client supporting the
  * {@link android.hardware.biometrics.fingerprint.V2_1} and
@@ -36,7 +38,7 @@
     private static final String TAG = "FingerprintRevokeChallengeClient";
 
     FingerprintRevokeChallengeClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             int userId, @NonNull String owner, int sensorId) {
         super(context, lazyDaemon, token, userId, owner, sensorId);
     }
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 d317984..cb9c33e 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
@@ -49,7 +49,7 @@
     private File mDirectory;
 
     FingerprintUpdateActiveUserClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
             @NonNull String owner, int sensorId, Supplier<Integer> currentUserId,
             boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
             boolean forceUpdateAuthenticatorId) {
diff --git a/services/core/java/com/android/server/clipboard/OWNERS b/services/core/java/com/android/server/clipboard/OWNERS
index 5449df9..0d5dbf9 100644
--- a/services/core/java/com/android/server/clipboard/OWNERS
+++ b/services/core/java/com/android/server/clipboard/OWNERS
@@ -1 +1,3 @@
 per-file EmulatorClipboardMonitor.java = bohu@google.com,lfy@google.com,rkir@google.com
+
+olilan@google.com
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index fce6737..603f206 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -18,14 +18,12 @@
 
 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;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
-import static android.net.NetworkTemplate.OEM_MANAGED_ALL;
 import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
@@ -95,6 +93,7 @@
     private static String TAG = MultipathPolicyTracker.class.getSimpleName();
 
     private static final boolean DBG = false;
+    private static final long MIN_THRESHOLD_BYTES = 2 * 1_048_576L; // 2MiB
 
     // This context is for the current user.
     private final Context mContext;
@@ -279,15 +278,11 @@
         }
 
         private NetworkIdentity getTemplateMatchingNetworkIdentity(NetworkCapabilities nc) {
-            return new NetworkIdentity(
-                    ConnectivityManager.TYPE_MOBILE,
-                    0 /* subType, unused for template matching */,
-                    subscriberId,
-                    null /* networkId, unused for matching mobile networks */,
-                    !nc.hasCapability(NET_CAPABILITY_NOT_ROAMING),
-                    !nc.hasCapability(NET_CAPABILITY_NOT_METERED),
-                    false /* defaultNetwork, templates should have DEFAULT_NETWORK_ALL */,
-                    OEM_MANAGED_ALL);
+            return new NetworkIdentity.Builder().setType(ConnectivityManager.TYPE_MOBILE)
+                    .setSubscriberId(subscriberId)
+                    .setRoaming(!nc.hasCapability(NET_CAPABILITY_NOT_ROAMING))
+                    .setMetered(!nc.hasCapability(NET_CAPABILITY_NOT_METERED))
+                    .build();
         }
 
         private long getRemainingDailyBudget(long limitBytes,
@@ -376,7 +371,7 @@
             // This will only be called if the total quota for the day changed, not if usage changed
             // since last time, so even if this is called very often the budget will not snap to 0
             // as soon as there are less than 2MB left for today.
-            if (budget > NetworkStatsManager.MIN_THRESHOLD_BYTES) {
+            if (budget > MIN_THRESHOLD_BYTES) {
                 if (DBG) {
                     Log.d(TAG, "Setting callback for " + budget + " bytes on network " + network);
                 }
@@ -409,8 +404,8 @@
 
         private void registerUsageCallback(long budget) {
             maybeUnregisterUsageCallback();
-            mStatsManager.registerUsageCallback(mNetworkTemplate, TYPE_MOBILE, budget,
-                    mUsageCallback, mHandler);
+            mStatsManager.registerUsageCallback(mNetworkTemplate, budget,
+                    (command) -> mHandler.post(command), mUsageCallback);
             mMultipathBudget = budget;
         }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 709af91..c2ca3a5 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -51,7 +51,6 @@
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.policy.DeviceStatePolicyImpl;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowProcessController;
 
@@ -142,7 +141,9 @@
     private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
 
     public DeviceStateManagerService(@NonNull Context context) {
-        this(context, new DeviceStatePolicyImpl(context));
+        this(context, DeviceStatePolicy.Provider
+                .fromResources(context.getResources())
+                .instantiate(context));
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
index 274b8e5..5c4e2f3 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
@@ -17,6 +17,11 @@
 package com.android.server.devicestate;
 
 import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.server.policy.DeviceStatePolicyImpl;
 
 /**
  * Interface for the component responsible for supplying the current device state as well as
@@ -24,9 +29,15 @@
  *
  * @see DeviceStateManagerService
  */
-public interface DeviceStatePolicy {
+public abstract class DeviceStatePolicy {
+    protected final Context mContext;
+
+    protected DeviceStatePolicy(@NonNull Context context) {
+        mContext = context;
+    }
+
     /** Returns the provider of device states. */
-    DeviceStateProvider getDeviceStateProvider();
+    public abstract DeviceStateProvider getDeviceStateProvider();
 
     /**
      * Configures the system into the provided state. Guaranteed not to be called again until the
@@ -36,5 +47,58 @@
      * @param onComplete a callback that must be triggered once the system has been properly
      *                   configured to match the supplied state.
      */
-    void configureDeviceForState(int state, @NonNull Runnable onComplete);
+    public abstract void configureDeviceForState(int state, @NonNull Runnable onComplete);
+
+    /** Provider for platform-default device state policy. */
+    static final class DefaultProvider implements DeviceStatePolicy.Provider {
+        @Override
+        public DeviceStatePolicy instantiate(@NonNull Context context) {
+            return new DeviceStatePolicyImpl(context);
+        }
+    }
+
+    /**
+     * Provider for {@link DeviceStatePolicy} instances.
+     *
+     * <p>By implementing this interface and overriding the
+     * {@code config_deviceSpecificDeviceStatePolicyProvider}, a device-specific implementations
+     * of {@link DeviceStatePolicy} can be supplied.
+     */
+    public interface Provider {
+        /**
+         * Instantiates a new {@link DeviceStatePolicy}.
+         *
+         * @see DeviceStatePolicy#DeviceStatePolicy
+         */
+        DeviceStatePolicy instantiate(@NonNull Context context);
+
+        /**
+         * Instantiates the device-specific {@link DeviceStatePolicy.Provider}.
+         *
+         * Checks the {@code config_deviceSpecificDeviceStatePolicyProvider} resource to see if
+         * a device specific policy provider has been supplied. If so, returns an instance of that
+         * provider. If there is no value provided then the method returns the
+         * {@link DeviceStatePolicy.DefaultProvider}.
+         *
+         * An {@link IllegalStateException} is thrown if there is a value provided for that
+         * resource, but it doesn't correspond to a class that is found.
+         */
+        static Provider fromResources(@NonNull Resources res) {
+            final String name = res.getString(
+                    com.android.internal.R.string.config_deviceSpecificDeviceStatePolicyProvider);
+            if (TextUtils.isEmpty(name)) {
+                return new DeviceStatePolicy.DefaultProvider();
+            }
+
+            try {
+                return (DeviceStatePolicy.Provider) Class.forName(name).newInstance();
+            } catch (ReflectiveOperationException | ClassCastException e) {
+                throw new IllegalStateException("Couldn't instantiate class " + name
+                        + " for config_deviceSpecificDeviceStatePolicyProvider:"
+                        + " make sure it has a public zero-argument constructor"
+                        + " and implements DeviceStatePolicy.Provider", e);
+            }
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 240168b..a1d722b 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -32,6 +32,7 @@
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.Preconditions;
 import com.android.server.display.utils.Plog;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -69,7 +70,7 @@
     @Nullable
     public static BrightnessMappingStrategy create(Resources resources,
             DisplayDeviceConfig displayDeviceConfig) {
-        return create(resources, displayDeviceConfig, /* isForIdleMode= */ false);
+        return create(resources, displayDeviceConfig, /* isForIdleMode= */ false, null);
     }
 
     /**
@@ -80,8 +81,10 @@
      */
     @Nullable
     public static BrightnessMappingStrategy createForIdleMode(Resources resources,
-            DisplayDeviceConfig displayDeviceConfig) {
-        return create(resources, displayDeviceConfig, /* isForIdleMode= */ true);
+            DisplayDeviceConfig displayDeviceConfig, DisplayWhiteBalanceController
+            displayWhiteBalanceController) {
+        return create(resources, displayDeviceConfig, /* isForIdleMode= */ true,
+                displayWhiteBalanceController);
     }
 
     /**
@@ -96,7 +99,8 @@
      */
     @Nullable
     private static BrightnessMappingStrategy create(Resources resources,
-            DisplayDeviceConfig displayDeviceConfig, boolean isForIdleMode) {
+            DisplayDeviceConfig displayDeviceConfig, boolean isForIdleMode,
+            DisplayWhiteBalanceController displayWhiteBalanceController) {
 
         // Display independent, mode dependent values
         float[] brightnessLevelsNits;
@@ -135,7 +139,7 @@
             builder.setShortTermModelLowerLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
             builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
             return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange,
-                    autoBrightnessAdjustmentMaxGamma, isForIdleMode);
+                    autoBrightnessAdjustmentMaxGamma, isForIdleMode, displayWhiteBalanceController);
         } else if (isValidMapping(luxLevels, brightnessLevelsBacklight) && !isForIdleMode) {
             return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight,
                     autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout);
@@ -770,9 +774,11 @@
         private float mUserLux;
         private float mUserBrightness;
         private final boolean mIsForIdleMode;
+        private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
 
         public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
-                float[] brightness, float maxGamma, boolean isForIdleMode) {
+                float[] brightness, float maxGamma, boolean isForIdleMode,
+                DisplayWhiteBalanceController displayWhiteBalanceController) {
 
             Preconditions.checkArgument(nits.length != 0 && brightness.length != 0,
                     "Nits and brightness arrays must not be empty!");
@@ -789,6 +795,7 @@
             mAutoBrightnessAdjustment = 0;
             mUserLux = -1;
             mUserBrightness = -1;
+            mDisplayWhiteBalanceController = displayWhiteBalanceController;
 
             mNits = nits;
             mBrightness = brightness;
@@ -836,6 +843,12 @@
         public float getBrightness(float lux, String packageName,
                 @ApplicationInfo.Category int category) {
             float nits = mBrightnessSpline.interpolate(lux);
+
+            // Adjust nits to compensate for display white balance colour strength.
+            if (mDisplayWhiteBalanceController != null && isForIdleMode()) {
+                nits = mDisplayWhiteBalanceController.calculateAdjustedBrightnessNits(nits);
+            }
+
             float brightness = mNitsToBrightnessSpline.interpolate(nits);
             // Correct the brightness according to the current application and its category, but
             // only if no user data point is set (as this will override the user setting).
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 3df2422..f3969b1 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.R;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.config.BrightnessThresholds;
 import com.android.server.display.config.Density;
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
@@ -42,6 +43,7 @@
 import com.android.server.display.config.RefreshRateRange;
 import com.android.server.display.config.SensorDetails;
 import com.android.server.display.config.ThermalStatus;
+import com.android.server.display.config.Thresholds;
 import com.android.server.display.config.XmlParser;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -130,6 +132,10 @@
     private float mBrightnessRampSlowIncrease = Float.NaN;
     private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
     private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS;
+    private float mScreenBrighteningMinThreshold = 0.0f;     // Retain behaviour as though there is
+    private float mScreenDarkeningMinThreshold = 0.0f;       // no minimum threshold for change in
+    private float mAmbientLuxBrighteningMinThreshold = 0.0f; // screen brightness or ambient
+    private float mAmbientLuxDarkeningMinThreshold = 0.0f;   // brightness.
     private Spline mBrightnessToBacklightSpline;
     private Spline mBacklightToBrightnessSpline;
     private Spline mBacklightToNitsSpline;
@@ -364,6 +370,22 @@
         return mAmbientHorizonShort;
     }
 
+    public float getScreenBrighteningMinThreshold() {
+        return mScreenBrighteningMinThreshold;
+    }
+
+    public float getScreenDarkeningMinThreshold() {
+        return mScreenDarkeningMinThreshold;
+    }
+
+    public float getAmbientLuxBrighteningMinThreshold() {
+        return mAmbientLuxBrighteningMinThreshold;
+    }
+
+    public float getAmbientLuxDarkeningMinThreshold() {
+        return mAmbientLuxDarkeningMinThreshold;
+    }
+
     SensorData getAmbientLightSensor() {
         return mAmbientLightSensor;
     }
@@ -425,6 +447,10 @@
                 + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease
                 + ", mAmbientHorizonLong=" + mAmbientHorizonLong
                 + ", mAmbientHorizonShort=" + mAmbientHorizonShort
+                + ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
+                + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
+                + ", mAmbientLuxDarkeningMinThreshold=" + mAmbientLuxDarkeningMinThreshold
+                + ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
                 + ", mAmbientLightSensor=" + mAmbientLightSensor
                 + ", mProximitySensor=" + mProximitySensor
                 + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
@@ -482,6 +508,7 @@
                 loadAmbientLightSensorFromDdc(config);
                 loadProxSensorFromDdc(config);
                 loadAmbientHorizonFromDdc(config);
+                loadBrightnessChangeThresholds(config);
             } else {
                 Slog.w(TAG, "DisplayDeviceConfig file is null");
             }
@@ -865,6 +892,45 @@
         }
     }
 
+    private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
+        Thresholds displayBrightnessThresholds = config.getDisplayBrightnessChangeThresholds();
+        Thresholds ambientBrightnessThresholds = config.getAmbientBrightnessChangeThresholds();
+
+        if (displayBrightnessThresholds != null) {
+            BrightnessThresholds brighteningScreen =
+                    displayBrightnessThresholds.getBrighteningThresholds();
+            BrightnessThresholds darkeningScreen =
+                    displayBrightnessThresholds.getDarkeningThresholds();
+
+            final BigDecimal screenBrighteningThreshold = brighteningScreen.getMinimum();
+            final BigDecimal screenDarkeningThreshold = darkeningScreen.getMinimum();
+
+            if (screenBrighteningThreshold != null) {
+                mScreenBrighteningMinThreshold = screenBrighteningThreshold.floatValue();
+            }
+            if (screenDarkeningThreshold != null) {
+                mScreenDarkeningMinThreshold = screenDarkeningThreshold.floatValue();
+            }
+        }
+
+        if (ambientBrightnessThresholds != null) {
+            BrightnessThresholds brighteningAmbientLux =
+                    ambientBrightnessThresholds.getBrighteningThresholds();
+            BrightnessThresholds darkeningAmbientLux =
+                    ambientBrightnessThresholds.getDarkeningThresholds();
+
+            final BigDecimal ambientBrighteningThreshold = brighteningAmbientLux.getMinimum();
+            final BigDecimal ambientDarkeningThreshold =  darkeningAmbientLux.getMinimum();
+
+            if (ambientBrighteningThreshold != null) {
+                mAmbientLuxBrighteningMinThreshold = ambientBrighteningThreshold.floatValue();
+            }
+            if (ambientDarkeningThreshold != null) {
+                mAmbientLuxDarkeningMinThreshold = ambientDarkeningThreshold.floatValue();
+            }
+        }
+    }
+
     private @PowerManager.ThermalStatus int convertThermalStatus(ThermalStatus value) {
         if (value == null) {
             return PowerManager.THERMAL_STATUS_NONE;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3feffc6..7ad4979 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1318,7 +1318,9 @@
 
         if (callingUid != Process.SYSTEM_UID
                 && (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
-            if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {
+            // The virtualDevice instance has been validated above using isValidVirtualDevice
+            if (virtualDevice == null
+                    && !checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {
                 throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
                         + "create a virtual display which is not in the default DisplayGroup.");
             }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c6d3829..ec4b91a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -50,6 +50,8 @@
 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;
@@ -895,7 +897,7 @@
                 mDisplayDeviceConfig);
         if (isIdleScreenBrightnessEnabled) {
             mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
-                    mDisplayDeviceConfig);
+                    mDisplayDeviceConfig, mDisplayWhiteBalanceController);
         }
 
         if (mInteractiveModeBrightnessMapper != null) {
@@ -909,9 +911,14 @@
                     com.android.internal.R.array.config_ambientDarkeningThresholds);
             int[] ambientThresholdLevels = resources.getIntArray(
                     com.android.internal.R.array.config_ambientThresholdLevels);
+            float ambientDarkeningMinThreshold =
+                    mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
+            float ambientBrighteningMinThreshold =
+                    mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
             HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
                     ambientBrighteningThresholds, ambientDarkeningThresholds,
-                    ambientThresholdLevels);
+                    ambientThresholdLevels, ambientDarkeningMinThreshold,
+                    ambientBrighteningMinThreshold);
 
             int[] screenBrighteningThresholds = resources.getIntArray(
                     com.android.internal.R.array.config_screenBrighteningThresholds);
@@ -919,8 +926,13 @@
                     com.android.internal.R.array.config_screenDarkeningThresholds);
             int[] screenThresholdLevels = resources.getIntArray(
                     com.android.internal.R.array.config_screenThresholdLevels);
+            float screenDarkeningMinThreshold =
+                    mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
+            float screenBrighteningMinThreshold =
+                    mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
             HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
-                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels);
+                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
+                    screenDarkeningMinThreshold, screenBrighteningMinThreshold);
 
             long brighteningLightDebounce = resources.getInteger(
                     com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce);
@@ -1380,6 +1392,7 @@
 
         // 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) {
@@ -1472,15 +1485,19 @@
                     // slider event so notify as if the system changed the brightness.
                     userInitiatedChange = false;
                 }
-                notifyBrightnessChanged(brightnessState, userInitiatedChange,
+                notifyBrightnessTrackerChanged(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.
-            saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
+            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
         } else {
-            saveBrightnessInfo(getScreenBrightnessSetting());
+            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting());
+        }
+
+        if (brightnessAdjusted) {
+            postBrightnessChangeRunnable();
         }
 
         // Log any changes to what is currently driving the brightness setting.
@@ -1596,31 +1613,50 @@
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
-                    mCachedBrightnessInfo.brightness,
-                    mCachedBrightnessInfo.adjustedBrightness,
-                    mCachedBrightnessInfo.brightnessMin,
-                    mCachedBrightnessInfo.brightnessMax,
-                    mCachedBrightnessInfo.hbmMode,
-                    mCachedBrightnessInfo.highBrightnessTransitionPoint);
+                    mCachedBrightnessInfo.brightness.value,
+                    mCachedBrightnessInfo.adjustedBrightness.value,
+                    mCachedBrightnessInfo.brightnessMin.value,
+                    mCachedBrightnessInfo.brightnessMax.value,
+                    mCachedBrightnessInfo.hbmMode.value,
+                    mCachedBrightnessInfo.hbmTransitionPoint.value);
         }
     }
 
-    private void saveBrightnessInfo(float brightness) {
-        saveBrightnessInfo(brightness, brightness);
+    private boolean saveBrightnessInfo(float brightness) {
+        return saveBrightnessInfo(brightness, brightness);
     }
 
-    private void saveBrightnessInfo(float brightness, float adjustedBrightness) {
+    private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
         synchronized (mCachedBrightnessInfo) {
-            mCachedBrightnessInfo.brightness = brightness;
-            mCachedBrightnessInfo.adjustedBrightness = adjustedBrightness;
-            mCachedBrightnessInfo.brightnessMin = mHbmController.getCurrentBrightnessMin();
-            mCachedBrightnessInfo.brightnessMax = mHbmController.getCurrentBrightnessMax();
-            mCachedBrightnessInfo.hbmMode = mHbmController.getHighBrightnessMode();
-            mCachedBrightnessInfo.highBrightnessTransitionPoint =
-                mHbmController.getTransitionPoint();
+            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;
         }
     }
 
+    void postBrightnessChangeRunnable() {
+        mHandler.post(mOnBrightnessChangeRunnable);
+    }
+
     private HighBrightnessModeController createHbmControllerLocked() {
         final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
@@ -1635,7 +1671,7 @@
                 displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
                 () -> {
                     sendUpdatePowerStateLocked();
-                    mHandler.post(mOnBrightnessChangeRunnable);
+                    postBrightnessChangeRunnable();
                     // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
                     if (mAutomaticBrightnessController != null) {
                         mAutomaticBrightnessController.update();
@@ -2137,7 +2173,7 @@
     private void setCurrentScreenBrightness(float brightnessValue) {
         if (brightnessValue != mCurrentScreenBrightnessSetting) {
             mCurrentScreenBrightnessSetting = brightnessValue;
-            mHandler.post(mOnBrightnessChangeRunnable);
+            postBrightnessChangeRunnable();
         }
     }
 
@@ -2189,7 +2225,7 @@
         return true;
     }
 
-    private void notifyBrightnessChanged(float brightness, boolean userInitiated,
+    private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
             boolean hadUserDataPoint) {
         final float brightnessInNits = convertToNits(brightness);
         if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
@@ -2299,16 +2335,17 @@
         pw.println("  mColorFadeFadesConfig=" + mColorFadeFadesConfig);
         pw.println("  mColorFadeEnabled=" + mColorFadeEnabled);
         synchronized (mCachedBrightnessInfo) {
-            pw.println("  mCachedBrightnessInfo.brightness=" + mCachedBrightnessInfo.brightness);
+            pw.println("  mCachedBrightnessInfo.brightness=" +
+                    mCachedBrightnessInfo.brightness.value);
             pw.println("  mCachedBrightnessInfo.adjustedBrightness=" +
-                    mCachedBrightnessInfo.adjustedBrightness);
+                    mCachedBrightnessInfo.adjustedBrightness.value);
             pw.println("  mCachedBrightnessInfo.brightnessMin=" +
-                    mCachedBrightnessInfo.brightnessMin);
+                    mCachedBrightnessInfo.brightnessMin.value);
             pw.println("  mCachedBrightnessInfo.brightnessMax=" +
-                    mCachedBrightnessInfo.brightnessMax);
-            pw.println("  mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode);
-            pw.println("  mCachedBrightnessInfo.highBrightnessTransitionPoint=" +
-                    mCachedBrightnessInfo.highBrightnessTransitionPoint);
+                    mCachedBrightnessInfo.brightnessMax.value);
+            pw.println("  mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
+            pw.println("  mCachedBrightnessInfo.hbmTransitionPoint=" +
+                    mCachedBrightnessInfo.hbmTransitionPoint.value);
         }
         pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
         pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2466,7 +2503,10 @@
     private void reportStats(float brightness) {
         float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX;
         synchronized(mCachedBrightnessInfo) {
-            hbmTransitionPoint = mCachedBrightnessInfo.highBrightnessTransitionPoint;
+            if (mCachedBrightnessInfo.hbmTransitionPoint == null) {
+                return;
+            }
+            hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value;
         }
 
         final boolean aboveTransition = brightness > hbmTransitionPoint;
@@ -2763,11 +2803,31 @@
     }
 
     static class CachedBrightnessInfo {
-        public float brightness;
-        public float adjustedBrightness;
-        public float brightnessMin;
-        public float brightnessMax;
-        public int hbmMode;
-        public float highBrightnessTransitionPoint;
+        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;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 2b56569..7a932ce 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -30,17 +30,13 @@
 public class HysteresisLevels {
     private static final String TAG = "HysteresisLevels";
 
-    // Default hysteresis constraints for brightening or darkening.
-    // The recent value must have changed by at least this fraction relative to the
-    // current value before a change will be considered.
-    private static final float DEFAULT_BRIGHTENING_HYSTERESIS = 0.10f;
-    private static final float DEFAULT_DARKENING_HYSTERESIS = 0.20f;
-
     private static final boolean DEBUG = false;
 
     private final float[] mBrighteningThresholds;
     private final float[] mDarkeningThresholds;
     private final float[] mThresholdLevels;
+    private final float mMinDarkening;
+    private final float mMinBrightening;
 
     /**
      * Creates a {@code HysteresisLevels} object with the given equal-length
@@ -48,9 +44,12 @@
      * @param brighteningThresholds an array of brightening hysteresis constraint constants.
      * @param darkeningThresholds an array of darkening hysteresis constraint constants.
      * @param thresholdLevels a monotonically increasing array of threshold levels.
+     * @param minBrighteningThreshold the minimum value for which the brightening value needs to
+     *                                return.
+     * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
     */
     HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
-            int[] thresholdLevels) {
+            int[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
         if (brighteningThresholds.length != darkeningThresholds.length
                 || darkeningThresholds.length != thresholdLevels.length + 1) {
             throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
@@ -58,6 +57,8 @@
         mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
         mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
         mThresholdLevels = setArrayFormat(thresholdLevels, 1.0f);
+        mMinDarkening = minDarkeningThreshold;
+        mMinBrightening = minBrighteningThreshold;
     }
 
     /**
@@ -65,11 +66,13 @@
      */
     public float getBrighteningThreshold(float value) {
         final float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
-        final float brightThreshold = value * (1.0f + brightConstant);
+        float brightThreshold = value * (1.0f + brightConstant);
         if (DEBUG) {
             Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
                     + brightThreshold + ", value=" + value);
         }
+
+        brightThreshold = Math.max(brightThreshold, value + mMinBrightening);
         return brightThreshold;
     }
 
@@ -78,12 +81,13 @@
      */
     public float getDarkeningThreshold(float value) {
         final float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
-        final float darkThreshold = value * (1.0f - darkConstant);
+        float darkThreshold = value * (1.0f - darkConstant);
         if (DEBUG) {
             Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
                     + darkThreshold + ", value=" + value);
         }
-        return darkThreshold;
+        darkThreshold = Math.min(darkThreshold, value - mMinDarkening);
+        return Math.max(darkThreshold, 0.0f);
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index f9a1368..8035526 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1453,7 +1453,7 @@
     /**
      * Local service that allows color transforms to be enabled from other system services.
      */
-    public final class ColorDisplayServiceInternal {
+    public class ColorDisplayServiceInternal {
 
         /**
          * Set the current CCT value for the display white balance transform, and if the transform
@@ -1472,6 +1472,11 @@
             return false;
         }
 
+        /** Get the luminance of the current chromatic adaptation matrix. */
+        public float getDisplayWhiteBalanceLuminance() {
+            return mDisplayWhiteBalanceTintController.getLuminance();
+        }
+
         /**
          * Reset the CCT value for the display white balance transform to its default value.
          */
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index bdbaaa8..936149c 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -251,6 +251,18 @@
         }
     }
 
+    public float getLuminance() {
+        synchronized (mLock) {
+            if (mChromaticAdaptationMatrix != null && mChromaticAdaptationMatrix.length == 9) {
+                // Compute only the luminance (y) value of the xyz * [1 1 1] transform.
+                return 1 / (mChromaticAdaptationMatrix[1] + mChromaticAdaptationMatrix[4]
+                        + mChromaticAdaptationMatrix[7]);
+            } else {
+                return -1;
+            }
+        }
+    }
+
     private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) {
         return new ColorSpace.Rgb(
                 "Display Color Space",
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index d64fcbc..151ec81 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -21,7 +21,6 @@
 import android.util.Spline;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.display.utils.AmbientFilter;
@@ -456,6 +455,22 @@
     }
 
     /**
+     * Calculate the adjusted brightness, in nits, due to the DWB color adaptation
+     *
+     * @param requestedBrightnessNits brightness the framework requires to be output
+     * @return the adjusted brightness the framework needs to output to counter the drop in
+     *         brightness due to DWB, or the requestedBrightnessNits if an adjustment cannot be made
+     */
+    public float calculateAdjustedBrightnessNits(float requestedBrightnessNits) {
+        float luminance = mColorDisplayServiceInternal.getDisplayWhiteBalanceLuminance();
+        if (luminance == -1) {
+            return requestedBrightnessNits;
+        }
+        float effectiveBrightness = requestedBrightnessNits * luminance;
+        return (requestedBrightnessNits - effectiveBrightness) + requestedBrightnessNits;
+    }
+
+    /**
      * The DisplayWhiteBalanceController decouples itself from its parent (DisplayPowerController)
      * by providing this interface to implement (and a method to set its callbacks object), and
      * calling these methods.
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 258689a..611b288 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -484,14 +484,15 @@
     }
 
     private static String componentsToString(ComponentName[] componentNames) {
+        if (componentNames == null) {
+            return null;
+        }
         StringBuilder names = new StringBuilder();
-        if (componentNames != null) {
-            for (ComponentName componentName : componentNames) {
-                if (names.length() > 0) {
-                    names.append(',');
-                }
-                names.append(componentName.flattenToString());
+        for (ComponentName componentName : componentNames) {
+            if (names.length() > 0) {
+                names.append(',');
             }
+            names.append(componentName.flattenToString());
         }
         return names.toString();
     }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 2dd7a10..b9c7123 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -317,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);
@@ -1797,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,
@@ -3487,6 +3492,11 @@
         }
 
         @Override
+        public void setPointerAcceleration(float acceleration) {
+            InputManagerService.this.setPointerAcceleration(acceleration);
+        }
+
+        @Override
         public void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
             InputManagerService.this.setDisplayEligibilityForPointerCapture(displayId, isEligible);
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.java b/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.java
new file mode 100644
index 0000000..1779333
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.view.ContextThemeWrapper;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Provides the window context for the IME switcher dialog.
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public final class InputMethodDialogWindowContext {
+    @Nullable
+    private Context mDialogWindowContext;
+
+    /**
+     * Returns the window context for IME switch dialogs to receive configuration changes.
+     *
+     * This method initializes the window context if it was not initialized, or moves the context to
+     * the targeted display if the current display of context is different from the display
+     * specified by {@code displayId}.
+     */
+    @NonNull
+    @VisibleForTesting(visibility = PACKAGE)
+    public Context get(int displayId) {
+        if (mDialogWindowContext == null || mDialogWindowContext.getDisplayId() != displayId) {
+            final Context systemUiContext = ActivityThread.currentActivityThread()
+                    .getSystemUiContext(displayId);
+            final Context windowContext = systemUiContext.createWindowContext(
+                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */);
+            mDialogWindowContext = new ContextThemeWrapper(
+                    windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings);
+        }
+        return mDialogWindowContext;
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0d41a37..feb0d138 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -160,7 +160,6 @@
 import com.android.internal.inputmethod.UnbindReason;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.util.DumpUtils;
@@ -204,7 +203,7 @@
 /**
  * This class provides a system service that manages input methods.
  */
-public class InputMethodManagerService extends IInputMethodManager.Stub
+public final class InputMethodManagerService extends IInputMethodManager.Stub
         implements Handler.Callback {
     static final boolean DEBUG = false;
     static final String TAG = "InputMethodManagerService";
@@ -270,7 +269,6 @@
     final WindowManagerInternal mWindowManagerInternal;
     final PackageManagerInternal mPackageManagerInternal;
     final InputManagerInternal mInputManagerInternal;
-    private final HandlerCaller mCaller;
     final boolean mHasFeature;
     private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
             new ArrayMap<>();
@@ -1526,8 +1524,8 @@
         @Override
         public void onUserUnlocking(@NonNull TargetUser user) {
             // Called on ActivityManager thread.
-            mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER,
-                    /* arg1= */ user.getUserIdentifier(), /* arg2= */ 0));
+            mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, user.getUserIdentifier(), 0)
+                    .sendToTarget();
         }
     }
 
@@ -1587,8 +1585,6 @@
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mImeDisplayValidator = mWindowManagerInternal::getDisplayImePolicy;
-        mCaller = new HandlerCaller(context, thread.getLooper(), this::handleMessage,
-                true /*asyncHandler*/);
         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
         mUserManager = mContext.getSystemService(UserManager.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -2199,6 +2195,24 @@
         }
     }
 
+    @NonNull
+    private Message obtainMessageOO(int what, Object arg1, Object arg2) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = arg1;
+        args.arg2 = arg2;
+        return mHandler.obtainMessage(what, 0, 0, args);
+    }
+
+    @NonNull
+    private Message obtainMessageIIIO(int what, int argi1, int argi2, int argi3, Object arg1) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = arg1;
+        args.argi1 = argi1;
+        args.argi2 = argi2;
+        args.argi3 = argi3;
+        return mHandler.obtainMessage(what, 0, 0, args);
+    }
+
     private void executeOrSendMessage(IInputMethodClient target, Message msg) {
          if (target.asBinder() instanceof Binder) {
              // This is supposed to be emulating the one-way semantics when the IME client is
@@ -2207,7 +2221,7 @@
              // We probably should create a simple wrapper of IInputMethodClient as the first step
              // to get rid of executeOrSendMessage() then should prohibit system_server to be the
              // IME client for long term.
-             mCaller.sendMessage(msg);
+             msg.sendToTarget();
          } else {
              handleMessage(msg);
              msg.recycle();
@@ -2229,7 +2243,7 @@
 
             scheduleSetActiveToClient(mCurClient, false /* active */, false /* fullscreen */,
                     false /* reportToImeController */);
-            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
+            executeOrSendMessage(mCurClient.client, mHandler.obtainMessage(
                     MSG_UNBIND_CLIENT, getSequenceNumberLocked(), unbindClientReason,
                     mCurClient.client));
             mCurClient.sessionRequested = false;
@@ -2506,8 +2520,9 @@
 
     @AnyThread
     void scheduleNotifyImeUidToAudioService(int uid) {
-        mCaller.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
-        mCaller.obtainMessageI(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid).sendToTarget();
+        mHandler.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
+        mHandler.obtainMessage(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid, 0 /* unused */)
+                .sendToTarget();
     }
 
     @BinderThread
@@ -2532,7 +2547,7 @@
                         InputBindResult res = attachNewInputLocked(
                                 StartInputReason.SESSION_CREATED_BY_IME, true);
                         if (res.method != null) {
-                            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
+                            executeOrSendMessage(mCurClient.client, obtainMessageOO(
                                     MSG_BIND_CLIENT, mCurClient.client, res));
                         }
                         return;
@@ -3637,9 +3652,10 @@
 
             // Always call subtype picker, because subtype picker is a superset of input method
             // picker.
-            mHandler.sendMessage(mCaller.obtainMessageII(
-                    MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode,
-                    (mCurClient != null) ? mCurClient.selfReportedDisplayId : DEFAULT_DISPLAY));
+            final int displayId =
+                    (mCurClient != null) ? mCurClient.selfReportedDisplayId : DEFAULT_DISPLAY;
+            mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
+                    .sendToTarget();
         }
     }
 
@@ -3654,8 +3670,8 @@
         }
         // Always call subtype picker, because subtype picker is a superset of input method
         // picker.
-        mHandler.sendMessage(mCaller.obtainMessageII(
-                MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId));
+        mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
+                .sendToTarget();
     }
 
     /**
@@ -3912,7 +3928,7 @@
     @Override
     public void removeImeSurface() {
         mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null);
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
+        mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
     }
 
     @Override
@@ -4380,8 +4396,8 @@
 
     private void scheduleSetActiveToClient(ClientState state, boolean active, boolean fullscreen,
             boolean reportToImeController) {
-        executeOrSendMessage(state.client, mCaller.obtainMessageIIIIO(MSG_SET_ACTIVE,
-                active ? 1 : 0, fullscreen ? 1 : 0, reportToImeController ? 1 : 0, 0, state));
+        executeOrSendMessage(state.client, obtainMessageIIIO(MSG_SET_ACTIVE,
+                active ? 1 : 0, fullscreen ? 1 : 0, reportToImeController ? 1 : 0, state));
     }
 
     @GuardedBy("ImfLock.class")
@@ -4944,14 +4960,13 @@
 
         @Override
         public void removeImeSurface() {
-            mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
+            mService.mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
         }
 
         @Override
         public void updateImeWindowStatus(boolean disableImeIcon) {
-            mService.mHandler.sendMessage(
-                    mService.mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS,
-                            disableImeIcon ? 1 : 0, 0));
+            mService.mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
+                    .sendToTarget();
         }
     }
 
@@ -5017,8 +5032,9 @@
             }
             if (mCurClient != null && mCurClient.client != null) {
                 mInFullscreenMode = fullscreen;
-                executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
-                        MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, mCurClient));
+                executeOrSendMessage(mCurClient.client, mHandler.obtainMessage(
+                        MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, 0 /* unused */,
+                        mCurClient));
             }
         }
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index fcb1be0..348bb2d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -16,22 +16,19 @@
 
 package com.android.server.inputmethod;
 
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
 import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
 
-import android.app.ActivityThread;
+import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
-import android.os.IBinder;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
-import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -45,7 +42,6 @@
 import android.widget.TextView;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
 import com.android.server.wm.WindowManagerInternal;
@@ -53,8 +49,7 @@
 import java.util.List;
 
 /** A controller to show/hide the input method menu */
-@VisibleForTesting(visibility = PACKAGE)
-public class InputMethodMenuController {
+final class InputMethodMenuController {
     private static final String TAG = InputMethodMenuController.class.getSimpleName();
 
     private final InputMethodManagerService mService;
@@ -64,17 +59,18 @@
     private final KeyguardManager mKeyguardManager;
     private final WindowManagerInternal mWindowManagerInternal;
 
-    private Context mSettingsContext;
     private AlertDialog.Builder mDialogBuilder;
     private AlertDialog mSwitchingDialog;
-    private IBinder mSwitchingDialogToken;
     private View mSwitchingDialogTitleView;
     private InputMethodInfo[] mIms;
     private int[] mSubtypeIds;
 
     private boolean mShowImeWithHardKeyboard;
 
-    @VisibleForTesting(visibility = PACKAGE)
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    private InputMethodDialogWindowContext mDialogWindowContext;
+
     public InputMethodMenuController(InputMethodManagerService service) {
         mService = service;
         mSettings = mService.mSettings;
@@ -132,8 +128,11 @@
                 }
             }
 
-            final Context settingsContext = getSettingsContext(displayId);
-            mDialogBuilder = new AlertDialog.Builder(settingsContext);
+            if (mDialogWindowContext == null) {
+                mDialogWindowContext = new InputMethodDialogWindowContext();
+            }
+            final Context dialogWindowContext = mDialogWindowContext.get(displayId);
+            mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
             mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
 
             final Context dialogContext = mDialogBuilder.getContext();
@@ -199,7 +198,7 @@
             // Use an alternate token for the dialog for that window manager can group the token
             // with other IME windows based on type vs. grouping based on whichever token happens
             // to get selected by the system later on.
-            attrs.token = mSwitchingDialogToken;
+            attrs.token = dialogWindowContext.getWindowContextToken();
             attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
             attrs.setTitle("Select input method");
             w.setAttributes(attrs);
@@ -208,27 +207,6 @@
         }
     }
 
-    /**
-     * Returns the window context for IME switch dialogs to receive configuration changes.
-     *
-     * This method initializes the window context if it was not initialized. This method also moves
-     * the context to the targeted display if the current display of context is different than
-     * the display specified by {@code displayId}.
-     */
-    @VisibleForTesting
-    public Context getSettingsContext(int displayId) {
-        if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) {
-            final Context systemUiContext = ActivityThread.currentActivityThread()
-                    .getSystemUiContext(displayId);
-            final Context windowContext = systemUiContext.createWindowContext(
-                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */);
-            mSettingsContext = new ContextThemeWrapper(
-                    windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings);
-            mSwitchingDialogToken = mSettingsContext.getWindowContextToken();
-        }
-        return mSettingsContext;
-    }
-
     private boolean isScreenLocked() {
         return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()
                 && mKeyguardManager.isKeyguardSecure();
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 6676987..d48ccd5 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -69,7 +69,7 @@
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -598,7 +598,7 @@
                     0,
                     0,
                     null,
-                    PackageUserState.DEFAULT,
+                    PackageUserStateInternal.DEFAULT,
                     UserHandle.getCallingUserId(),
                     null);
         } catch (Exception e) {
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index 57a7620..ac42646 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -83,9 +83,9 @@
             }
 
             @Override
-            public void onError() {
+            public void onError(Throwable t) {
                 try {
-                    listener.onResults("Service not Available", Collections.emptyList());
+                    listener.onResults(t.toString(), Collections.emptyList());
                 } catch (RemoteException e) {
                     // ignore
                 }
@@ -110,9 +110,9 @@
             }
 
             @Override
-            public void onError() {
+            public void onError(Throwable t) {
                 try {
-                    listener.onResults("Service not Available", Collections.emptyList());
+                    listener.onResults(t.toString(), Collections.emptyList());
                 } catch (RemoteException e) {
                     // ignore
                 }
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 4d9253e..718f98a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -215,7 +215,9 @@
         }
         @Override
         public void onPreciseCallStateChanged(PreciseCallState state) {
-            if (state.PRECISE_CALL_STATE_ACTIVE == state.getForegroundCallState()) {
+            if (PreciseCallState.PRECISE_CALL_STATE_ACTIVE == state.getForegroundCallState()
+                    || PreciseCallState.PRECISE_CALL_STATE_DIALING
+                    == state.getForegroundCallState()) {
                 mActiveSubId = mSubId;
                 if (DEBUG) Log.d(TAG, "mActiveSubId: " + mActiveSubId);
             }
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 2b3f4207..05966da 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -208,7 +208,7 @@
             }
 
             @Override
-            public void onError() {
+            public void onError(Throwable t) {
                 synchronized (mLock) {
                     mFlushListeners.remove(callback);
                 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index e555c13..fac7a40 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -24,7 +24,6 @@
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
-import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -101,7 +100,6 @@
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
-import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
 import static android.os.Trace.TRACE_TAG_NETWORK;
 import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED;
@@ -130,7 +128,6 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
-import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -184,7 +181,6 @@
 import android.net.NetworkStateSnapshot;
 import android.net.NetworkTemplate;
 import android.net.TelephonyNetworkSpecifier;
-import android.net.TrafficStats;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.BestClock;
@@ -1010,10 +1006,11 @@
             userFilter.addAction(ACTION_USER_REMOVED);
             mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
 
-            // listen for stats update events
-            final IntentFilter statsFilter = new IntentFilter(ACTION_NETWORK_STATS_UPDATED);
-            mContext.registerReceiver(
-                    mStatsReceiver, statsFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
+            // listen for stats updated callbacks for interested network types.
+            mNetworkStats.registerUsageCallback(new NetworkTemplate.Builder(MATCH_MOBILE).build(),
+                    0 /* thresholdBytes */, new HandlerExecutor(mHandler), mStatsCallback);
+            mNetworkStats.registerUsageCallback(new NetworkTemplate.Builder(MATCH_WIFI).build(),
+                    0 /* thresholdBytes */, new HandlerExecutor(mHandler), mStatsCallback);
 
             // Listen for snooze from notifications
             mContext.registerReceiver(mSnoozeReceiver,
@@ -1214,19 +1211,16 @@
     };
 
     /**
-     * Receiver that watches for {@link NetworkStatsManager} updates, which we
-     * use to check against {@link NetworkPolicy#warningBytes}.
+     * Listener that watches for {@link NetworkStatsManager} updates, which
+     * NetworkPolicyManagerService uses to check against {@link NetworkPolicy#warningBytes}.
      */
-    private final NetworkStatsBroadcastReceiver mStatsReceiver =
-            new NetworkStatsBroadcastReceiver();
-    private class NetworkStatsBroadcastReceiver extends BroadcastReceiver {
-        private boolean mIsAnyIntentReceived = false;
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // on background handler thread, and verified
-            // READ_NETWORK_USAGE_HISTORY permission above.
+    private final StatsCallback mStatsCallback = new StatsCallback();
+    private class StatsCallback extends NetworkStatsManager.UsageCallback {
+        private boolean mIsAnyCallbackReceived = false;
 
-            mIsAnyIntentReceived = true;
+        @Override
+        public void onThresholdReached(int networkType, String subscriberId) {
+            mIsAnyCallbackReceived = true;
 
             synchronized (mNetworkPoliciesSecondLock) {
                 updateNetworkRulesNL();
@@ -1236,11 +1230,11 @@
         }
 
         /**
-         * Return whether any {@code ACTION_NETWORK_STATS_UPDATED} intent is received.
+         * Return whether any callback is received.
          * Used to determine if NetworkStatsService is ready.
          */
-        public boolean isAnyIntentReceived() {
-            return mIsAnyIntentReceived;
+        public boolean isAnyCallbackReceived() {
+            return mIsAnyCallbackReceived;
         }
     };
 
@@ -1453,7 +1447,7 @@
 
         // Skip if not ready. NetworkStatsService will block public API calls until it is
         // ready. To prevent NPMS be blocked on that, skip and fail fast instead.
-        if (!mStatsReceiver.isAnyIntentReceived()) return null;
+        if (!mStatsCallback.isAnyCallbackReceived()) return null;
 
         final List<NetworkStats.Bucket> stats = mDeps.getNetworkUidBytes(template, start, end);
         for (final NetworkStats.Bucket entry : stats) {
@@ -2303,7 +2297,7 @@
         if (dataWarningConfig == WARNING_DISABLED) {
             return WARNING_DISABLED;
         } else {
-            return dataWarningConfig * MB_IN_BYTES;
+            return DataUnit.MEBIBYTES.toBytes(dataWarningConfig);
         }
     }
 
@@ -3472,9 +3466,9 @@
                 plans.add(SubscriptionPlan.Builder
                         .createRecurringMonthly(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"))
                         .setTitle("G-Mobile")
-                        .setDataLimit(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataLimit(DataUnit.GIBIBYTES.toBytes(5),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_BILLED)
-                        .setDataUsage(1 * TrafficStats.GB_IN_BYTES,
+                        .setDataUsage(DataUnit.GIBIBYTES.toBytes(1),
                                 ZonedDateTime.now().minusHours(36).toInstant().toEpochMilli())
                         .build());
                 plans.add(SubscriptionPlan.Builder
@@ -3482,15 +3476,15 @@
                         .setTitle("G-Mobile Happy")
                         .setDataLimit(SubscriptionPlan.BYTES_UNLIMITED,
                                 SubscriptionPlan.LIMIT_BEHAVIOR_BILLED)
-                        .setDataUsage(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataUsage(DataUnit.GIBIBYTES.toBytes(5),
                                 ZonedDateTime.now().minusHours(36).toInstant().toEpochMilli())
                         .build());
                 plans.add(SubscriptionPlan.Builder
                         .createRecurringMonthly(ZonedDateTime.parse("2017-03-14T00:00:00.000Z"))
                         .setTitle("G-Mobile, Charged after limit")
-                        .setDataLimit(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataLimit(DataUnit.GIBIBYTES.toBytes(5),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_BILLED)
-                        .setDataUsage(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataUsage(DataUnit.GIBIBYTES.toBytes(5),
                                 ZonedDateTime.now().minusHours(36).toInstant().toEpochMilli())
                         .build());
             } else if ("month_soft".equals(fake)) {
@@ -3499,25 +3493,25 @@
                         .setTitle("G-Mobile is the carriers name who this plan belongs to")
                         .setSummary("Crazy unlimited bandwidth plan with incredibly long title "
                                 + "that should be cut off to prevent UI from looking terrible")
-                        .setDataLimit(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataLimit(DataUnit.GIBIBYTES.toBytes(5),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
-                        .setDataUsage(1 * TrafficStats.GB_IN_BYTES,
+                        .setDataUsage(DataUnit.GIBIBYTES.toBytes(1),
                                 ZonedDateTime.now().minusHours(1).toInstant().toEpochMilli())
                         .build());
                 plans.add(SubscriptionPlan.Builder
                         .createRecurringMonthly(ZonedDateTime.parse("2017-03-14T00:00:00.000Z"))
                         .setTitle("G-Mobile, Throttled after limit")
-                        .setDataLimit(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataLimit(DataUnit.GIBIBYTES.toBytes(5),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
-                        .setDataUsage(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataUsage(DataUnit.GIBIBYTES.toBytes(5),
                                 ZonedDateTime.now().minusHours(1).toInstant().toEpochMilli())
                         .build());
                 plans.add(SubscriptionPlan.Builder
                         .createRecurringMonthly(ZonedDateTime.parse("2017-03-14T00:00:00.000Z"))
                         .setTitle("G-Mobile, No data connection after limit")
-                        .setDataLimit(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataLimit(DataUnit.GIBIBYTES.toBytes(5),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED)
-                        .setDataUsage(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataUsage(DataUnit.GIBIBYTES.toBytes(5),
                                 ZonedDateTime.now().minusHours(1).toInstant().toEpochMilli())
                         .build());
 
@@ -3525,25 +3519,25 @@
                 plans.add(SubscriptionPlan.Builder
                         .createRecurringMonthly(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"))
                         .setTitle("G-Mobile is the carriers name who this plan belongs to")
-                        .setDataLimit(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataLimit(DataUnit.GIBIBYTES.toBytes(5),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
-                        .setDataUsage(6 * TrafficStats.GB_IN_BYTES,
+                        .setDataUsage(DataUnit.GIBIBYTES.toBytes(6),
                                 ZonedDateTime.now().minusHours(1).toInstant().toEpochMilli())
                         .build());
                 plans.add(SubscriptionPlan.Builder
                         .createRecurringMonthly(ZonedDateTime.parse("2017-03-14T00:00:00.000Z"))
                         .setTitle("G-Mobile, Throttled after limit")
-                        .setDataLimit(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataLimit(DataUnit.GIBIBYTES.toBytes(5),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
-                        .setDataUsage(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataUsage(DataUnit.GIBIBYTES.toBytes(5),
                                 ZonedDateTime.now().minusHours(1).toInstant().toEpochMilli())
                         .build());
                 plans.add(SubscriptionPlan.Builder
                         .createRecurringMonthly(ZonedDateTime.parse("2017-03-14T00:00:00.000Z"))
                         .setTitle("G-Mobile, No data connection after limit")
-                        .setDataLimit(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataLimit(DataUnit.GIBIBYTES.toBytes(5),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED)
-                        .setDataUsage(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataUsage(DataUnit.GIBIBYTES.toBytes(5),
                                 ZonedDateTime.now().minusHours(1).toInstant().toEpochMilli())
                         .build());
 
@@ -3557,9 +3551,9 @@
                         .createNonrecurring(ZonedDateTime.now().minusDays(20),
                                 ZonedDateTime.now().plusDays(10))
                         .setTitle("G-Mobile")
-                        .setDataLimit(512 * TrafficStats.MB_IN_BYTES,
+                        .setDataLimit(DataUnit.MEBIBYTES.toBytes(512),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED)
-                        .setDataUsage(100 * TrafficStats.MB_IN_BYTES,
+                        .setDataUsage(DataUnit.MEBIBYTES.toBytes(100),
                                 ZonedDateTime.now().minusHours(3).toInstant().toEpochMilli())
                         .build());
             } else if ("prepaid_crazy".equals(fake)) {
@@ -3567,9 +3561,9 @@
                         .createNonrecurring(ZonedDateTime.now().minusDays(20),
                                 ZonedDateTime.now().plusDays(10))
                         .setTitle("G-Mobile Anytime")
-                        .setDataLimit(512 * TrafficStats.MB_IN_BYTES,
+                        .setDataLimit(DataUnit.MEBIBYTES.toBytes(512),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED)
-                        .setDataUsage(100 * TrafficStats.MB_IN_BYTES,
+                        .setDataUsage(DataUnit.MEBIBYTES.toBytes(100),
                                 ZonedDateTime.now().minusHours(3).toInstant().toEpochMilli())
                         .build());
                 plans.add(SubscriptionPlan.Builder
@@ -3577,9 +3571,9 @@
                                 ZonedDateTime.now().plusDays(20))
                         .setTitle("G-Mobile Nickel Nights")
                         .setSummary("5¢/GB between 1-5AM")
-                        .setDataLimit(5 * TrafficStats.GB_IN_BYTES,
+                        .setDataLimit(DataUnit.GIBIBYTES.toBytes(5),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
-                        .setDataUsage(15 * TrafficStats.MB_IN_BYTES,
+                        .setDataUsage(DataUnit.MEBIBYTES.toBytes(15),
                                 ZonedDateTime.now().minusHours(30).toInstant().toEpochMilli())
                         .build());
                 plans.add(SubscriptionPlan.Builder
@@ -3587,9 +3581,9 @@
                                 ZonedDateTime.now().plusDays(20))
                         .setTitle("G-Mobile Bonus 3G")
                         .setSummary("Unlimited 3G data")
-                        .setDataLimit(1 * TrafficStats.GB_IN_BYTES,
+                        .setDataLimit(DataUnit.GIBIBYTES.toBytes(1),
                                 SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
-                        .setDataUsage(300 * TrafficStats.MB_IN_BYTES,
+                        .setDataUsage(DataUnit.MEBIBYTES.toBytes(300),
                                 ZonedDateTime.now().minusHours(1).toInstant().toEpochMilli())
                         .build());
             } else if ("unlimited".equals(fake)) {
@@ -3599,7 +3593,7 @@
                         .setTitle("G-Mobile Awesome")
                         .setDataLimit(SubscriptionPlan.BYTES_UNLIMITED,
                                 SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
-                        .setDataUsage(50 * TrafficStats.MB_IN_BYTES,
+                        .setDataUsage(DataUnit.MEBIBYTES.toBytes(50),
                                 ZonedDateTime.now().minusHours(3).toInstant().toEpochMilli())
                         .build());
             }
@@ -5450,7 +5444,7 @@
     private long getTotalBytes(NetworkTemplate template, long start, long end) {
         // Skip if not ready. NetworkStatsService will block public API calls until it is
         // ready. To prevent NPMS be blocked on that, skip and fail fast instead.
-        if (!mStatsReceiver.isAnyIntentReceived()) return 0;
+        if (!mStatsCallback.isAnyCallbackReceived()) return 0;
         return mDeps.getNetworkTotalBytes(template, start, end);
     }
 
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index 54dd113..e5d07bc 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -16,6 +16,9 @@
 
 package com.android.server.notification;
 
+import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
+import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
@@ -30,6 +33,7 @@
 import com.android.internal.R;
 import com.android.server.pm.PackageManagerService;
 
+import java.time.Duration;
 import java.util.Arrays;
 
 /**
@@ -89,8 +93,7 @@
      * Safely create a {@link VibrationEffect} from given waveform description.
      *
      * <p>The waveform is described by a sequence of values for target amplitude, frequency and
-     * duration, that are forwarded to
-     * {@link VibrationEffect.WaveformBuilder#addRamp(float, float, int)}.
+     * duration, that are forwarded to {@link VibrationEffect.WaveformBuilder#addTransition}.
      *
      * <p>This method returns {@code null} if the pattern is also {@code null} or invalid.
      *
@@ -114,16 +117,17 @@
 
             VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform();
             for (int i = 0; i < length; i += 3) {
-                waveformBuilder.addRamp(
-                        /* amplitude= */ values[i],
-                        /* frequencyHz= */ values[i + 1],
-                        /* duration= */ (int) values[i + 2]);
+                waveformBuilder.addTransition(Duration.ofMillis((int) values[i + 2]),
+                        targetAmplitude(values[i]), targetFrequency(values[i + 1]));
             }
 
+            VibrationEffect effect = waveformBuilder.build();
             if (insistent) {
-                return waveformBuilder.build(/* repeat= */ 0);
+                return VibrationEffect.startComposition()
+                        .repeatEffectIndefinitely(effect)
+                        .compose();
             }
-            return waveformBuilder.build();
+            return effect;
         } catch (IllegalArgumentException e) {
             Slog.e(TAG, "Error creating vibration PWLE waveform with pattern: "
                     + Arrays.toString(values));
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index cd4244b..ba003d2 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -36,14 +36,6 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedProviderImpl;
-import com.android.server.pm.pkg.component.ParsedService;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -62,7 +54,15 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.component.ComponentMutateUtils;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedComponent;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedMainComponent;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedProviderImpl;
+import com.android.server.pm.pkg.component.ParsedService;
 import com.android.server.utils.Snappable;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.WatchableImpl;
@@ -380,14 +380,13 @@
                     continue;
                 }
                 // See PM.queryContentProviders()'s javadoc for why we have the metaData parameter.
-                if (metaDataKey != null
-                        && (p.getMetaData() == null || !p.getMetaData().containsKey(metaDataKey))) {
+                if (metaDataKey != null && !p.getMetaData().containsKey(metaDataKey)) {
                     continue;
                 }
                 if (appInfoGenerator == null) {
                     appInfoGenerator = new CachedApplicationInfoGenerator();
                 }
-                final PackageUserState state = ps.getUserStateOrDefault(userId);
+                final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
                 final ApplicationInfo appInfo =
                         appInfoGenerator.generate(pkg, flags, state, userId, ps);
                 if (appInfo == null) {
@@ -424,7 +423,7 @@
             if (pkg == null) {
                 return null;
             }
-            final PackageUserState state = ps.getUserStateOrDefault(userId);
+            final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
             ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
                     pkg, flags, state, userId, ps);
             if (appInfo == null) {
@@ -461,7 +460,7 @@
                 if (appInfoGenerator == null) {
                     appInfoGenerator = new CachedApplicationInfoGenerator();
                 }
-                final PackageUserState state = ps.getUserStateOrDefault(userId);
+                final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
                 final ApplicationInfo appInfo =
                         appInfoGenerator.generate(pkg, 0, state, userId, ps);
                 if (appInfo == null) {
@@ -1537,7 +1536,7 @@
                 }
                 return null;
             }
-            final PackageUserState userState = ps.getUserStateOrDefault(userId);
+            final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
             ActivityInfo ai = PackageInfoUtils.generateActivityInfo(pkg, activity, mFlags,
                     userState, userId, ps);
             if (ai == null) {
@@ -1854,7 +1853,7 @@
             if (ps == null) {
                 return null;
             }
-            final PackageUserState userState = ps.getUserStateOrDefault(userId);
+            final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
             final boolean matchVisibleToInstantApp = (mFlags
                     & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
             final boolean isInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
@@ -2099,7 +2098,7 @@
             if (ps == null) {
                 return null;
             }
-            final PackageUserState userState = ps.getUserStateOrDefault(userId);
+            final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
             ServiceInfo si = PackageInfoUtils.generateServiceInfo(pkg, service, mFlags,
                     userState, userId, ps);
             if (si == null) {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 69c475a..cca1b97 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -823,7 +823,7 @@
         }
         if (resolveComponentName().equals(component)) {
             return PackageInfoWithoutStateUtils.generateDelegateActivityInfo(mResolveActivity,
-                    flags, PackageUserState.DEFAULT, userId);
+                    flags, PackageUserStateInternal.DEFAULT, userId);
         }
         return null;
     }
@@ -1547,7 +1547,7 @@
             flags |= MATCH_ANY_USER;
         }
 
-        final PackageUserState state = ps.getUserStateOrDefault(userId);
+        final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
         AndroidPackage p = ps.getPkg();
         if (p != null) {
             // Compute GIDs only if requested
@@ -3548,7 +3548,7 @@
             if (shouldFilterApplication(ps, callingUid, userId)) {
                 return false;
             }
-            final PackageUserState state = ps.getUserStateOrDefault(userId);
+            final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
             if (state != null) {
                 return PackageUserStateUtils.isAvailable(state, 0);
             }
@@ -4011,7 +4011,7 @@
                     ps, callingUid, component, TYPE_PROVIDER, userId)) {
                 return null;
             }
-            PackageUserState state = ps.getUserStateOrDefault(userId);
+            PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
             final ApplicationInfo appInfo =
                     PackageInfoUtils.generateApplicationInfo(ps.getPkg(), flags, state, userId, ps);
             if (appInfo == null) {
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index a3f1435..a3134a0 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -199,30 +199,6 @@
                     .addDataType("video/*")
                     .build();
 
-    // TODO(b/199068419): Remove once GEM enables the intent for Googlers
-    /** Pick images can be forwarded to work profile. */
-    private static final DefaultCrossProfileIntentFilter ACTION_PICK_IMAGES_TO_PROFILE =
-            new DefaultCrossProfileIntentFilter.Builder(
-                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
-                    /* flags= */ 0,
-                    /* letsPersonalDataIntoProfile= */ true)
-                    .addAction(MediaStore.ACTION_PICK_IMAGES)
-                    .addCategory(Intent.CATEGORY_DEFAULT)
-                    .build();
-    // TODO(b/199068419): Remove once GEM enables the intent for Googlers
-    /** Pick images can be forwarded to work profile. */
-    private static final DefaultCrossProfileIntentFilter
-            ACTION_PICK_IMAGES_WITH_DATA_TYPES_TO_PROFILE =
-            new DefaultCrossProfileIntentFilter.Builder(
-                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
-                    /* flags= */ 0,
-                    /* letsPersonalDataIntoProfile= */ true)
-                    .addAction(MediaStore.ACTION_PICK_IMAGES)
-                    .addCategory(Intent.CATEGORY_DEFAULT)
-                    .addDataType("image/*")
-                    .addDataType("video/*")
-                    .build();
-
     /** Open document intent can be forwarded to parent user. */
     private static final DefaultCrossProfileIntentFilter OPEN_DOCUMENT =
             new DefaultCrossProfileIntentFilter.Builder(
@@ -336,8 +312,6 @@
                 ACTION_PICK_DATA,
                 ACTION_PICK_IMAGES,
                 ACTION_PICK_IMAGES_WITH_DATA_TYPES,
-                ACTION_PICK_IMAGES_TO_PROFILE,
-                ACTION_PICK_IMAGES_WITH_DATA_TYPES_TO_PROFILE,
                 OPEN_DOCUMENT,
                 GET_CONTENT,
                 USB_DEVICE_ATTACHED,
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index 9efe81a..06405ae 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -30,8 +30,10 @@
 import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
 import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
 import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_FRAMEWORK_RES_SPLITS;
 
 import android.annotation.Nullable;
+import android.content.pm.parsing.ApkLiteParseUtils;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -91,6 +93,29 @@
         mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM;
     }
 
+    private List<File> getFrameworkResApkSplitFiles() {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanFrameworkResApkSplits");
+        try {
+            final List<File> splits = new ArrayList<>();
+            final List<ApexManager.ActiveApexInfo> activeApexInfos =
+                    mPm.mApexManager.getActiveApexInfos();
+            for (int i = 0; i < activeApexInfos.size(); i++) {
+                ApexManager.ActiveApexInfo apexInfo = activeApexInfos.get(i);
+                File splitsFolder = new File(apexInfo.apexDirectory, "etc/splits");
+                if (splitsFolder.isDirectory()) {
+                    for (File file : splitsFolder.listFiles()) {
+                        if (ApkLiteParseUtils.isApkFile(file)) {
+                            splits.add(file);
+                        }
+                    }
+                }
+            }
+            return splits;
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
     private List<ScanPartition> getSystemScanPartitions() {
         final List<ScanPartition> scanPartitions = new ArrayList<>();
         scanPartitions.addAll(mPm.mInjector.getSystemPartitions());
@@ -184,7 +209,8 @@
         if (!mPm.isOnlyCoreApps()) {
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                     SystemClock.uptimeMillis());
-            scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN, 0,
+            scanDirTracedLI(mPm.getAppInstallDir(), /* frameworkSplits= */ null, 0,
+                    mScanFlags | SCAN_REQUIRE_KNOWN, 0,
                     packageParser, executorService);
 
         }
@@ -247,12 +273,14 @@
             if (partition.getOverlayFolder() == null) {
                 continue;
             }
-            scanDirTracedLI(partition.getOverlayFolder(), mSystemParseFlags,
-                    mSystemScanFlags | partition.scanFlag, 0,
+            scanDirTracedLI(partition.getOverlayFolder(), /* frameworkSplits= */ null,
+                    mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0,
                     packageParser, executorService);
         }
 
-        scanDirTracedLI(frameworkDir, mSystemParseFlags,
+        List<File> frameworkSplits = getFrameworkResApkSplitFiles();
+        scanDirTracedLI(frameworkDir, frameworkSplits,
+                mSystemParseFlags | PARSE_FRAMEWORK_RES_SPLITS,
                 mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
                 packageParser, executorService);
         if (!mPm.mPackages.containsKey("android")) {
@@ -263,12 +291,13 @@
         for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
             final ScanPartition partition = mDirsToScanAsSystem.get(i);
             if (partition.getPrivAppFolder() != null) {
-                scanDirTracedLI(partition.getPrivAppFolder(), mSystemParseFlags,
+                scanDirTracedLI(partition.getPrivAppFolder(), /* frameworkSplits= */ null,
+                        mSystemParseFlags,
                         mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
                         packageParser, executorService);
             }
-            scanDirTracedLI(partition.getAppFolder(), mSystemParseFlags,
-                    mSystemScanFlags | partition.scanFlag, 0,
+            scanDirTracedLI(partition.getAppFolder(), /* frameworkSplits= */ null,
+                    mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0,
                     packageParser, executorService);
         }
     }
@@ -285,12 +314,13 @@
     }
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
+    private void scanDirTracedLI(File scanDir, List<File> frameworkSplits,
+            final int parseFlags, int scanFlags,
             long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
         try {
-            mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags,
-                    currentTime, packageParser, executorService);
+            mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
+                    scanFlags, currentTime, packageParser, executorService);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 80699ac..b636c8e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -168,10 +168,7 @@
 
 import dalvik.system.VMRuntime;
 
-import libcore.io.IoUtils;
-
 import java.io.File;
-import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.security.DigestException;
@@ -1788,9 +1785,7 @@
      */
     private void setUpFsVerityIfPossible(AndroidPackage pkg) throws Installer.InstallerException,
             PrepareFailure, IOException, DigestException, NoSuchAlgorithmException {
-        final boolean standardMode = PackageManagerServiceUtils.isApkVerityEnabled();
-        final boolean legacyMode = PackageManagerServiceUtils.isLegacyApkVerityEnabled();
-        if (!standardMode && !legacyMode) {
+        if (!PackageManagerServiceUtils.isApkVerityEnabled()) {
             return;
         }
 
@@ -1801,40 +1796,24 @@
 
         // Collect files we care for fs-verity setup.
         ArrayMap<String, String> fsverityCandidates = new ArrayMap<>();
-        if (legacyMode) {
-            synchronized (mPm.mLock) {
-                final PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
-                if (ps != null && ps.isPrivileged()) {
-                    fsverityCandidates.put(pkg.getBaseApkPath(), null);
-                    if (pkg.getSplitCodePaths() != null) {
-                        for (String splitPath : pkg.getSplitCodePaths()) {
-                            fsverityCandidates.put(splitPath, null);
-                        }
-                    }
-                }
-            }
-        } else {
-            // NB: These files will become only accessible if the signing key is loaded in kernel's
-            // .fs-verity keyring.
-            fsverityCandidates.put(pkg.getBaseApkPath(),
-                    VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));
+        // NB: These files will become only accessible if the signing key is loaded in kernel's
+        // .fs-verity keyring.
+        fsverityCandidates.put(pkg.getBaseApkPath(),
+                VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));
 
-            final String dmPath = DexMetadataHelper.buildDexMetadataPathForApk(
-                    pkg.getBaseApkPath());
-            if (new File(dmPath).exists()) {
-                fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
-            }
+        final String dmPath = DexMetadataHelper.buildDexMetadataPathForApk(
+                pkg.getBaseApkPath());
+        if (new File(dmPath).exists()) {
+            fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
+        }
 
-            if (pkg.getSplitCodePaths() != null) {
-                for (String path : pkg.getSplitCodePaths()) {
-                    fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
+        for (String path : pkg.getSplitCodePaths()) {
+            fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
 
-                    final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
-                    if (new File(splitDmPath).exists()) {
-                        fsverityCandidates.put(splitDmPath,
-                                VerityUtils.getFsveritySignatureFilePath(splitDmPath));
-                    }
-                }
+            final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
+            if (new File(splitDmPath).exists()) {
+                fsverityCandidates.put(splitDmPath,
+                        VerityUtils.getFsveritySignatureFilePath(splitDmPath));
             }
         }
 
@@ -1843,43 +1822,14 @@
             final String filePath = entry.getKey();
             final String signaturePath = entry.getValue();
 
-            if (!legacyMode) {
-                // fs-verity is optional for now.  Only set up if signature is provided.
-                if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
-                    try {
-                        VerityUtils.setUpFsverity(filePath, signaturePath);
-                    } catch (IOException e) {
-                        throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
-                                "Failed to enable fs-verity: " + e);
-                    }
-                }
-                continue;
-            }
-
-            // In legacy mode, fs-verity can only be enabled by process with CAP_SYS_ADMIN.
-            final VerityUtils.SetupResult result = VerityUtils.generateApkVeritySetupData(filePath);
-            if (result.isOk()) {
-                if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling verity to " + filePath);
-                final FileDescriptor fd = result.getUnownedFileDescriptor();
+            // fs-verity is optional for now.  Only set up if signature is provided.
+            if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
                 try {
-                    final byte[] rootHash = VerityUtils.generateApkVerityRootHash(filePath);
-                    try {
-                        // A file may already have fs-verity, e.g. when reused during a split
-                        // install. If the measurement succeeds, no need to attempt to set up.
-                        mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath,
-                                rootHash);
-                    } catch (Installer.InstallerException e) {
-                        mPm.mInstaller.installApkVerity(packageName, filePath, fd,
-                                result.getContentSize());
-                        mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath,
-                                rootHash);
-                    }
-                } finally {
-                    IoUtils.closeQuietly(fd);
+                    VerityUtils.setUpFsverity(filePath, signaturePath);
+                } catch (IOException e) {
+                    throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
+                            "Failed to enable fs-verity: " + e);
                 }
-            } else if (result.isFailed()) {
-                throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
-                        "Failed to generate verity");
             }
         }
     }
@@ -1989,9 +1939,7 @@
                             oldCodePaths = new ArraySet<>();
                         }
                         Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath());
-                        if (oldPackage.getSplitCodePaths() != null) {
-                            Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths());
-                        }
+                        Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths());
                         ps1.setOldCodePaths(oldCodePaths);
                     } else {
                         ps1.setOldCodePaths(null);
@@ -3412,8 +3360,9 @@
     }
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
-            long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
+    public void installPackagesFromDir(File scanDir, List<File> frameworkSplits, int parseFlags,
+            int scanFlags, long currentTime, PackageParser2 packageParser,
+            ExecutorService executorService) {
         final File[] files = scanDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
             Log.d(TAG, "No files in app dir " + scanDir);
@@ -3425,7 +3374,7 @@
                     + " flags=0x" + Integer.toHexString(parseFlags));
         }
         ParallelPackageParser parallelPackageParser =
-                new ParallelPackageParser(packageParser, executorService);
+                new ParallelPackageParser(packageParser, executorService, frameworkSplits);
 
         // Submit files for parsing in parallel
         int fileCount = 0;
@@ -3964,14 +3913,14 @@
      */
     private boolean canSkipForcedPackageVerification(AndroidPackage pkg) {
         final String packageName = pkg.getPackageName();
-        if (!canSkipForcedApkVerification(packageName, pkg.getBaseApkPath())) {
+        if (!VerityUtils.hasFsverity(pkg.getBaseApkPath())) {
             return false;
         }
         // TODO: Allow base and splits to be verified individually.
         String[] splitCodePaths = pkg.getSplitCodePaths();
         if (!ArrayUtils.isEmpty(splitCodePaths)) {
             for (int i = 0; i < splitCodePaths.length; i++) {
-                if (!canSkipForcedApkVerification(packageName, splitCodePaths[i])) {
+                if (!VerityUtils.hasFsverity(splitCodePaths[i])) {
                     return false;
                 }
             }
@@ -3980,34 +3929,6 @@
     }
 
     /**
-     * Returns if forced apk verification can be skipped, depending on current FSVerity setup and
-     * whether the apk contains signed root hash.  Note that the signer's certificate still needs to
-     * match one in a trusted source, and should be done separately.
-     */
-    private boolean canSkipForcedApkVerification(String packageName, String apkPath) {
-        if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
-            return VerityUtils.hasFsverity(apkPath);
-        }
-
-        try {
-            final byte[] rootHashObserved = VerityUtils.generateApkVerityRootHash(apkPath);
-            if (rootHashObserved == null) {
-                return false;  // APK does not contain Merkle tree root hash.
-            }
-            synchronized (mPm.mInstallLock) {
-                // Returns whether the observed root hash matches what kernel has.
-                mPm.mInstaller.assertFsverityRootHashMatches(packageName, apkPath,
-                        rootHashObserved);
-                return true;
-            }
-        } catch (Installer.InstallerException | IOException | DigestException
-                | NoSuchAlgorithmException e) {
-            Slog.w(TAG, "Error in fsverity check. Fallback to full apk verification.", e);
-        }
-        return false;
-    }
-
-    /**
      * Clear the package profile if this was an upgrade and the package
      * version was updated.
      */
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 47be7e6..c4389a7 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -39,7 +39,6 @@
 import dalvik.system.BlockGuard;
 import dalvik.system.VMRuntime;
 
-import java.io.FileDescriptor;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -726,34 +725,6 @@
         }
     }
 
-    /**
-     * Enables legacy apk-verity for an apk.
-     */
-    public void installApkVerity(String packageName, String filePath, FileDescriptor verityInput,
-            int contentSize) throws InstallerException {
-        if (!checkBeforeRemote()) return;
-        BlockGuard.getVmPolicy().onPathAccess(filePath);
-        try {
-            mInstalld.installApkVerity(packageName, filePath, verityInput, contentSize);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    /**
-     * Checks if provided hash matches the file's fs-verity merkle tree root hash.
-     */
-    public void assertFsverityRootHashMatches(String packageName, String filePath,
-            @NonNull byte[] expectedHash) throws InstallerException {
-        if (!checkBeforeRemote()) return;
-        BlockGuard.getVmPolicy().onPathAccess(filePath);
-        try {
-            mInstalld.assertFsverityRootHashMatches(packageName, filePath, expectedHash);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
     public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
             String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException {
         for (int i = 0; i < isas.length; i++) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d9ade96..390dd3f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2264,10 +2264,6 @@
         }
 
         if (params.isStaged) {
-            // TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even
-            //  though ideally, we just need to send session committed broadcast.
-            sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged", null);
-
             mStagedSession.verifySession();
         } else {
             verify();
@@ -2514,6 +2510,7 @@
                 mStagedSession.notifyEndPreRebootVerification();
                 if (error == SessionInfo.SESSION_NO_ERROR) {
                     mStagingManager.commitSession(mStagedSession);
+                    sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged", null);
                 } else {
                     dispatchSessionFinished(INSTALL_FAILED_VERIFICATION_FAILURE, msg, null);
                     maybeFinishChildSessions(INSTALL_FAILED_VERIFICATION_FAILURE, msg);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e00f4f5..e0d404a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -232,7 +232,7 @@
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUtils;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.component.ParsedInstrumentation;
 import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.mutate.PackageStateMutator;
@@ -8765,7 +8765,7 @@
             // The instance created in PackageManagerService is special cased to be non-user
             // specific, so initialize all the needed fields here.
             ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                    PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
+                    PackageUserStateInternal.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
 
             // Set up information for custom user intent resolution activity.
             mResolveActivity.applicationInfo = appInfo;
@@ -8797,7 +8797,7 @@
             // The instance stored in PackageManagerService is special cased to be non-user
             // specific, so initialize all the needed fields here.
             mAndroidApplication = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                    PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
+                    PackageUserStateInternal.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
 
             if (!mResolverReplaced) {
                 mResolveActivity.applicationInfo = mAndroidApplication;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index e03cf0a..dcc4386 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -228,11 +228,9 @@
         }
         final File baseFile = new File(pkg.getBaseApkPath());
         long maxModifiedTime = baseFile.lastModified();
-        if (pkg.getSplitCodePaths() != null) {
-            for (int i = pkg.getSplitCodePaths().length - 1; i >=0; --i) {
-                final File splitFile = new File(pkg.getSplitCodePaths()[i]);
-                maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
-            }
+        for (int i = pkg.getSplitCodePaths().length - 1; i >=0; --i) {
+            final File splitFile = new File(pkg.getSplitCodePaths()[i]);
+            maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
         }
         return maxModifiedTime;
     }
@@ -484,12 +482,6 @@
     /** Default is to not use fs-verity since it depends on kernel support. */
     private static final int FSVERITY_DISABLED = 0;
 
-    /**
-     * Experimental implementation targeting priv apps, with Android specific kernel patches to
-     * extend fs-verity.
-     */
-    private static final int FSVERITY_LEGACY = 1;
-
     /** Standard fs-verity. */
     private static final int FSVERITY_ENABLED = 2;
 
@@ -500,10 +492,6 @@
                         == FSVERITY_ENABLED;
     }
 
-    static boolean isLegacyApkVerityEnabled() {
-        return SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) == FSVERITY_LEGACY;
-    }
-
     /** Returns true to force apk verification if the package is considered privileged. */
     static boolean isApkVerificationForced(@Nullable PackageSetting ps) {
         // TODO(b/154310064): re-enable.
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index fd2256f..99f70b2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3563,18 +3563,27 @@
             }
             final LocalIntentReceiver receiver = new LocalIntentReceiver();
             session.commit(receiver.getIntentSender());
-            final Intent result = receiver.getResult();
-            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                    PackageInstaller.STATUS_FAILURE);
-            if (status == PackageInstaller.STATUS_SUCCESS) {
+            if (!session.isStaged()) {
+                final Intent result = receiver.getResult();
+                final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                        PackageInstaller.STATUS_FAILURE);
+                if (status == PackageInstaller.STATUS_SUCCESS) {
+                    if (logSuccess) {
+                        pw.println("Success");
+                    }
+                } else {
+                    pw.println("Failure ["
+                            + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+                }
+                return status;
+            } else {
+                // Return immediately without retrieving the result. The caller will decide
+                // whether to wait for the session to become ready.
                 if (logSuccess) {
                     pw.println("Success");
                 }
-            } else {
-                pw.println("Failure ["
-                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+                return PackageInstaller.STATUS_SUCCESS;
             }
-            return status;
         } finally {
             IoUtils.closeQuietly(session);
         }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9dbf57d..5fc840c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -295,14 +295,12 @@
             proto.write(PackageProto.SplitProto.REVISION_CODE, pkg.getBaseRevisionCode());
             proto.end(splitToken);
 
-            if (pkg.getSplitNames() != null) {
-                for (int i = 0; i < pkg.getSplitNames().length; i++) {
-                    splitToken = proto.start(PackageProto.SPLITS);
-                    proto.write(PackageProto.SplitProto.NAME, pkg.getSplitNames()[i]);
-                    proto.write(PackageProto.SplitProto.REVISION_CODE,
-                            pkg.getSplitRevisionCodes()[i]);
-                    proto.end(splitToken);
-                }
+            for (int i = 0; i < pkg.getSplitNames().length; i++) {
+                splitToken = proto.start(PackageProto.SPLITS);
+                proto.write(PackageProto.SplitProto.NAME, pkg.getSplitNames()[i]);
+                proto.write(PackageProto.SplitProto.REVISION_CODE,
+                        pkg.getSplitRevisionCodes()[i]);
+                proto.end(splitToken);
             }
 
             long sourceToken = proto.start(PackageProto.INSTALL_SOURCE);
@@ -1263,8 +1261,8 @@
 
     @Nullable
     @Override
-    public Integer getSharedUserId() {
-        return sharedUser == null ? null : sharedUser.userId;
+    public int getSharedUserId() {
+        return sharedUser == null ? -1 : sharedUser.userId;
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 5625884..45030bf 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -27,6 +27,7 @@
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 
 import java.io.File;
+import java.util.List;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ExecutorService;
@@ -54,9 +55,17 @@
 
     private final ExecutorService mExecutorService;
 
+    private final List<File> mFrameworkSplits;
+
     ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService) {
+        this(packageParser, executorService, /* frameworkSplits= */ null);
+    }
+
+    ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService,
+            List<File> frameworkSplits) {
         mPackageParser = packageParser;
         mExecutorService = executorService;
+        mFrameworkSplits = frameworkSplits;
     }
 
     static class ParseResult {
@@ -125,6 +134,6 @@
     @VisibleForTesting
     protected ParsedPackage parsePackage(File scanFile, int parseFlags)
             throws PackageManagerException {
-        return mPackageParser.parsePackage(scanFile, parseFlags, true);
+        return mPackageParser.parsePackage(scanFile, parseFlags, true, mFrameworkSplits);
     }
 }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 45837717..f21bc93 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4245,7 +4245,7 @@
         final PackageSetting ps = mPackages.get(componentInfo.packageName);
         if (ps == null) return false;
 
-        final PackageUserState userState = ps.readUserState(userId);
+        final PackageUserStateInternal userState = ps.readUserState(userId);
         return PackageUserStateUtils.isMatch(userState, componentInfo, flags);
     }
 
@@ -4255,7 +4255,7 @@
         final PackageSetting ps = mPackages.get(component.getPackageName());
         if (ps == null) return false;
 
-        final PackageUserState userState = ps.readUserState(userId);
+        final PackageUserStateInternal userState = ps.readUserState(userId);
         return PackageUserStateUtils.isMatch(userState, pkg.isSystem(), pkg.isEnabled(), component,
                 flags);
     }
@@ -4497,13 +4497,11 @@
                 pw.print(checkinTag); pw.print("-"); pw.print("splt,");
                 pw.print("base,");
                 pw.println(pkg.getBaseRevisionCode());
-                if (pkg.getSplitNames() != null) {
-                    int[] splitRevisionCodes = pkg.getSplitRevisionCodes();
-                    for (int i = 0; i < pkg.getSplitNames().length; i++) {
-                        pw.print(checkinTag); pw.print("-"); pw.print("splt,");
-                        pw.print(pkg.getSplitNames()[i]); pw.print(",");
-                        pw.println(splitRevisionCodes[i]);
-                    }
+                int[] splitRevisionCodes = pkg.getSplitRevisionCodes();
+                for (int i = 0; i < pkg.getSplitNames().length; i++) {
+                    pw.print(checkinTag); pw.print("-"); pw.print("splt,");
+                    pw.print(pkg.getSplitNames()[i]); pw.print(",");
+                    pw.println(splitRevisionCodes[i]);
                 }
             }
             for (UserInfo user : users) {
@@ -5169,13 +5167,11 @@
             }
             String[] splitNames = pkg.getSplitNames();
             int[] splitRevisionCodes = pkg.getSplitRevisionCodes();
-            if (splitNames != null) {
-                for (int i = 0; i < splitNames.length; i++) {
-                    pw.print(", ");
-                    pw.print(splitNames[i]);
-                    if (splitRevisionCodes[i] != 0) {
-                        pw.print(":"); pw.print(splitRevisionCodes[i]);
-                    }
+            for (int i = 0; i < splitNames.length; i++) {
+                pw.print(", ");
+                pw.print(splitNames[i]);
+                if (splitRevisionCodes[i] != 0) {
+                    pw.print(":"); pw.print(splitRevisionCodes[i]);
                 }
             }
             pw.print("]");
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 15e64df..72db242 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -35,8 +35,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.LocusId;
-import android.content.pm.AppSearchPerson;
 import android.content.pm.AppSearchShortcutInfo;
+import android.content.pm.AppSearchShortcutPerson;
 import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
@@ -965,7 +965,7 @@
      */
     public ArraySet<String> getUsedBitmapFiles() {
         final ArraySet<String> usedFiles = new ArraySet<>(1);
-        forEachShortcut(AppSearchShortcutInfo.QUERY_HAS_BITMAP_PATH, si -> {
+        forEachShortcut(si -> {
             if (si.getBitmapPath() != null) {
                 usedFiles.add(getFileName(si.getBitmapPath()));
             }
@@ -1176,7 +1176,7 @@
 
         // Keep the previous IDs.
         final ArraySet<String> toDisableList = new ArraySet<>(1);
-        forEachShortcut(AppSearchShortcutInfo.QUERY_IS_MANIFEST, si -> {
+        forEachShortcut(si -> {
             if (si.isManifestShortcut()) {
                 toDisableList.add(si.getId());
             }
@@ -1319,7 +1319,7 @@
     private ArrayMap<ComponentName, ArrayList<ShortcutInfo>> sortShortcutsToActivities() {
         final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> activitiesToShortcuts
                 = new ArrayMap<>();
-        forEachShortcut(AppSearchShortcutInfo.QUERY_IS_NOT_FLOATING, si -> {
+        forEachShortcut(si -> {
             if (si.isFloating()) {
                 return; // Ignore floating shortcuts, which are not tied to any activities.
             }
@@ -1369,14 +1369,7 @@
         // (If it's for update, then don't count dynamic shortcuts, since they'll be replaced
         // anyway.)
         final ArrayMap<ComponentName, Integer> counts = new ArrayMap<>(4);
-        final String query;
-        if (operation != ShortcutService.OPERATION_SET) {
-            query = AppSearchShortcutInfo.QUERY_IS_MANIFEST + " OR "
-                    + AppSearchShortcutInfo.QUERY_IS_DYNAMIC;
-        } else {
-            query = AppSearchShortcutInfo.QUERY_IS_MANIFEST;
-        }
-        forEachShortcut(query, shortcut -> {
+        forEachShortcut(shortcut -> {
             if (shortcut.isManifestShortcut()) {
                 incrementCountForActivity(counts, shortcut.getActivity(), 1);
             } else if (shortcut.isDynamic() && (operation != ShortcutService.OPERATION_SET)) {
@@ -1539,7 +1532,7 @@
     /** @return true if there's any shortcuts that are not manifest shortcuts. */
     public boolean hasNonManifestShortcuts() {
         final boolean[] condition = new boolean[1];
-        forEachShortcutStopWhen(AppSearchShortcutInfo.QUERY_IS_NOT_MANIFEST, si -> {
+        forEachShortcutStopWhen(si -> {
             if (!si.isDeclaredInManifest()) {
                 condition[0] = true;
                 return true;
@@ -2287,12 +2280,7 @@
     }
 
     private void forEachShortcut(@NonNull final Consumer<ShortcutInfo> cb) {
-        forEachShortcut("", cb);
-    }
-
-    private void forEachShortcut(
-            @NonNull final String query, @NonNull final Consumer<ShortcutInfo> cb) {
-        forEachShortcutStopWhen(query, si -> {
+        forEachShortcutStopWhen(si -> {
             cb.accept(si);
             return false;
         });
@@ -2307,11 +2295,6 @@
 
     private void forEachShortcutStopWhen(
             @NonNull final Function<ShortcutInfo, Boolean> cb) {
-        forEachShortcutStopWhen("", cb);
-    }
-
-    private void forEachShortcutStopWhen(
-            @NonNull final String query, @NonNull final Function<ShortcutInfo, Boolean> cb) {
         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
             if (cb.apply(si)) {
@@ -2328,12 +2311,12 @@
                     + " pkg=" + getPackageName());
         }
         SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
-                .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA)
+                .addSchemas(AppSearchShortcutPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA)
                 .setForceOverride(true);
         for (PackageIdentifier pi : mPackageIdentifiers.values()) {
             schemaBuilder = schemaBuilder
                     .setSchemaTypeVisibilityForPackage(
-                            AppSearchPerson.SCHEMA_TYPE, true, pi)
+                            AppSearchShortcutPerson.SCHEMA_TYPE, true, pi)
                     .setSchemaTypeVisibilityForPackage(
                             AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
         }
@@ -2403,8 +2386,8 @@
                     .addIds(ids).build(), mShortcutUser.mExecutor, result -> {
                     final List<ShortcutInfo> ret = result.getSuccesses().values()
                             .stream().map(doc ->
-                                    new AppSearchShortcutInfo(doc)
-                                            .toShortcutInfo(mShortcutUser.getUserId()))
+                                    ShortcutInfo.createFromGenericDocument(
+                                            mShortcutUser.getUserId(), doc))
                             .collect(Collectors.toList());
                     cb.accept(ret);
                 });
@@ -2480,8 +2463,8 @@
                 }
                 cb.complete(results.getResultValue().stream()
                         .map(SearchResult::getGenericDocument)
-                        .map(AppSearchShortcutInfo::new)
-                        .map(si -> si.toShortcutInfo(mShortcutUser.getUserId()))
+                        .map(doc -> ShortcutInfo.createFromGenericDocument(
+                                mShortcutUser.getUserId(), doc))
                         .collect(Collectors.toList()));
             });
         }));
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 057f8de..8393dee 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -41,7 +41,6 @@
 import android.content.IntentSender.SendIntentException;
 import android.content.LocusId;
 import android.content.pm.ActivityInfo;
-import android.content.pm.AppSearchShortcutInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ComponentInfo;
 import android.content.pm.IPackageManager;
@@ -2805,28 +2804,6 @@
         }
     }
 
-    private String createQuery(final boolean matchDynamic, final boolean matchPinned,
-            final boolean matchManifest, final boolean matchCached) {
-
-        final List<String> queries = new ArrayList<>(1);
-        if (matchDynamic) {
-            queries.add(AppSearchShortcutInfo.QUERY_IS_DYNAMIC);
-        }
-        if (matchPinned) {
-            queries.add(AppSearchShortcutInfo.QUERY_IS_PINNED);
-        }
-        if (matchManifest) {
-            queries.add(AppSearchShortcutInfo.QUERY_IS_MANIFEST);
-        }
-        if (matchCached) {
-            queries.add(AppSearchShortcutInfo.QUERY_IS_CACHED);
-        }
-        if (queries.isEmpty()) {
-            return "";
-        }
-        return "(" + String.join(" OR ", queries) + ")";
-    }
-
     /**
      * Remove all the information associated with a package.  This will really remove all the
      * information, including the restore information (i.e. it'll remove packages even if they're
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 045a295..5047690 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -22,6 +22,7 @@
 import android.content.pm.UserInfo;
 import android.os.Environment;
 import android.os.FileUtils;
+import android.os.RecoverySystem;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.os.SystemProperties;
@@ -115,6 +116,13 @@
                 // Try one last time; if we fail again we're really in trouble
                 prepareUserDataLI(volumeUuid, userId, userSerial,
                     flags | StorageManager.FLAG_STORAGE_DE, false);
+            } else {
+                try {
+                    Log.e(TAG, "prepareUserData failed", e);
+                    RecoverySystem.rebootPromptAndWipeUserData(mContext, "prepareUserData failed");
+                } catch (IOException e2) {
+                    throw new RuntimeException("error rebooting into recovery", e2);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index fc88af9..beea86d 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -78,7 +78,7 @@
 
         String baseApkContextClassLoader = encodeClassLoader(
                 "", pkg.getClassLoaderName(), sharedLibrariesContext);
-        if (pkg.getSplitCodePaths() == null) {
+        if (ArrayUtils.isEmpty(pkg.getSplitCodePaths())) {
             // The application has no splits.
             return new String[] {baseApkContextClassLoader};
         }
diff --git a/services/core/java/com/android/server/pm/package-info.java b/services/core/java/com/android/server/pm/package-info.java
new file mode 100644
index 0000000..04b8e0a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * @hide
+ * TODO(b/146466118) remove this javadoc tag
+ */
+@android.annotation.Hide
+package com.android.server.pm;
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 0fa0dc3..2d0a3ef 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -59,7 +59,7 @@
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUnserialized;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 
 import libcore.util.EmptyArray;
 
@@ -85,8 +85,8 @@
     @Nullable
     public static PackageInfo generate(AndroidPackage pkg, int[] gids,
             @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
-            long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
+            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
         return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime,
                 grantedPermissions, state, userId, null, pkgSetting);
     }
@@ -98,7 +98,7 @@
     public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, int flags,
             @Nullable PackageStateInternal pkgSetting) {
         return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
-                PackageUserState.DEFAULT, UserHandle.getCallingUserId(), apexInfo, pkgSetting);
+                PackageUserStateInternal.DEFAULT, UserHandle.getCallingUserId(), apexInfo, pkgSetting);
     }
 
     /**
@@ -106,8 +106,9 @@
      */
     private static PackageInfo generateWithComponents(AndroidPackage pkg, int[] gids,
             @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
-            long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId,
-            @Nullable ApexInfo apexInfo, @Nullable PackageStateInternal pkgSetting) {
+            long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
+            @UserIdInt int userId, @Nullable ApexInfo apexInfo,
+            @Nullable PackageStateInternal pkgSetting) {
         ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId,
                 pkgSetting);
         if (applicationInfo == null) {
@@ -209,8 +210,9 @@
      */
     @Nullable
     public static ApplicationInfo generateApplicationInfo(AndroidPackage pkg,
-            @PackageManager.ApplicationInfoFlagsBits long flags, @NonNull PackageUserState state,
-            int userId, @Nullable PackageStateInternal pkgSetting) {
+            @PackageManager.ApplicationInfoFlagsBits long flags,
+            @NonNull PackageUserStateInternal state, @UserIdInt int userId,
+            @Nullable PackageStateInternal pkgSetting) {
         if (pkg == null) {
             return null;
         }
@@ -255,7 +257,8 @@
      */
     @Nullable
     public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
-            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId,
+            @PackageManager.ComponentInfoFlagsBits long flags,
+            @NonNull PackageUserStateInternal state, @UserIdInt int userId,
             @Nullable PackageStateInternal pkgSetting) {
         return generateActivityInfo(pkg, a, flags, state, null, userId, pkgSetting);
     }
@@ -265,9 +268,9 @@
      */
     @Nullable
     private static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
-            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
-            @Nullable ApplicationInfo applicationInfo, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @PackageManager.ComponentInfoFlagsBits long flags,
+            @NonNull PackageUserStateInternal state, @Nullable ApplicationInfo applicationInfo,
+            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
         if (a == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -291,8 +294,8 @@
      */
     @Nullable
     public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
-            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
+            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
         return generateServiceInfo(pkg, s, flags, state, null, userId, pkgSetting);
     }
 
@@ -301,7 +304,7 @@
      */
     @Nullable
     private static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
-            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
             @Nullable ApplicationInfo applicationInfo, int userId,
             @Nullable PackageStateInternal pkgSetting) {
         if (s == null) return null;
@@ -326,7 +329,7 @@
      */
     @Nullable
     public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
-            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
             @NonNull ApplicationInfo applicationInfo, int userId,
             @Nullable PackageStateInternal pkgSetting) {
         if (p == null) return null;
@@ -418,11 +421,11 @@
     }
 
     /**
-     * Returns true if the package is installed and not hidden, or if the caller
-     * explicitly wanted all uninstalled and hidden packages as well.
+     * Returns true if the package is installed and not hidden, or if the caller explicitly wanted
+     * all uninstalled and hidden packages as well.
      */
     public static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
-            PackageStateInternal pkgSetting, PackageUserState state,
+            PackageStateInternal pkgSetting, PackageUserStateInternal state,
             @PackageManager.PackageInfoFlagsBits long flags) {
         // Returns false if the package is hidden system app until installed.
         if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
@@ -466,7 +469,9 @@
         return hasFlag ? flag : 0;
     }
 
-    /** @see ApplicationInfo#flags */
+    /**
+     * @see ApplicationInfo#flags
+     */
     public static int appInfoFlags(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) {
         // @formatter:off
         int pkgWithoutStateFlags = PackageInfoWithoutStateUtils.appInfoFlags(pkg)
@@ -628,7 +633,7 @@
          */
         @Nullable
         public ApplicationInfo generate(AndroidPackage pkg,
-                @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserState state,
+                @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserStateInternal state,
                 int userId, @Nullable PackageStateInternal pkgSetting) {
             ApplicationInfo appInfo = mCache.get(pkg.getPackageName());
             if (appInfo != null) {
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index 08e2f7d..b2e15e7 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -22,9 +22,6 @@
 import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
@@ -41,6 +38,9 @@
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import java.io.File;
 import java.util.List;
@@ -147,6 +147,15 @@
     @AnyThread
     public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
             throws PackageManagerException {
+        return parsePackage(packageFile, flags, useCaches, /* frameworkSplits= */ null);
+    }
+
+    /**
+     * TODO(b/135203078): Document new package parsing
+     */
+    @AnyThread
+    public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches,
+            List<File> frameworkSplits) throws PackageManagerException {
         if (useCaches && mCacher != null) {
             ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);
             if (parsed != null) {
@@ -156,7 +165,8 @@
 
         long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
         ParseInput input = mSharedResult.get().reset();
-        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
+        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags,
+                frameworkSplits);
         if (result.isError()) {
             throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
                     result.getException());
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 9b3d6d6..8e41c9b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -73,11 +73,6 @@
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.SigningDetails;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
-import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.metrics.LogMaker;
 import android.os.AsyncTask;
@@ -136,6 +131,10 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.component.ComponentMutateUtils;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedPermissionGroup;
+import com.android.server.pm.pkg.component.ParsedPermissionUtils;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.SoftRestrictedPermissionPolicy;
 
@@ -3172,18 +3171,17 @@
                 }
             } else if (NOTIFICATION_PERMISSIONS.contains(newPerm)) {
                 //&& (origPs.getPermissionState(newPerm) == null) {
-                // TODO(b/205888750): add back line about origPs once propagated through droidfood
+                // TODO(b/205888750): add back line about origPs once all TODO sections below are
+                //  propagated through droidfood
                 Permission bp = mRegistry.getPermission(newPerm);
                 if (bp == null) {
                     throw new IllegalStateException("Unknown new permission " + newPerm);
                 }
-                // TODO(b/205888750): remove the line for REVOKE_WHEN_REQUESTED once propagated
-                //  through droidfood
                 if (!isUserSetOrPregrantedOrFixed(ps.getPermissionFlags(newPerm))) {
                     updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
-                    ps.updatePermissionFlags(bp, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
-                                    | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
-                            PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED);
+                    int setFlag = ps.isPermissionGranted(newPerm)
+                            ? 0 : FLAG_PERMISSION_REVIEW_REQUIRED;
+                    ps.updatePermissionFlags(bp, FLAG_PERMISSION_REVIEW_REQUIRED, setFlag);
                     // TODO(b/205888750): remove if/else block once propagated through droidfood
                     if (ps.isPermissionGranted(newPerm)
                             && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) {
@@ -3192,6 +3190,10 @@
                             && pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
                         ps.grantPermission(bp);
                     }
+                } else {
+                    // TODO(b/205888750): remove once propagated through droidfood
+                    ps.updatePermissionFlags(bp, FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+                            | FLAG_PERMISSION_REVIEW_REQUIRED, 0);
                 }
             }
         }
@@ -4779,9 +4781,10 @@
 
         // Handle REVIEW_REQUIRED
         if ((newFlags & priorityFixedMask) == 0) {
-            if (NOTIFICATION_PERMISSIONS.contains(srcState.getName())) {
+            if ((newFlags & (defaultGrantMask | userSettableMask)) == 0
+                    && NOTIFICATION_PERMISSIONS.contains(srcState.getName())) {
                 // For notification permissions, inherit from both states
-                // if no priority FIXED flags are set
+                // if no priority FIXED or DEFAULT_GRANT or USER_SET flags are set
                 newFlags |= (combinedFlags & FLAG_PERMISSION_REVIEW_REQUIRED);
             } else if ((newFlags & priorityMask) == 0) {
                 // Else inherit from destState if no priority flags are set
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
index 3fde41a..d3c8c7b 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
@@ -16,21 +16,337 @@
 
 package com.android.server.pm.pkg;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.util.SparseArray;
 
 import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.parsing.pkg.PkgAppInfo;
-import com.android.server.pm.parsing.pkg.PkgPackageInfo;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedAttribution;
+import com.android.server.pm.pkg.component.ParsedInstrumentation;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedService;
+
+import java.util.List;
 
 /**
  * Explicit interface used for consumers like mainline who need a {@link SystemApi @SystemApi} form
- * of {@link AndroidPackage}.
- * <p>
- * There should be no methods in this class. All of them must come from other interfaces that group
- * the actual methods. This is done to ensure proper separation of the (legacy?) Info object APIs.
+ * of {@link AndroidPackage}. *
+ * @hide
  */
-// TODO(b/173807334): Expose API
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-public interface AndroidPackageApi extends PkgPackageInfo, PkgAppInfo {
+public interface AndroidPackageApi {
 
+    boolean areAttributionsUserVisible();
+
+    @Nullable
+    String getAppComponentFactory();
+
+    int getAutoRevokePermissions();
+
+    @Nullable
+    String getBackupAgentName();
+
+    int getBanner();
+
+    @NonNull
+    String getBaseApkPath();
+
+    int getCategory();
+
+    @Nullable
+    String getClassLoaderName();
+
+    @Nullable
+    String getClassName();
+
+    int getCompatibleWidthLimitDp();
+
+    int getDataExtractionRules();
+
+    int getDescriptionRes();
+
+    int getFullBackupContent();
+
+    int getGwpAsanMode();
+
+    int getIconRes();
+
+    int getInstallLocation();
+
+    int getLabelRes();
+
+    int getLargestWidthLimitDp();
+
+    int getLogo();
+
+    @Nullable
+    String getManageSpaceActivityName();
+
+    float getMaxAspectRatio();
+
+    int getMemtagMode();
+
+    float getMinAspectRatio();
+
+    int getMinSdkVersion();
+
+    int getNativeHeapZeroInitialized();
+
+    int getNetworkSecurityConfigRes();
+
+    @Nullable
+    CharSequence getNonLocalizedLabel();
+
+    @NonNull
+    String getPath();
+
+    @Nullable
+    String getPermission();
+
+    @NonNull
+    String getProcessName();
+
+    int getRequiresSmallestWidthDp();
+
+    @SuppressLint("AutoBoxing")
+    @Nullable
+    Boolean getResizeableActivity();
+
+    int getRoundIconRes();
+
+    @NonNull
+    String[] getSplitClassLoaderNames();
+
+    @NonNull
+    String[] getSplitCodePaths();
+
+    @Nullable
+    SparseArray<int[]> getSplitDependencies();
+
+    int getTargetSdkVersion();
+
+    int getTargetSandboxVersion();
+
+    @Nullable
+    String getTaskAffinity();
+
+    int getTheme();
+
+    int getUiOptions();
+
+    @Nullable
+    String getVolumeUuid();
+
+    @Nullable
+    String getZygotePreloadName();
+
+    boolean hasRequestForegroundServiceExemption();
+
+    @SuppressLint("AutoBoxing")
+    @Nullable
+    Boolean hasRequestRawExternalStorageAccess();
+
+    boolean isAllowAudioPlaybackCapture();
+
+    boolean isAllowBackup();
+
+    boolean isAllowClearUserData();
+
+    boolean isAllowClearUserDataOnFailedRestore();
+
+    boolean isAllowNativeHeapPointerTagging();
+
+    boolean isAllowTaskReparenting();
+
+    boolean isAnyDensity();
+
+    boolean isBackupInForeground();
+
+    boolean isBaseHardwareAccelerated();
+
+    boolean isCantSaveState();
+
+    boolean isCrossProfile();
+
+    boolean isDebuggable();
+
+    boolean isDefaultToDeviceProtectedStorage();
+
+    boolean isDirectBootAware();
+
+    boolean isEnabled();
+
+    boolean isExternalStorage();
+
+    boolean isExtractNativeLibs();
+
+    boolean isFullBackupOnly();
+
+    boolean isHasCode();
+
+    boolean isHasDomainUrls();
+
+    boolean isHasFragileUserData();
+
+    boolean isIsolatedSplitLoading();
+
+    boolean isKillAfterRestore();
+
+    boolean isLargeHeap();
+
+    boolean isMultiArch();
+
+    boolean isOverlay();
+
+    boolean isPartiallyDirectBootAware();
+
+    boolean isPersistent();
+
+    boolean isProfileable();
+
+    boolean isProfileableByShell();
+
+    boolean isRequestLegacyExternalStorage();
+
+    boolean isResizeable();
+
+    boolean isResizeableActivityViaSdkVersion();
+
+    boolean isRestoreAnyVersion();
+
+    boolean isStaticSharedLibrary();
+
+    boolean isSdkLibrary();
+
+    boolean isSupportsExtraLargeScreens();
+
+    boolean isSupportsLargeScreens();
+
+    boolean isSupportsNormalScreens();
+
+    boolean isSupportsRtl();
+
+    boolean isSupportsSmallScreens();
+
+    boolean isTestOnly();
+
+    boolean isUseEmbeddedDex();
+
+    boolean isUsesCleartextTraffic();
+
+    boolean isUsesNonSdkApi();
+
+    boolean isVmSafeMode();
+
+    @NonNull
+    List<ParsedActivity> getActivities();
+
+    @NonNull
+    List<ParsedAttribution> getAttributions();
+
+    @NonNull
+    List<String> getAdoptPermissions();
+
+    int getBaseRevisionCode();
+
+    int getCompileSdkVersion();
+
+    @Nullable
+    String getCompileSdkVersionCodeName();
+
+    @NonNull
+    List<ConfigurationInfo> getConfigPreferences();
+
+    @NonNull
+    List<FeatureGroupInfo> getFeatureGroups();
+
+    @NonNull
+    List<ParsedInstrumentation> getInstrumentations();
+
+    long getLongVersionCode();
+
+    @NonNull
+    String getPackageName();
+
+    @NonNull
+    List<ParsedPermission> getPermissions();
+
+    @NonNull
+    List<ParsedProvider> getProviders();
+
+    @NonNull
+    List<ParsedActivity> getReceivers();
+
+    @NonNull
+    List<FeatureInfo> getRequestedFeatures();
+
+    @NonNull
+    List<String> getRequestedPermissions();
+
+    @Nullable
+    String getRequiredAccountType();
+
+    @Nullable
+    String getRestrictedAccountType();
+
+    @NonNull
+    List<ParsedService> getServices();
+
+    @Nullable
+    String getSharedUserId();
+
+    int getSharedUserLabel();
+
+    @NonNull
+    String[] getSplitNames();
+
+    @NonNull
+    int[] getSplitRevisionCodes();
+
+    @Nullable
+    String getVersionName();
+
+    boolean isRequiredForAllUsers();
+
+    @Nullable
+    String getNativeLibraryDir();
+
+    @Nullable
+    String getNativeLibraryRootDir();
+
+    @Nullable
+    String getSecondaryNativeLibraryDir();
+
+    int getUid();
+
+    boolean isFactoryTest();
+
+    boolean isNativeLibraryRootRequiresIsa();
+
+    boolean isOdm();
+
+    boolean isOem();
+
+    boolean isPrivileged();
+
+    boolean isProduct();
+
+    boolean isSignedWithPlatformKey();
+
+    boolean isSystem();
+
+    boolean isSystemExt();
+
+    boolean isVendor();
+
+    boolean isCoreApp();
+
+    boolean isStub();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index f5ee8d9..9fa9644 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -54,7 +54,6 @@
  *
  * @hide
  */
-// TODO(b/173807334): Expose API
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface PackageState {
 
@@ -163,10 +162,9 @@
      * Retrieves the shared user ID. Note that the actual shared user data is not available here and
      * must be queried separately.
      *
-     * @return the shared user this package is a part of, or null if it's not part of a shared user.
+     * @return the shared user this package is a part of, or -1 if it's not part of a shared user.
      */
-    @Nullable
-    Integer getSharedUserId();
+    int getSharedUserId();
 
     @NonNull
     SigningInfo getSigningInfo();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index a5d399e..9395ca5 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -127,7 +127,7 @@
     @Nullable
     private final String mSecondaryCpuAbi;
     @Nullable
-    private final Integer mSharedUserId;
+    private final int mSharedUserId;
     @NonNull
     private final String[] mUsesSdkLibraries;
     @NonNull
@@ -615,7 +615,7 @@
     }
 
     @DataClass.Generated.Member
-    public @Nullable Integer getSharedUserId() {
+    public @Nullable int getSharedUserId() {
         return mSharedUserId;
     }
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
index 656c445..91e9b2f 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
@@ -58,7 +58,7 @@
             ComponentInfo componentInfo, long flags, int userId) {
         if (packageState == null) return false;
 
-        final PackageUserState userState = packageState.getUserStateOrDefault(userId);
+        final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
         return PackageUserStateUtils.isMatch(userState, componentInfo, flags);
     }
 
@@ -72,7 +72,7 @@
         if (pkg == null) {
             return false;
         }
-        final PackageUserState userState = packageState.getUserStateOrDefault(userId);
+        final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
         return PackageUserStateUtils.isMatch(userState, packageState.isSystem(),
                 pkg.isEnabled(), component, flags);
     }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
index d47c5ec..bed1401 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.content.pm.PackageManager;
 import android.content.pm.overlay.OverlayPaths;
 import android.os.UserHandle;
@@ -30,15 +32,20 @@
  *
  * The parent of this class is {@link PackageState}, which handles non-user state, exposing this
  * interface for per-user state.
+ *
+ * @hide
  */
 // TODO(b/173807334): Expose API
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface PackageUserState {
 
+    /** @hide */
+    @NonNull
     PackageUserState DEFAULT = PackageUserStateInternal.DEFAULT;
 
     /**
      * Combination of {@link #getOverlayPaths()} and {@link #getSharedLibraryOverlayPaths()}
+     * @hide
      */
     @Nullable
     OverlayPaths getAllOverlayPaths();
@@ -84,9 +91,11 @@
     @Nullable
     String getLastDisableAppCaller();
 
+    /** @hide */
     @Nullable
     OverlayPaths getOverlayPaths();
 
+    /** @hide */
     @NonNull
     Map<String, OverlayPaths> getSharedLibraryOverlayPaths();
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
index bd8b3ab..bc521ce 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
+import android.content.pm.pkg.FrameworkPackageUserState;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -28,7 +29,7 @@
  * still read-only and should be used inside system server code when possible over the
  * implementation.
  */
-public interface PackageUserStateInternal extends PackageUserState {
+public interface PackageUserStateInternal extends PackageUserState, FrameworkPackageUserState {
 
     PackageUserStateInternal DEFAULT = new PackageUserStateDefault();
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
index 6d978c4..c2b3cbc 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
@@ -21,11 +21,13 @@
 import android.content.pm.ActivityInfo;
 
 /** @hide **/
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedActivity extends ParsedMainComponent {
 
     /**
      * Generate activity object that forwards user to App Details page automatically.
      * This activity should be invisible to user and user should not know or see it.
+     * @hide
      */
     @NonNull
     static ParsedActivity makeAppDetailsActivity(String packageName, String processName,
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
index ff97c13..91c0b07 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
@@ -20,16 +20,16 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 
+import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -37,11 +37,15 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
-/** @hide **/
+/**
+ * @hide
+ **/
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedActivityImpl extends ParsedMainComponentImpl implements ParsedActivity {
+public class ParsedActivityImpl extends ParsedMainComponentImpl implements ParsedActivity,
+        Parcelable {
 
     private int theme;
     private int uiOptions;
@@ -112,8 +116,8 @@
     }
 
     /**
-     * Generate activity object that forwards user to App Details page automatically.
-     * This activity should be invisible to user and user should not know or see it.
+     * Generate activity object that forwards user to App Details page automatically. This activity
+     * should be invisible to user and user should not know or see it.
      */
     @NonNull
     static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName,
@@ -641,10 +645,10 @@
     }
 
     @DataClass.Generated(
-            time = 1630600615936L,
+            time = 1641431949361L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java",
-            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAlias(java.lang.String,android.content.pm.parsing.component.ParsedActivity)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setPermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedActivity]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAlias(java.lang.String,android.content.pm.parsing.component.ParsedActivity)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setPermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index db8815e..a6c22a18 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -18,8 +18,9 @@
 
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
+
 import static com.android.server.pm.pkg.component.ComponentParseUtils.flag;
+import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,9 +28,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseInput.DeferredError;
 import android.content.pm.parsing.result.ParseResult;
@@ -49,6 +47,9 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -353,10 +354,10 @@
 
             final ParseResult result;
             if (parser.getName().equals("intent-filter")) {
-                ParseResult<ParsedIntentInfo> intentResult = parseIntentFilter(pkg, activity,
+                ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
                         !isReceiver, visibleToEphemeral, resources, parser, input);
                 if (intentResult.isSuccess()) {
-                    ParsedIntentInfo intentInfo = intentResult.getResult();
+                    ParsedIntentInfoImpl intentInfo = intentResult.getResult();
                     if (intentInfo != null) {
                         IntentFilter intentFilter = intentInfo.getIntentFilter();
                         activity.setOrder(Math.max(intentFilter.getOrder(), activity.getOrder()));
@@ -386,11 +387,11 @@
             } else if (parser.getName().equals("property")) {
                 result = ParsedComponentUtils.addProperty(activity, pkg, resources, parser, input);
             } else if (!isReceiver && !isAlias && parser.getName().equals("preferred")) {
-                ParseResult<ParsedIntentInfo> intentResult = parseIntentFilter(pkg, activity,
+                ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
                         true /*allowImplicitEphemeralVisibility*/, visibleToEphemeral,
                         resources, parser, input);
                 if (intentResult.isSuccess()) {
-                    ParsedIntentInfo intent = intentResult.getResult();
+                    ParsedIntentInfoImpl intent = intentResult.getResult();
                     if (intent != null) {
                         pkg.addPreferredActivityFilter(activity.getClassName(), intent);
                     }
@@ -413,7 +414,7 @@
         }
 
         if (!isAlias && activity.getLaunchMode() != LAUNCH_SINGLE_INSTANCE_PER_TASK
-                && activity.getMetaData() != null && activity.getMetaData().containsKey(
+                && activity.getMetaData().containsKey(
                 ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE)) {
             final String launchMode = activity.getMetaData().getString(
                     ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE);
@@ -427,7 +428,7 @@
             // set to false.
             boolean canDisplayOnRemoteDevices = array.getBoolean(
                     R.styleable.AndroidManifestActivity_canDisplayOnRemoteDevices, true);
-            if (activity.getMetaData() != null && !activity.getMetaData().getBoolean(
+            if (!activity.getMetaData().getBoolean(
                     ParsingPackageUtils.METADATA_CAN_DISPLAY_ON_REMOTE_DEVICES, true)) {
                 canDisplayOnRemoteDevices = false;
             }
@@ -463,11 +464,11 @@
     }
 
     @NonNull
-    private static ParseResult<ParsedIntentInfo> parseIntentFilter(ParsingPackage pkg,
+    private static ParseResult<ParsedIntentInfoImpl> parseIntentFilter(ParsingPackage pkg,
             ParsedActivityImpl activity, boolean allowImplicitEphemeralVisibility,
             boolean visibleToEphemeral, Resources resources, XmlResourceParser parser,
             ParseInput input) throws IOException, XmlPullParserException {
-        ParseResult<ParsedIntentInfo> result = ParsedMainComponentUtils.parseIntentFilter(activity,
+        ParseResult<ParsedIntentInfoImpl> result = ParsedMainComponentUtils.parseIntentFilter(activity,
                 pkg, resources, parser, visibleToEphemeral, true /*allowGlobs*/,
                 true /*allowAutoVerify*/, allowImplicitEphemeralVisibility,
                 true /*failOnNoActions*/, input);
@@ -475,7 +476,7 @@
             return input.error(result);
         }
 
-        ParsedIntentInfo intent = result.getResult();
+        ParsedIntentInfoImpl intent = result.getResult();
         if (intent != null) {
             final IntentFilter intentFilter = intent.getIntentFilter();
             if (intentFilter.isVisibleToInstantApp()) {
@@ -574,7 +575,7 @@
     private static ParseResult<ActivityInfo.WindowLayout> resolveActivityWindowLayout(
             ParsedActivity activity, ParseInput input) {
         // There isn't a metadata for us to fall back. Whatever is in layout is correct.
-        if (activity.getMetaData() == null || !activity.getMetaData().containsKey(
+        if (!activity.getMetaData().containsKey(
                 ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
             return input.success(activity.getWindowLayout());
         }
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
index 7690818..586d2c4 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
@@ -18,10 +18,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Parcelable;
 
 /** @hide */
-public interface ParsedApexSystemService extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedApexSystemService {
 
     @NonNull
     String getName();
@@ -34,5 +34,4 @@
 
     @Nullable
     String getMaxSdkVersion();
-
 }
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
index 8c4d8f0..1e427d0 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
@@ -19,15 +19,18 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
 
-/** @hide **/
+/**
+ * @hide
+ **/
 @DataClass(genGetters = true, genAidl = false, genSetters = true, genParcelable = true)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
+public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Parcelable {
 
     @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
     @NonNull
@@ -49,6 +52,7 @@
     }
 
 
+
     // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
@@ -213,8 +217,8 @@
     }
 
     @DataClass.Generated.Member
-    public static final @NonNull android.os.Parcelable.Creator<ParsedApexSystemServiceImpl> CREATOR
-            = new android.os.Parcelable.Creator<ParsedApexSystemServiceImpl>() {
+    public static final @NonNull Parcelable.Creator<ParsedApexSystemServiceImpl> CREATOR
+            = new Parcelable.Creator<ParsedApexSystemServiceImpl>() {
         @Override
         public ParsedApexSystemServiceImpl[] newArray(int size) {
             return new ParsedApexSystemServiceImpl[size];
@@ -227,10 +231,10 @@
     };
 
     @DataClass.Generated(
-            time = 1638903241144L,
+            time = 1641431950080L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java",
-            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
+            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java
index 3b91f28..1a5d110 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.StringRes;
-import android.os.Parcelable;
 
 import java.util.List;
 
@@ -28,7 +27,8 @@
  *
  * @hide
  */
-public interface ParsedAttribution extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedAttribution {
 
     /**
      * Maximum length of attribution tag
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
index a4eb4f1..b59f511 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
@@ -35,7 +35,7 @@
  */
 @DataClass(genAidl = false, genSetters = true, genBuilder = false, genParcelable = true)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedAttributionImpl implements ParsedAttribution {
+public class ParsedAttributionImpl implements ParsedAttribution, Parcelable {
 
     /** Maximum amount of attributions per package */
     static final int MAX_NUM_ATTRIBUTIONS = 10000;
@@ -206,10 +206,10 @@
     };
 
     @DataClass.Generated(
-            time = 1627594502974L,
+            time = 1641431950829L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java",
-            inputSignatures = "static final  int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\nclass ParsedAttributionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedAttribution]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false, genParcelable=true)")
+            inputSignatures = "static final  int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\nclass ParsedAttributionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedAttribution, android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false, genParcelable=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java
index 1a8230d..5b6ecba 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java
@@ -21,13 +21,13 @@
 import android.content.ComponentName;
 import android.content.pm.PackageManager.Property;
 import android.os.Bundle;
-import android.os.Parcelable;
 
 import java.util.List;
 import java.util.Map;
 
 /** @hide */
-public interface ParsedComponent extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedComponent {
 
     int getBanner();
 
@@ -47,7 +47,7 @@
 
     int getLogo();
 
-    @Nullable
+    @NonNull
     Bundle getMetaData();
 
     @NonNull
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
index 9125e8c..c1a4b2e7 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
@@ -25,28 +25,31 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.pm.PackageManager.Property;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.os.Bundle;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-/** @hide */
-@DataClass(genGetters = true, genSetters = true, genConstructor = false, genBuilder = false)
+/**
+ * @hide
+ */
+@DataClass(genGetters = true, genSetters = true, genConstructor = false, genBuilder = false,
+        genParcelable = false)
 @DataClass.Suppress({"setComponentName", "setProperties", "setIntents"})
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public abstract class ParsedComponentImpl implements ParsedComponent {
+public abstract class ParsedComponentImpl implements ParsedComponent, Parcelable {
 
     @NonNull
     @DataClass.ParcelWith(ForInternedString.class)
@@ -68,7 +71,7 @@
 
     @NonNull
     @DataClass.PluralOf("intent")
-    private List<ParsedIntentInfo> intents = Collections.emptyList();
+    private List<ParsedIntentInfoImpl> intents = Collections.emptyList();
 
     @Nullable
     private ComponentName componentName;
@@ -95,16 +98,18 @@
         this.flags = other.getFlags();
         this.packageName = other.getPackageName();
         this.componentName = other.getComponentName();
-        this.intents = new ArrayList<>(other.getIntents());
+        this.intents = new ArrayList<>(((ParsedComponentImpl) other).intents);
         this.mProperties = new ArrayMap<>();
         this.mProperties.putAll(other.getProperties());
     }
 
-    public void addIntent(ParsedIntentInfo intent) {
+    public void addIntent(ParsedIntentInfoImpl intent) {
         this.intents = CollectionUtils.add(this.intents, intent);
     }
 
-    /** Add a property to the component */
+    /**
+     * Add a property to the component
+     */
     public void addProperty(@NonNull Property property) {
         this.mProperties = CollectionUtils.add(this.mProperties, property.getName(), property);
     }
@@ -133,6 +138,18 @@
         return componentName;
     }
 
+    @NonNull
+    @Override
+    public Bundle getMetaData() {
+        return metaData == null ? Bundle.EMPTY : metaData;
+    }
+
+    @NonNull
+    @Override
+    public List<ParsedIntentInfo> getIntents() {
+        return new ArrayList<>(intents);
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -149,7 +166,7 @@
         dest.writeInt(this.getDescriptionRes());
         dest.writeInt(this.getFlags());
         sForInternedString.parcel(this.packageName, dest, flags);
-        dest.writeTypedList(this.getIntents());
+        dest.writeTypedList(this.intents);
         dest.writeBundle(this.metaData);
         dest.writeMap(this.mProperties);
     }
@@ -234,16 +251,6 @@
     }
 
     @DataClass.Generated.Member
-    public @NonNull List<ParsedIntentInfo> getIntents() {
-        return intents;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable Bundle getMetaData() {
-        return metaData;
-    }
-
-    @DataClass.Generated.Member
     public @NonNull Map<String,Property> getProperties() {
         return mProperties;
     }
@@ -297,10 +304,10 @@
     }
 
     @DataClass.Generated(
-            time = 1627680195484L,
+            time = 1641414207885L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java",
-            inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String name\nprivate  int icon\nprivate  int labelRes\nprivate @android.annotation.Nullable java.lang.CharSequence nonLocalizedLabel\nprivate  int logo\nprivate  int banner\nprivate  int descriptionRes\nprivate  int flags\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String packageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"intent\") java.util.List<android.content.pm.parsing.component.ParsedIntentInfo> intents\nprivate @android.annotation.Nullable android.content.ComponentName componentName\nprivate @android.annotation.Nullable android.os.Bundle metaData\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.PackageManager.Property> mProperties\npublic  void addIntent(android.content.pm.parsing.component.ParsedIntentInfo)\npublic  void addProperty(android.content.pm.PackageManager.Property)\npublic  android.content.pm.parsing.component.ParsedComponentImpl setName(java.lang.String)\npublic @android.annotation.CallSuper void setPackageName(java.lang.String)\npublic @java.lang.Override @android.annotation.NonNull android.content.ComponentName getComponentName()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedComponentImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedComponent]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genConstructor=false, genBuilder=false)")
+            inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String name\nprivate  int icon\nprivate  int labelRes\nprivate @android.annotation.Nullable java.lang.CharSequence nonLocalizedLabel\nprivate  int logo\nprivate  int banner\nprivate  int descriptionRes\nprivate  int flags\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String packageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"intent\") java.util.List<android.content.pm.parsing.component.ParsedIntentInfoImpl> intents\nprivate @android.annotation.Nullable android.content.ComponentName componentName\nprivate @android.annotation.Nullable android.os.Bundle metaData\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.PackageManager.Property> mProperties\n  void addIntent(android.content.pm.parsing.component.ParsedIntentInfoImpl)\n  void addProperty(android.content.pm.PackageManager.Property)\npublic  android.content.pm.parsing.component.ParsedComponentImpl setName(java.lang.String)\npublic @android.annotation.CallSuper void setPackageName(java.lang.String)\npublic @java.lang.Override @android.annotation.NonNull android.content.ComponentName getComponentName()\npublic @android.annotation.NonNull @java.lang.Override android.os.Bundle getMetaData()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.parsing.component.ParsedIntentInfo> getIntents()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedComponentImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genConstructor=false, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
index e208854..c6b9b1a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
@@ -21,9 +21,6 @@
 import android.annotation.NonNull;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.Property;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -34,6 +31,9 @@
 import android.util.TypedValue;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 /** @hide */
 class ParsedComponentUtils {
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java
index a0eae8c..c325d8d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 
 /** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedInstrumentation extends ParsedComponent {
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
index c8baa9e..23ebdc8 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
@@ -33,7 +33,7 @@
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class ParsedInstrumentationImpl extends ParsedComponentImpl implements
-        ParsedInstrumentation {
+        ParsedInstrumentation, Parcelable {
 
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
@@ -165,10 +165,10 @@
     }
 
     @DataClass.Generated(
-            time = 1627595809880L,
+            time = 1641431951575L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetPackage\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetProcesses\nprivate  boolean handleProfiling\nprivate  boolean functionalTest\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedInstrumentationImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetPackage(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetProcesses(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedInstrumentationImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedInstrumentation]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetPackage\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetProcesses\nprivate  boolean handleProfiling\nprivate  boolean functionalTest\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedInstrumentationImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetPackage(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetProcesses(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedInstrumentationImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedInstrumentation, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
index 51e1428..c63a689 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
@@ -19,7 +19,6 @@
 import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
 
 import android.annotation.NonNull;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -27,12 +26,15 @@
 import android.content.res.XmlResourceParser;
 
 import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
 
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 
-/** @hide */
+/**
+ * @hide
+ */
 public class ParsedInstrumentationUtils {
 
     @NonNull
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java
index 57b486a..a7f7b00 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java
@@ -19,10 +19,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentFilter;
-import android.os.Parcelable;
 
 /** @hide **/
-public interface ParsedIntentInfo extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedIntentInfo {
 
     boolean isHasDefault();
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
index 1c816da..5b6375d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
@@ -32,7 +32,7 @@
         genBuilder = false, genConstructor = false)
 @DataClass.Suppress({"setIntentFilter"})
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedIntentInfoImpl implements ParsedIntentInfo {
+public class ParsedIntentInfoImpl implements ParsedIntentInfo, Parcelable {
 
     private boolean mHasDefault;
     private int mLabelRes;
@@ -169,10 +169,10 @@
     };
 
     @DataClass.Generated(
-            time = 1627691925408L,
+            time = 1641431952314L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java",
-            inputSignatures = "private  boolean mHasDefault\nprivate  int mLabelRes\nprivate @android.annotation.Nullable java.lang.CharSequence mNonLocalizedLabel\nprivate  int mIcon\nprivate @android.annotation.NonNull android.content.IntentFilter mIntentFilter\nclass ParsedIntentInfoImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedIntentInfo]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false, genConstructor=false)")
+            inputSignatures = "private  boolean mHasDefault\nprivate  int mLabelRes\nprivate @android.annotation.Nullable java.lang.CharSequence mNonLocalizedLabel\nprivate  int mIcon\nprivate @android.annotation.NonNull android.content.IntentFilter mIntentFilter\nclass ParsedIntentInfoImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedIntentInfo, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false, genConstructor=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
index 1e6f630..4f0a504 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -21,9 +21,6 @@
 import android.annotation.NonNull;
 import android.content.Intent;
 import android.content.IntentFilter;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -34,6 +31,9 @@
 import android.util.TypedValue;
 
 import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -49,7 +49,7 @@
     public static final boolean DEBUG = false;
 
     @NonNull
-    public static ParseResult<ParsedIntentInfo> parseIntentInfo(String className,
+    public static ParseResult<ParsedIntentInfoImpl> parseIntentInfo(String className,
             ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs,
             boolean allowAutoVerify, ParseInput input)
             throws XmlPullParserException, IOException {
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java
index 8c1d6c8..b926d53 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java
@@ -16,17 +16,20 @@
 
 package com.android.server.pm.pkg.component;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 /** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedMainComponent extends ParsedComponent {
 
-    @Nullable
+    @NonNull
     String[] getAttributionTags();
 
     /**
      * A main component's name is a class name. This makes code slightly more readable.
      */
+    @NonNull
     String getClassName();
 
     boolean isDirectBootAware();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
index 9b57f48..a2f2f93 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -27,10 +28,15 @@
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
-/** @hide */
+import libcore.util.EmptyArray;
+
+/**
+ * @hide
+ */
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedMainComponentImpl extends ParsedComponentImpl implements ParsedMainComponent {
+public class ParsedMainComponentImpl extends ParsedComponentImpl implements ParsedMainComponent,
+        Parcelable {
 
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
@@ -71,6 +77,12 @@
         return getName();
     }
 
+    @NonNull
+    @Override
+    public String[] getAttributionTags() {
+        return attributionTags == null ? EmptyArray.STRING : attributionTags;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -178,51 +190,46 @@
     }
 
     @DataClass.Generated.Member
-    public @Nullable String[] getAttributionTags() {
-        return attributionTags;
-    }
-
-    @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setDirectBootAware( boolean value) {
+    public @NonNull ParsedMainComponentImpl setDirectBootAware( boolean value) {
         directBootAware = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setEnabled( boolean value) {
+    public @NonNull ParsedMainComponentImpl setEnabled( boolean value) {
         enabled = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setExported( boolean value) {
+    public @NonNull ParsedMainComponentImpl setExported( boolean value) {
         exported = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setOrder( int value) {
+    public @NonNull ParsedMainComponentImpl setOrder( int value) {
         order = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setSplitName(@android.annotation.NonNull String value) {
+    public @NonNull ParsedMainComponentImpl setSplitName(@NonNull String value) {
         splitName = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setAttributionTags(@android.annotation.NonNull String... value) {
+    public @NonNull ParsedMainComponentImpl setAttributionTags(@NonNull String... value) {
         attributionTags = value;
         return this;
     }
 
     @DataClass.Generated(
-            time = 1627324857874L,
+            time = 1641414540422L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate  boolean directBootAware\nprivate  boolean enabled\nprivate  boolean exported\nprivate  int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final  android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedMainComponentImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic  java.lang.String getClassName()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedMainComponent]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate  boolean directBootAware\nprivate  boolean enabled\nprivate  boolean exported\nprivate  int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final  android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedMainComponentImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic  java.lang.String getClassName()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getAttributionTags()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedMainComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
index 2a3e653..f52ad13 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
@@ -21,8 +21,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentFilter;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Configuration;
@@ -33,6 +31,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -110,13 +110,13 @@
         return input.success(component);
     }
 
-    static ParseResult<ParsedIntentInfo> parseIntentFilter(
+    static ParseResult<ParsedIntentInfoImpl> parseIntentFilter(
             ParsedMainComponent mainComponent,
             ParsingPackage pkg, Resources resources, XmlResourceParser parser,
             boolean visibleToEphemeral, boolean allowGlobs, boolean allowAutoVerify,
             boolean allowImplicitEphemeralVisibility, boolean failOnNoActions,
             ParseInput input) throws IOException, XmlPullParserException {
-        ParseResult<ParsedIntentInfo> intentResult = ParsedIntentInfoUtils.parseIntentInfo(
+        ParseResult<ParsedIntentInfoImpl> intentResult = ParsedIntentInfoUtils.parseIntentInfo(
                 mainComponent.getName(), pkg, resources, parser, allowGlobs,
                 allowAutoVerify, input);
         if (intentResult.isError()) {
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java
index 4a6d2c3..dc5347a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java
@@ -16,11 +16,13 @@
 
 package com.android.server.pm.pkg.component;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 import java.util.Set;
 
 /** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedPermission extends ParsedComponent {
 
     @Nullable
@@ -29,7 +31,7 @@
     @Nullable
     String getGroup();
 
-    @Nullable
+    @NonNull
     Set<String> getKnownCerts();
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java
index 73b5ffa..64f4fbd 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java
@@ -17,15 +17,16 @@
 package com.android.server.pm.pkg.component;
 
 /** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedPermissionGroup extends ParsedComponent {
 
-    int getBackgroundRequestDetailResourceId();
+    int getBackgroundRequestDetailRes();
 
-    int getBackgroundRequestResourceId();
+    int getBackgroundRequestRes();
 
     int getPriority();
 
-    int getRequestDetailResourceId();
+    int getRequestDetailRes();
 
     int getRequestRes();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
index f47fb75..59075de 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
@@ -16,21 +16,25 @@
 
 package com.android.server.pm.pkg.component;
 
+import android.annotation.NonNull;
 import android.os.Parcel;
+import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
 
-/** @hide */
+/**
+ * @hide
+ */
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = true,
         genAidl = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class ParsedPermissionGroupImpl extends ParsedComponentImpl implements
-        ParsedPermissionGroup {
+        ParsedPermissionGroup, Parcelable {
 
-    private int requestDetailResourceId;
-    private int backgroundRequestResourceId;
-    private int backgroundRequestDetailResourceId;
+    private int requestDetailRes;
+    private int backgroundRequestRes;
+    private int backgroundRequestDetailRes;
     private int requestRes;
     private int priority;
 
@@ -43,6 +47,25 @@
     public ParsedPermissionGroupImpl() {
     }
 
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(requestDetailRes);
+        dest.writeInt(backgroundRequestRes);
+        dest.writeInt(backgroundRequestDetailRes);
+        dest.writeInt(requestRes);
+        dest.writeInt(priority);
+    }
+
+    protected ParsedPermissionGroupImpl(@NonNull Parcel in) {
+        super(in);
+        this.requestDetailRes = in.readInt();
+        this.backgroundRequestRes = in.readInt();
+        this.backgroundRequestDetailRes = in.readInt();
+        this.requestRes = in.readInt();
+        this.priority = in.readInt();
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -51,7 +74,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -60,14 +83,14 @@
 
     @DataClass.Generated.Member
     public ParsedPermissionGroupImpl(
-            int requestDetailResourceId,
-            int backgroundRequestResourceId,
-            int backgroundRequestDetailResourceId,
+            int requestDetailRes,
+            int backgroundRequestRes,
+            int backgroundRequestDetailRes,
             int requestRes,
             int priority) {
-        this.requestDetailResourceId = requestDetailResourceId;
-        this.backgroundRequestResourceId = backgroundRequestResourceId;
-        this.backgroundRequestDetailResourceId = backgroundRequestDetailResourceId;
+        this.requestDetailRes = requestDetailRes;
+        this.backgroundRequestRes = backgroundRequestRes;
+        this.backgroundRequestDetailRes = backgroundRequestDetailRes;
         this.requestRes = requestRes;
         this.priority = priority;
 
@@ -75,18 +98,18 @@
     }
 
     @DataClass.Generated.Member
-    public int getRequestDetailResourceId() {
-        return requestDetailResourceId;
+    public int getRequestDetailRes() {
+        return requestDetailRes;
     }
 
     @DataClass.Generated.Member
-    public int getBackgroundRequestResourceId() {
-        return backgroundRequestResourceId;
+    public int getBackgroundRequestRes() {
+        return backgroundRequestRes;
     }
 
     @DataClass.Generated.Member
-    public int getBackgroundRequestDetailResourceId() {
-        return backgroundRequestDetailResourceId;
+    public int getBackgroundRequestDetailRes() {
+        return backgroundRequestDetailRes;
     }
 
     @DataClass.Generated.Member
@@ -100,97 +123,58 @@
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedPermissionGroupImpl setRequestDetailResourceId( int value) {
-        requestDetailResourceId = value;
+    public @NonNull ParsedPermissionGroupImpl setRequestDetailRes( int value) {
+        requestDetailRes = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedPermissionGroupImpl setBackgroundRequestResourceId( int value) {
-        backgroundRequestResourceId = value;
+    public @NonNull ParsedPermissionGroupImpl setBackgroundRequestRes( int value) {
+        backgroundRequestRes = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedPermissionGroupImpl setBackgroundRequestDetailResourceId( int value) {
-        backgroundRequestDetailResourceId = value;
+    public @NonNull ParsedPermissionGroupImpl setBackgroundRequestDetailRes( int value) {
+        backgroundRequestDetailRes = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedPermissionGroupImpl setRequestRes( int value) {
+    public @NonNull ParsedPermissionGroupImpl setRequestRes( int value) {
         requestRes = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedPermissionGroupImpl setPriority( int value) {
+    public @NonNull ParsedPermissionGroupImpl setPriority( int value) {
         priority = value;
         return this;
     }
 
     @Override
     @DataClass.Generated.Member
-    public void writeToParcel(@android.annotation.NonNull Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        super.writeToParcel(dest, flags);
-
-        dest.writeInt(requestDetailResourceId);
-        dest.writeInt(backgroundRequestResourceId);
-        dest.writeInt(backgroundRequestDetailResourceId);
-        dest.writeInt(requestRes);
-        dest.writeInt(priority);
-    }
-
-    @Override
-    @DataClass.Generated.Member
     public int describeContents() { return 0; }
 
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
     @DataClass.Generated.Member
-    protected ParsedPermissionGroupImpl(@android.annotation.NonNull Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        super(in);
-
-        int _requestDetailResourceId = in.readInt();
-        int _backgroundRequestResourceId = in.readInt();
-        int _backgroundRequestDetailResourceId = in.readInt();
-        int _requestRes = in.readInt();
-        int _priority = in.readInt();
-
-        this.requestDetailResourceId = _requestDetailResourceId;
-        this.backgroundRequestResourceId = _backgroundRequestResourceId;
-        this.backgroundRequestDetailResourceId = _backgroundRequestDetailResourceId;
-        this.requestRes = _requestRes;
-        this.priority = _priority;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @android.annotation.NonNull android.os.Parcelable.Creator<ParsedPermissionGroupImpl> CREATOR
-            = new android.os.Parcelable.Creator<ParsedPermissionGroupImpl>() {
+    public static final @NonNull Parcelable.Creator<ParsedPermissionGroupImpl> CREATOR
+            = new Parcelable.Creator<ParsedPermissionGroupImpl>() {
         @Override
         public ParsedPermissionGroupImpl[] newArray(int size) {
             return new ParsedPermissionGroupImpl[size];
         }
 
         @Override
-        public ParsedPermissionGroupImpl createFromParcel(@android.annotation.NonNull Parcel in) {
+        public ParsedPermissionGroupImpl createFromParcel(@NonNull Parcel in) {
             return new ParsedPermissionGroupImpl(in);
         }
     };
 
     @DataClass.Generated(
-            time = 1627602253988L,
+            time = 1642132854167L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java",
-            inputSignatures = "private  int requestDetailResourceId\nprivate  int backgroundRequestResourceId\nprivate  int backgroundRequestDetailResourceId\nprivate  int requestRes\nprivate  int priority\npublic  java.lang.String toString()\nclass ParsedPermissionGroupImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermissionGroup]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java",
+            inputSignatures = "private  int requestDetailRes\nprivate  int backgroundRequestRes\nprivate  int backgroundRequestDetailRes\nprivate  int requestRes\nprivate  int priority\npublic  java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.server.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
index 98007ff..70986c3 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
@@ -29,13 +29,17 @@
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
 
+import java.util.Collections;
 import java.util.Locale;
 import java.util.Set;
 
-/** @hide */
+/**
+ * @hide
+ */
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedPermission {
+public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedPermission,
+        Parcelable {
 
     private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
 
@@ -56,14 +60,8 @@
     public ParsedPermissionImpl() {
     }
 
-    public ParsedPermissionImpl(ParsedPermission other) {
-        super(other);
-        this.backgroundPermission = other.getBackgroundPermission();
-        this.group = other.getGroup();
-        this.requestRes = other.getRequestRes();
-        this.protectionLevel = other.getProtectionLevel();
-        this.tree = other.isTree();
-        this.parsedPermissionGroup = other.getParsedPermissionGroup();
+    public ParsedPermissionGroup getParsedPermissionGroup() {
+        return parsedPermissionGroup;
     }
 
     public ParsedPermissionImpl setGroup(String group) {
@@ -84,6 +82,12 @@
         }
     }
 
+    @NonNull
+    @Override
+    public Set<String> getKnownCerts() {
+        return knownCerts == null ? Collections.emptySet() : knownCerts;
+    }
+
     public String toString() {
         return "Permission{"
                 + Integer.toHexString(System.identityHashCode(this))
@@ -103,7 +107,7 @@
         dest.writeInt(this.requestRes);
         dest.writeInt(this.protectionLevel);
         dest.writeBoolean(this.tree);
-        dest.writeParcelable(this.parsedPermissionGroup, flags);
+        dest.writeParcelable((ParsedPermissionGroupImpl) this.parsedPermissionGroup, flags);
         sForStringSet.parcel(knownCerts, dest, flags);
     }
 
@@ -115,7 +119,7 @@
         this.protectionLevel = in.readInt();
         this.tree = in.readBoolean();
         this.parsedPermissionGroup = in.readParcelable(ParsedPermissionGroup.class.getClassLoader(),
-                ParsedPermissionGroup.class);
+                ParsedPermissionGroupImpl.class);
         this.knownCerts = sForStringSet.unparcel(in);
     }
 
@@ -155,7 +159,7 @@
             int requestRes,
             int protectionLevel,
             boolean tree,
-            @Nullable ParsedPermissionGroup parsedPermissionGroup,
+            @Nullable ParsedPermissionGroupImpl parsedPermissionGroup,
             @Nullable Set<String> knownCerts) {
         this.backgroundPermission = backgroundPermission;
         this.group = group;
@@ -194,16 +198,6 @@
     }
 
     @DataClass.Generated.Member
-    public @Nullable ParsedPermissionGroup getParsedPermissionGroup() {
-        return parsedPermissionGroup;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable Set<String> getKnownCerts() {
-        return knownCerts;
-    }
-
-    @DataClass.Generated.Member
     public @NonNull ParsedPermissionImpl setBackgroundPermission(@NonNull String value) {
         backgroundPermission = value;
         return this;
@@ -240,10 +234,10 @@
     }
 
     @DataClass.Generated(
-            time = 1627598236506L,
+            time = 1641414649731L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java",
-            inputSignatures = "private static  com.android.internal.util.Parcelling.BuiltIn.ForStringSet sForStringSet\nprivate @android.annotation.Nullable java.lang.String backgroundPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String group\nprivate  int requestRes\nprivate  int protectionLevel\nprivate  boolean tree\nprivate @android.annotation.Nullable android.content.pm.parsing.component.ParsedPermissionGroup parsedPermissionGroup\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> knownCerts\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedPermissionImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedPermissionImpl setGroup(java.lang.String)\nprotected  void setKnownCert(java.lang.String)\nprotected  void setKnownCerts(java.lang.String[])\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermission]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private static  com.android.internal.util.Parcelling.BuiltIn.ForStringSet sForStringSet\nprivate @android.annotation.Nullable java.lang.String backgroundPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String group\nprivate  int requestRes\nprivate  int protectionLevel\nprivate  boolean tree\nprivate @android.annotation.Nullable android.content.pm.parsing.component.ParsedPermissionGroupImpl parsedPermissionGroup\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> knownCerts\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedPermissionImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedPermissionGroup getParsedPermissionGroup()\npublic  android.content.pm.parsing.component.ParsedPermissionImpl setGroup(java.lang.String)\nprotected  void setKnownCert(java.lang.String)\nprotected  void setKnownCerts(java.lang.String[])\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownCerts()\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
index 8562fdf..f2e2f4f 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
@@ -20,8 +20,6 @@
 
 import android.annotation.NonNull;
 import android.content.pm.PermissionInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -31,13 +29,17 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 import java.util.List;
 
-/** @hide */
+/**
+ * @hide
+ */
 public class ParsedPermissionUtils {
 
     private static final String TAG = ParsingUtils.TAG;
@@ -107,7 +109,7 @@
                         permission.setKnownCert(knownCert);
                     }
                 }
-                if (permission.getKnownCerts() == null) {
+                if (permission.getKnownCerts().isEmpty()) {
                     Slog.w(TAG, packageName + " defines a knownSigner permission but"
                             + " the provided knownCerts resource is null");
                 }
@@ -228,9 +230,9 @@
             }
 
             // @formatter:off
-            permissionGroup.setRequestDetailResourceId(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0))
-                    .setBackgroundRequestResourceId(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0))
-                    .setBackgroundRequestDetailResourceId(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0))
+            permissionGroup.setRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0))
+                    .setBackgroundRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0))
+                    .setBackgroundRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0))
                     .setRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_request, 0))
                     .setPriority(sa.getInt(R.styleable.AndroidManifestPermissionGroup_priority, 0))
                     .setFlags(sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0));
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java
index ff391ff..608d08e 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java
@@ -17,14 +17,15 @@
 package com.android.server.pm.pkg.component;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.content.pm.ApplicationInfo;
-import android.os.Parcelable;
 import android.util.ArrayMap;
 
 import java.util.Set;
 
 /** @hide */
-public interface ParsedProcess extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedProcess {
 
     @NonNull
     Set<String> getDeniedPermissions();
@@ -44,6 +45,7 @@
      * It's a map, because in shared processes, different packages can have different application
      * classes.
      */
+    @SuppressLint("ConcreteCollection")
     @NonNull
     ArrayMap<String, String> getAppClassNamesByPackage();
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
index 96560c7..6d52f65 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
@@ -36,7 +36,7 @@
 @DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false,
         genBuilder = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedProcessImpl implements ParsedProcess {
+public class ParsedProcessImpl implements ParsedProcess, Parcelable {
 
     @NonNull
     private String name;
@@ -305,10 +305,10 @@
     };
 
     @DataClass.Generated(
-            time = 1639076603310L,
+            time = 1641431953775L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\npublic  void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\npublic  void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
index d03f153..4f4c2d5 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
@@ -18,8 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.pm.ApplicationInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -31,6 +29,8 @@
 import com.android.internal.R;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java
index 8cc6fc7..ba85e07 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java
@@ -16,11 +16,15 @@
 
 package com.android.server.pm.pkg.component;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.PathPermission;
 import android.os.PatternMatcher;
 
+import java.util.List;
+
 /** @hide **/
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedProvider extends ParsedMainComponent {
 
     @Nullable
@@ -30,13 +34,17 @@
 
     boolean isMultiProcess();
 
-    @Nullable PathPermission[] getPathPermissions();
+    @NonNull
+    List<PathPermission> getPathPermissions();
 
-    @Nullable String getReadPermission();
+    @Nullable
+    String getReadPermission();
 
-    @Nullable PatternMatcher[] getUriPermissionPatterns();
+    @NonNull
+    List<PatternMatcher> getUriPermissionPatterns();
 
-    @Nullable String getWritePermission();
+    @Nullable
+    String getWritePermission();
 
     boolean isForceUriPermissions();
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
index e04fc86..e77ecb3 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
@@ -28,13 +28,22 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
-/** @hide **/
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @hide
+ **/
 @DataClass(genSetters = true, genGetters = true, genParcelable = false, genBuilder = false)
+@DataClass.Suppress({"setUriPermissionPatterns", "setPathPermissions"})
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedProviderImpl extends ParsedMainComponentImpl implements ParsedProvider {
+public class ParsedProviderImpl extends ParsedMainComponentImpl implements ParsedProvider,
+        Parcelable {
 
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
@@ -50,10 +59,10 @@
     private boolean forceUriPermissions;
     private boolean multiProcess;
     private int initOrder;
-    @Nullable
-    private PatternMatcher[] uriPermissionPatterns;
-    @Nullable
-    private PathPermission[] pathPermissions;
+    @NonNull
+    private List<PatternMatcher> uriPermissionPatterns = Collections.emptyList();
+    @NonNull
+    private List<PathPermission> pathPermissions = Collections.emptyList();
 
     public ParsedProviderImpl(ParsedProvider other) {
         super(other);
@@ -66,8 +75,8 @@
         this.forceUriPermissions = other.isForceUriPermissions();
         this.multiProcess = other.isMultiProcess();
         this.initOrder = other.getInitOrder();
-        this.uriPermissionPatterns = other.getUriPermissionPatterns();
-        this.pathPermissions = other.getPathPermissions();
+        this.uriPermissionPatterns = new ArrayList<>(other.getUriPermissionPatterns());
+        this.pathPermissions = new ArrayList<>(other.getPathPermissions());
     }
 
     public ParsedProviderImpl setReadPermission(String readPermission) {
@@ -84,6 +93,18 @@
         return this;
     }
 
+    @NonNull
+    public ParsedProviderImpl addUriPermissionPattern(@NonNull PatternMatcher value) {
+        uriPermissionPatterns = CollectionUtils.add(uriPermissionPatterns, value);
+        return this;
+    }
+
+    @NonNull
+    public ParsedProviderImpl addPathPermission(@NonNull PathPermission value) {
+        pathPermissions = CollectionUtils.add(pathPermissions, value);
+        return this;
+    }
+
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
         sb.append("Provider{");
@@ -110,8 +131,8 @@
         dest.writeBoolean(this.forceUriPermissions);
         dest.writeBoolean(this.multiProcess);
         dest.writeInt(this.initOrder);
-        dest.writeTypedArray(this.uriPermissionPatterns, flags);
-        dest.writeTypedArray(this.pathPermissions, flags);
+        dest.writeTypedList(this.uriPermissionPatterns, flags);
+        dest.writeTypedList(this.pathPermissions, flags);
     }
 
     public ParsedProviderImpl() {
@@ -127,8 +148,8 @@
         this.forceUriPermissions = in.readBoolean();
         this.multiProcess = in.readBoolean();
         this.initOrder = in.readInt();
-        this.uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
-        this.pathPermissions = in.createTypedArray(PathPermission.CREATOR);
+        this.uriPermissionPatterns = in.createTypedArrayList(PatternMatcher.CREATOR);
+        this.pathPermissions = in.createTypedArrayList(PathPermission.CREATOR);
     }
 
     @NonNull
@@ -153,7 +174,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -170,8 +191,8 @@
             boolean forceUriPermissions,
             boolean multiProcess,
             int initOrder,
-            @Nullable PatternMatcher[] uriPermissionPatterns,
-            @Nullable PathPermission[] pathPermissions) {
+            @NonNull List<PatternMatcher> uriPermissionPatterns,
+            @NonNull List<PathPermission> pathPermissions) {
         this.authority = authority;
         this.syncable = syncable;
         this.readPermission = readPermission;
@@ -181,7 +202,11 @@
         this.multiProcess = multiProcess;
         this.initOrder = initOrder;
         this.uriPermissionPatterns = uriPermissionPatterns;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, uriPermissionPatterns);
         this.pathPermissions = pathPermissions;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, pathPermissions);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -227,12 +252,12 @@
     }
 
     @DataClass.Generated.Member
-    public @Nullable PatternMatcher[] getUriPermissionPatterns() {
+    public @NonNull List<PatternMatcher> getUriPermissionPatterns() {
         return uriPermissionPatterns;
     }
 
     @DataClass.Generated.Member
-    public @Nullable PathPermission[] getPathPermissions() {
+    public @NonNull List<PathPermission> getPathPermissions() {
         return pathPermissions;
     }
 
@@ -272,23 +297,11 @@
         return this;
     }
 
-    @DataClass.Generated.Member
-    public @NonNull ParsedProviderImpl setUriPermissionPatterns(@NonNull PatternMatcher... value) {
-        uriPermissionPatterns = value;
-        return this;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull ParsedProviderImpl setPathPermissions(@NonNull PathPermission... value) {
-        pathPermissions = value;
-        return this;
-    }
-
     @DataClass.Generated(
-            time = 1627590522169L,
+            time = 1642560323360L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate  boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate  boolean grantUriPermissions\nprivate  boolean forceUriPermissions\nprivate  boolean multiProcess\nprivate  int initOrder\nprivate @android.annotation.Nullable android.os.PatternMatcher[] uriPermissionPatterns\nprivate @android.annotation.Nullable android.content.pm.PathPermission[] pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedProviderImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedProvider]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java",
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate  boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate  boolean grantUriPermissions\nprivate  boolean forceUriPermissions\nprivate  boolean multiProcess\nprivate  int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic  com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic  com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
index 9d3129b..4cb4dd0 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
@@ -16,16 +16,14 @@
 
 package com.android.server.pm.pkg.component;
 
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
 import static com.android.server.pm.pkg.component.ComponentParseUtils.flag;
+import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentFilter;
 import android.content.pm.PathPermission;
 import android.content.pm.ProviderInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -36,6 +34,8 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -173,14 +173,14 @@
             final ParseResult result;
             switch (name) {
                 case "intent-filter":
-                    ParseResult<ParsedIntentInfo> intentResult = ParsedMainComponentUtils
+                    ParseResult<ParsedIntentInfoImpl> intentResult = ParsedMainComponentUtils
                             .parseIntentFilter(provider, pkg, res, parser, visibleToEphemeral,
                                     true /*allowGlobs*/, false /*allowAutoVerify*/,
                                     false /*allowImplicitEphemeralVisibility*/,
                                     false /*failOnNoActions*/, input);
                     result = intentResult;
                     if (intentResult.isSuccess()) {
-                        ParsedIntentInfo intent = intentResult.getResult();
+                        ParsedIntentInfoImpl intent = intentResult.getResult();
                         IntentFilter intentFilter = intent.getIntentFilter();
                         provider.setOrder(Math.max(intentFilter.getOrder(), provider.getOrder()));
                         provider.addIntent(intent);
@@ -253,16 +253,7 @@
             }
 
             if (pa != null) {
-                if (provider.getUriPermissionPatterns() == null) {
-                    provider.setUriPermissionPatterns(new PatternMatcher[1]);
-                    provider.getUriPermissionPatterns()[0] = pa;
-                } else {
-                    final int N = provider.getUriPermissionPatterns().length;
-                    PatternMatcher[] newp = new PatternMatcher[N + 1];
-                    System.arraycopy(provider.getUriPermissionPatterns(), 0, newp, 0, N);
-                    newp[N] = pa;
-                    provider.setUriPermissionPatterns(newp);
-                }
+                provider.addUriPermissionPattern(pa);
                 provider.setGrantUriPermissions(true);
             } else {
                 if (RIGID_PARSER) {
@@ -357,16 +348,7 @@
             }
 
             if (pa != null) {
-                if (provider.getPathPermissions() == null) {
-                    provider.setPathPermissions(new PathPermission[1]);
-                    provider.getPathPermissions()[0] = pa;
-                } else {
-                    final int N = provider.getPathPermissions().length;
-                    PathPermission[] newp = new PathPermission[N + 1];
-                    System.arraycopy(provider.getPathPermissions(), 0, newp, 0, N);
-                    newp[N] = pa;
-                    provider.setPathPermissions(newp);
-                }
+                provider.addPathPermission(pa);
             } else {
                 if (RIGID_PARSER) {
                     return input.error(
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedService.java
index 11696be..5fc251c 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedService.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 
 /** @hide **/
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedService extends ParsedMainComponent {
 
     int getForegroundServiceType();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
index 0171c49..c00e01a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
@@ -32,7 +32,8 @@
 /** @hide **/
 @DataClass(genSetters = true, genGetters = true, genParcelable = false, genBuilder = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedServiceImpl extends ParsedMainComponentImpl implements ParsedService {
+public class ParsedServiceImpl extends ParsedMainComponentImpl implements ParsedService,
+        Parcelable {
 
     private int foregroundServiceType;
     @Nullable
@@ -138,10 +139,10 @@
     }
 
     @DataClass.Generated(
-            time = 1627592563052L,
+            time = 1641431954479L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java",
-            inputSignatures = "private  int foregroundServiceType\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedServiceImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedMainComponent setPermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedServiceImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedService]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
+            inputSignatures = "private  int foregroundServiceType\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedServiceImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedMainComponent setPermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedServiceImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
index 6fe9411..1940eb5 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
@@ -23,8 +23,6 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ServiceInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseInput.DeferredError;
 import android.content.pm.parsing.result.ParseResult;
@@ -34,6 +32,8 @@
 import android.os.Build;
 
 import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -133,14 +133,14 @@
             final ParseResult parseResult;
             switch (parser.getName()) {
                 case "intent-filter":
-                    ParseResult<ParsedIntentInfo> intentResult = ParsedMainComponentUtils
+                    ParseResult<ParsedIntentInfoImpl> intentResult = ParsedMainComponentUtils
                             .parseIntentFilter(service, pkg, res, parser, visibleToEphemeral,
                                     true /*allowGlobs*/, false /*allowAutoVerify*/,
                                     false /*allowImplicitEphemeralVisibility*/,
                                     false /*failOnNoActions*/, input);
                     parseResult = intentResult;
                     if (intentResult.isSuccess()) {
-                        ParsedIntentInfo intent = intentResult.getResult();
+                        ParsedIntentInfoImpl intent = intentResult.getResult();
                         IntentFilter intentFilter = intent.getIntentFilter();
                         service.setOrder(Math.max(intentFilter.getOrder(), service.getOrder()));
                         service.addIntent(intent);
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java
index 8e3401e..e17d1c4 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.content.pm.PackageInfo;
-import android.os.Parcelable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -30,7 +29,8 @@
  *
  * @hide
  */
-public interface ParsedUsesPermission extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedUsesPermission {
 
     /**
      * Strong assertion by a developer that they will never use this permission to derive the
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
index 70d6f24..9b89373 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
@@ -33,7 +33,7 @@
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = true,
         genAidl = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedUsesPermissionImpl implements ParsedUsesPermission {
+public class ParsedUsesPermissionImpl implements ParsedUsesPermission, Parcelable {
 
     @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
     @NonNull
@@ -157,10 +157,10 @@
     };
 
     @DataClass.Generated(
-            time = 1627674645598L,
+            time = 1641431955242L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java",
-            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @android.content.pm.parsing.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\nclass ParsedUsesPermissionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedUsesPermission]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
+            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @android.content.pm.parsing.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\nclass ParsedUsesPermissionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedUsesPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
index 2d6c616..b92b845 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ProviderInfo;
@@ -41,6 +42,7 @@
 import android.content.pm.SigningInfo;
 import android.content.pm.overlay.OverlayPaths;
 import android.os.Environment;
+import android.os.PatternMatcher;
 import android.os.UserHandle;
 
 import com.android.internal.util.ArrayUtils;
@@ -651,8 +653,8 @@
         pi.forceUriPermissions = p.isForceUriPermissions();
         pi.multiprocess = p.isMultiProcess();
         pi.initOrder = p.getInitOrder();
-        pi.uriPermissionPatterns = p.getUriPermissionPatterns();
-        pi.pathPermissions = p.getPathPermissions();
+        pi.uriPermissionPatterns = p.getUriPermissionPatterns().toArray(new PatternMatcher[0]);
+        pi.pathPermissions = p.getPathPermissions().toArray(new PathPermission[0]);
         if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
             pi.uriPermissionPatterns = null;
         }
@@ -690,9 +692,11 @@
         ii.sourceDir = pkg.getBaseApkPath();
         ii.publicSourceDir = pkg.getBaseApkPath();
         ii.splitNames = pkg.getSplitNames();
-        ii.splitSourceDirs = pkg.getSplitCodePaths();
-        ii.splitPublicSourceDirs = pkg.getSplitCodePaths();
-        ii.splitDependencies = pkg.getSplitDependencies();
+        ii.splitSourceDirs = pkg.getSplitCodePaths().length == 0 ? null : pkg.getSplitCodePaths();
+        ii.splitPublicSourceDirs = pkg.getSplitCodePaths().length == 0
+                ? null : pkg.getSplitCodePaths();
+        ii.splitDependencies = pkg.getSplitDependencies().size() == 0
+                ? null : pkg.getSplitDependencies();
 
         if (assignUserFields) {
             assignUserFields(pkg, ii, userId);
@@ -734,9 +738,9 @@
         if (pg == null) return null;
 
         PermissionGroupInfo pgi = new PermissionGroupInfo(
-                pg.getRequestDetailResourceId(),
-                pg.getBackgroundRequestResourceId(),
-                pg.getBackgroundRequestDetailResourceId()
+                pg.getRequestDetailRes(),
+                pg.getBackgroundRequestRes(),
+                pg.getBackgroundRequestDetailRes()
         );
 
         assignSharedFieldsForPackageItemInfo(pgi, pg);
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
index 1f21938..84422cd 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
@@ -24,6 +24,7 @@
 import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
@@ -81,11 +82,8 @@
 import com.android.server.pm.pkg.component.ParsedServiceImpl;
 import com.android.server.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageHidden;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
+
+import libcore.util.EmptyArray;
 
 import java.security.PublicKey;
 import java.util.Collections;
@@ -107,6 +105,7 @@
  */
 public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, Parcelable {
 
+    private static final SparseArray<int[]> EMPTY_INT_ARRAY_SPARSE_ARRAY = new SparseArray<>();
     public static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
     public static ForInternedString sForInternedString = Parcelling.Cache.getOrCreate(
             ForInternedString.class);
@@ -660,6 +659,7 @@
         return this;
     }
 
+    @SuppressLint("MissingSuperCall")
     @CallSuper
     @Override
     public Object hideAsParsed() {
@@ -1125,7 +1125,8 @@
 //        appInfo.sharedLibraryInfos
 //        appInfo.showUserIcon
         appInfo.splitClassLoaderNames = splitClassLoaderNames;
-        appInfo.splitDependencies = splitDependencies;
+        appInfo.splitDependencies = (splitDependencies == null || splitDependencies.size() == 0)
+                ? null : splitDependencies;
         appInfo.splitNames = splitNames;
         appInfo.storageUuid = mStorageUuid;
         appInfo.targetSandboxVersion = targetSandboxVersion;
@@ -1144,8 +1145,8 @@
         appInfo.setBaseResourcePath(mBaseApkPath);
         appInfo.setCodePath(mPath);
         appInfo.setResourcePath(mPath);
-        appInfo.setSplitCodePaths(splitCodePaths);
-        appInfo.setSplitResourcePaths(splitCodePaths);
+        appInfo.setSplitCodePaths(ArrayUtils.size(splitCodePaths) == 0 ? null : splitCodePaths);
+        appInfo.setSplitResourcePaths(ArrayUtils.size(splitCodePaths) == 0 ? null : splitCodePaths);
         appInfo.setVersionCode(mLongVersionCode);
         appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
         appInfo.setLocaleConfigRes(mLocaleConfigRes);
@@ -1257,20 +1258,20 @@
         dest.writeStringList(this.originalPackages);
         sForInternedStringList.parcel(this.adoptPermissions, dest, flags);
         sForInternedStringList.parcel(this.requestedPermissions, dest, flags);
-        dest.writeTypedList(this.usesPermissions);
+        ParsingUtils.writeParcelableList(dest, this.usesPermissions);
         sForInternedStringList.parcel(this.implicitPermissions, dest, flags);
         sForStringSet.parcel(this.upgradeKeySets, dest, flags);
         ParsingPackageUtils.writeKeySetMapping(dest, this.keySetMapping);
         sForInternedStringList.parcel(this.protectedBroadcasts, dest, flags);
-        dest.writeTypedList(this.activities);
-        dest.writeTypedList(this.apexSystemServices);
-        dest.writeTypedList(this.receivers);
-        dest.writeTypedList(this.services);
-        dest.writeTypedList(this.providers);
-        dest.writeTypedList(this.attributions);
-        dest.writeTypedList(this.permissions);
-        dest.writeTypedList(this.permissionGroups);
-        dest.writeTypedList(this.instrumentations);
+        ParsingUtils.writeParcelableList(dest, this.activities);
+        ParsingUtils.writeParcelableList(dest, this.apexSystemServices);
+        ParsingUtils.writeParcelableList(dest, this.receivers);
+        ParsingUtils.writeParcelableList(dest, this.services);
+        ParsingUtils.writeParcelableList(dest, this.providers);
+        ParsingUtils.writeParcelableList(dest, this.attributions);
+        ParsingUtils.writeParcelableList(dest, this.permissions);
+        ParsingUtils.writeParcelableList(dest, this.permissionGroups);
+        ParsingUtils.writeParcelableList(dest, this.instrumentations);
         sForIntentInfoPairs.parcel(this.preferredActivityFilters, dest, flags);
         dest.writeMap(this.processes);
         dest.writeBundle(this.metaData);
@@ -1906,22 +1907,22 @@
         return queriesProviders;
     }
 
-    @Nullable
+    @NonNull
     @Override
     public String[] getSplitClassLoaderNames() {
-        return splitClassLoaderNames;
+        return splitClassLoaderNames == null ? EmptyArray.STRING : splitClassLoaderNames;
     }
 
-    @Nullable
+    @NonNull
     @Override
     public String[] getSplitCodePaths() {
-        return splitCodePaths;
+        return splitCodePaths == null ? EmptyArray.STRING : splitCodePaths;
     }
 
     @Nullable
     @Override
     public SparseArray<int[]> getSplitDependencies() {
-        return splitDependencies;
+        return splitDependencies == null ? EMPTY_INT_ARRAY_SPARSE_ARRAY : splitDependencies;
     }
 
     @Nullable
@@ -1930,16 +1931,16 @@
         return splitFlags;
     }
 
-    @Nullable
+    @NonNull
     @Override
     public String[] getSplitNames() {
-        return splitNames;
+        return splitNames == null ? EmptyArray.STRING : splitNames;
     }
 
-    @Nullable
+    @NonNull
     @Override
     public int[] getSplitRevisionCodes() {
-        return splitRevisionCodes;
+        return splitRevisionCodes == null ? EmptyArray.INT : splitRevisionCodes;
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index bf7c55f..e8f03ff 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -65,6 +65,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -101,6 +102,7 @@
 import com.android.server.pm.pkg.component.ParsedInstrumentation;
 import com.android.server.pm.pkg.component.ParsedInstrumentationUtils;
 import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
 import com.android.server.pm.pkg.component.ParsedIntentInfoUtils;
 import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.component.ParsedPermission;
@@ -230,6 +232,8 @@
      * of required system property within the overlay tag.
      */
     public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
+    public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
+
     public static final int PARSE_CHATTY = 1 << 31;
 
     @IntDef(flag = true, prefix = { "PARSE_" }, value = {
@@ -241,6 +245,7 @@
             PARSE_IS_SYSTEM_DIR,
             PARSE_MUST_BE_APK,
             PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
+            PARSE_FRAMEWORK_RES_SPLITS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ParseFlags {}
@@ -289,7 +294,7 @@
                 return new ParsingPackageImpl(packageName, baseApkPath, path, manifestArray);
             }
         });
-        result = parser.parsePackage(input, file, parseFlags);
+        result = parser.parsePackage(input, file, parseFlags, /* frameworkSplits= */ null);
         if (result.isError()) {
             return input.error(result);
         }
@@ -343,26 +348,44 @@
      * not check whether {@code packageFile} has changed since the last parse, it's up to callers to
      * do so.
      */
-    public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) {
-        if (packageFile.isDirectory()) {
-            return parseClusterPackage(input, packageFile, flags);
+    public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags,
+            List<File> frameworkSplits) {
+        if (((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0)
+                && frameworkSplits.size() > 0
+                && packageFile.getAbsolutePath().endsWith("/framework-res.apk")) {
+            return parseClusterPackage(input, packageFile, frameworkSplits, flags);
+        } else if (packageFile.isDirectory()) {
+            return parseClusterPackage(input, packageFile, /* frameworkSplits= */null, flags);
         } else {
             return parseMonolithicPackage(input, packageFile, flags);
         }
     }
 
     /**
-     * Parse all APKs contained in the given directory, treating them as a single package. This also
-     * performs validity checking, such as requiring identical package name and version codes, a
-     * single base APK, and unique split names.
+     * Parse all APKs contained in the given directory, treating them as a
+     * single package. This also performs validity checking, such as requiring
+     * identical package name and version codes, a single base APK, and unique
+     * split names.
+     * <p>
+     * Can also be passed the framework-res.apk file and a list of split apks coming from apexes
+     * (via {@code frameworkSplits}) in which case they will be parsed similar to cluster packages
+     * even if they are in different folders. Note that this code path may have other behaviour
+     * differences.
      * <p>
      * Note that this <em>does not</em> perform signature verification; that must be done separately
      * in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}.
      */
     private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
-            int flags) {
+            List<File> frameworkSplits, int flags) {
+        // parseClusterPackageLite should receive no flags (0) for regular splits but we want to
+        // pass the flags for framework splits
+        int liteParseFlags = 0;
+        if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) {
+            liteParseFlags = flags;
+        }
         final ParseResult<PackageLite> liteResult =
-                ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0);
+                ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits,
+                        liteParseFlags);
         if (liteResult.isError()) {
             return input.error(liteResult);
         }
@@ -1673,7 +1696,7 @@
                 continue;
             }
             if (parser.getName().equals("intent")) {
-                ParseResult<ParsedIntentInfo> result = ParsedIntentInfoUtils.parseIntentInfo(
+                ParseResult<ParsedIntentInfoImpl> result = ParsedIntentInfoUtils.parseIntentInfo(
                         null /*className*/, pkg, res, parser, true /*allowGlobs*/,
                         true /*allowAutoVerify*/, input);
                 if (result.isError()) {
@@ -2679,9 +2702,8 @@
             // as it would have already been set when we processed the activity. We wait to
             // process the meta data here since this method is called at the end of processing
             // the application and all meta data is guaranteed.
-            final float activityAspectRatio = activity.getMetaData() != null
-                    ? activity.getMetaData().getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio)
-                    : maxAspectRatio;
+            final float activityAspectRatio = activity.getMetaData()
+                    .getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio);
 
             ComponentMutateUtils.setMaxAspectRatio(activity, activity.getResizeMode(),
                     activityAspectRatio);
@@ -2716,9 +2738,8 @@
         int activitiesSize = activities.size();
         for (int index = 0; index < activitiesSize; index++) {
             ParsedActivity activity = activities.get(index);
-            if (supportsSizeChanges || (activity.getMetaData() != null
-                    && activity.getMetaData().getBoolean(
-                            METADATA_SUPPORTS_SIZE_CHANGES, false))) {
+            if (supportsSizeChanges || activity.getMetaData()
+                    .getBoolean(METADATA_SUPPORTS_SIZE_CHANGES, false)) {
                 ComponentMutateUtils.setSupportsSizeChanges(activity, true);
             }
         }
@@ -2991,9 +3012,12 @@
             }
 
             signingDetails = result.getResult();
-
+            final File frameworkRes = new File(Environment.getRootDirectory(),
+                    "framework/framework-res.apk");
+            boolean isFrameworkResSplit = frameworkRes.getAbsolutePath()
+                    .equals(pkg.getBaseApkPath());
             String[] splitCodePaths = pkg.getSplitCodePaths();
-            if (!ArrayUtils.isEmpty(splitCodePaths)) {
+            if (!ArrayUtils.isEmpty(splitCodePaths) && !isFrameworkResSplit) {
                 for (int i = 0; i < splitCodePaths.length; i++) {
                     result = getSigningDetails(
                             input,
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
index 95fec36..9430e98 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
@@ -20,8 +20,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.XmlResourceParser;
@@ -32,6 +30,8 @@
 
 import com.android.internal.util.Parcelling;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -84,7 +84,8 @@
     }
 
     /**
-     * Use with {@link Parcel#writeTypedList(List)}
+     * Use with {@link Parcel#writeTypedList(List)} or
+     * {@link #writeInterfaceAsImplList(Parcel, List)}
      *
      * @see Parcel#createTypedArrayList(Parcelable.Creator)
      */
@@ -103,6 +104,29 @@
         return list;
     }
 
+    /**
+     * Use with {@link #createTypedInterfaceList(Parcel, Parcelable.Creator)}.
+     *
+     * Writes a list that can be cast as Parcelable types at runtime.
+     * TODO: Remove with ImmutableList multi-casting support
+     *
+     * @see Parcel#writeTypedList(List)
+     */
+    @NonNull
+    public static void writeParcelableList(@NonNull Parcel parcel, List<?> list) {
+        if (list == null) {
+            parcel.writeInt(-1);
+            return;
+        }
+        int size = list.size();
+        int index = 0;
+        parcel.writeInt(size);
+        while (index < size) {
+            parcel.writeTypedObject((Parcelable) list.get(index), 0);
+            index++;
+        }
+    }
+
     public static class StringPairListParceler implements
             Parcelling<List<Pair<String, ParsedIntentInfo>>> {
 
@@ -120,7 +144,7 @@
             for (int index = 0; index < size; index++) {
                 Pair<String, ParsedIntentInfo> pair = item.get(index);
                 dest.writeString(pair.first);
-                dest.writeParcelable(pair.second, parcelFlags);
+                dest.writeParcelable((Parcelable) pair.second, parcelFlags);
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
index a323e20..2ef90ac 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
@@ -324,13 +324,13 @@
      * @see ApplicationInfo#splitSourceDirs
      * @see ApplicationInfo#getSplitCodePaths
      */
-    @Nullable
+    @NonNull
     String[] getSplitCodePaths();
 
     /**
      * @see ApplicationInfo#splitDependencies
      */
-    @Nullable
+    @NonNull
     SparseArray<int[]> getSplitDependencies();
 
     /**
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java
index 2bc4ee7..78ce6f1 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java
@@ -249,12 +249,13 @@
      * @see ApplicationInfo#splitNames
      * @see PackageInfo#splitNames
      */
-    @Nullable
+    @NonNull
     String[] getSplitNames();
 
     /**
      * @see PackageInfo#splitRevisionCodes
      */
+    @NonNull
     int[] getSplitRevisionCodes();
 
     /**
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index d0b50d27..d6c89f7 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -60,7 +60,7 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUtils;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.PackageUserStateUtils;
 import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
@@ -1751,7 +1751,7 @@
         int approvalLevel = approvalLevelForDomainInternal(pkgSetting, host, includeNegative,
                 userId, debugObject);
         if (includeNegative && approvalLevel == APPROVAL_LEVEL_NONE) {
-            PackageUserState pkgUserState = pkgSetting.getUserStateOrDefault(userId);
+            PackageUserStateInternal pkgUserState = pkgSetting.getUserStateOrDefault(userId);
             if (!pkgUserState.isInstalled()) {
                 return APPROVAL_LEVEL_NOT_INSTALLED;
             }
@@ -1783,7 +1783,7 @@
             return APPROVAL_LEVEL_UNDECLARED;
         }
 
-        final PackageUserState pkgUserState = pkgSetting.getUserStates().get(userId);
+        final PackageUserStateInternal pkgUserState = pkgSetting.getUserStates().get(userId);
         if (pkgUserState == null) {
             if (DEBUG_APPROVAL) {
                 debugApproval(packageName, debugObject, userId, false,
diff --git a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
index 154f9a4..7754944 100644
--- a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
@@ -27,12 +27,11 @@
  *
  * @see DeviceStateProviderImpl
  */
-public final class DeviceStatePolicyImpl implements DeviceStatePolicy {
-    private final Context mContext;
+public final class DeviceStatePolicyImpl extends DeviceStatePolicy {
     private final DeviceStateProvider mProvider;
 
-    public DeviceStatePolicyImpl(Context context) {
-        mContext = context;
+    public DeviceStatePolicyImpl(@NonNull Context context) {
+        super(context);
         mProvider = DeviceStateProviderImpl.create(mContext);
     }
 
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 9127484..c1bfdf7 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -16,9 +16,16 @@
 
 package com.android.server.power;
 
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
 import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+import static android.os.PowerManagerInternal.isInteractive;
 
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.PowerManager;
+import android.os.Trace;
+import android.util.Slog;
 import android.view.Display;
 
 /**
@@ -31,47 +38,66 @@
  */
 public class PowerGroup {
     private static final String TAG = PowerGroup.class.getSimpleName();
+    private static final boolean DEBUG = false;
 
     private final DisplayPowerRequest mDisplayPowerRequest;
+    private final PowerGroupListener mWakefulnessListener;
     private final boolean mSupportsSandman;
     private final int mGroupId;
-
-    // True if DisplayManagerService has applied all the latest display states that were requested
-    // for this group
+    /** True if DisplayManagerService has applied all the latest display states that were requested
+     *  for this group. */
     private boolean mReady;
-    // True if this group is in the process of powering on
+    /** True if this group is in the process of powering on */
     private boolean mPoweringOn;
-    // True if this group is about to dream
+    /** True if this group is about to dream */
     private boolean mIsSandmanSummoned;
     private int mUserActivitySummary;
-    // The current wakefulness of this group
+    /** The current wakefulness of this group */
     private int mWakefulness;
     private int mWakeLockSummary;
     private long mLastPowerOnTime;
     private long mLastUserActivityTime;
     private long mLastUserActivityTimeNoChangeLights;
+    /** Timestamp (milliseconds since boot) of the last time the power group was awoken.*/
+    private long mLastWakeTime;
+    /** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
+    private long mLastSleepTime;
 
-    PowerGroup(int groupId, DisplayPowerRequest displayPowerRequest, int wakefulness, boolean ready,
-            boolean supportsSandman) {
-        this.mGroupId = groupId;
-        this.mDisplayPowerRequest = displayPowerRequest;
-        this.mWakefulness = wakefulness;
-        this.mReady = ready;
-        this.mSupportsSandman = supportsSandman;
+    PowerGroup(int groupId, PowerGroupListener wakefulnessListener,
+            DisplayPowerRequest displayPowerRequest, int wakefulness, boolean ready,
+            boolean supportsSandman, long eventTime) {
+        mGroupId = groupId;
+        mWakefulnessListener = wakefulnessListener;
+        mDisplayPowerRequest = displayPowerRequest;
+        mWakefulness = wakefulness;
+        mReady = ready;
+        mSupportsSandman = supportsSandman;
+        mLastWakeTime = eventTime;
+        mLastSleepTime = eventTime;
     }
 
-    PowerGroup() {
-        this.mGroupId = Display.DEFAULT_DISPLAY_GROUP;
-        this.mDisplayPowerRequest = new DisplayPowerRequest();
-        this.mWakefulness = WAKEFULNESS_AWAKE;
-        this.mReady = false;
-        this.mSupportsSandman = true;
-    }
+    PowerGroup(int wakefulness, PowerGroupListener wakefulnessListener, long eventTime) {
+        mGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        mWakefulnessListener = wakefulnessListener;
+        mDisplayPowerRequest = new DisplayPowerRequest();
+        mWakefulness = wakefulness;
+        mReady = false;
+        mSupportsSandman = true;
+        mLastWakeTime = eventTime;
+        mLastSleepTime = eventTime;    }
 
     DisplayPowerRequest getDisplayPowerRequestLocked() {
         return mDisplayPowerRequest;
     }
 
+    long getLastWakeTimeLocked() {
+        return mLastWakeTime;
+    }
+
+    long getLastSleepTimeLocked() {
+        return mLastSleepTime;
+    }
+
     int getWakefulnessLocked() {
         return mWakefulness;
     }
@@ -85,9 +111,19 @@
      *
      * @return {@code true} if the wakefulness value was changed; {@code false} otherwise.
      */
-    boolean setWakefulnessLocked(int newWakefulness) {
+    boolean setWakefulnessLocked(int newWakefulness, long eventTime, int uid, int reason, int opUid,
+            String opPackageName, String details) {
         if (mWakefulness != newWakefulness) {
+            if (newWakefulness == WAKEFULNESS_AWAKE) {
+                setLastPowerOnTimeLocked(eventTime);
+                setIsPoweringOnLocked(true);
+                mLastWakeTime = eventTime;
+            } else if (isInteractive(mWakefulness) && !isInteractive(newWakefulness)) {
+                mLastSleepTime = eventTime;
+            }
             mWakefulness = newWakefulness;
+            mWakefulnessListener.onWakefulnessChangedLocked(mGroupId, mWakefulness, eventTime,
+                    reason, uid, opUid, opPackageName, details);
             return true;
         }
         return false;
@@ -105,8 +141,9 @@
      * Sets whether the displays of this group are all ready.
      *
      * <p>A display is ready if its reported
-     * {@link DisplayManagerInternal.DisplayPowerCallbacks#onStateChanged() actual state} matches
-     * its {@link DisplayManagerInternal#requestPowerState requested state}.
+     * {@link android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks#onStateChanged()
+     * actual state} matches its
+     * {@link android.hardware.display.DisplayManagerInternal#requestPowerState requested state}.
      *
      * @param isReady {@code true} if every display in the group is ready; otherwise {@code false}.
      * @return {@code true} if the ready state changed; otherwise {@code false}.
@@ -148,6 +185,62 @@
         mIsSandmanSummoned = isSandmanSummoned;
     }
 
+    boolean dreamLocked(long eventTime, int uid) {
+        if (eventTime < mLastWakeTime || mWakefulness != WAKEFULNESS_AWAKE) {
+            return false;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_POWER, "dreamPowerGroup" + getGroupId());
+        try {
+            Slog.i(TAG, "Napping power group (groupId=" + getGroupId() + ", uid=" + uid + ")...");
+            setSandmanSummonedLocked(true);
+            setWakefulnessLocked(WAKEFULNESS_DREAMING, eventTime, uid, /* reason= */0,
+                    /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_POWER);
+        }
+        return true;
+    }
+
+    boolean dozeLocked(long eventTime, int uid, int reason) {
+        if (eventTime < getLastWakeTimeLocked() || !isInteractive(mWakefulness)) {
+            return false;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_POWER, "powerOffDisplay");
+        try {
+            reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
+                    Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
+            Slog.i(TAG, "Powering off display group due to "
+                    + PowerManager.sleepReasonToString(reason)  + " (groupId= " + getGroupId()
+                    + ", uid= " + uid + ")...");
+
+            setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
+            setWakefulnessLocked(WAKEFULNESS_DOZING, eventTime, uid, reason, /* opUid= */ 0,
+                    /* opPackageName= */ null, /* details= */ null);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_POWER);
+        }
+        return true;
+    }
+
+    boolean sleepLocked(long eventTime, int uid, int reason) {
+        if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
+            return false;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_POWER, "sleepPowerGroup");
+        try {
+            Slog.i(TAG, "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ")...");
+            setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
+            setWakefulnessLocked(WAKEFULNESS_ASLEEP, eventTime, uid, reason, /* opUid= */0,
+                    /* opPackageName= */ null, /* details= */ null);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_POWER);
+        }
+        return true;
+    }
+
     long getLastUserActivityTimeLocked() {
         return mLastUserActivityTime;
     }
@@ -187,4 +280,22 @@
     public boolean supportsSandmanLocked() {
         return mSupportsSandman;
     }
+
+    protected interface PowerGroupListener {
+        /**
+         * Informs the recipient about a wakefulness change of a {@link PowerGroup}.
+         *
+         * @param groupId The PowerGroup's id for which the wakefulness has changed.
+         * @param wakefulness The new wakefulness.
+         * @param eventTime The time of the event.
+         * @param reason The reason, any of {@link android.os.PowerManager.WakeReason} or
+         *               {@link android.os.PowerManager.GoToSleepReason}.
+         * @param uid The uid which caused the wakefulness change.
+         * @param opUid The uid used for AppOps.
+         * @param opPackageName The Package name used for AppOps.
+         * @param details Details about the event.
+         */
+        void onWakefulnessChangedLocked(int groupId, int wakefulness, long eventTime, int reason,
+                int uid, int opUid, String opPackageName, String details);
+    }
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index abfa016..4185b2d 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -29,6 +29,7 @@
 import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
 import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
 import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+import static android.os.PowerManagerInternal.isInteractive;
 import static android.os.PowerManagerInternal.wakefulnessToString;
 
 import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN;
@@ -338,12 +339,12 @@
     private boolean mRequestWaitForNegativeProximity;
 
     // Timestamp of the last time the device was awoken or put to sleep.
-    private long mLastWakeTime;
-    private long mLastSleepTime;
+    private long mLastGlobalWakeTime;
+    private long mLastGlobalSleepTime;
 
     // Last reason the device went to sleep.
-    private @WakeReason int mLastWakeReason;
-    private int mLastSleepReason;
+    private @WakeReason int mLastGlobalWakeReason;
+    private int mLastGlobalSleepReason;
 
     // Timestamp of last time power boost interaction was sent.
     private long mLastInteractivePowerHintTime;
@@ -352,6 +353,8 @@
     private long mLastScreenBrightnessBoostTime;
     private boolean mScreenBrightnessBoostInProgress;
 
+    private final PowerGroupWakefulnessChangeListener mPowerGroupWakefulnessChangeListener;
+
     // The suspend blocker used to keep the CPU alive while the device is booting.
     private final SuspendBlocker mBootingSuspendBlocker;
 
@@ -397,6 +400,10 @@
     // The current battery level percentage.
     private int mBatteryLevel;
 
+    // True if updatePowerStateLocked() is already in progress.
+    // TODO(b/215518989): Remove this once transactions are in place
+    private boolean mUpdatePowerStateInProgress;
+
     /**
      * The lock that should be held when interacting with {@link #mEnhancedDischargeTimeElapsed},
      * {@link #mLastEnhancedDischargeTimeUpdatedElapsed}, and
@@ -640,6 +647,23 @@
     // but the DreamService has not yet been told to start (it's an async process).
     private boolean mDozeStartInProgress;
 
+    private final class PowerGroupWakefulnessChangeListener implements
+            PowerGroup.PowerGroupListener {
+        @GuardedBy("mLock")
+        @Override
+        public void onWakefulnessChangedLocked(int groupId, int wakefulness, long eventTime,
+                int reason, int uid, int opUid, String opPackageName, String details) {
+            if (wakefulness == WAKEFULNESS_AWAKE) {
+                // Kick user activity to prevent newly awake group from timing out instantly.
+                userActivityNoUpdateLocked(mPowerGroups.get(groupId), eventTime,
+                        PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid);
+            }
+            mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
+            updateGlobalWakefulnessLocked(eventTime, reason, uid, opUid, opPackageName, details);
+            updatePowerStateLocked();
+        }
+    }
+
     private final class DisplayGroupPowerChangeListener implements
             DisplayManagerInternal.DisplayGroupListener {
 
@@ -658,10 +682,12 @@
                 final boolean supportsSandman = groupId == Display.DEFAULT_DISPLAY_GROUP;
                 final PowerGroup powerGroup = new PowerGroup(
                         groupId,
+                        mPowerGroupWakefulnessChangeListener,
                         new DisplayPowerRequest(),
-                        getGlobalWakefulnessLocked(),
+                        WAKEFULNESS_AWAKE,
                         /* ready= */ false,
-                        supportsSandman);
+                        supportsSandman,
+                        mClock.uptimeMillis());
                 mPowerGroups.append(groupId, powerGroup);
                 onPowerGroupEventLocked(DISPLAY_GROUP_ADDED, powerGroup);
             }
@@ -992,6 +1018,8 @@
         mInattentiveSleepWarningOverlayController =
                 mInjector.createInattentiveSleepWarningController();
 
+        mPowerGroupWakefulnessChangeListener = new PowerGroupWakefulnessChangeListener();
+
         // Save brightness values:
         // Get float values from config.
         // Store float if valid
@@ -1144,10 +1172,10 @@
 
                 updatePowerStateLocked();
                 if (sQuiescent) {
-                    sleepDisplayGroupNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
+                    sleepPowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
                             mClock.uptimeMillis(),
                             PowerManager.GO_TO_SLEEP_REASON_QUIESCENT,
-                            PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
+                            Process.SYSTEM_UID);
                 }
 
                 mContext.getSystemService(DeviceStateManager.class).registerCallback(
@@ -1164,7 +1192,9 @@
             mPolicy = getLocalService(WindowManagerPolicy.class);
             mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
             mAttentionDetector.systemReady(mContext);
-            mPowerGroups.append(Display.DEFAULT_DISPLAY_GROUP, new PowerGroup());
+            mPowerGroups.append(Display.DEFAULT_DISPLAY_GROUP,
+                    new PowerGroup(WAKEFULNESS_AWAKE, mPowerGroupWakefulnessChangeListener,
+                            mClock.uptimeMillis()));
             DisplayGroupPowerChangeListener displayGroupPowerChangeListener =
                     new DisplayGroupPowerChangeListener();
             mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
@@ -1503,7 +1533,7 @@
                 opUid = wakeLock.mOwnerUid;
             }
             for (int idx = 0; idx < mPowerGroups.size(); idx++) {
-                wakeDisplayGroupNoUpdateLocked(mPowerGroups.valueAt(idx), mClock.uptimeMillis(),
+                wakePowerGroupLocked(mPowerGroups.valueAt(idx), mClock.uptimeMillis(),
                         PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag, opUid, opPackageName,
                         opUid);
             }
@@ -1777,13 +1807,15 @@
     @GuardedBy("mLock")
     private boolean userActivityNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
             int event, int flags, int uid) {
+        final int groupId = powerGroup.getGroupId();
         if (DEBUG_SPEW) {
-            Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + powerGroup.getGroupId()
+            Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId
                     + ", eventTime=" + eventTime + ", event=" + event
                     + ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid);
         }
 
-        if (eventTime < mLastSleepTime || eventTime < mLastWakeTime || !mSystemReady) {
+        if (eventTime < powerGroup.getLastSleepTimeLocked()
+                || eventTime < powerGroup.getLastWakeTimeLocked() || !mSystemReady) {
             return false;
         }
 
@@ -1801,7 +1833,6 @@
                 mUserInactiveOverrideFromWindowManager = false;
                 mOverriddenTimeout = -1;
             }
-
             final int wakefulness = powerGroup.getWakefulnessLocked();
             if (wakefulness == WAKEFULNESS_ASLEEP
                     || wakefulness == WAKEFULNESS_DOZING
@@ -1846,42 +1877,33 @@
         }
     }
 
-    private void wakeDisplayGroup(int groupId, long eventTime, @WakeReason int reason,
-            String details, int uid, String opPackageName, int opUid) {
-        synchronized (mLock) {
-            if (wakeDisplayGroupNoUpdateLocked(mPowerGroups.get(groupId), eventTime, reason,
-                    details, uid, opPackageName, opUid)) {
-                updatePowerStateLocked();
-            }
-        }
-    }
-
     @GuardedBy("mLock")
-    private boolean wakeDisplayGroupNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
+    private void wakePowerGroupLocked(final PowerGroup powerGroup, long eventTime,
             @WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
         final int groupId = powerGroup.getGroupId();
         if (DEBUG_SPEW) {
-            Slog.d(TAG, "wakeDisplayGroupNoUpdateLocked: eventTime=" + eventTime
+            Slog.d(TAG, "wakePowerGroupLocked: eventTime=" + eventTime
                     + ", groupId=" + groupId + ", uid=" + uid);
         }
 
-        if (eventTime < mLastSleepTime || mForceSuspendActive || !mSystemReady) {
-            return false;
+        if (eventTime < powerGroup.getLastSleepTimeLocked() || mForceSuspendActive
+                || !mSystemReady) {
+            return;
         }
 
-        final int currentState = powerGroup.getWakefulnessLocked();
-        if (currentState == WAKEFULNESS_AWAKE) {
+        final int currentWakefulness = powerGroup.getWakefulnessLocked();
+        if (currentWakefulness == WAKEFULNESS_AWAKE) {
             if (!mBootCompleted && sQuiescent) {
                 mDirty |= DIRTY_QUIESCENT;
-                return true;
+                updatePowerStateLocked();
             }
-            return false;
+            return;
         }
 
         Trace.traceBegin(Trace.TRACE_TAG_POWER, "powerOnDisplay");
         try {
-            Slog.i(TAG, "Powering on display group from"
-                    + PowerManagerInternal.wakefulnessToString(currentState)
+            Slog.i(TAG, "Waking up power group from "
+                    + PowerManagerInternal.wakefulnessToString(currentWakefulness)
                     + " (groupId=" + groupId
                     + ", uid=" + uid
                     + ", reason=" + PowerManager.wakeReasonToString(reason)
@@ -1892,183 +1914,96 @@
             LatencyTracker.getInstance(mContext)
                     .onActionStart(ACTION_TURN_ON_SCREEN, String.valueOf(groupId));
 
-            setWakefulnessLocked(powerGroup, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid,
+            powerGroup.setWakefulnessLocked(WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid,
                     opPackageName, details);
-            powerGroup.setLastPowerOnTimeLocked(eventTime);
-            powerGroup.setIsPoweringOnLocked(true);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
-
-        return true;
-    }
-
-    private void sleepDisplayGroup(int groupId, long eventTime, int reason, int flags,
-            int uid) {
-        synchronized (mLock) {
-            if (sleepDisplayGroupNoUpdateLocked(mPowerGroups.get(groupId), eventTime, reason, flags,
-                    uid)) {
-                updatePowerStateLocked();
-            }
-        }
     }
 
     @GuardedBy("mLock")
-    private boolean sleepDisplayGroupNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
-            int reason, int flags, int uid) {
+    private boolean dreamPowerGroupLocked(PowerGroup powerGroup, long eventTime, int uid) {
+        if (DEBUG_SPEW) {
+            Slog.d(TAG, "dreamPowerGroup: groupId=" + powerGroup.getGroupId() + ", eventTime="
+                    + eventTime + ", uid=" + uid);
+        }
+        if (!mBootCompleted || !mSystemReady) {
+            return false;
+        }
+        return powerGroup.dreamLocked(eventTime, uid);
+    }
+
+    @GuardedBy("mLock")
+    private boolean dozePowerGroupLocked(final PowerGroup powerGroup, long eventTime,
+            int reason, int uid) {
         if (DEBUG_SPEW) {
             Slog.d(TAG, "sleepDisplayGroupNoUpdateLocked: eventTime=" + eventTime
                     + ", groupId=" + powerGroup.getGroupId() + ", reason=" + reason
-                    + ", flags=" + flags + ", uid=" + uid);
+                    + ", uid=" + uid);
         }
 
-        if (eventTime < mLastWakeTime
-                || !PowerManagerInternal.isInteractive(getWakefulnessLocked())
-                || !mSystemReady
-                || !mBootCompleted) {
+        if (!mSystemReady || !mBootCompleted) {
             return false;
         }
 
-        final int wakefulness = powerGroup.getWakefulnessLocked();
-        if (!PowerManagerInternal.isInteractive(wakefulness)) {
-            return false;
-        }
-
-        Trace.traceBegin(Trace.TRACE_TAG_POWER, "powerOffDisplay");
-        try {
-            reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
-                    Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
-            Slog.i(TAG, "Powering off display group due to "
-                    + PowerManager.sleepReasonToString(reason)
-                    + " (groupId= " + powerGroup.getGroupId() + ", uid= " + uid + ")...");
-
-            powerGroup.setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
-            setWakefulnessLocked(powerGroup, WAKEFULNESS_DOZING, eventTime, uid, reason,
-                    /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null);
-            if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) {
-                reallySleepDisplayGroupNoUpdateLocked(powerGroup, eventTime, uid);
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_POWER);
-        }
-        return true;
-    }
-
-    private void dreamDisplayGroup(int groupId, long eventTime, int uid) {
-        synchronized (mLock) {
-            if (dreamDisplayGroupNoUpdateLocked(mPowerGroups.get(groupId), eventTime, uid)) {
-                updatePowerStateLocked();
-            }
-        }
+        return powerGroup.dozeLocked(eventTime, uid, reason);
     }
 
     @GuardedBy("mLock")
-    private boolean dreamDisplayGroupNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
+    private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime, int reason,
             int uid) {
         if (DEBUG_SPEW) {
-            Slog.d(TAG, "dreamDisplayGroupNoUpdateLocked: eventTime=" + eventTime
-                    + ", uid=" + uid);
+            Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime + ", uid=" + uid);
         }
-
-        if (eventTime < mLastWakeTime || getWakefulnessLocked() != WAKEFULNESS_AWAKE
-                || !mBootCompleted || !mSystemReady) {
+        if (!mBootCompleted || !mSystemReady) {
             return false;
         }
 
-        Trace.traceBegin(Trace.TRACE_TAG_POWER, "napDisplayGroup");
-        try {
-            Slog.i(TAG, "Napping display group (groupId=" + powerGroup.getGroupId() + ", uid=" + uid
-                    + ")...");
-
-            powerGroup.setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
-            setWakefulnessLocked(powerGroup, WAKEFULNESS_DREAMING, eventTime, uid,
-                    /* reason= */0, /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null);
-
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_POWER);
-        }
-        return true;
-    }
-
-    @GuardedBy("mLock")
-    private boolean reallySleepDisplayGroupNoUpdateLocked(final PowerGroup powerGroup,
-            long eventTime, int uid) {
-        if (DEBUG_SPEW) {
-            Slog.d(TAG, "reallySleepDisplayGroupNoUpdateLocked: eventTime=" + eventTime
-                    + ", uid=" + uid);
-        }
-
-        if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP
-                || !mBootCompleted || !mSystemReady
-                || powerGroup.getWakefulnessLocked()
-                == WAKEFULNESS_ASLEEP) {
-            return false;
-        }
-
-        Trace.traceBegin(Trace.TRACE_TAG_POWER, "reallySleepDisplayGroup");
-        try {
-            Slog.i(TAG,
-                    "Sleeping display group (groupId=" + powerGroup.getGroupId() + ", uid=" + uid
-                            + ")...");
-
-            setWakefulnessLocked(powerGroup, WAKEFULNESS_ASLEEP, eventTime, uid,
-                    PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,  /* opUid= */ 0,
-                    /* opPackageName= */ null, /* details= */ null);
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_POWER);
-        }
-        return true;
+        return powerGroup.sleepLocked(eventTime, uid, reason);
     }
 
     @VisibleForTesting
     @GuardedBy("mLock")
     void setWakefulnessLocked(int groupId, int wakefulness, long eventTime, int uid, int reason,
             int opUid, String opPackageName, String details) {
-        setWakefulnessLocked(mPowerGroups.get(groupId), wakefulness, eventTime, uid, reason, opUid,
+        mPowerGroups.get(groupId).setWakefulnessLocked(wakefulness, eventTime, uid, reason, opUid,
                 opPackageName, details);
     }
 
-    @GuardedBy("mLock")
-    private void setWakefulnessLocked(final PowerGroup powerGroup, int wakefulness, long eventTime,
-            int uid, int reason, int opUid, String opPackageName, String details) {
-        if (powerGroup.setWakefulnessLocked(wakefulness)) {
-            mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
-            setGlobalWakefulnessLocked(getGlobalWakefulnessLocked(),
-                    eventTime, reason, uid, opUid, opPackageName, details);
-            if (wakefulness == WAKEFULNESS_AWAKE) {
-                // Kick user activity to prevent newly awake group from timing out instantly.
-                userActivityNoUpdateLocked(powerGroup, eventTime,
-                        PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid);
-            }
-        }
-    }
-
     @SuppressWarnings("deprecation")
     @GuardedBy("mLock")
-    private void setGlobalWakefulnessLocked(int wakefulness, long eventTime, int reason, int uid,
+    private void updateGlobalWakefulnessLocked(long eventTime, int reason, int uid,
             int opUid, String opPackageName, String details) {
-        if (getWakefulnessLocked() == wakefulness) {
+        int newWakefulness = recalculateGlobalWakefulnessLocked();
+        int currentWakefulness = getGlobalWakefulnessLocked();
+        if (currentWakefulness == newWakefulness) {
             return;
         }
 
         // Phase 1: Handle pre-wakefulness change bookkeeping.
         final String traceMethodName;
-        switch (wakefulness) {
+        switch (newWakefulness) {
             case WAKEFULNESS_ASLEEP:
                 traceMethodName = "reallyGoToSleep";
                 Slog.i(TAG, "Sleeping (uid " + uid + ")...");
+                // TODO(b/215518989): Remove this once transactions are in place
+                if (currentWakefulness != WAKEFULNESS_DOZING) {
+                    // in case we are going to sleep without dozing before
+                    mLastGlobalSleepTime = eventTime;
+                    mLastGlobalSleepReason = reason;
+                }
                 break;
 
             case WAKEFULNESS_AWAKE:
                 traceMethodName = "wakeUp";
                 Slog.i(TAG, "Waking up from "
-                        + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked())
+                        + PowerManagerInternal.wakefulnessToString(currentWakefulness)
                         + " (uid=" + uid
                         + ", reason=" + PowerManager.wakeReasonToString(reason)
                         + ", details=" + details
                         + ")...");
-                mLastWakeTime = eventTime;
-                mLastWakeReason = reason;
+                mLastGlobalWakeTime = eventTime;
+                mLastGlobalWakeReason = reason;
                 break;
 
             case WAKEFULNESS_DREAMING:
@@ -2081,13 +2016,13 @@
                 Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
                         + " (uid " + uid + ")...");
 
-                mLastSleepTime = eventTime;
-                mLastSleepReason = reason;
+                mLastGlobalSleepTime = eventTime;
+                mLastGlobalSleepReason = reason;
                 mDozeStartInProgress = true;
                 break;
 
             default:
-                throw new IllegalArgumentException("Unexpected wakefulness: " + wakefulness);
+                throw new IllegalArgumentException("Unexpected wakefulness: " + newWakefulness);
         }
 
         Trace.traceBegin(Trace.TRACE_TAG_POWER, traceMethodName);
@@ -2095,20 +2030,20 @@
             // Phase 2: Handle wakefulness change and bookkeeping.
             // Under lock, invalidate before set ensures caches won't return stale values.
             mInjector.invalidateIsInteractiveCaches();
-            mWakefulnessRaw = wakefulness;
+            mWakefulnessRaw = newWakefulness;
             mWakefulnessChanging = true;
             mDirty |= DIRTY_WAKEFULNESS;
 
             // This is only valid while we are in wakefulness dozing. Set to false otherwise.
-            mDozeStartInProgress &= (getWakefulnessLocked() == WAKEFULNESS_DOZING);
+            mDozeStartInProgress &= (newWakefulness == WAKEFULNESS_DOZING);
 
             if (mNotifier != null) {
-                mNotifier.onWakefulnessChangeStarted(wakefulness, reason, eventTime);
+                mNotifier.onWakefulnessChangeStarted(newWakefulness, reason, eventTime);
             }
-            mAttentionDetector.onWakefulnessChangeStarted(wakefulness);
+            mAttentionDetector.onWakefulnessChangeStarted(newWakefulness);
 
             // Phase 3: Handle post-wakefulness change bookkeeping.
-            switch (wakefulness) {
+            switch (newWakefulness) {
                 case WAKEFULNESS_AWAKE:
                     mNotifier.onWakeUp(reason, details, uid, opPackageName, opUid);
                     if (sQuiescent) {
@@ -2116,7 +2051,13 @@
                     }
                     break;
 
+                case WAKEFULNESS_ASLEEP:
+                    // fallthrough
                 case WAKEFULNESS_DOZING:
+                    if (!isInteractive(currentWakefulness)) {
+                        // TODO(b/215518989): remove this once transactions are in place
+                        break;
+                    }
                     // Report the number of wake locks that will be cleared by going to sleep.
                     int numWakeLocksCleared = 0;
                     final int numWakeLocks = mWakeLocks.size();
@@ -2140,7 +2081,7 @@
 
     @VisibleForTesting
     @GuardedBy("mLock")
-    int getWakefulnessLocked() {
+    int getGlobalWakefulnessLocked() {
         return mWakefulnessRaw;
     }
 
@@ -2163,10 +2104,9 @@
      * </ol>
      */
     @GuardedBy("mLock")
-    int getGlobalWakefulnessLocked() {
-        final int size = mPowerGroups.size();
+    int recalculateGlobalWakefulnessLocked() {
         int deviceWakefulness = WAKEFULNESS_ASLEEP;
-        for (int i = 0; i < size; i++) {
+        for (int i = 0; i < mPowerGroups.size(); i++) {
             final int wakefulness = mPowerGroups.valueAt(i).getWakefulnessLocked();
             if (wakefulness == WAKEFULNESS_AWAKE) {
                 return WAKEFULNESS_AWAKE;
@@ -2187,10 +2127,10 @@
     void onPowerGroupEventLocked(int event, PowerGroup powerGroup) {
         final int groupId = powerGroup.getGroupId();
         if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_REMOVED) {
-            mPowerGroups.remove(groupId);
+            mPowerGroups.delete(groupId);
         }
-        final int oldWakefulness = getWakefulnessLocked();
-        final int newWakefulness = getGlobalWakefulnessLocked();
+        final int oldWakefulness = getGlobalWakefulnessLocked();
+        final int newWakefulness = recalculateGlobalWakefulnessLocked();
 
         if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_ADDED
                 && newWakefulness == WAKEFULNESS_AWAKE) {
@@ -2215,13 +2155,9 @@
                 default:
                     reason = 0;
             }
-
-            setGlobalWakefulnessLocked(
-                    getGlobalWakefulnessLocked(),
-                    mClock.uptimeMillis(), reason, Process.SYSTEM_UID, Process.SYSTEM_UID,
-                    mContext.getOpPackageName(), "groupId: " + groupId);
+            updateGlobalWakefulnessLocked(mClock.uptimeMillis(), reason, Process.SYSTEM_UID,
+                    Process.SYSTEM_UID, mContext.getOpPackageName(), "groupId: " + groupId);
         }
-
         mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
         updatePowerStateLocked();
     }
@@ -2243,15 +2179,15 @@
     @GuardedBy("mLock")
     private void finishWakefulnessChangeIfNeededLocked() {
         if (mWakefulnessChanging && areAllDisplaysReadyLocked()) {
-            if (getWakefulnessLocked() == WAKEFULNESS_DOZING
+            if (getGlobalWakefulnessLocked() == WAKEFULNESS_DOZING
                     && (mWakeLockSummary & WAKE_LOCK_DOZE) == 0) {
                 return; // wait until dream has enabled dozing
             } else {
                 // Doze wakelock acquired (doze started) or device is no longer dozing.
                 mDozeStartInProgress = false;
             }
-            if (getWakefulnessLocked() == WAKEFULNESS_DOZING
-                    || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
+            if (getGlobalWakefulnessLocked() == WAKEFULNESS_DOZING
+                    || getGlobalWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
                 logSleepTimeoutRecapturedLocked();
             }
             mWakefulnessChanging = false;
@@ -2282,7 +2218,7 @@
      */
     @GuardedBy("mLock")
     private void updatePowerStateLocked() {
-        if (!mSystemReady || mDirty == 0) {
+        if (!mSystemReady || mDirty == 0 || mUpdatePowerStateInProgress) {
             return;
         }
         if (!Thread.holdsLock(mLock)) {
@@ -2290,6 +2226,7 @@
         }
 
         Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState");
+        mUpdatePowerStateInProgress = true;
         try {
             // Phase 0: Basic state updates.
             updateIsPoweredLocked(mDirty);
@@ -2332,6 +2269,7 @@
             updateSuspendBlockerLocked();
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
+            mUpdatePowerStateInProgress = false;
         }
     }
 
@@ -2397,7 +2335,7 @@
                 final long now = mClock.uptimeMillis();
                 if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType,
                         dockedOnWirelessCharger)) {
-                    wakeDisplayGroupNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
+                    wakePowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
                             now, PowerManager.WAKE_REASON_PLUGGED_IN,
                             "android.server.power:PLUGGED:" + mIsPowered, Process.SYSTEM_UID,
                             mContext.getOpPackageName(), Process.SYSTEM_UID);
@@ -2446,7 +2384,7 @@
         }
 
         // If already dreaming and becoming powered, then don't wake.
-        if (mIsPowered && getWakefulnessLocked() == WAKEFULNESS_DREAMING) {
+        if (mIsPowered && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING) {
             return false;
         }
 
@@ -2456,7 +2394,7 @@
         }
 
         // On Always On Display, SystemUI shows the charging indicator
-        if (mAlwaysOnEnabled && getWakefulnessLocked() == WAKEFULNESS_DOZING) {
+        if (mAlwaysOnEnabled && getGlobalWakefulnessLocked() == WAKEFULNESS_DOZING) {
             return false;
         }
 
@@ -2540,24 +2478,23 @@
 
             for (int idx = 0; idx < mPowerGroups.size(); idx++) {
                 final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
-                final int wakeLockSummary = adjustWakeLockSummary(
-                        powerGroup.getWakefulnessLocked(),
+                final int wakeLockSummary = adjustWakeLockSummary(powerGroup.getWakefulnessLocked(),
                         invalidGroupWakeLockSummary | powerGroup.getWakeLockSummaryLocked());
                 powerGroup.setWakeLockSummaryLocked(wakeLockSummary);
             }
 
-            mWakeLockSummary = adjustWakeLockSummary(getWakefulnessLocked(),
+            mWakeLockSummary = adjustWakeLockSummary(getGlobalWakefulnessLocked(),
                     mWakeLockSummary);
 
             for (int i = 0; i < numProfiles; i++) {
                 final ProfilePowerState profile = mProfilePowerState.valueAt(i);
-                profile.mWakeLockSummary = adjustWakeLockSummary(getWakefulnessLocked(),
+                profile.mWakeLockSummary = adjustWakeLockSummary(getGlobalWakefulnessLocked(),
                         profile.mWakeLockSummary);
             }
 
             if (DEBUG_SPEW) {
                 Slog.d(TAG, "updateWakeLockSummaryLocked: mWakefulness="
-                        + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked())
+                        + PowerManagerInternal.wakefulnessToString(getGlobalWakefulnessLocked())
                         + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary));
             }
         }
@@ -2706,11 +2643,12 @@
             int groupUserActivitySummary = 0;
             long groupNextTimeout = 0;
             final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
-            if (powerGroup.getWakefulnessLocked() != WAKEFULNESS_ASLEEP) {
+            final int wakefulness = powerGroup.getWakefulnessLocked();
+            if (wakefulness != WAKEFULNESS_ASLEEP) {
                 final long lastUserActivityTime = powerGroup.getLastUserActivityTimeLocked();
                 final long lastUserActivityTimeNoChangeLights =
                         powerGroup.getLastUserActivityTimeNoChangeLightsLocked();
-                if (lastUserActivityTime >= mLastWakeTime) {
+                if (lastUserActivityTime >= powerGroup.getLastWakeTimeLocked()) {
                     groupNextTimeout = lastUserActivityTime + screenOffTimeout - screenDimDuration;
                     if (now < groupNextTimeout) {
                         groupUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
@@ -2721,8 +2659,8 @@
                         }
                     }
                 }
-                if (groupUserActivitySummary == 0
-                        && lastUserActivityTimeNoChangeLights >= mLastWakeTime) {
+                if (groupUserActivitySummary == 0 && lastUserActivityTimeNoChangeLights
+                        >= powerGroup.getLastWakeTimeLocked()) {
                     groupNextTimeout = lastUserActivityTimeNoChangeLights + screenOffTimeout;
                     if (now < groupNextTimeout) {
                         final DisplayPowerRequest displayPowerRequest =
@@ -2740,7 +2678,7 @@
                     if (sleepTimeout >= 0) {
                         final long anyUserActivity = Math.max(lastUserActivityTime,
                                 lastUserActivityTimeNoChangeLights);
-                        if (anyUserActivity >= mLastWakeTime) {
+                        if (anyUserActivity >= powerGroup.getLastWakeTimeLocked()) {
                             groupNextTimeout = anyUserActivity + sleepTimeout;
                             if (now < groupNextTimeout) {
                                 groupUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
@@ -2786,7 +2724,7 @@
 
             if (DEBUG_SPEW) {
                 Slog.d(TAG, "updateUserActivitySummaryLocked: groupId=" + powerGroup.getGroupId()
-                        + ", mWakefulness=" + wakefulnessToString(powerGroup.getWakefulnessLocked())
+                        + ", mWakefulness=" + wakefulnessToString(wakefulness)
                         + ", mUserActivitySummary=0x" + Integer.toHexString(
                         groupUserActivitySummary)
                         + ", nextTimeout=" + TimeUtils.formatUptime(groupNextTimeout));
@@ -2884,7 +2822,7 @@
             return false;
         }
 
-        if (getWakefulnessLocked() != WAKEFULNESS_AWAKE) {
+        if (getGlobalWakefulnessLocked() != WAKEFULNESS_AWAKE) {
             mInattentiveSleepWarningOverlayController.dismiss(false);
             return true;
         } else if (attentiveTimeout < 0 || isBeingKeptFromInattentiveSleepLocked()
@@ -3024,14 +2962,13 @@
                 if (DEBUG) {
                     Slog.i(TAG, "Going to sleep now due to long user inactivity");
                 }
-                changed = sleepDisplayGroupNoUpdateLocked(powerGroup, time,
-                        PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE,
-                        PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
+                changed = sleepPowerGroupLocked(powerGroup, time,
+                        PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, Process.SYSTEM_UID);
             } else if (shouldNapAtBedTimeLocked()) {
-                changed = dreamDisplayGroupNoUpdateLocked(powerGroup, time, Process.SYSTEM_UID);
+                changed = dreamPowerGroupLocked(powerGroup, time, Process.SYSTEM_UID);
             } else {
-                changed = sleepDisplayGroupNoUpdateLocked(powerGroup, time,
-                        PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID);
+                changed = dozePowerGroupLocked(powerGroup, time,
+                        PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, Process.SYSTEM_UID);
             }
         }
         return changed;
@@ -3228,25 +3165,27 @@
 
                 // Dream has ended or will be stopped.  Update the power state.
                 if (isItBedTimeYetLocked(powerGroup)) {
-                    final int flags = isAttentiveTimeoutExpired(powerGroup, now)
-                            ? PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE : 0;
-                    sleepDisplayGroupNoUpdateLocked(powerGroup, now,
-                            PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, flags, Process.SYSTEM_UID);
+                    if (isAttentiveTimeoutExpired(powerGroup, now)) {
+                        sleepPowerGroupLocked(powerGroup, now,
+                                PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, Process.SYSTEM_UID);
+                    } else {
+                        dozePowerGroupLocked(powerGroup, now,
+                                PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, Process.SYSTEM_UID);
+                    }
                 } else {
-                    wakeDisplayGroupNoUpdateLocked(powerGroup, now,
+                    wakePowerGroupLocked(powerGroup, now,
                             PowerManager.WAKE_REASON_UNKNOWN,
                             "android.server.power:DREAM_FINISHED", Process.SYSTEM_UID,
                             mContext.getOpPackageName(), Process.SYSTEM_UID);
                 }
-                updatePowerStateLocked();
             } else if (wakefulness == WAKEFULNESS_DOZING) {
                 if (isDreaming) {
                     return; // continue dozing
                 }
 
                 // Doze has ended or will be stopped.  Update the power state.
-                reallySleepDisplayGroupNoUpdateLocked(powerGroup, now, Process.SYSTEM_UID);
-                updatePowerStateLocked();
+                sleepPowerGroupLocked(powerGroup, now,  PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
+                        Process.SYSTEM_UID);
             }
         }
 
@@ -3263,7 +3202,7 @@
     private boolean canDreamLocked(final PowerGroup powerGroup) {
         final DisplayPowerRequest displayPowerRequest = powerGroup.getDisplayPowerRequestLocked();
         if (!mBootCompleted
-                || getWakefulnessLocked() != WAKEFULNESS_DREAMING
+                || getGlobalWakefulnessLocked() != WAKEFULNESS_DREAMING
                 || !mDreamsSupportedConfig
                 || !mDreamsEnabledSetting
                 || !displayPowerRequest.isBrightOrDim()
@@ -3294,7 +3233,7 @@
     @GuardedBy("mLock")
     private boolean canDozeLocked() {
         // TODO (b/175764708): Support per-display doze.
-        return getWakefulnessLocked() == WAKEFULNESS_DOZING;
+        return getGlobalWakefulnessLocked() == WAKEFULNESS_DOZING;
     }
 
     /**
@@ -3375,15 +3314,15 @@
 
                 final boolean ready = mDisplayManagerInternal.requestPowerState(groupId,
                         displayPowerRequest, mRequestWaitForNegativeProximity);
-                mNotifier.onScreenPolicyUpdate(groupId, displayPowerRequest.policy);
+                mNotifier.onScreenPolicyUpdate(powerGroup.getGroupId(), displayPowerRequest.policy);
+                int wakefulness = powerGroup.getWakefulnessLocked();
 
                 if (DEBUG_SPEW) {
                     Slog.d(TAG, "updateDisplayPowerStateLocked: displayReady=" + ready
                             + ", groupId=" + groupId
                             + ", policy=" + policyToString(displayPowerRequest.policy)
                             + ", mWakefulness="
-                            + PowerManagerInternal.wakefulnessToString(
-                            powerGroup.getWakefulnessLocked())
+                            + PowerManagerInternal.wakefulnessToString(wakefulness)
                             + ", mWakeLockSummary=0x" + Integer.toHexString(
                             powerGroup.getWakeLockSummaryLocked())
                             + ", mUserActivitySummary=0x" + Integer.toHexString(
@@ -3401,7 +3340,7 @@
                 final boolean displayReadyStateChanged = powerGroup.setReadyLocked(ready);
                 final boolean poweringOn = powerGroup.isPoweringOnLocked();
                 if (ready && displayReadyStateChanged && poweringOn
-                        && powerGroup.getWakefulnessLocked() == WAKEFULNESS_AWAKE) {
+                        && wakefulness == WAKEFULNESS_AWAKE) {
                     powerGroup.setIsPoweringOnLocked(false);
                     LatencyTracker.getInstance(mContext).onActionEnd(ACTION_TURN_ON_SCREEN);
                     Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId);
@@ -3424,7 +3363,7 @@
             if (mScreenBrightnessBoostInProgress) {
                 final long now = mClock.uptimeMillis();
                 mHandler.removeMessages(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT);
-                if (mLastScreenBrightnessBoostTime > mLastSleepTime) {
+                if (mLastScreenBrightnessBoostTime > mLastGlobalSleepTime) {
                     final long boostTimeout = mLastScreenBrightnessBoostTime +
                             SCREEN_BRIGHTNESS_BOOST_TIMEOUT;
                     if (boostTimeout > now) {
@@ -3656,7 +3595,7 @@
         // Here we wait for mWakefulnessChanging to become false since the wakefulness
         // transition to DOZING isn't considered "changed" until the doze wake lock is
         // acquired.
-        if (getWakefulnessLocked() == WAKEFULNESS_DOZING && mDozeStartInProgress) {
+        if (getGlobalWakefulnessLocked() == WAKEFULNESS_DOZING && mDozeStartInProgress) {
             return true;
         }
 
@@ -3721,7 +3660,7 @@
 
     private boolean isInteractiveInternal() {
         synchronized (mLock) {
-            return PowerManagerInternal.isInteractive(getWakefulnessLocked());
+            return PowerManagerInternal.isInteractive(getGlobalWakefulnessLocked());
         }
     }
 
@@ -4092,7 +4031,7 @@
 
     private void boostScreenBrightnessInternal(long eventTime, int uid) {
         synchronized (mLock) {
-            if (!mSystemReady || getWakefulnessLocked() == WAKEFULNESS_ASLEEP
+            if (!mSystemReady || getGlobalWakefulnessLocked() == WAKEFULNESS_ASLEEP
                     || eventTime < mLastScreenBrightnessBoostTime) {
                 return;
             }
@@ -4225,14 +4164,9 @@
             synchronized (mLock) {
                 mForceSuspendActive = true;
                 // Place the system in an non-interactive state
-                boolean updatePowerState = false;
                 for (int idx = 0; idx < mPowerGroups.size(); idx++) {
-                    updatePowerState |= sleepDisplayGroupNoUpdateLocked(mPowerGroups.valueAt(idx),
-                            mClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_FORCE_SUSPEND,
-                            PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, uid);
-                }
-                if (updatePowerState) {
-                    updatePowerStateLocked();
+                    sleepPowerGroupLocked(mPowerGroups.valueAt(idx), mClock.uptimeMillis(),
+                            PowerManager.GO_TO_SLEEP_REASON_FORCE_SUSPEND, uid);
                 }
 
                 // Disable all the partial wake locks as well
@@ -4331,7 +4265,7 @@
             mConstants.dump(pw);
             pw.println("  mDirty=0x" + Integer.toHexString(mDirty));
             pw.println("  mWakefulness="
-                    + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked()));
+                    + PowerManagerInternal.wakefulnessToString(getGlobalWakefulnessLocked()));
             pw.println("  mWakefulnessChanging=" + mWakefulnessChanging);
             pw.println("  mIsPowered=" + mIsPowered);
             pw.println("  mPlugType=" + mPlugType);
@@ -4382,9 +4316,10 @@
             pw.println("  mDeviceIdleMode=" + mDeviceIdleMode);
             pw.println("  mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist));
             pw.println("  mDeviceIdleTempWhitelist=" + Arrays.toString(mDeviceIdleTempWhitelist));
-            pw.println("  mLastWakeTime=" + TimeUtils.formatUptime(mLastWakeTime));
-            pw.println("  mLastSleepTime=" + TimeUtils.formatUptime(mLastSleepTime));
-            pw.println("  mLastSleepReason=" + PowerManager.sleepReasonToString(mLastSleepReason));
+            pw.println("  mLastWakeTime=" + TimeUtils.formatUptime(mLastGlobalWakeTime));
+            pw.println("  mLastSleepTime=" + TimeUtils.formatUptime(mLastGlobalSleepTime));
+            pw.println("  mLastSleepReason=" + PowerManager.sleepReasonToString(
+                    mLastGlobalSleepReason));
             pw.println("  mLastInteractivePowerHintTime="
                     + TimeUtils.formatUptime(mLastInteractivePowerHintTime));
             pw.println("  mLastScreenBrightnessBoostTime="
@@ -4565,7 +4500,7 @@
         synchronized (mLock) {
             mConstants.dumpProto(proto);
             proto.write(PowerManagerServiceDumpProto.DIRTY, mDirty);
-            proto.write(PowerManagerServiceDumpProto.WAKEFULNESS, getWakefulnessLocked());
+            proto.write(PowerManagerServiceDumpProto.WAKEFULNESS, getGlobalWakefulnessLocked());
             proto.write(PowerManagerServiceDumpProto.IS_WAKEFULNESS_CHANGING, mWakefulnessChanging);
             proto.write(PowerManagerServiceDumpProto.IS_POWERED, mIsPowered);
             proto.write(PowerManagerServiceDumpProto.PLUG_TYPE, mPlugType);
@@ -4664,8 +4599,8 @@
                 proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id);
             }
 
-            proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime);
-            proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime);
+            proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastGlobalWakeTime);
+            proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastGlobalSleepTime);
             proto.write(
                     PowerManagerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS,
                     mLastInteractivePowerHintTime);
@@ -5505,8 +5440,10 @@
             final int uid = Binder.getCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                wakeDisplayGroup(Display.DEFAULT_DISPLAY_GROUP, eventTime, reason, details, uid,
-                        opPackageName, uid);
+                synchronized (mLock) {
+                    wakePowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP), eventTime,
+                            reason, details, uid, opPackageName, uid);
+                }
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -5524,7 +5461,14 @@
             final int uid = Binder.getCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                sleepDisplayGroup(Display.DEFAULT_DISPLAY_GROUP, eventTime, reason, flags, uid);
+                synchronized (mLock) {
+                    PowerGroup defaultPowerGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP);
+                    if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) {
+                        sleepPowerGroupLocked(defaultPowerGroup, eventTime, reason, uid);
+                    } else {
+                        dozePowerGroupLocked(defaultPowerGroup, eventTime, reason, uid);
+                    }
+                }
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -5542,7 +5486,10 @@
             final int uid = Binder.getCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                dreamDisplayGroup(Display.DEFAULT_DISPLAY_GROUP, eventTime, uid);
+                synchronized (mLock) {
+                    dreamPowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
+                            eventTime, uid);
+                }
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -6181,13 +6128,15 @@
 
     private int getLastSleepReasonInternal() {
         synchronized (mLock) {
-            return mLastSleepReason;
+            return mLastGlobalSleepReason;
         }
     }
 
+    @VisibleForTesting
     private PowerManager.WakeData getLastWakeupInternal() {
         synchronized (mLock) {
-            return new WakeData(mLastWakeTime, mLastWakeReason, mLastWakeTime - mLastSleepTime);
+            return new WakeData(mLastGlobalWakeTime, mLastGlobalWakeReason,
+                    mLastGlobalWakeTime - mLastGlobalSleepTime);
         }
     }
 
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
index 030bbd2..5636718 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
@@ -70,7 +70,7 @@
          * cleanup in response to a single binder operation, it should not be used to propagate
          * errors further. Run on the ServiceWatcher thread.
          */
-        default void onError() {}
+        default void onError(Throwable t) {}
     }
 
     /**
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
index 631be38..94ea463 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -239,7 +240,7 @@
             Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
 
             if (mBinder == null) {
-                operation.onError();
+                operation.onError(new DeadObjectException());
                 return;
             }
 
@@ -249,7 +250,7 @@
                 // binders may propagate some specific non-RemoteExceptions from the other side
                 // through the binder as well - we cannot allow those to crash the system server
                 Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
-                operation.onError();
+                operation.onError(e);
             }
         }
 
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b7ca4de..277d802 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -18,21 +18,18 @@
 
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
-import static android.app.usage.NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN;
-import static android.app.usage.NetworkStatsManager.FLAG_POLL_FORCE;
-import static android.app.usage.NetworkStatsManager.FLAG_POLL_ON_OPEN;
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static android.net.NetworkIdentity.OEM_PAID;
-import static android.net.NetworkIdentity.OEM_PRIVATE;
 import static android.net.NetworkStats.METERED_YES;
 import static android.net.NetworkTemplate.MATCH_ETHERNET;
 import static android.net.NetworkTemplate.MATCH_MOBILE;
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.NetworkTemplate.OEM_MANAGED_ALL;
+import static android.net.NetworkTemplate.OEM_MANAGED_PAID;
+import static android.net.NetworkTemplate.OEM_MANAGED_PRIVATE;
 import static android.net.NetworkTemplate.getAllCollapsedRatTypes;
 import static android.os.Debug.getIonHeapsSizeKb;
 import static android.os.Process.LAST_SHARED_APPLICATION_GID;
@@ -82,6 +79,7 @@
 import android.app.RuntimeAppOpAccessMessage;
 import android.app.StatsManager;
 import android.app.StatsManager.PullAtomMetadata;
+import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.UidTraffic;
@@ -100,8 +98,6 @@
 import android.media.MediaDrm;
 import android.media.UnsupportedSchemeException;
 import android.net.ConnectivityManager;
-import android.net.INetworkStatsService;
-import android.net.INetworkStatsSession;
 import android.net.Network;
 import android.net.NetworkRequest;
 import android.net.NetworkStats;
@@ -197,6 +193,7 @@
 import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.net.module.util.NetworkStatsUtils;
 import com.android.role.RoleManagerLocal;
 import com.android.server.BinderCallsStatsService;
 import com.android.server.LocalManagerRegistry;
@@ -244,7 +241,7 @@
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import java.util.function.BiConsumer;
+import java.util.function.Function;
 
 /**
  * SystemService containing PullAtomCallbacks that are registered with statsd.
@@ -340,6 +337,7 @@
     private WifiManager mWifiManager;
     private TelephonyManager mTelephony;
     private SubscriptionManager mSubscriptionManager;
+    private NetworkStatsManager mNetworkStatsManager;
 
     @GuardedBy("mKernelWakelockLock")
     private KernelWakelockReader mKernelWakelockReader;
@@ -788,7 +786,7 @@
                 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
         mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
-
+        mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
         // Initialize DiskIO
         mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
 
@@ -981,32 +979,6 @@
         registerOemManagedBytesTransfer();
     }
 
-    /**
-     * Return the {@code INetworkStatsSession} object that holds the necessary properties needed
-     * for the subsequent queries to {@link com.android.server.net.NetworkStatsService}. Or
-     * null if the service or binder cannot be obtained. Calling this method will trigger poll
-     * in NetworkStatsService with once per 15 seconds rate-limit, unless {@code bypassRateLimit}
-     * is set to true. This is needed in {@link #getUidNetworkStatsSnapshotForTemplate}, where
-     * bypassing the limit is necessary for perfd to supply realtime stats to developers looking at
-     * the network usage of their app.
-     */
-    @Nullable
-    private INetworkStatsSession getNetworkStatsSession(boolean bypassRateLimit) {
-        final INetworkStatsService networkStatsService =
-                INetworkStatsService.Stub.asInterface(
-                        ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
-        if (networkStatsService == null) return null;
-
-        try {
-            return networkStatsService.openSessionForUsageStats(
-                    FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN | (bypassRateLimit ? FLAG_POLL_FORCE
-                            : FLAG_POLL_ON_OPEN), mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Cannot get NetworkStats session", e);
-            return null;
-        }
-    }
-
     private IThermalService getIThermalService() {
         synchronized (mThermalLock) {
             if (mThermalService == null) {
@@ -1137,8 +1109,8 @@
             case FrameworkStatsLog.WIFI_BYTES_TRANSFER: {
                 final NetworkStats stats = getUidNetworkStatsSnapshotForTransport(TRANSPORT_WIFI);
                 if (stats != null) {
-                    ret.add(new NetworkStatsExt(stats.groupedByUid(), new int[] {TRANSPORT_WIFI},
-                                    /*slicedByFgbg=*/false));
+                    ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
+                            new int[] {TRANSPORT_WIFI}, /*slicedByFgbg=*/false));
                 }
                 break;
             }
@@ -1154,7 +1126,7 @@
                 final NetworkStats stats =
                         getUidNetworkStatsSnapshotForTransport(TRANSPORT_CELLULAR);
                 if (stats != null) {
-                    ret.add(new NetworkStatsExt(stats.groupedByUid(),
+                    ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
                                     new int[] {TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
                 }
                 break;
@@ -1245,23 +1217,19 @@
 
     private void addNetworkStats(int atomTag, @NonNull List<StatsEvent> ret,
             @NonNull NetworkStatsExt statsExt) {
-        int size = statsExt.stats.size();
-        final NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
-        for (int j = 0; j < size; j++) {
-            statsExt.stats.getValues(j, entry);
+        for (NetworkStats.Entry entry : statsExt.stats) {
             StatsEvent statsEvent;
-
             if (statsExt.slicedByFgbg) {
                 // MobileBytesTransferByFgBg atom or WifiBytesTransferByFgBg atom.
                 statsEvent = FrameworkStatsLog.buildStatsEvent(
-                        atomTag, entry.uid,
-                        (entry.set > 0), entry.rxBytes, entry.rxPackets, entry.txBytes,
-                        entry.txPackets);
+                        atomTag, entry.getUid(),
+                        (entry.getSet() > 0), entry.getRxBytes(), entry.getRxPackets(),
+                        entry.getTxBytes(), entry.getTxPackets());
             } else {
                 // MobileBytesTransfer atom or WifiBytesTransfer atom.
                 statsEvent = FrameworkStatsLog.buildStatsEvent(
-                        atomTag, entry.uid, entry.rxBytes,
-                        entry.rxPackets, entry.txBytes, entry.txPackets);
+                        atomTag, entry.getUid(), entry.getRxBytes(),
+                        entry.getRxPackets(), entry.getTxBytes(), entry.getTxPackets());
             }
             ret.add(statsEvent);
         }
@@ -1269,13 +1237,12 @@
 
     private void addBytesTransferByTagAndMeteredAtoms(@NonNull NetworkStatsExt statsExt,
             @NonNull List<StatsEvent> pulledData) {
-        final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
-        for (int i = 0; i < statsExt.stats.size(); i++) {
-            statsExt.stats.getValues(i, entry);
+        for (NetworkStats.Entry entry : statsExt.stats) {
             pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                    FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED, entry.uid,
-                    entry.metered == NetworkStats.METERED_YES, entry.tag, entry.rxBytes,
-                    entry.rxPackets, entry.txBytes, entry.txPackets));
+                    FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED, entry.getUid(),
+                    entry.getMetered() == NetworkStats.METERED_YES, entry.getTag(),
+                    entry.getRxBytes(), entry.getRxPackets(), entry.getTxBytes(),
+                    entry.getTxPackets()));
         }
     }
 
@@ -1290,12 +1257,11 @@
         // Report NR connected in 5G non-standalone mode, or if the RAT type is NR to begin with.
         final boolean isNR = is5GNsa || statsExt.ratType == TelephonyManager.NETWORK_TYPE_NR;
 
-        final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
-        for (int i = 0; i < statsExt.stats.size(); i++) {
-            statsExt.stats.getValues(i, entry);
+        for (NetworkStats.Entry entry : statsExt.stats) {
             pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                    FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER, entry.set, entry.rxBytes,
-                    entry.rxPackets, entry.txBytes, entry.txPackets,
+                    FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER,
+                    entry.getSet(), entry.getRxBytes(), entry.getRxPackets(),
+                    entry.getTxBytes(), entry.getTxPackets(),
                     is5GNsa ? TelephonyManager.NETWORK_TYPE_LTE : statsExt.ratType,
                     // Fill information about subscription, these cannot be null since invalid data
                     // would be filtered when adding into subInfo list.
@@ -1309,15 +1275,13 @@
 
     private void addOemDataUsageBytesTransferAtoms(@NonNull NetworkStatsExt statsExt,
             @NonNull List<StatsEvent> pulledData) {
-        final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
         final int oemManaged = statsExt.oemManaged;
         for (final int transport : statsExt.transports) {
-            for (int i = 0; i < statsExt.stats.size(); i++) {
-                statsExt.stats.getValues(i, entry);
+            for (NetworkStats.Entry entry : statsExt.stats) {
                 pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                        FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER, entry.uid, (entry.set > 0),
-                        oemManaged, transport, entry.rxBytes, entry.rxPackets, entry.txBytes,
-                        entry.txPackets));
+                        FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER, entry.getUid(),
+                        (entry.getSet() > 0), oemManaged, transport, entry.getRxBytes(),
+                        entry.getRxPackets(), entry.getTxBytes(), entry.getTxPackets()));
             }
         }
     }
@@ -1328,7 +1292,8 @@
                 new Pair(MATCH_MOBILE, TRANSPORT_CELLULAR),
                 new Pair(MATCH_WIFI, TRANSPORT_WIFI)
         );
-        final int[] oemManagedTypes = new int[] {OEM_PAID | OEM_PRIVATE, OEM_PAID, OEM_PRIVATE};
+        final int[] oemManagedTypes = new int[] {OEM_MANAGED_PAID | OEM_MANAGED_PRIVATE,
+                OEM_MANAGED_PAID, OEM_MANAGED_PRIVATE};
 
         final List<NetworkStatsExt> ret = new ArrayList<>();
 
@@ -1385,22 +1350,32 @@
         final long currentTimeInMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
         final long bucketDuration = Settings.Global.getLong(mContext.getContentResolver(),
                 NETSTATS_UID_BUCKET_DURATION, NETSTATS_UID_DEFAULT_BUCKET_DURATION_MS);
-        try {
-            // TODO (b/156313635): This is short-term hack to allow perfd gets updated networkStats
-            //  history when query in every second in order to show realtime statistics. However,
-            //  this is not a good long-term solution since NetworkStatsService will make frequent
-            //  I/O and also block main thread when polling.
-            //  Consider making perfd queries NetworkStatsService directly.
-            final NetworkStats stats = getNetworkStatsSession(template.getMatchRule()
-                    == NetworkTemplate.MATCH_WIFI_WILDCARD).getSummaryForAllUid(template,
-                    currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
-                    currentTimeInMillis, includeTags);
-            return stats;
-        } catch (RemoteException | NullPointerException e) {
-            Slog.e(TAG, "Pulling netstats for template=" + template + " and includeTags="
-                    + includeTags  + " causes error", e);
+
+        // TODO (b/156313635): This is short-term hack to allow perfd gets updated networkStats
+        //  history when query in every second in order to show realtime statistics. However,
+        //  this is not a good long-term solution since NetworkStatsService will make frequent
+        //  I/O and also block main thread when polling.
+        //  Consider making perfd queries NetworkStatsService directly.
+        if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
+            mNetworkStatsManager.forceUpdate();
         }
-        return null;
+
+        final android.app.usage.NetworkStats queryNonTaggedStats =
+                mNetworkStatsManager.querySummary(
+                template, currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
+                currentTimeInMillis);
+
+        final NetworkStats nonTaggedStats =
+                NetworkStatsUtils.fromPublicNetworkStats(queryNonTaggedStats);
+        if (!includeTags) return nonTaggedStats;
+
+        final android.app.usage.NetworkStats queryTaggedStats =
+                mNetworkStatsManager.queryTaggedSummary(template,
+                currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
+                currentTimeInMillis);
+        final NetworkStats taggedStats =
+                NetworkStatsUtils.fromPublicNetworkStats(queryTaggedStats);
+        return nonTaggedStats.add(taggedStats);
     }
 
     @NonNull private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForSub(
@@ -1424,27 +1399,51 @@
         return ret;
     }
 
+    @NonNull private NetworkStats sliceNetworkStatsByUid(@NonNull NetworkStats stats) {
+        return sliceNetworkStats(stats,
+                (entry) -> {
+                        return new NetworkStats.Entry(null /* IFACE_ALL */, entry.getUid(),
+                                NetworkStats.SET_ALL, NetworkStats.TAG_NONE,
+                                NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+                                NetworkStats.DEFAULT_NETWORK_ALL,
+                                entry.getRxBytes(), entry.getRxPackets(),
+                                entry.getTxBytes(), entry.getTxPackets(), 0);
+                });
+    }
+
     @NonNull private NetworkStats sliceNetworkStatsByFgbg(@NonNull NetworkStats stats) {
         return sliceNetworkStats(stats,
-                (newEntry, oldEntry) -> {
-                    newEntry.set = oldEntry.set;
+                (entry) -> {
+                        return new NetworkStats.Entry(null /* IFACE_ALL */, NetworkStats.UID_ALL,
+                                entry.getSet(), NetworkStats.TAG_NONE,
+                                NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+                                NetworkStats.DEFAULT_NETWORK_ALL,
+                                entry.getRxBytes(), entry.getRxPackets(),
+                                entry.getTxBytes(), entry.getTxPackets(), 0);
                 });
     }
 
     @NonNull private NetworkStats sliceNetworkStatsByUidAndFgbg(@NonNull NetworkStats stats) {
         return sliceNetworkStats(stats,
-                (newEntry, oldEntry) -> {
-                    newEntry.uid = oldEntry.uid;
-                    newEntry.set = oldEntry.set;
+                (entry) -> {
+                        return new NetworkStats.Entry(null /* IFACE_ALL */, entry.getUid(),
+                                entry.getSet(), NetworkStats.TAG_NONE,
+                                NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+                                NetworkStats.DEFAULT_NETWORK_ALL,
+                                entry.getRxBytes(), entry.getRxPackets(),
+                                entry.getTxBytes(), entry.getTxPackets(), 0);
                 });
     }
 
     @NonNull private NetworkStats sliceNetworkStatsByUidTagAndMetered(@NonNull NetworkStats stats) {
         return sliceNetworkStats(stats,
-                (newEntry, oldEntry) -> {
-                    newEntry.uid = oldEntry.uid;
-                    newEntry.tag = oldEntry.tag;
-                    newEntry.metered = oldEntry.metered;
+                (entry) -> {
+                        return new NetworkStats.Entry(null /* IFACE_ALL */, entry.getUid(),
+                                NetworkStats.SET_ALL, entry.getTag(),
+                                entry.getMetered(), NetworkStats.ROAMING_ALL,
+                                NetworkStats.DEFAULT_NETWORK_ALL,
+                                entry.getRxBytes(), entry.getRxPackets(),
+                                entry.getTxBytes(), entry.getTxPackets(), 0);
                 });
     }
 
@@ -1454,46 +1453,31 @@
      *
      * This function iterates through each NetworkStats.Entry, sets its dimensions equal to the
      * default state (with the presumption that we don't want to slice on anything), and then
-     * applies the slicer lambda to allow users to control which dimensions to slice on. This is
-     * adapted from groupedByUid within NetworkStats.java
+     * applies the slicer lambda to allow users to control which dimensions to slice on.
      *
-     * @param slicer An operation taking into two parameters, new NetworkStats.Entry and old
-     *               NetworkStats.Entry, that should be used to copy state from the old to the new.
+     * @param slicer An operation taking one parameter, NetworkStats.Entry, that should be used to
+     *               get the state from entry to replace the default value.
      *               This is useful for slicing by particular dimensions. For example, if we wished
      *               to slice by uid and tag, we could write the following lambda:
-     *                  (new, old) -> {
-     *                          new.uid = old.uid;
-     *                          new.tag = old.tag;
+     *                  (entry) -> {
+     *                   return new NetworkStats.Entry(null, entry.getUid(),
+     *                           NetworkStats.SET_ALL, entry.getTag(),
+     *                           NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+     *                           NetworkStats.DEFAULT_NETWORK_ALL,
+     *                           entry.getRxBytes(), entry.getRxPackets(),
+     *                           entry.getTxBytes(), entry.getTxPackets(), 0);
      *                  }
-     *               If no slicer is provided, the data is not sliced by any dimensions.
      * @return new NeworkStats object appropriately sliced
      */
     @NonNull private NetworkStats sliceNetworkStats(@NonNull NetworkStats stats,
-            @Nullable BiConsumer<NetworkStats.Entry, NetworkStats.Entry> slicer) {
-        final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
-
-        final NetworkStats.Entry entry = new NetworkStats.Entry();
-        entry.uid = NetworkStats.UID_ALL;
-        entry.iface = NetworkStats.IFACE_ALL;
-        entry.set = NetworkStats.SET_ALL;
-        entry.tag = NetworkStats.TAG_NONE;
-        entry.metered = NetworkStats.METERED_ALL;
-        entry.roaming = NetworkStats.ROAMING_ALL;
-        entry.defaultNetwork = NetworkStats.DEFAULT_NETWORK_ALL;
-
-        final NetworkStats.Entry recycle = new NetworkStats.Entry(); // used for retrieving values
-        for (int i = 0; i < stats.size(); i++) {
-            stats.getValues(i, recycle);
+            @NonNull Function<NetworkStats.Entry, NetworkStats.Entry> slicer) {
+        NetworkStats ret = new NetworkStats(0, 1);
+        NetworkStats.Entry entry = new NetworkStats.Entry();
+        for (NetworkStats.Entry e : stats) {
             if (slicer != null) {
-                slicer.accept(entry, recycle);
+                entry = slicer.apply(e);
             }
-
-            entry.rxBytes = recycle.rxBytes;
-            entry.rxPackets = recycle.rxPackets;
-            entry.txBytes = recycle.txBytes;
-            entry.txPackets = recycle.txPackets;
-            // Operations purposefully omitted since we don't use them for statsd.
-            ret.combineValues(entry);
+            ret = ret.addEntry(entry);
         }
         return ret;
     }
diff --git a/services/core/java/com/android/server/statusbar/SessionMonitor.java b/services/core/java/com/android/server/statusbar/SessionMonitor.java
new file mode 100644
index 0000000..f4356bd
--- /dev/null
+++ b/services/core/java/com/android/server/statusbar/SessionMonitor.java
@@ -0,0 +1,166 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.statusbar;
+
+import static android.app.StatusBarManager.ALL_SESSIONS;
+import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+import static android.app.StatusBarManager.SessionFlags;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.ISessionListener;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Monitors session starts and ends. Session instanceIds can be used to correlate logs.
+ */
+public class SessionMonitor {
+    private static final String TAG = "SessionMonitor";
+
+    private final Context mContext;
+    private final Map<Integer, Set<ISessionListener>> mSessionToListeners =
+            new HashMap<>();
+
+    /** */
+    public SessionMonitor(Context context) {
+        mContext = context;
+        // initialize all sessions in the map
+        for (int session : ALL_SESSIONS) {
+            mSessionToListeners.put(session, new HashSet<>());
+        }
+    }
+
+    /**
+     * Registers a listener for all sessionTypes included in sessionFlags.
+     */
+    public void registerSessionListener(@SessionFlags int sessionFlags,
+            ISessionListener listener) {
+        requireListenerPermissions(sessionFlags);
+        synchronized (mSessionToListeners) {
+            for (int sessionType : ALL_SESSIONS) {
+                if ((sessionFlags & sessionType) != 0) {
+                    mSessionToListeners.get(sessionType).add(listener);
+                }
+            }
+        }
+    }
+
+    /**
+     * Unregisters a listener for all sessionTypes included in sessionFlags.
+     */
+    public void unregisterSessionListener(@SessionFlags int sessionFlags,
+            ISessionListener listener) {
+        synchronized (mSessionToListeners) {
+            for (int sessionType : ALL_SESSIONS) {
+                if ((sessionFlags & sessionType) != 0) {
+                    mSessionToListeners.get(sessionType).remove(listener);
+                }
+            }
+        }
+    }
+
+    /**
+     * Starts a session with the given sessionType, creating a new instanceId.
+     * Sends this message to all listeners registered for the given sessionType.
+     *
+     * Callers require special permission to start and end a session depending on the session.
+     */
+    public void onSessionStarted(@SessionFlags int sessionType, @NonNull InstanceId instanceId) {
+        requireSetterPermissions(sessionType);
+
+        if (!isValidSessionType(sessionType)) {
+            Log.e(TAG, "invalid onSessionStarted sessionType=" + sessionType);
+            return;
+        }
+
+        synchronized (mSessionToListeners) {
+            for (ISessionListener listener : mSessionToListeners.get(sessionType)) {
+                try {
+                    listener.onSessionStarted(sessionType, instanceId);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "unable to send session start to listener=" + listener, e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Ends a session with the given sessionType and instanceId. Sends this message
+     * to all listeners registered for the given sessionType.
+     *
+     * Callers require special permission to start and end a session depending on the session.
+     */
+    public void onSessionEnded(@SessionFlags int sessionType, @NonNull InstanceId instanceId) {
+        requireSetterPermissions(sessionType);
+
+        if (!isValidSessionType(sessionType)) {
+            Log.e(TAG, "invalid onSessionEnded sessionType=" + sessionType);
+            return;
+        }
+
+        synchronized (mSessionToListeners) {
+            for (ISessionListener listener : mSessionToListeners.get(sessionType)) {
+                try {
+                    listener.onSessionEnded(sessionType, instanceId);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "unable to send session end to listener=" + listener, e);
+                }
+            }
+        }
+    }
+
+    private boolean isValidSessionType(@SessionFlags int sessionType) {
+        return ALL_SESSIONS.contains(sessionType);
+    }
+
+    private void requireListenerPermissions(@SessionFlags int sessionFlags) {
+        if ((sessionFlags & SESSION_KEYGUARD) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_BIOMETRIC,
+                    "StatusBarManagerService.SessionMonitor");
+        }
+
+        if ((sessionFlags & SESSION_BIOMETRIC_PROMPT) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_BIOMETRIC,
+                    "StatusBarManagerService.SessionMonitor");
+        }
+    }
+
+    private void requireSetterPermissions(@SessionFlags int sessionFlags) {
+        if ((sessionFlags & SESSION_KEYGUARD) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_KEYGUARD,
+                    "StatusBarManagerService.SessionMonitor");
+        }
+
+        if ((sessionFlags & SESSION_BIOMETRIC_PROMPT) != 0) {
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+                    "StatusBarManagerService.SessionMonitor");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 0edd06a..e71ff78 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -21,6 +21,7 @@
 import static android.app.StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS;
 import static android.app.StatusBarManager.NAV_BAR_MODE_OVERRIDE_NONE;
 import static android.app.StatusBarManager.NavBarModeOverride;
+import static android.app.StatusBarManager.SessionFlags;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
 
@@ -84,8 +85,10 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.logging.InstanceId;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.statusbar.IAddTileResultCallback;
+import com.android.internal.statusbar.ISessionListener;
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
@@ -145,6 +148,7 @@
     private final ActivityManagerInternal mActivityManagerInternal;
     private final ActivityTaskManagerInternal mActivityTaskManager;
     private final PackageManagerInternal mPackageManagerInternal;
+    private final SessionMonitor mSessionMonitor;
     private int mCurrentUserId;
     private boolean mTracingEnabled;
 
@@ -260,6 +264,7 @@
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
 
         mTileRequestTracker = new TileRequestTracker(mContext);
+        mSessionMonitor = new SessionMonitor(mContext);
     }
 
     private IOverlayManager getOverlayManager() {
@@ -1870,6 +1875,28 @@
         }
     }
 
+    @Override
+    public void onSessionStarted(@SessionFlags int sessionType, InstanceId instance) {
+        mSessionMonitor.onSessionStarted(sessionType, instance);
+    }
+
+    @Override
+    public void onSessionEnded(@SessionFlags int sessionType, InstanceId instance) {
+        mSessionMonitor.onSessionEnded(sessionType, instance);
+    }
+
+    @Override
+    public void registerSessionListener(@SessionFlags int sessionFlags,
+            ISessionListener listener) {
+        mSessionMonitor.registerSessionListener(sessionFlags, listener);
+    }
+
+    @Override
+    public void unregisterSessionListener(@SessionFlags int sessionFlags,
+            ISessionListener listener) {
+        mSessionMonitor.unregisterSessionListener(sessionFlags, listener);
+    }
+
     public String[] getStatusBarIcons() {
         return mContext.getResources().getStringArray(R.array.config_statusBarIcons);
     }
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index b3649a7..53a9244 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -134,7 +134,12 @@
 
         for (ResolveInfo ri : services) {
             ServiceInfo si = ri.serviceInfo;
-            // TODO: add BIND_TV_INTERACTIVE_APP permission and check it here
+            if (!android.Manifest.permission.BIND_TV_INTERACTIVE_APP.equals(si.permission)) {
+                Slog.w(TAG, "Skipping TV interactiva app service " + si.name
+                        + ": it does not require the permission "
+                        + android.Manifest.permission.BIND_TV_INTERACTIVE_APP);
+                continue;
+            }
 
             ComponentName component = new ComponentName(si.packageName, si.name);
             try {
@@ -788,10 +793,12 @@
                                 componentName, tiasId, resolvedUserId);
                         serviceState.addPendingAppLinkCommand(command);
                         userState.mServiceStateMap.put(componentName, serviceState);
+                        updateServiceConnectionLocked(componentName, resolvedUserId);
                     } else if (serviceState.mService != null) {
                         serviceState.mService.sendAppLinkCommand(command);
                     } else {
                         serviceState.addPendingAppLinkCommand(command);
+                        updateServiceConnectionLocked(componentName, resolvedUserId);
                     }
                 }
             } catch (RemoteException e) {
@@ -1673,7 +1680,8 @@
 
         boolean shouldBind = (!serviceState.mSessionTokens.isEmpty())
                 || (serviceState.mPendingPrepare)
-                || (!serviceState.mPendingAppLinkInfo.isEmpty());
+                || (!serviceState.mPendingAppLinkInfo.isEmpty())
+                || (!serviceState.mPendingAppLinkCommand.isEmpty());
 
         if (serviceState.mService == null && shouldBind) {
             // This means that the service is not yet connected but its state indicates that we
@@ -2092,7 +2100,7 @@
 
         @Override
         public void onCommandRequest(
-                @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
+                @TvInteractiveAppService.PlaybackCommandType String cmdType,
                 Bundle parameters) {
             synchronized (mLock) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index eafd9d7..6c5d952 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -179,7 +179,7 @@
         try {
             ActivityManager.getService().registerUidObserver(mUidObserver,
                     ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
-                    ActivityManager.PROCESS_STATE_UNKNOWN, mContext.getOpPackageName());
+                    ActivityManager.PROCESS_STATE_UNKNOWN, null);
         } catch (RemoteException e) {
             // ignored; both services live in system_server
         }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index a95b6c9..b2e34da 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -16,6 +16,9 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
+import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -65,6 +68,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
@@ -1711,7 +1715,8 @@
             long duration = Long.parseLong(getNextArgRequired());
             int amplitude = hasAmplitude ? Integer.parseInt(getNextArgRequired())
                     : VibrationEffect.DEFAULT_AMPLITUDE;
-            composition.addEffect(VibrationEffect.createOneShot(duration, amplitude), delay);
+            composition.addOffDuration(Duration.ofMillis(delay));
+            composition.addEffect(VibrationEffect.createOneShot(duration, amplitude));
         }
 
         private void addWaveformToComposition(VibrationEffect.Composition composition) {
@@ -1762,23 +1767,44 @@
                 }
             }
 
+            // Add delay before the waveform.
+            composition.addOffDuration(Duration.ofMillis(delay));
+
             VibrationEffect.WaveformBuilder waveform = VibrationEffect.startWaveform();
             for (int i = 0; i < durations.size(); i++) {
-                if (isContinuous) {
-                    if (hasFrequencies) {
-                        waveform.addRamp(amplitudes.get(i), frequencies.get(i), durations.get(i));
-                    } else {
-                        waveform.addRamp(amplitudes.get(i), durations.get(i));
-                    }
+                Duration transitionDuration = isContinuous
+                        ? Duration.ofMillis(durations.get(i))
+                        : Duration.ZERO;
+
+                if (hasFrequencies) {
+                    waveform.addTransition(transitionDuration, targetAmplitude(amplitudes.get(i)),
+                            targetFrequency(frequencies.get(i)));
                 } else {
+                    waveform.addTransition(transitionDuration, targetAmplitude(amplitudes.get(i)));
+                }
+                if (!isContinuous) {
+                    waveform.addSustain(Duration.ofMillis(durations.get(i)));
+                }
+
+                if ((i > 0) && (i == repeat)) {
+                    // Add segment that is not repeated to the composition and reset builder.
+                    composition.addEffect(waveform.build());
+
                     if (hasFrequencies) {
-                        waveform.addStep(amplitudes.get(i), frequencies.get(i), durations.get(i));
+                        waveform = VibrationEffect.startWaveform(targetAmplitude(amplitudes.get(i)),
+                                targetFrequency(frequencies.get(i)));
                     } else {
-                        waveform.addStep(amplitudes.get(i), durations.get(i));
+                        waveform = VibrationEffect.startWaveform(
+                                targetAmplitude(amplitudes.get(i)));
                     }
                 }
             }
-            composition.addEffect(waveform.build(repeat), delay);
+            if (repeat < 0) {
+                composition.addEffect(waveform.build());
+            } else {
+                // The waveform was already split at the repeat index, just repeat what remains.
+                composition.repeatEffectIndefinitely(waveform.build());
+            }
         }
 
         private void addPrebakedToComposition(VibrationEffect.Composition composition) {
@@ -1796,7 +1822,8 @@
             }
 
             int effectId = Integer.parseInt(getNextArgRequired());
-            composition.addEffect(VibrationEffect.get(effectId, shouldFallback), delay);
+            composition.addOffDuration(Duration.ofMillis(delay));
+            composition.addEffect(VibrationEffect.get(effectId, shouldFallback));
         }
 
         private void addPrimitivesToComposition(VibrationEffect.Composition composition) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 76434c7..c0eee61 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -95,6 +95,7 @@
 import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
 import static android.os.Build.VERSION_CODES.HONEYCOMB;
 import static android.os.Build.VERSION_CODES.O;
+import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.COLOR_MODE_DEFAULT;
@@ -394,9 +395,9 @@
     // How many activities have to be scheduled to stop to force a stop pass.
     private static final int MAX_STOPPING_TO_FORCE = 3;
 
-    private static final int STARTING_WINDOW_TYPE_NONE = 0;
-    private static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1;
-    private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2;
+    static final int STARTING_WINDOW_TYPE_NONE = 0;
+    static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1;
+    static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2;
 
     static final int INVALID_PID = -1;
 
@@ -635,7 +636,7 @@
     private boolean mOccludesParent;
 
     // The input dispatching timeout for this application token in milliseconds.
-    long mInputDispatchingTimeoutMillis;
+    long mInputDispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 
     private boolean mShowWhenLocked;
     private boolean mInheritShownWhenLocked;
@@ -1443,7 +1444,6 @@
         if (oldParent == null && newParent != null) {
             // First time we are adding the activity to the system.
             mVoiceInteraction = newTask.voiceSession != null;
-            mInputDispatchingTimeoutMillis = getInputDispatchingTimeoutMillisLocked(this);
 
             // TODO(b/36505427): Maybe this call should be moved inside
             // updateOverrideConfiguration()
@@ -1995,6 +1995,7 @@
             task.setRootProcess(proc);
         }
         proc.addActivityIfNeeded(this);
+        mInputDispatchingTimeoutMillis = getInputDispatchingTimeoutMillisLocked(this);
 
         // Update the associated task fragment after setting the process, since it's required for
         // filtering to only report activities that belong to the same process.
@@ -2148,7 +2149,8 @@
 
         final int typeParameter = StartingSurfaceController
                 .makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning,
-                        allowTaskSnapshot, activityCreated, useEmpty, useLegacy, activityAllDrawn);
+                        allowTaskSnapshot, activityCreated, useEmpty, useLegacy, activityAllDrawn,
+                        type, packageName, mUserId);
 
         if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
             if (isActivityTypeHome()) {
@@ -3578,6 +3580,7 @@
             app.removeActivity(this, false /* keepAssociation */);
         }
         app = null;
+        mInputDispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
     }
 
     void makeFinishingLocked() {
@@ -6565,7 +6568,8 @@
         return null;
     }
 
-    private boolean shouldUseEmptySplashScreen(ActivityRecord sourceRecord, boolean startActivity) {
+    private boolean shouldUseEmptySplashScreen(ActivityRecord sourceRecord, boolean startActivity,
+            ActivityOptions options) {
         if (sourceRecord == null && !startActivity) {
             // Use empty style if this activity is not top activity. This could happen when adding
             // a splash screen window to the warm start activity which is re-create because top is
@@ -6575,8 +6579,8 @@
                 return true;
             }
         }
-        if (mPendingOptions != null) {
-            final int optionsStyle = mPendingOptions.getSplashScreenStyle();
+        if (options != null) {
+            final int optionsStyle = options.getSplashScreenStyle();
             if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_EMPTY) {
                 return true;
             } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON) {
@@ -6605,11 +6609,11 @@
                 || mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME);
     }
 
-    private int getSplashscreenTheme() {
+    private int getSplashscreenTheme(ActivityOptions options) {
         // Find the splash screen theme. User can override the persisted theme by
         // ActivityOptions.
-        String splashScreenThemeResName = mPendingOptions != null
-                ? mPendingOptions.getSplashScreenThemeResName() : null;
+        String splashScreenThemeResName = options != null
+                ? options.getSplashScreenThemeResName() : null;
         if (splashScreenThemeResName == null || splashScreenThemeResName.isEmpty()) {
             try {
                 splashScreenThemeResName = mAtmService.getPackageManager()
@@ -6636,7 +6640,7 @@
     void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
             boolean startActivity, ActivityRecord sourceRecord) {
         showStartingWindow(prev, newTask, taskSwitch, isProcessRunning(), startActivity,
-                sourceRecord);
+                sourceRecord, null /* candidateOptions */);
     }
 
     /**
@@ -6644,22 +6648,27 @@
      * @param processRunning Whether the client process is running.
      * @param startActivity Whether this activity is just created from starter.
      * @param sourceRecord The source activity which start this activity.
+     * @param candidateOptions The options for the style of starting window.
      */
     void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
-            boolean processRunning, boolean startActivity, ActivityRecord sourceRecord) {
+            boolean processRunning, boolean startActivity, ActivityRecord sourceRecord,
+            ActivityOptions candidateOptions) {
         if (mTaskOverlay) {
             // We don't show starting window for overlay activities.
             return;
         }
-        if (mPendingOptions != null
-                && mPendingOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+        final ActivityOptions startOptions = candidateOptions != null
+                ? candidateOptions : mPendingOptions;
+        if (startOptions != null
+                && startOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
             // Don't show starting window when using shared element transition.
             return;
         }
 
-        mSplashScreenStyleEmpty = shouldUseEmptySplashScreen(sourceRecord, startActivity);
+        mSplashScreenStyleEmpty = shouldUseEmptySplashScreen(
+                sourceRecord, startActivity, startOptions);
 
-        final int splashScreenTheme = startActivity ? getSplashscreenTheme() : 0;
+        final int splashScreenTheme = startActivity ? getSplashscreenTheme(startOptions) : 0;
         final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme,
                 splashScreenTheme);
 
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 87fb290..2a26050 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -484,7 +484,8 @@
                         }
                     }
                 } finally {
-                    mService.mWindowManager.mStartingSurfaceController.endDeferAddStartingWindow();
+                    mService.mWindowManager.mStartingSurfaceController.endDeferAddStartingWindow(
+                            options != null ? options.getOriginalOptions() : null);
                     mService.continueWindowLayout();
                 }
             }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e119a9a..5164bf0 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2924,7 +2924,7 @@
         final boolean onTop =
                 (aOptions == null || !aOptions.getAvoidMoveToFront()) && !mLaunchTaskBehind;
         return mRootWindowContainer.getLaunchRootTask(r, aOptions, task, mSourceRootTask, onTop,
-                mLaunchParams, launchFlags, mRequest.realCallingPid, mRequest.realCallingUid);
+                mLaunchParams, launchFlags);
     }
 
     private boolean isLaunchModeOneOf(int mode1, int mode2) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ed9dcef..fc252ef 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3959,6 +3959,11 @@
         getActivityStartController().dump(pw, "", dumpPackage);
     }
 
+    /** Dumps installed packages having app-specific config. */
+    void dumpInstalledPackagesConfig(PrintWriter pw) {
+        mPackageConfigPersister.dump(pw, getCurrentUserId());
+    }
+
     /**
      * There are three things that cmd can be:
      * - a flattened component name that matches an existing activity
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index dd394ca..5d879ce 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2224,25 +2224,9 @@
             return;
         }
 
-        if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) {
-            if (task.mTransitionController.isShellTransitionsEnabled()) return;
-            // Dismiss docked root task. If task appeared to be in docked root task but is not
-            // resizable - we need to move it to top of fullscreen root task, otherwise it will
-            // be covered.
-            final TaskDisplayArea taskDisplayArea = task.getDisplayArea();
-            if (taskDisplayArea.isSplitScreenModeActivated()) {
-                // Display a warning toast that we tried to put an app that doesn't support
-                // split-screen in split-screen.
-                mService.getTaskChangeNotificationController()
-                        .notifyActivityDismissingDockedRootTask();
-                taskDisplayArea.onSplitScreenModeDismissed(task);
-                taskDisplayArea.mDisplayContent.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
-                        true /* notifyClients */);
-            }
-            return;
+        if (!forceNonResizable) {
+            handleForcedResizableTaskIfNeeded(task, FORCED_RESIZEABLE_REASON_SPLIT_SCREEN);
         }
-
-        handleForcedResizableTaskIfNeeded(task, FORCED_RESIZEABLE_REASON_SPLIT_SCREEN);
     }
 
     /** Notifies that the top activity of the task is forced to be resizeable. */
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
similarity index 97%
rename from services/core/java/com/android/server/wm/FadeRotationAnimationController.java
rename to services/core/java/com/android/server/wm/AsyncRotationController.java
index 2cefd99..0990f1f 100644
--- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -35,7 +35,7 @@
  * both fixed rotation and normal rotation to hide some non-activity windows. The caller should show
  * the windows until they are drawn with the new rotation.
  */
-public class FadeRotationAnimationController extends FadeAnimationController {
+public class AsyncRotationController extends FadeAnimationController {
 
     /** The map of window token to its animation leash. */
     private final ArrayMap<WindowToken, SurfaceControl> mTargetWindowTokens = new ArrayMap<>();
@@ -70,7 +70,7 @@
     private final int mOriginalRotation;
     private final boolean mHasScreenRotationAnimation;
 
-    public FadeRotationAnimationController(DisplayContent displayContent) {
+    public AsyncRotationController(DisplayContent displayContent) {
         super(displayContent);
         mService = displayContent.mWmService;
         mOriginalRotation = displayContent.getWindowConfiguration().getRotation();
@@ -81,7 +81,7 @@
         mIsStartTransactionCommitted = !mIsChangeTransition;
         mTimeoutRunnable = displayContent.inTransition() ? () -> {
             synchronized (mService.mGlobalLock) {
-                displayContent.finishFadeRotationAnimationIfPossible();
+                displayContent.finishAsyncRotationIfPossible();
                 mService.mWindowPlacerLocked.performSurfacePlacement();
             }
         } : null;
@@ -277,7 +277,7 @@
                 mIsStartTransactionCommitted = true;
                 if (mPendingShowTokens == null) return;
                 for (int i = mPendingShowTokens.size() - 1; i >= 0; i--) {
-                    mDisplayContent.finishFadeRotationAnimation(mPendingShowTokens.get(i));
+                    mDisplayContent.finishAsyncRotation(mPendingShowTokens.get(i));
                 }
                 mPendingShowTokens = null;
             }
@@ -298,7 +298,7 @@
                 // Only fade in the drawn windows. If the remaining windows are drawn later,
                 // show(WindowToken) will be called to fade in them.
                 if (token.getChildAt(j).isDrawFinishedLw()) {
-                    mDisplayContent.finishFadeRotationAnimation(token);
+                    mDisplayContent.finishAsyncRotation(token);
                     break;
                 }
             }
@@ -320,7 +320,7 @@
             }
             return true;
         }
-        mDisplayContent.finishFadeRotationAnimation(w.mToken);
+        mDisplayContent.finishAsyncRotation(w.mToken);
         return false;
     }
 
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index bb4519c..5c1ddd9 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -64,6 +64,11 @@
         void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
     }
 
+    interface SyncEngineListener {
+        /** Called when there is no more active sync set. */
+        void onSyncEngineFree();
+    }
+
     /**
      * Holds state associated with a single synchronous set of operations.
      */
@@ -137,6 +142,9 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             mActiveSyncs.remove(mSyncId);
             mWm.mH.removeCallbacks(mOnTimeout);
+            if (mSyncEngineListener != null && mActiveSyncs.size() == 0) {
+                mSyncEngineListener.onSyncEngineFree();
+            }
         }
 
         private void setReady(boolean ready) {
@@ -175,22 +183,54 @@
     private final WindowManagerService mWm;
     private int mNextSyncId = 0;
     private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>();
+    private SyncEngineListener mSyncEngineListener;
 
     BLASTSyncEngine(WindowManagerService wms) {
         mWm = wms;
     }
 
+    /** Sets listener listening to whether the sync engine is free. */
+    void setSyncEngineListener(SyncEngineListener listener) {
+        mSyncEngineListener = listener;
+    }
+
+    /**
+     * Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet}
+     * before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}.
+     */
+    SyncGroup prepareSyncSet(TransactionReadyListener listener, String name) {
+        return new SyncGroup(listener, mNextSyncId++, name);
+    }
+
     int startSyncSet(TransactionReadyListener listener) {
         return startSyncSet(listener, WindowState.BLAST_TIMEOUT_DURATION, "");
     }
 
     int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name) {
-        final int id = mNextSyncId++;
-        final SyncGroup s = new SyncGroup(listener, id, name);
-        mActiveSyncs.put(id, s);
-        ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", id, listener);
+        final SyncGroup s = prepareSyncSet(listener, name);
+        startSyncSet(s, timeoutMs);
+        return s.mSyncId;
+    }
+
+    void startSyncSet(SyncGroup s) {
+        startSyncSet(s, WindowState.BLAST_TIMEOUT_DURATION);
+    }
+
+    void startSyncSet(SyncGroup s, long timeoutMs) {
+        if (mActiveSyncs.size() != 0) {
+            // We currently only support one sync at a time, so start a new SyncGroup when there is
+            // another may cause issue.
+            ProtoLog.w(WM_DEBUG_SYNC_ENGINE,
+                    "SyncGroup %d: Started when there is other active SyncGroup", s.mSyncId);
+        }
+        mActiveSyncs.put(s.mSyncId, s);
+        ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s",
+                s.mSyncId, s.mListener);
         scheduleTimeout(s, timeoutMs);
-        return id;
+    }
+
+    boolean hasActiveSync() {
+        return mActiveSyncs.size() != 0;
     }
 
     @VisibleForTesting
@@ -199,11 +239,11 @@
     }
 
     void addToSyncSet(int id, WindowContainer wc) {
-        mActiveSyncs.get(id).addToSync(wc);
+        getSyncGroup(id).addToSync(wc);
     }
 
     void setReady(int id, boolean ready) {
-        mActiveSyncs.get(id).setReady(ready);
+        getSyncGroup(id).setReady(ready);
     }
 
     void setReady(int id) {
@@ -211,14 +251,22 @@
     }
 
     boolean isReady(int id) {
-        return mActiveSyncs.get(id).mReady;
+        return getSyncGroup(id).mReady;
     }
 
     /**
      * Aborts the sync (ie. it doesn't wait for ready or anything to finish)
      */
     void abort(int id) {
-        mActiveSyncs.get(id).finishNow();
+        getSyncGroup(id).finishNow();
+    }
+
+    private SyncGroup getSyncGroup(int id) {
+        final SyncGroup syncGroup = mActiveSyncs.get(id);
+        if (syncGroup == null) {
+            throw new IllegalStateException("SyncGroup is not started yet id=" + id);
+        }
+        return syncGroup;
     }
 
     void onSurfacePlacement() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e449dde..a1c823e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -123,6 +123,7 @@
 import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS;
 import static com.android.server.wm.DisplayContentProto.IS_SLEEPING;
 import static com.android.server.wm.DisplayContentProto.KEEP_CLEAR_AREAS;
+import static com.android.server.wm.DisplayContentProto.MIN_SIZE_OF_RESIZEABLE_TASK_DP;
 import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
 import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
 import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
@@ -193,13 +194,13 @@
 import android.util.DisplayMetrics;
 import android.util.IntArray;
 import android.util.RotationUtils;
+import android.util.Size;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.DisplayCutout;
-import android.view.DisplayCutout.CutoutPathParserInfo;
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.IDisplayWindowInsetsController;
@@ -238,7 +239,6 @@
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.wm.utils.DisplayRotationUtil;
 import com.android.server.wm.utils.RotationCache;
 import com.android.server.wm.utils.WmDisplayCutout;
 
@@ -323,6 +323,11 @@
      */
     private Rect mLastMirroredDisplayAreaBounds = null;
 
+    /**
+     * The default per Display minimal size of tasks. Calculated at construction.
+     */
+    int mMinSizeOfResizeableTaskDp = -1;
+
     // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
     // on the IME target. We mainly have this container grouping so we can keep track of all the IME
     // window containers together and move them in-sync if/when needed. We use a subclass of
@@ -547,7 +552,7 @@
 
     /** The delay to avoid toggling the animation quickly. */
     private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250;
-    private FadeRotationAnimationController mFadeRotationAnimationController;
+    private AsyncRotationController mAsyncRotationController;
 
     final FixedRotationTransitionListener mFixedRotationTransitionListener =
             new FixedRotationTransitionListener();
@@ -578,8 +583,6 @@
     /** Caches the value whether told display manager that we have content. */
     private boolean mLastHasContent;
 
-    private static DisplayRotationUtil sRotationUtil = new DisplayRotationUtil();
-
     /**
      * The input method window for this display.
      */
@@ -1098,7 +1101,7 @@
 
         mInputMonitor = new InputMonitor(mWmService, this);
         mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
-
+        mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display);
 
         mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
@@ -1554,6 +1557,19 @@
         return config;
     }
 
+    private int getMinimalTaskSizeDp() {
+        final Context displayConfigurationContext =
+                mAtmService.mContext.createConfigurationContext(getConfiguration());
+        final float minimalSize =
+                displayConfigurationContext.getResources().getDimension(
+                                com.android.internal.R.dimen.default_minimal_size_resizable_task);
+        if (Double.compare(mDisplayMetrics.density, 0.0) == 0) {
+            throw new IllegalArgumentException("Display with ID=" + getDisplayId() + "has invalid "
+                + "DisplayMetrics.density= 0.0");
+        }
+        return (int) (minimalSize / mDisplayMetrics.density);
+    }
+
     private boolean updateOrientation(boolean forceUpdate) {
         final int orientation = getOrientation();
         // The last orientation source is valid only after getOrientation.
@@ -1712,8 +1728,8 @@
     }
 
     @VisibleForTesting
-    @Nullable FadeRotationAnimationController getFadeRotationAnimationController() {
-        return mFadeRotationAnimationController;
+    @Nullable AsyncRotationController getAsyncRotationController() {
+        return mAsyncRotationController;
     }
 
     void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
@@ -1723,13 +1739,13 @@
     void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
         if (mFixedRotationLaunchingApp == null && r != null) {
             mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
-            startFadeRotationAnimation(
+            startAsyncRotation(
                     // Delay the hide animation to avoid blinking by clicking navigation bar that
                     // may toggle fixed rotation in a short time.
                     r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */);
         } else if (mFixedRotationLaunchingApp != null && r == null) {
             mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
-            finishFadeRotationAnimationIfPossible();
+            finishAsyncRotationIfPossible();
         }
         mFixedRotationLaunchingApp = r;
     }
@@ -1842,9 +1858,9 @@
         return mDisplayRotation.getRotation() != getWindowConfiguration().getRotation();
     }
 
-    private void startFadeRotationAnimationIfNeeded() {
+    private void startAsyncRotationIfNeeded() {
         if (isRotationChanging()) {
-            startFadeRotationAnimation(false /* shouldDebounce */);
+            startAsyncRotation(false /* shouldDebounce */);
         }
     }
 
@@ -1853,12 +1869,12 @@
      *
      * @return {@code true} if the animation is executed right now.
      */
-    private boolean startFadeRotationAnimation(boolean shouldDebounce) {
+    private boolean startAsyncRotation(boolean shouldDebounce) {
         if (shouldDebounce) {
             mWmService.mH.postDelayed(() -> {
                 synchronized (mWmService.mGlobalLock) {
                     if (mFixedRotationLaunchingApp != null
-                            && startFadeRotationAnimation(false /* shouldDebounce */)) {
+                            && startAsyncRotation(false /* shouldDebounce */)) {
                         // Apply the transaction so the animation leash can take effect immediately.
                         getPendingTransaction().apply();
                     }
@@ -1866,28 +1882,28 @@
             }, FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS);
             return false;
         }
-        if (mFadeRotationAnimationController == null) {
-            mFadeRotationAnimationController = new FadeRotationAnimationController(this);
-            mFadeRotationAnimationController.hide();
+        if (mAsyncRotationController == null) {
+            mAsyncRotationController = new AsyncRotationController(this);
+            mAsyncRotationController.hide();
             return true;
         }
         return false;
     }
 
     /** Re-show the previously hidden windows if all seamless rotated windows are done. */
-    void finishFadeRotationAnimationIfPossible() {
-        final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+    void finishAsyncRotationIfPossible() {
+        final AsyncRotationController controller = mAsyncRotationController;
         if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
             controller.show();
-            mFadeRotationAnimationController = null;
+            mAsyncRotationController = null;
         }
     }
 
     /** Shows the given window which may be hidden for screen rotation. */
-    void finishFadeRotationAnimation(WindowToken windowToken) {
-        final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+    void finishAsyncRotation(WindowToken windowToken) {
+        final AsyncRotationController controller = mAsyncRotationController;
         if (controller != null && controller.show(windowToken)) {
-            mFadeRotationAnimationController = null;
+            mAsyncRotationController = null;
         }
     }
 
@@ -1897,7 +1913,7 @@
             // The window should look no different before and after rotation.
             return false;
         }
-        final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+        final AsyncRotationController controller = mAsyncRotationController;
         return controller == null || !controller.isHandledToken(w.mToken);
     }
 
@@ -2077,20 +2093,12 @@
             return WmDisplayCutout.computeSafeInsets(
                     cutout, displayWidth, displayHeight);
         }
-        final Insets waterfallInsets =
-                RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
+        final DisplayCutout rotatedCutout =
+                cutout.getRotated(displayWidth, displayHeight, ROTATION_0, rotation);
         final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
-        final Rect[] newBounds = sRotationUtil.getRotatedBounds(
-                cutout.getBoundingRectsAll(),
-                rotation, displayWidth, displayHeight);
-        final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
-        final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
-                info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
-                info.getCutoutSpec(), rotation, info.getScale());
-        return WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
+        return new WmDisplayCutout(rotatedCutout, new Size(
                 rotated ? displayHeight : displayWidth,
-                rotated ? displayWidth : displayHeight);
+                rotated ? displayWidth : displayHeight));
     }
 
     private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
@@ -2711,6 +2719,7 @@
             //                    layout.
             mInsetsStateController.onDisplayInfoUpdated(false /* notifyInsetsChanged */);
         }
+        mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         mInputMonitor.layoutInputConsumers(info.logicalWidth, info.logicalHeight);
         mDisplayPolicy.onDisplayInfoChanged(info);
     }
@@ -3034,13 +3043,6 @@
             mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
         }
         amendWindowTapExcludeRegion(mTouchExcludeRegion);
-        // TODO(multi-display): Support docked root tasks on secondary displays & task containers.
-        if (mDisplayId == DEFAULT_DISPLAY
-                && getDefaultTaskDisplayArea().isSplitScreenModeActivated()) {
-            mDividerControllerLocked.getTouchRegion(mTmpRect);
-            mTmpRegion.set(mTmpRect);
-            mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
-        }
         mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion);
     }
 
@@ -3206,7 +3208,7 @@
         // Hide the windows which are not significant in rotation animation. So that the windows
         // don't need to block the unfreeze time.
         if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
-            startFadeRotationAnimationIfNeeded();
+            startAsyncRotationIfNeeded();
         }
     }
 
@@ -3228,7 +3230,7 @@
             }
             if (!controller.isCollecting(this)) {
                 controller.collect(this);
-                startFadeRotationAnimationIfNeeded();
+                startAsyncRotationIfNeeded();
             }
             return;
         }
@@ -3240,7 +3242,7 @@
                 mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
                 controller.mTransitionMetricsReporter.associate(t,
                         startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
-                startFadeRotationAnimation(false /* shouldDebounce */);
+                startAsyncRotation(false /* shouldDebounce */);
             }
             t.setKnownConfigChanges(this, changes);
         }
@@ -3272,6 +3274,7 @@
             screenRotationAnimation.dumpDebug(proto, SCREEN_ROTATION_ANIMATION);
         }
         mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
+        proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp);
         if (mTransitionController.isShellTransitionsEnabled()) {
             mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION);
         } else {
@@ -3349,6 +3352,7 @@
         pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
         pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity);
         pw.print("dpi");
+        pw.print(" mMinSizeOfResizeableTaskDp="); pw.print(mMinSizeOfResizeableTaskDp);
         if (mInitialDisplayWidth != mBaseDisplayWidth
                 || mInitialDisplayHeight != mBaseDisplayHeight
                 || mInitialDisplayDensity != mBaseDisplayDensity) {
@@ -3429,12 +3433,6 @@
         if (rootPinnedTask != null) {
             pw.println(prefix + "rootPinnedTask=" + rootPinnedTask.getName());
         }
-        final Task rootSplitScreenPrimaryTask = getDefaultTaskDisplayArea()
-                .getRootSplitScreenPrimaryTask();
-        if (rootSplitScreenPrimaryTask != null) {
-            pw.println(
-                    prefix + "rootSplitScreenPrimaryTask=" + rootSplitScreenPrimaryTask.getName());
-        }
         // TODO: Support recents on non-default task containers
         final Task rootRecentsTask = getDefaultTaskDisplayArea().getRootTask(
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
@@ -3983,8 +3981,8 @@
             mInputMethodWindow.mToken.linkFixedRotationTransform(mImeLayeringTarget.mToken);
             // Hide the window until the rotation is done to avoid intermediate artifacts if the
             // parent surface of IME container is changed.
-            if (mFadeRotationAnimationController != null) {
-                mFadeRotationAnimationController.hideImmediately(mInputMethodWindow.mToken);
+            if (mAsyncRotationController != null) {
+                mAsyncRotationController.hideImmediately(mInputMethodWindow.mToken);
             }
         }
     }
@@ -4838,13 +4836,10 @@
         }
 
         private static boolean skipImeWindowsDuringTraversal(DisplayContent dc) {
-            // We skip IME windows so they're processed just above their target, except
-            // in split-screen mode where we process the IME containers above the docked divider.
+            // We skip IME windows so they're processed just above their target.
             // Note that this method check should align with {@link
             // WindowState#applyImeWindowsIfNeeded} in case of any state mismatch.
             return dc.mImeLayeringTarget != null
-                    && (!dc.getDefaultTaskDisplayArea().isSplitScreenModeActivated()
-                             || dc.mImeLayeringTarget.getTask() == null)
                     // Make sure that the IME window won't be skipped to report that it has
                     // completed the orientation change.
                     && !dc.mWmService.mDisplayFrozen;
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8c8b33f..7387ea3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -638,7 +638,7 @@
         }, true /* traverseTopToBottom */);
         mSeamlessRotationCount = 0;
         mRotatingSeamlessly = false;
-        mDisplayContent.finishFadeRotationAnimationIfPossible();
+        mDisplayContent.finishAsyncRotationIfPossible();
     }
 
     private void prepareSeamlessRotation() {
@@ -729,7 +729,7 @@
                     "Performing post-rotate rotation after seamless rotation");
             // Finish seamless rotation.
             mRotatingSeamlessly = false;
-            mDisplayContent.finishFadeRotationAnimationIfPossible();
+            mDisplayContent.finishAsyncRotationIfPossible();
 
             updateRotationAndSendNewConfigIfChanged();
         }
@@ -1103,6 +1103,21 @@
         return oldRotation != rotation;
     }
 
+
+    /**
+     * Resets whether the screen can be rotated via the accelerometer in all 4 rotations as the
+     * default behavior.
+     *
+     * To be called if there is potential that the value changed. For example if the active display
+     * changed.
+     *
+     * At the moment it is called from
+     * {@link DisplayWindowSettings#applyRotationSettingsToDisplayLocked}.
+     */
+    void resetAllowAllRotations() {
+        mAllowAllRotations = ALLOW_ALL_ROTATIONS_UNDEFINED;
+    }
+
     /**
      * Given an orientation constant, returns the appropriate surface rotation, taking into account
      * sensors, docking mode, rotation lock, and other factors.
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 483c799..70c769d 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -297,6 +297,8 @@
         final boolean ignoreOrientationRequest = settings.mIgnoreOrientationRequest != null
                 ? settings.mIgnoreOrientationRequest : false;
         dc.setIgnoreOrientationRequest(ignoreOrientationRequest);
+
+        dc.getDisplayRotation().resetAllowAllRotations();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index a8a9231..21eea94 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -298,9 +298,9 @@
 
     private Point getWindowFrameSurfacePosition() {
         if (mControl != null) {
-            final FadeRotationAnimationController fadeController =
-                    mWin.mDisplayContent.getFadeRotationAnimationController();
-            if (fadeController != null && fadeController.shouldFreezeInsetsPosition(mWin)) {
+            final AsyncRotationController controller =
+                    mWin.mDisplayContent.getAsyncRotationController();
+            if (controller != null && controller.shouldFreezeInsetsPosition(mWin)) {
                 // Use previous position because the fade-out animation runs in old rotation.
                 return mControl.getSurfacePosition();
             }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index baf7f87..5c8502b 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -450,16 +450,6 @@
             return;
         }
 
-        // Dismiss split screen
-        // The lock screen is currently showing, but is occluded by a window that can
-        // show on top of the lock screen. In this can we want to dismiss the docked
-        // stack since it will be complicated/risky to try to put the activity on top
-        // of the lock screen in the right fullscreen configuration.
-        final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
-        if (taskDisplayArea.isSplitScreenModeActivated()) {
-            taskDisplayArea.onSplitScreenModeDismissed();
-        }
-
         // Dismiss freeform windowing mode
         if (currentTaskControllingOcclusion == null) {
             return;
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 3793e4b..8e5d73f 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -150,10 +150,8 @@
 
             if (mTmpParams.hasWindowingMode() && task.isRootTask()
                     && mTmpParams.mWindowingMode != task.getWindowingMode()) {
-                final int activityType = activity != null
-                        ? activity.getActivityType() : task.getActivityType();
                 task.setWindowingMode(task.getDisplayArea().validateWindowingMode(
-                        mTmpParams.mWindowingMode, activity, task, activityType));
+                        mTmpParams.mWindowingMode, activity, task));
             }
 
             if (mTmpParams.mBounds.isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
index 80f2ab6..0ae119a 100644
--- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
@@ -80,8 +80,8 @@
      * @param show true for fade-in, otherwise for fade-out.
      */
     public void fadeWindowToken(boolean show) {
-        final FadeRotationAnimationController controller =
-                mDisplayContent.getFadeRotationAnimationController();
+        final AsyncRotationController controller =
+                mDisplayContent.getAsyncRotationController();
         final Runnable fadeAnim = () -> fadeWindowToken(show, mNavigationBar.mToken,
                 ANIMATION_TYPE_APP_TRANSITION);
         if (controller == null) {
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 49d30cd..36c092b 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -92,7 +92,7 @@
                 || transit == TRANSIT_OLD_WALLPAPER_CLOSE)
                 && displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                 && service.getRecentsAnimationController() == null
-                && displayContent.getFadeRotationAnimationController() == null;
+                && displayContent.getAsyncRotationController() == null;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 7a7fb65..16f4377 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -40,6 +40,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.util.HashMap;
 
 /**
@@ -308,6 +309,27 @@
         }
     }
 
+    /**
+     * Dumps app-specific configurations for all packages for which the records
+     * exist.
+     */
+    void dump(PrintWriter pw, int userId) {
+        pw.println("INSTALLED PACKAGES HAVING APP-SPECIFIC CONFIGURATIONS");
+        pw.println("Current user ID : " + userId);
+        synchronized (mLock) {
+            HashMap<String, PackageConfigRecord> persistedPackageConfigMap = mModified.get(userId);
+            if (persistedPackageConfigMap != null) {
+                for (PackageConfigPersister.PackageConfigRecord packageConfig
+                        : persistedPackageConfigMap.values()) {
+                    pw.println();
+                    pw.println("    PackageName : " + packageConfig.mName);
+                    pw.println("        NightMode : " + packageConfig.mNightMode);
+                    pw.println("        Locales : " + packageConfig.mLocales);
+                }
+            }
+        }
+    }
+
     // store a changed data so we don't need to get the process
     static class PackageConfigRecord {
         final String mName;
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index f97a48b..1183094 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -614,7 +614,7 @@
     private void attachNavigationBarToApp() {
         if (!mShouldAttachNavBarToAppDuringTransition
                 // Skip the case where the nav bar is controlled by fade rotation.
-                || mDisplayContent.getFadeRotationAnimationController() != null) {
+                || mDisplayContent.getAsyncRotationController() != null) {
             return;
         }
         boolean shouldTranslateNavBar = false;
@@ -701,7 +701,7 @@
     void animateNavigationBarForAppLaunch(long duration) {
         if (!mShouldAttachNavBarToAppDuringTransition
                 // Skip the case where the nav bar is controlled by fade rotation.
-                || mDisplayContent.getFadeRotationAnimationController() != null
+                || mDisplayContent.getAsyncRotationController() != null
                 || mNavigationBarAttachedToApp
                 || mNavBarAttachedApp == null) {
             return;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index d031bec..76a7981 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -73,7 +73,6 @@
 import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
 import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
 import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
-import static com.android.server.wm.RootWindowContainerProto.DEFAULT_MIN_SIZE_RESIZABLE_TASK;
 import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
 import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
 import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
@@ -111,7 +110,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
@@ -134,7 +132,6 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.Slog;
@@ -1220,11 +1217,6 @@
         pw.println(mTopFocusedDisplayId);
     }
 
-    void dumpDefaultMinSizeOfResizableTask(PrintWriter pw) {
-        pw.print("  mDefaultMinSizeOfResizeableTaskDp=");
-        pw.println(mDefaultMinSizeOfResizeableTaskDp);
-    }
-
     void dumpLayoutNeededDisplayIds(PrintWriter pw) {
         if (!isLayoutNeeded()) {
             return;
@@ -1271,7 +1263,6 @@
         mTaskSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER);
         proto.write(IS_HOME_RECENTS_COMPONENT,
                 mTaskSupervisor.mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
-        proto.write(DEFAULT_MIN_SIZE_RESIZABLE_TASK, mDefaultMinSizeOfResizeableTaskDp);
         proto.end(token);
     }
 
@@ -1359,7 +1350,6 @@
                 mDefaultDisplay = displayContent;
             }
         }
-        calculateDefaultMinimalSizeOfResizeableTasks();
 
         final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
         defaultTaskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
@@ -1917,10 +1907,6 @@
         final Task topFocusedRootTask = getTopDisplayFocusedRootTask();
         final int focusRootTaskId = topFocusedRootTask != null
                 ? topFocusedRootTask.getRootTaskId() : INVALID_TASK_ID;
-        // We dismiss the docked root task whenever we switch users.
-        if (getDefaultTaskDisplayArea().isSplitScreenModeActivated()) {
-            getDefaultTaskDisplayArea().onSplitScreenModeDismissed();
-        }
         // Also dismiss the pinned root task whenever we switch users. Removing the pinned root task
         // will also cause all tasks to be moved to the fullscreen root task at a position that is
         // appropriate.
@@ -2775,8 +2761,7 @@
     Task getLaunchRootTask(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
             @Nullable Task candidateTask, boolean onTop) {
         return getLaunchRootTask(r, options, candidateTask, null /* sourceTask */, onTop,
-                null /* launchParams */, 0 /* launchFlags */, -1 /* no realCallingPid */,
-                -1 /* no realCallingUid */);
+                null /* launchParams */, 0 /* launchFlags */);
     }
 
     /**
@@ -2795,8 +2780,7 @@
     Task getLaunchRootTask(@Nullable ActivityRecord r,
             @Nullable ActivityOptions options, @Nullable Task candidateTask,
             @Nullable Task sourceTask, boolean onTop,
-            @Nullable LaunchParamsController.LaunchParams launchParams, int launchFlags,
-            int realCallingPid, int realCallingUid) {
+            @Nullable LaunchParamsController.LaunchParams launchParams, int launchFlags) {
         int taskId = INVALID_TASK_ID;
         int displayId = INVALID_DISPLAY;
         TaskDisplayArea taskDisplayArea = null;
@@ -2845,11 +2829,7 @@
 
         if (taskDisplayArea != null) {
             final int tdaDisplayId = taskDisplayArea.getDisplayId();
-            final boolean canLaunchOnDisplayFromStartRequest =
-                    realCallingPid != 0 && realCallingUid > 0 && r != null
-                            && mTaskSupervisor.canPlaceEntityOnDisplay(tdaDisplayId,
-                            realCallingPid, realCallingUid, r.info);
-            if (canLaunchOnDisplayFromStartRequest || canLaunchOnDisplay(r, tdaDisplayId)) {
+            if (canLaunchOnDisplay(r, tdaDisplayId)) {
                 if (r != null) {
                     final Task result = getValidLaunchRootTaskInTaskDisplayArea(
                             taskDisplayArea, r, candidateTask, options, launchParams);
@@ -2882,8 +2862,7 @@
             container = rootTask.getDisplayArea();
             if (container != null && canLaunchOnDisplay(r, container.mDisplayContent.mDisplayId)) {
                 if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
-                    windowingMode = container.resolveWindowingMode(r, options, candidateTask,
-                            activityType);
+                    windowingMode = container.resolveWindowingMode(r, options, candidateTask);
                 }
                 // Always allow organized tasks that created by organizer since the activity type
                 // of an organized task is decided by the activity type of its top child, which
@@ -2899,8 +2878,7 @@
                 || !canLaunchOnDisplay(r, container.mDisplayContent.mDisplayId)) {
             container = getDefaultTaskDisplayArea();
             if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
-                windowingMode = container.resolveWindowingMode(r, options, candidateTask,
-                        activityType);
+                windowingMode = container.resolveWindowingMode(r, options, candidateTask);
             }
         }
 
@@ -2962,8 +2940,7 @@
             windowingMode = options != null ? options.getLaunchWindowingMode()
                     : r.getWindowingMode();
         }
-        windowingMode = taskDisplayArea.validateWindowingMode(windowingMode, r, candidateTask,
-                r.getActivityType());
+        windowingMode = taskDisplayArea.validateWindowingMode(windowingMode, r, candidateTask);
 
         // Return the topmost valid root task on the display.
         final int targetWindowingMode = windowingMode;
@@ -3477,17 +3454,6 @@
         mService.startLaunchPowerMode(reason);
     }
 
-    // TODO(b/191434136): handle this properly when we add multi-window support on secondary
-    //  display.
-    private void calculateDefaultMinimalSizeOfResizeableTasks() {
-        final Resources res = mService.mContext.getResources();
-        final float minimalSize = res.getDimension(
-                com.android.internal.R.dimen.default_minimal_size_resizable_task);
-        final DisplayMetrics dm = res.getDisplayMetrics();
-
-        mDefaultMinSizeOfResizeableTaskDp = (int) (minimalSize / dm.density);
-    }
-
     /**
      * Dumps the activities matching the given {@param name} in the either the focused root task
      * or all visible root tasks if {@param dumpVisibleRootTasksOnly} is true.
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7acc0c5..9b94f44 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -45,7 +45,6 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ShortcutServiceInternal;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Binder;
@@ -222,17 +221,17 @@
 
     @Override
     public int relayout(IWindow window, WindowManager.LayoutParams attrs,
-            int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
+            int requestedWidth, int requestedHeight, int viewFlags, int flags,
             ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
             SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
+            InsetsSourceControl[] outActiveControls) {
         if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
                 + Binder.getCallingPid());
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
         int res = mService.relayoutWindow(this, window, attrs,
-                requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
+                requestedWidth, requestedHeight, viewFlags, flags,
                 outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
-                outActiveControls, outSurfaceSize);
+                outActiveControls);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
                 + Binder.getCallingPid());
@@ -252,6 +251,11 @@
     }
 
     @Override
+    public void clearTouchableRegion(IWindow window) {
+        mService.clearTouchableRegion(this, window);
+    }
+
+    @Override
     public void finishDrawing(IWindow window,
             @Nullable SurfaceControl.Transaction postDrawTransaction) {
         if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index 6ed59e9..2eab3ba 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -141,8 +141,7 @@
                 && mShellRootLayer != SHELL_ROOT_LAYER_PIP) {
             return null;
         }
-        if (mShellRootLayer == SHELL_ROOT_LAYER_DIVIDER
-                && !mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()) {
+        if (mShellRootLayer == SHELL_ROOT_LAYER_DIVIDER) {
             return null;
         }
         if (mShellRootLayer == SHELL_ROOT_LAYER_PIP
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index eb73cd8..58091c8 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -18,6 +18,7 @@
 
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_HANDLE_EMPTY_SCREEN;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
@@ -25,12 +26,18 @@
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_EMPTY_SPLASH_SCREEN;
 
+import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.content.pm.ApplicationInfo;
+import android.os.UserHandle;
 import android.util.Slog;
 import android.window.TaskSnapshot;
 
@@ -43,6 +50,14 @@
 public class StartingSurfaceController {
     private static final String TAG = TAG_WITH_CLASS_NAME
             ? StartingSurfaceController.class.getSimpleName() : TAG_WM;
+    /**
+     * Allow the empty style splash screen view can be copy and transfer to another process if
+     * the app targeting to {@link android.os.Build.VERSION_CODES#TIRAMISU} or higher.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+    private static final long ALLOW_COPY_EMPTY_VIEW = 205907456L;
+
     private final WindowManagerService mService;
     private final SplashScreenExceptionList mSplashScreenExceptionsList;
 
@@ -81,7 +96,8 @@
 
     static int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch,
             boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated,
-            boolean useEmpty, boolean useLegacy, boolean activityDrawn) {
+            boolean useEmpty, boolean useLegacy, boolean activityDrawn, int startingWindowType,
+            String packageName, int userId) {
         int parameter = 0;
         if (newTask) {
             parameter |= TYPE_PARAMETER_NEW_TASK;
@@ -107,6 +123,11 @@
         if (activityDrawn) {
             parameter |= TYPE_PARAMETER_ACTIVITY_DRAWN;
         }
+        if (startingWindowType == STARTING_WINDOW_TYPE_SPLASH_SCREEN
+                && CompatChanges.isChangeEnabled(ALLOW_COPY_EMPTY_VIEW, packageName,
+                UserHandle.of(userId))) {
+            parameter |= TYPE_PARAMETER_ALLOW_HANDLE_EMPTY_SCREEN;
+        }
         return parameter;
     }
 
@@ -195,12 +216,12 @@
                 deferring, prev, source));
     }
 
-    private void showStartingWindowFromDeferringActivities() {
+    private void showStartingWindowFromDeferringActivities(ActivityOptions topOptions) {
         // Attempt to add starting window from the top-most activity.
         for (int i = mDeferringAddStartActivities.size() - 1; i >= 0; --i) {
             final DeferringStartingWindowRecord next = mDeferringAddStartActivities.get(i);
             next.mDeferring.showStartingWindow(next.mPrev, mInitNewTask, mInitTaskSwitch,
-                    mInitProcessRunning, true /* startActivity */, next.mSource);
+                    mInitProcessRunning, true /* startActivity */, next.mSource, topOptions);
             // If one succeeds, it is done.
             if (next.mDeferring.mStartingData != null) {
                 break;
@@ -223,9 +244,9 @@
     /**
      * End deferring add starting window.
      */
-    void endDeferAddStartingWindow() {
+    void endDeferAddStartingWindow(ActivityOptions topOptions) {
         mDeferringAddStartingWindow = false;
-        showStartingWindowFromDeferringActivities();
+        showStartingWindowFromDeferringActivities(topOptions);
     }
 
     final class StartingSurface {
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 50c9b31..53e3378 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -88,6 +88,8 @@
 
     private boolean mAnimationStartDelayed;
 
+    private boolean mAnimationFinished;
+
     /**
      * @param animatable The object to animate.
      * @param staticAnimationFinishedCallback Callback to invoke when an animation has finished
@@ -137,6 +139,7 @@
                         || anim.shouldDeferAnimationFinish(resetAndInvokeFinish))) {
                     resetAndInvokeFinish.run();
                 }
+                mAnimationFinished = true;
             }
         };
     }
@@ -302,6 +305,9 @@
             Slog.w(TAG, "Unable to transfer animation, surface or parent is null");
             cancelAnimation();
             return;
+        } else if (from.mAnimationFinished) {
+            Slog.w(TAG, "Unable to transfer animation, because " + from + " animation is finished");
+            return;
         }
         endDelayingAnimationStart();
         final Transaction t = mAnimatable.getPendingTransaction();
@@ -392,6 +398,7 @@
         SurfaceControl leash = mLeash;
         mLeash = null;
         final boolean scheduleAnim = removeLeash(t, mAnimatable, leash, destroyLeash);
+        mAnimationFinished = false;
         if (scheduleAnim) {
             mService.scheduleAnimationLocked();
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 2331dc4..91c1374 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -281,6 +281,8 @@
     // code.
     static final int PERSIST_TASK_VERSION = 1;
 
+    private static final int DEFAULT_MIN_TASK_SIZE_DP = 220;
+
     private float mShadowRadius = 0;
 
     /**
@@ -2052,7 +2054,9 @@
         // so that the user can not render the task fragment too small to manipulate. We don't need
         // to do this for the root pinned task as the bounds are controlled by the system.
         if (!inPinnedWindowingMode()) {
-            final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
+            // Use Display specific min sizes when there is one associated with this Task.
+            final int defaultMinSizeDp = mDisplayContent == null
+                    ? DEFAULT_MIN_TASK_SIZE_DP : mDisplayContent.mMinSizeOfResizeableTaskDp;
             final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
             final int defaultMinSize = (int) (defaultMinSizeDp * density);
 
@@ -3413,7 +3417,8 @@
         info.isResizeable = isResizeable();
         info.minWidth = mMinWidth;
         info.minHeight = mMinHeight;
-        info.defaultMinSize = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
+        info.defaultMinSize = mDisplayContent == null
+                ? DEFAULT_MIN_TASK_SIZE_DP : mDisplayContent.mMinSizeOfResizeableTaskDp;
 
         info.positionInParent = getRelativePosition();
 
@@ -4495,22 +4500,11 @@
         // right mode.
         if (!creating) {
             if (!taskDisplayArea.isValidWindowingMode(windowingMode, null /* ActivityRecord */,
-                    topTask, getActivityType())) {
+                    topTask)) {
                 windowingMode = WINDOWING_MODE_UNDEFINED;
             }
         }
 
-        final boolean alreadyInSplitScreenMode = taskDisplayArea.isSplitScreenModeActivated();
-
-        if (creating && alreadyInSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
-                && isActivityTypeStandardOrUndefined()) {
-            // If the root task is being created explicitly in fullscreen mode, dismiss split-screen
-            // and display a warning toast about it.
-            mAtmService.getTaskChangeNotificationController()
-                    .notifyActivityDismissingDockedRootTask();
-            taskDisplayArea.onSplitScreenModeDismissed(this);
-        }
-
         if (currentMode == windowingMode) {
             // You are already in the window mode, so we can skip most of the work below. However,
             // it's possible that we have inherited the current windowing mode from a parent. So,
@@ -6496,9 +6490,8 @@
 
             if (!TaskDisplayArea.isWindowingModeSupported(mWindowingMode,
                     mAtmService.mSupportsMultiWindow,
-                    mAtmService.mSupportsSplitScreenMultiWindow,
                     mAtmService.mSupportsFreeformWindowManagement,
-                    mAtmService.mSupportsPictureInPicture, mActivityType)) {
+                    mAtmService.mSupportsPictureInPicture)) {
                 throw new IllegalArgumentException("Can't create root task for unsupported "
                         + "windowingMode=" + mWindowingMode);
             }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index dfb559f..1bba103 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.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.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
@@ -53,8 +51,6 @@
 import android.util.Slog;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -114,7 +110,6 @@
     // through the list to find them.
     private Task mRootHomeTask;
     private Task mRootPinnedTask;
-    private Task mRootSplitScreenPrimaryTask;
 
     // TODO(b/159029784): Remove when getStack() behavior is cleaned-up
     private Task mRootRecentsTask;
@@ -232,8 +227,6 @@
         }
         if (windowingMode == WINDOWING_MODE_PINNED) {
             return mRootPinnedTask;
-        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            return mRootSplitScreenPrimaryTask;
         }
         return getRootTask(rootTask -> {
             if (activityType == ACTIVITY_TYPE_UNDEFINED
@@ -265,21 +258,6 @@
         return mRootPinnedTask;
     }
 
-    Task getRootSplitScreenPrimaryTask() {
-        return mRootSplitScreenPrimaryTask;
-    }
-
-    Task getRootSplitScreenSecondaryTask() {
-        // Only check the direct child Task for now, since the primary is also a direct child Task.
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final Task task = mChildren.get(i).asTask();
-            if (task != null && task.inSplitScreenSecondaryWindowingMode()) {
-                return task;
-            }
-        }
-        return null;
-    }
-
     ArrayList<Task> getVisibleTasks() {
         final ArrayList<Task> visibleTasks = new ArrayList<>();
         forAllTasks(task -> {
@@ -333,14 +311,6 @@
                                 + " already exist on display=" + this + " rootTask=" + rootTask);
             }
             mRootPinnedTask = rootTask;
-        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            if (mRootSplitScreenPrimaryTask != null) {
-                throw new IllegalArgumentException(
-                        "addRootTaskReferenceIfNeeded: root split screen primary task="
-                                + mRootSplitScreenPrimaryTask
-                                + " already exist on display=" + this + " rootTask=" + rootTask);
-            }
-            mRootSplitScreenPrimaryTask = rootTask;
         }
     }
 
@@ -351,8 +321,6 @@
             mRootRecentsTask = null;
         } else if (rootTask == mRootPinnedTask) {
             mRootPinnedTask = null;
-        } else if (rootTask == mRootSplitScreenPrimaryTask) {
-            mRootSplitScreenPrimaryTask = null;
         }
     }
 
@@ -708,39 +676,13 @@
             }, SCREEN_ORIENTATION_UNSET);
         }
 
-        if (isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
-            // Apps and their containers are not allowed to specify an orientation while using
-            // root tasks...except for the root home task if it is not resizable and currently
-            // visible (top of) its root task.
-            if (mRootHomeTask != null && !mRootHomeTask.isResizeable()) {
-                // Manually nest one-level because because getOrientation() checks fillsParent()
-                // which checks that requestedOverrideBounds() is empty. However, in this case,
-                // it is not empty because it's been overridden to maintain the fullscreen size
-                // within a smaller split-root.
-                final Task topHomeTask = mRootHomeTask.getTopMostTask();
-                final ActivityRecord topHomeActivity = topHomeTask.getTopNonFinishingActivity();
-                // If a home activity is in the process of launching and isn't yet visible we
-                // should still respect the root task's preferred orientation to ensure rotation
-                // occurs before the home activity finishes launching.
-                final boolean isHomeActivityLaunching = topHomeActivity != null
-                        && topHomeActivity.mVisibleRequested;
-                if (topHomeTask.isVisible() || isHomeActivityLaunching) {
-                    final int orientation = topHomeTask.getOrientation();
-                    if (orientation != SCREEN_ORIENTATION_UNSET) {
-                        return orientation;
-                    }
-                }
-            }
+        // Apps and their containers are not allowed to specify an orientation of non floating
+        // visible tasks created by organizer. The organizer handles the orientation instead.
+        final Task nonFloatingTopTask =
+                getRootTask(t -> !t.getWindowConfiguration().tasksAreFloating());
+        if (nonFloatingTopTask != null && nonFloatingTopTask.mCreatedByOrganizer
+                && nonFloatingTopTask.isVisible()) {
             return SCREEN_ORIENTATION_UNSPECIFIED;
-        } else {
-            // Apps and their containers are not allowed to specify an orientation of non floating
-            // visible tasks created by organizer. The organizer handles the orientation instead.
-            final Task nonFloatingTopTask =
-                    getRootTask(t -> !t.getWindowConfiguration().tasksAreFloating());
-            if (nonFloatingTopTask != null && nonFloatingTopTask.mCreatedByOrganizer
-                    && nonFloatingTopTask.isVisible()) {
-                return SCREEN_ORIENTATION_UNSPECIFIED;
-            }
         }
 
         final int orientation = super.getOrientation(candidate);
@@ -845,7 +787,7 @@
                     && child.inMultiWindowMode()
                     && childTask.getRootTask().getAdjacentTaskFragment() != null;
 
-            if (inAdjacentTask || child.inSplitScreenWindowingMode()) {
+            if (inAdjacentTask) {
                 hasAdjacentTask = true;
             } else if (hasAdjacentTask && startLayer < SPLIT_DIVIDER_LAYER) {
                 // Task on top of adjacent tasks should be higher than split divider layer so
@@ -1086,7 +1028,7 @@
         // Validate that our desired windowingMode will work under the current conditions.
         // UNDEFINED windowing mode is a valid result and means that the new root task will inherit
         // it's display's windowing mode.
-        windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType);
+        windowingMode = validateWindowingMode(windowingMode, r, candidateTask);
         return getOrCreateRootTask(windowingMode, activityType, onTop, candidateTask, sourceTask,
                 options, launchFlags);
     }
@@ -1283,23 +1225,6 @@
                 continue;
             }
 
-            if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                    && candidate == null && rootTask.inSplitScreenPrimaryWindowingMode()) {
-                // If the currently focused root task is in split-screen secondary we save off the
-                // top primary split-screen root task as a candidate for focus because we might
-                // prefer focus to move to an other root task to avoid primary split-screen root
-                // task overlapping with a fullscreen root task when a fullscreen root task is
-                // higher in z than the next split-screen root task. Assistant root task, I am
-                // looking at you...
-                // We only move the focus to the primary-split screen root task if there isn't a
-                // better alternative.
-                candidate = rootTask;
-                continue;
-            }
-            if (candidate != null && rootTask.inSplitScreenSecondaryWindowingMode()) {
-                // Use the candidate root task since we are now at the secondary split-screen.
-                return candidate;
-            }
             return rootTask;
         }
         return candidate;
@@ -1416,75 +1341,18 @@
         return someActivityPaused[0] > 0;
     }
 
-    void onSplitScreenModeDismissed() {
-        // The focused task could be a non-resizeable fullscreen root task that is on top of the
-        // other split-screen tasks, therefore had to dismiss split-screen, make sure the current
-        // focused root task can still be on top after dismissal
-        final Task rootTask = getFocusedRootTask();
-        final Task toTop =
-                rootTask != null && !rootTask.inSplitScreenWindowingMode() ? rootTask : null;
-        onSplitScreenModeDismissed(toTop);
-    }
-
-    void onSplitScreenModeDismissed(Task toTop) {
-        mAtmService.deferWindowLayout();
-        try {
-            moveSplitScreenTasksToFullScreen();
-        } finally {
-            final Task topFullscreenRootTask = toTop != null
-                    ? toTop : getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
-            final Task rootHomeTask = getOrCreateRootHomeTask();
-            if (rootHomeTask != null && ((topFullscreenRootTask != null && !isTopRootTask(
-                    rootHomeTask)) || toTop != null)) {
-                // Whenever split-screen is dismissed we want the root home task directly behind the
-                // current top fullscreen root task so it shows up when the top root task is
-                // finished. Or, if the caller specified a root task to be on top after
-                // split-screen is dismissed.
-                // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
-                // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
-                // once we have that.
-                rootHomeTask.moveToFront("onSplitScreenModeDismissed");
-                topFullscreenRootTask.moveToFront("onSplitScreenModeDismissed");
-            }
-            mAtmService.continueWindowLayout();
-        }
-    }
-
-    private void moveSplitScreenTasksToFullScreen() {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTmpTasks.clear();
-        forAllTasks(task -> {
-            if (task.mCreatedByOrganizer && task.inSplitScreenWindowingMode() && task.hasChild()) {
-                mTmpTasks.add(task);
-            }
-        });
-
-        for (int i = mTmpTasks.size() - 1; i >= 0; i--) {
-            final Task root = mTmpTasks.get(i);
-            for (int j = 0; j < root.getChildCount(); j++) {
-                final WindowContainerToken token =
-                        root.getChildAt(j).mRemoteToken.toWindowContainerToken();
-                wct.reparent(token, null, true /* toTop */);
-                wct.setBounds(token, null);
-            }
-        }
-        mAtmService.mWindowOrganizerController.applyTransaction(wct);
-    }
 
     /**
      * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
      *
      * @param windowingMode       The windowing mode we are checking support for.
      * @param supportsMultiWindow If we should consider support for multi-window mode in general.
-     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
      * @param supportsFreeform    If we should consider support for freeform multi-window.
      * @param supportsPip         If we should consider support for picture-in-picture mutli-window.
-     * @param activityType        The activity type under consideration.
      * @return true if the windowing mode is supported.
      */
     static boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
-            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
-            int activityType) {
+            boolean supportsFreeform, boolean supportsPip) {
 
         if (windowingMode == WINDOWING_MODE_UNDEFINED
                 || windowingMode == WINDOWING_MODE_FULLSCREEN) {
@@ -1498,12 +1366,6 @@
             return true;
         }
 
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-            return supportsSplitScreen
-                    && WindowConfiguration.supportSplitScreenWindowingMode(activityType);
-        }
-
         if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
             return false;
         }
@@ -1525,7 +1387,7 @@
      * @return The resolved (not UNDEFINED) windowing-mode that the activity would be in.
      */
     int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
-            @Nullable Task task, int activityType) {
+            @Nullable Task task) {
 
         // First preference if the windowing mode in the activity options if set.
         int windowingMode = (options != null)
@@ -1545,7 +1407,7 @@
                 windowingMode = getWindowingMode();
             }
         }
-        windowingMode = validateWindowingMode(windowingMode, r, task, activityType);
+        windowingMode = validateWindowingMode(windowingMode, r, task);
         return windowingMode != WINDOWING_MODE_UNDEFINED
                 ? windowingMode : WINDOWING_MODE_FULLSCREEN;
     }
@@ -1557,19 +1419,16 @@
      * @param windowingMode The windowing-mode to validate.
      * @param r             The {@link ActivityRecord} to check against.
      * @param task          The {@link Task} to check against.
-     * @param activityType  An activity type.
      * @return {@code true} if windowingMode is valid, {@code false} otherwise.
      */
-    boolean isValidWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
-            int activityType) {
+    boolean isValidWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task
+    ) {
         // Make sure the windowing mode we are trying to use makes sense for what is supported.
         boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow;
-        boolean supportsSplitScreen = mAtmService.mSupportsSplitScreenMultiWindow;
         boolean supportsFreeform = mAtmService.mSupportsFreeformWindowManagement;
         boolean supportsPip = mAtmService.mSupportsPictureInPicture;
         if (supportsMultiWindow) {
             if (task != null) {
-                supportsSplitScreen = task.supportsSplitScreenWindowingModeInDisplayArea(this);
                 supportsFreeform = task.supportsFreeformInDisplayArea(this);
                 supportsMultiWindow = task.supportsMultiWindowInDisplayArea(this)
                         // When the activity needs to be moved to PIP while the Task is not in PIP,
@@ -1577,7 +1436,6 @@
                         // always valid for Task as long as the device supports it.
                         || (windowingMode == WINDOWING_MODE_PINNED && supportsPip);
             } else if (r != null) {
-                supportsSplitScreen = r.supportsSplitScreenWindowingModeInDisplayArea(this);
                 supportsFreeform = r.supportsFreeformInDisplayArea(this);
                 supportsPip = r.supportsPictureInPicture();
                 supportsMultiWindow = r.supportsMultiWindowInDisplayArea(this);
@@ -1585,8 +1443,8 @@
         }
 
         return windowingMode != WINDOWING_MODE_UNDEFINED
-                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
-                supportsFreeform, supportsPip, activityType);
+                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsFreeform,
+                supportsPip);
     }
 
     /**
@@ -1596,20 +1454,10 @@
      * @param windowingMode The windowing-mode to validate.
      * @param r             The {@link ActivityRecord} to check against.
      * @param task          The {@link Task} to check against.
-     * @param activityType  An activity type.
      * @return The provided windowingMode or the closest valid mode which is appropriate.
      */
-    int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
-            int activityType) {
-        final boolean inSplitScreenMode = isSplitScreenModeActivated();
-        if (!inSplitScreenMode && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-            // Switch to the display's windowing mode if we are not in split-screen mode and we are
-            // trying to launch in split-screen secondary.
-            windowingMode = WINDOWING_MODE_UNDEFINED;
-        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_UNDEFINED) {
-            windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-        }
-        if (!isValidWindowingMode(windowingMode, r, task, activityType)) {
+    int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task) {
+        if (!isValidWindowingMode(windowingMode, r, task)) {
             return WINDOWING_MODE_UNDEFINED;
         }
         return windowingMode;
@@ -1774,11 +1622,6 @@
         return homeTask;
     }
 
-    boolean isSplitScreenModeActivated() {
-        Task task = getRootSplitScreenPrimaryTask();
-        return task != null && task.hasChild();
-    }
-
     /**
      * Returns the topmost root task on the display that is compatible with the input windowing
      * mode. Null is no compatible root task on the display.
@@ -1866,8 +1709,7 @@
                 continue;
             }
             final int winMode = s.getWindowingMode();
-            final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN
-                    || winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+            final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN;
             if (s.shouldBeVisible(null) && isValidWindowingMode) {
                 // Move the provided root task to behind this root task
                 final int position = Math.max(0, rootTaskNdx - 1);
@@ -1882,8 +1724,7 @@
     private Task getBottomMostVisibleRootTask(Task excludeRootTask) {
         return getRootTask(task -> {
             final int winMode = task.getWindowingMode();
-            final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN
-                    || winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+            final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN;
             return task.shouldBeVisible(null) && isValidWindowingMode;
         }, false /* traverseTopToBottom */);
     }
@@ -2067,20 +1908,11 @@
             numRootTasks = mChildren.size();
         }
 
-        if (lastReparentedRootTask != null) {
-            if (toDisplayArea.isSplitScreenModeActivated()
-                    && !lastReparentedRootTask.supportsSplitScreenWindowingModeInDisplayArea(
-                            toDisplayArea)) {
-                // Dismiss split screen if the last reparented root task doesn't support split mode.
-                mAtmService.getTaskChangeNotificationController()
-                        .notifyActivityDismissingDockedRootTask();
-                toDisplayArea.onSplitScreenModeDismissed(lastReparentedRootTask);
-            } else if (!lastReparentedRootTask.isRootTask()) {
-                // Update focus when the last reparented root task is not a root task anymore.
-                // (For example, if it has been reparented to a split screen root task, move the
-                // focus to the split root task)
-                lastReparentedRootTask.getRootTask().moveToFront("display-removed");
-            }
+        if (lastReparentedRootTask != null && !lastReparentedRootTask.isRootTask()) {
+            // Update focus when the last reparented root task is not a root task anymore.
+            // (For example, if it has been reparented to a split screen root task, move the
+            // focus to the split root task)
+            lastReparentedRootTask.getRootTask().moveToFront("display-removed");
         }
 
         mRemoved = true;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b13c9a9..ded58f4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -37,7 +37,6 @@
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -72,6 +71,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.animation.Animation;
@@ -101,6 +101,9 @@
     /** The default package for resources */
     private static final String DEFAULT_PACKAGE = "android";
 
+    /** The transition has been created but isn't collecting yet. */
+    private static final int STATE_PENDING = -1;
+
     /** The transition has been created and is collecting, but hasn't formally started. */
     private static final int STATE_COLLECTING = 0;
 
@@ -122,6 +125,7 @@
     private static final int STATE_ABORT = 3;
 
     @IntDef(prefix = { "STATE_" }, value = {
+            STATE_PENDING,
             STATE_COLLECTING,
             STATE_STARTED,
             STATE_PLAYING,
@@ -131,7 +135,7 @@
     @interface TransitionState {}
 
     final @TransitionType int mType;
-    private int mSyncId;
+    private int mSyncId = -1;
     private @TransitionFlags int mFlags;
     private final TransitionController mController;
     private final BLASTSyncEngine mSyncEngine;
@@ -152,7 +156,7 @@
     final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
 
     /** The final animation targets derived from participants after promotion. */
-    private ArraySet<WindowContainer> mTargets = null;
+    private ArrayList<WindowContainer> mTargets;
 
     /** The main display running this transition. */
     private DisplayContent mTargetDisplay;
@@ -171,7 +175,7 @@
     private IRemoteCallback mClientAnimationStartCallback = null;
     private IRemoteCallback mClientAnimationFinishCallback = null;
 
-    private @TransitionState int mState = STATE_COLLECTING;
+    private @TransitionState int mState = STATE_PENDING;
     private final ReadyTracker mReadyTracker = new ReadyTracker();
 
     // TODO(b/188595497): remove when not needed.
@@ -179,13 +183,12 @@
     private boolean mNavBarAttachedToApp = false;
     private int mRecentsDisplayId = INVALID_DISPLAY;
 
-    Transition(@TransitionType int type, @TransitionFlags int flags, long timeoutMs,
+    Transition(@TransitionType int type, @TransitionFlags int flags,
             TransitionController controller, BLASTSyncEngine syncEngine) {
         mType = type;
         mFlags = flags;
         mController = controller;
         mSyncEngine = syncEngine;
-        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
     }
 
     void addFlag(int flag) {
@@ -216,13 +219,24 @@
         return mFlags;
     }
 
+    /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
+    void startCollecting(long timeoutMs) {
+        if (mState != STATE_PENDING) {
+            throw new IllegalStateException("Attempting to re-use a transition");
+        }
+        mState = STATE_COLLECTING;
+        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
+    }
+
     /**
      * Formally starts the transition. Participants can be collected before this is started,
      * but this won't consider itself ready until started -- even if all the participants have
      * drawn.
      */
     void start() {
-        if (mState >= STATE_STARTED) {
+        if (mState < STATE_COLLECTING) {
+            throw new IllegalStateException("Can't start Transition which isn't collecting.");
+        } else if (mState >= STATE_STARTED) {
             Slog.w(TAG, "Transition already started: " + mSyncId);
         }
         mState = STATE_STARTED;
@@ -235,6 +249,9 @@
      * Adds wc to set of WindowContainers participating in this transition.
      */
     void collect(@NonNull WindowContainer wc) {
+        if (mState < STATE_COLLECTING) {
+            throw new IllegalStateException("Transition hasn't started collecting.");
+        }
         if (mSyncId < 0) return;
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
                 mSyncId, wc);
@@ -368,7 +385,7 @@
         // usually only size 1
         final ArraySet<DisplayContent> displays = new ArraySet<>();
         for (int i = mTargets.size() - 1; i >= 0; --i) {
-            final WindowContainer target = mTargets.valueAt(i);
+            final WindowContainer target = mTargets.get(i);
             if (target.getParent() != null) {
                 final SurfaceControl targetLeash = getLeashSurface(target);
                 final SurfaceControl origParent = getOrigParentSurface(target);
@@ -493,10 +510,10 @@
             dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
         }
 
-        final FadeRotationAnimationController fadeRotationController =
-                mTargetDisplay.getFadeRotationAnimationController();
-        if (fadeRotationController != null) {
-            fadeRotationController.onTransitionFinished();
+        final AsyncRotationController asyncRotationController =
+                mTargetDisplay.getAsyncRotationController();
+        if (asyncRotationController != null) {
+            asyncRotationController.onTransitionFinished();
         }
         // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget),
         // so re-compute in case the IME target is changed after transition.
@@ -638,7 +655,7 @@
 
         // This is non-null only if display has changes. It handles the visible windows that don't
         // need to be participated in the transition.
-        final FadeRotationAnimationController controller = dc.getFadeRotationAnimationController();
+        final AsyncRotationController controller = dc.getAsyncRotationController();
         if (controller != null) {
             controller.setupStartTransaction(transaction);
         }
@@ -728,7 +745,7 @@
 
         if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                 // Skip the case where the nav bar is controlled by fade rotation.
-                || dc.getFadeRotationAnimationController() != null) {
+                || dc.getAsyncRotationController() != null) {
             return;
         }
 
@@ -801,7 +818,7 @@
         // Search for the home task. If it is supposed to be visible, then the navbar is not at
         // the bottom of the screen, so we need to animate it.
         for (int i = 0; i < mTargets.size(); ++i) {
-            final Task task = mTargets.valueAt(i).asTask();
+            final Task task = mTargets.get(i).asTask();
             if (task == null || !task.isHomeOrRecentsRootTask()) continue;
             animate = task.isVisibleRequested();
             break;
@@ -884,23 +901,8 @@
     private static boolean reportIfNotTop(WindowContainer wc) {
         // Organized tasks need to be reported anyways because Core won't show() their surfaces
         // and we can't rely on onTaskAppeared because it isn't in sync.
-        // Also report wallpaper so it can be handled properly during display change/rotation.
         // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
-        return wc.isOrganized() || isWallpaper(wc);
-    }
-
-    /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */
-    private static int getChildDepth(WindowContainer child, WindowContainer ancestor) {
-        WindowContainer parent = child;
-        int depth = 0;
-        while (parent != null) {
-            if (parent == ancestor) {
-                return depth;
-            }
-            parent = parent.getParent();
-            ++depth;
-        }
-        return -1;
+        return wc.isOrganized();
     }
 
     private static boolean isWallpaper(WindowContainer wc) {
@@ -930,61 +932,48 @@
      *
      * @return {@code true} if transition in target can be promoted to its parent.
      */
-    private static boolean canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets,
+    private static boolean canPromote(WindowContainer<?> target, Targets targets,
             ArrayMap<WindowContainer, ChangeInfo> changes) {
-        final WindowContainer parent = target.getParent();
-        final ChangeInfo parentChanges = parent != null ? changes.get(parent) : null;
-        if (parent == null || !parent.canCreateRemoteAnimationTarget()
-                || parentChanges == null || !parentChanges.hasChanged(parent)) {
+        final WindowContainer<?> parent = target.getParent();
+        final ChangeInfo parentChange = changes.get(parent);
+        if (!parent.canCreateRemoteAnimationTarget()
+                || parentChange == null || !parentChange.hasChanged(parent)) {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: %s",
-                    parent == null ? "no parent" : ("parent can't be target " + parent));
+                    "parent can't be target " + parent);
             return false;
         }
         if (isWallpaper(target)) {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: is wallpaper");
             return false;
         }
-        @TransitionInfo.TransitionMode int mode = TRANSIT_NONE;
-        // Go through all siblings of this target to see if any of them would prevent
-        // the target from promoting.
-        siblingLoop:
+
+        final @TransitionInfo.TransitionMode int mode = changes.get(target).getTransitMode(target);
         for (int i = parent.getChildCount() - 1; i >= 0; --i) {
-            final WindowContainer sibling = parent.getChildAt(i);
+            final WindowContainer<?> sibling = parent.getChildAt(i);
+            if (target == sibling) continue;
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      check sibling %s",
                     sibling);
-            // Check if any topTargets are the sibling or within it
-            for (int j = topTargets.size() - 1; j >= 0; --j) {
-                final int depth = getChildDepth(topTargets.valueAt(j), sibling);
-                if (depth < 0) continue;
-                if (depth == 0) {
-                    final int siblingMode = changes.get(sibling).getTransitMode(sibling);
+            final ChangeInfo siblingChange = changes.get(sibling);
+            if (siblingChange == null || !targets.wasParticipated(sibling)) {
+                if (sibling.isVisibleRequested()) {
+                    // Sibling is visible but not animating, so no promote.
                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                            "        sibling is a top target with mode %s",
-                            TransitionInfo.modeToString(siblingMode));
-                    if (mode == TRANSIT_NONE) {
-                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                "          no common mode yet, so set it");
-                        mode = siblingMode;
-                    } else if (mode != siblingMode) {
-                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                "          SKIP: common mode mismatch. was %s",
-                                TransitionInfo.modeToString(mode));
-                        return false;
-                    }
-                    continue siblingLoop;
-                } else {
-                    // Sibling subtree may not be promotable.
-                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                            "        SKIP: sibling contains top target %s",
-                            topTargets.valueAt(j));
+                            "        SKIP: sibling is visible but not part of transition");
                     return false;
                 }
-            }
-            // No other animations are playing in this sibling
-            if (sibling.isVisibleRequested()) {
-                // Sibling is visible but not animating, so no promote.
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                        "        SKIP: sibling is visible but not part of transition");
+                        "        unrelated invisible sibling %s", sibling);
+                continue;
+            }
+
+            final int siblingMode = siblingChange.getTransitMode(sibling);
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                    "        sibling is a participant with mode %s",
+                    TransitionInfo.modeToString(siblingMode));
+            if (mode != siblingMode) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "          SKIP: common mode mismatch. was %s",
+                        TransitionInfo.modeToString(mode));
                 return false;
             }
         }
@@ -994,58 +983,40 @@
     /**
      * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
      *
-     * @param topTargets set of just the top-most targets in the hierarchy of participants.
      * @param targets all targets that will be sent to the player.
-     * @return {@code true} if something was promoted.
      */
-    private static boolean tryPromote(ArraySet<WindowContainer> topTargets,
-            ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
-        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  --- Start combine pass ---");
-        // Go through each target until we find one that can be promoted.
-        for (WindowContainer targ : topTargets) {
-            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", targ);
-            if (!canPromote(targ, topTargets, changes)) {
+    private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
+        WindowContainer<?> lastNonPromotableParent = null;
+        // Go through from the deepest target.
+        for (int i = targets.mArray.size() - 1; i >= 0; --i) {
+            final WindowContainer<?> target = targets.mArray.valueAt(i);
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", target);
+            final WindowContainer<?> parent = target.getParent();
+            if (parent == lastNonPromotableParent) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "      SKIP: its sibling was rejected");
                 continue;
             }
-            // No obstructions found to promotion, so promote
-            final WindowContainer parent = targ.getParent();
-            final ChangeInfo parentInfo = changes.get(parent);
-            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                    "      CAN PROMOTE: promoting to parent %s", parent);
-            targets.add(parent);
-
-            // Go through all children of newly-promoted container and remove them from the
-            // top-targets.
-            for (int i = parent.getChildCount() - 1; i >= 0; --i) {
-                final WindowContainer child = parent.getChildAt(i);
-                int idx = targets.indexOf(child);
-                if (idx >= 0) {
-                    final ChangeInfo childInfo = changes.get(child);
-                    if (reportIfNotTop(child)) {
-                        childInfo.mParent = parent;
-                        parentInfo.addChild(child);
-                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                "        keep as target %s", child);
-                    } else {
-                        if (childInfo.mChildren != null) {
-                            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                    "        merging children in from %s: %s", child,
-                                    childInfo.mChildren);
-                            parentInfo.addChildren(childInfo.mChildren);
-                        }
-                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                "        remove from targets %s", child);
-                        targets.removeAt(idx);
-                    }
-                }
-                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                        "        remove from topTargets %s", child);
-                topTargets.remove(child);
+            if (!canPromote(target, targets, changes)) {
+                lastNonPromotableParent = parent;
+                continue;
             }
-            topTargets.add(parent);
-            return true;
+            if (reportIfNotTop(target)) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "        keep as target %s", target);
+            } else {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "        remove from targets %s", target);
+                targets.remove(i, target);
+            }
+            if (targets.mArray.indexOfValue(parent) < 0) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "      CAN PROMOTE: promoting to parent %s", parent);
+                // The parent has lower depth, so it will be checked in the later iteration.
+                i++;
+                targets.add(parent);
+            }
         }
-        return false;
     }
 
     /**
@@ -1054,22 +1025,15 @@
      */
     @VisibleForTesting
     @NonNull
-    static ArraySet<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants,
+    static ArrayList<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants,
             ArrayMap<WindowContainer, ChangeInfo> changes) {
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                 "Start calculating TransitionInfo based on participants: %s", participants);
 
-        final ArraySet<WindowContainer> topTargets = new ArraySet<>();
-        // The final animation targets which cannot promote to higher level anymore.
-        final ArraySet<WindowContainer> targets = new ArraySet<>();
-
-        final ArrayList<WindowContainer> tmpList = new ArrayList<>();
-
-        // Build initial set of top-level participants by removing any participants that are no-ops
-        // or children of other participants or are otherwise invalid; however, keep around a list
-        // of participants that should always be reported even if they aren't top.
-        for (WindowContainer wc : participants) {
-            // Don't include detached windows.
+        // Add all valid participants to the target container.
+        final Targets targets = new Targets();
+        for (int i = participants.size() - 1; i >= 0; --i) {
+            final WindowContainer<?> wc = participants.valueAt(i);
             if (!wc.isAttached()) {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "  Rejecting as detached: %s", wc);
@@ -1084,70 +1048,62 @@
                         "  Rejecting as no-op: %s", wc);
                 continue;
             }
-
-            // Search through ancestors to find the top-most participant (if one exists)
-            WindowContainer topParent = null;
-            tmpList.clear();
-            if (reportIfNotTop(wc)) {
-                tmpList.add(wc);
-            }
-            // Wallpaper must be the top (regardless of how nested it is in DisplayAreas).
-            boolean skipIntermediateReports = isWallpaper(wc);
-            for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) {
-                if (!p.isAttached() || changes.get(p) == null || !changes.get(p).hasChanged(p)) {
-                    // Again, we're skipping no-ops
-                    break;
-                }
-                if (participants.contains(p)) {
-                    topParent = p;
-                    break;
-                } else if (isWallpaper(p)) {
-                    skipIntermediateReports = true;
-                } else if (reportIfNotTop(p) && !skipIntermediateReports) {
-                    tmpList.add(p);
-                }
-            }
-            if (topParent != null) {
-                // There was an ancestor participant, so don't add wc to targets unless always-
-                // report. Similarly, add any always-report parents along the way.
-                for (int i = 0; i < tmpList.size(); ++i) {
-                    targets.add(tmpList.get(i));
-                    final ChangeInfo info = changes.get(tmpList.get(i));
-                    info.mParent = i < tmpList.size() - 1 ? tmpList.get(i + 1) : topParent;
-                }
-                continue;
-            }
-            // No ancestors in participant-list, so wc is a top target.
             targets.add(wc);
-            topTargets.add(wc);
+            targets.mValidParticipants.add(wc);
         }
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s",
+                targets.mArray);
+        // Combine the targets from bottom to top if possible.
+        tryPromote(targets, changes);
+        // Establish the relationship between the targets and their top changes.
+        populateParentChanges(targets, changes);
 
-        // Populate children lists
-        for (int i = targets.size() - 1; i >= 0; --i) {
-            if (changes.get(targets.valueAt(i)).mParent != null) {
-                changes.get(changes.get(targets.valueAt(i)).mParent).addChild(targets.valueAt(i));
-            }
-        }
-
-        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s", targets);
-        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Top targets: %s", topTargets);
-
-        // Combine targets by repeatedly going through the topTargets to see if they can be
-        // promoted until there aren't any promotions possible.
-        while (tryPromote(topTargets, targets, changes)) {
-            // Empty on purpose
-        }
-        return targets;
+        final ArrayList<WindowContainer> targetList = targets.getListSortedByZ();
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Final targets: %s", targetList);
+        return targetList;
     }
 
-    /** Add any of `members` within `root` to `out` in top-to-bottom z-order. */
-    private static void addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members,
-            ArrayList<WindowContainer> out) {
-        for (int i = root.getChildCount() - 1; i >= 0; --i) {
-            final WindowContainer child = root.getChildAt(i);
-            addMembersInOrder(child, members, out);
-            if (members.contains(child)) {
-                out.add(child);
+    /** Populates parent to the change info and collects intermediate targets. */
+    private static void populateParentChanges(Targets targets,
+            ArrayMap<WindowContainer, ChangeInfo> changes) {
+        final ArrayList<WindowContainer<?>> intermediates = new ArrayList<>();
+        for (int i = targets.mValidParticipants.size() - 1; i >= 0; --i) {
+            WindowContainer<?> wc = targets.mValidParticipants.get(i);
+            // Go up if the participant has been represented by its parent.
+            while (targets.mArray.indexOfValue(wc) < 0 && wc.getParent() != null) {
+                wc = wc.getParent();
+            }
+            // Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas).
+            final boolean skipIntermediateReports = isWallpaper(wc);
+            intermediates.clear();
+            // Collect the intermediate parents between target and top changed parent.
+            for (WindowContainer<?> p = wc.getParent(); p != null; p = p.getParent()) {
+                final ChangeInfo parentChange = changes.get(p);
+                if (parentChange == null || !parentChange.hasChanged(p)) break;
+                if (parentChange.mParent != null && !skipIntermediateReports) {
+                    changes.get(wc).mParent = p;
+                    // The chain above the parent was processed.
+                    break;
+                }
+                if (targets.mValidParticipants.contains(p)) {
+                    if (skipIntermediateReports) {
+                        changes.get(wc).mParent = p;
+                    } else {
+                        intermediates.add(p);
+                    }
+                    // The parent reaches a participant.
+                    break;
+                } else if (reportIfNotTop(p) && !skipIntermediateReports) {
+                    intermediates.add(p);
+                }
+            }
+            if (intermediates.isEmpty()) continue;
+            // Add any always-report parents along the way.
+            changes.get(wc).mParent = intermediates.get(0);
+            for (int j = 0; j < intermediates.size() - 1; j++) {
+                final WindowContainer<?> intermediate = intermediates.get(j);
+                changes.get(intermediate).mParent = intermediates.get(j + 1);
+                targets.add(intermediate);
             }
         }
     }
@@ -1184,32 +1140,36 @@
     /**
      * Construct a TransitionInfo object from a set of targets and changes. Also populates the
      * root surface.
+     * @param sortedTargets The targets sorted by z-order from top (index 0) to bottom.
      */
     @VisibleForTesting
     @NonNull
     static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
-            ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
+            ArrayList<WindowContainer> sortedTargets,
+            ArrayMap<WindowContainer, ChangeInfo> changes) {
         final TransitionInfo out = new TransitionInfo(type, flags);
 
-        final ArraySet<WindowContainer> appTargets = new ArraySet<>();
-        final ArraySet<WindowContainer> wallpapers = new ArraySet<>();
-        for (int i = targets.size() - 1; i >= 0; --i) {
-            (isWallpaper(targets.valueAt(i)) ? wallpapers : appTargets).add(targets.valueAt(i));
+        WindowContainer<?> topApp = null;
+        for (int i = 0; i < sortedTargets.size(); i++) {
+            final WindowContainer<?> wc = sortedTargets.get(i);
+            if (!isWallpaper(wc)) {
+                topApp = wc;
+                break;
+            }
         }
-
-        // Find the top-most shared ancestor of app targets
-        if (appTargets.isEmpty()) {
+        if (topApp == null) {
             out.setRootLeash(new SurfaceControl(), 0, 0);
             return out;
         }
-        WindowContainer ancestor = appTargets.valueAt(appTargets.size() - 1).getParent();
 
+        // Find the top-most shared ancestor of app targets.
+        WindowContainer<?> ancestor = topApp.getParent();
         // Go up ancestor parent chain until all targets are descendants.
         ancestorLoop:
         while (ancestor != null) {
-            for (int i = appTargets.size() - 1; i >= 0; --i) {
-                final WindowContainer wc = appTargets.valueAt(i);
-                if (!wc.isDescendantOf(ancestor)) {
+            for (int i = sortedTargets.size() - 1; i >= 0; --i) {
+                final WindowContainer wc = sortedTargets.get(i);
+                if (!isWallpaper(wc) && !wc.isDescendantOf(ancestor)) {
                     ancestor = ancestor.getParent();
                     continue ancestorLoop;
                 }
@@ -1217,11 +1177,6 @@
             break;
         }
 
-        // Sort targets top-to-bottom in Z. Check ALL targets here in case the display area itself
-        // is animating: then we want to include wallpapers at the right position.
-        ArrayList<WindowContainer> sortedTargets = new ArrayList<>();
-        addMembersInOrder(ancestor, targets, sortedTargets);
-
         // make leash based on highest (z-order) direct child of ancestor with a participant.
         WindowContainer leashReference = sortedTargets.get(0);
         while (leashReference.getParent() != ancestor) {
@@ -1235,14 +1190,6 @@
         t.close();
         out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top);
 
-        // add the wallpapers at the bottom
-        for (int i = wallpapers.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = wallpapers.valueAt(i);
-            // If the displayarea itself is animating, then the wallpaper was already added.
-            if (wc.isDescendantOf(ancestor)) break;
-            sortedTargets.add(wc);
-        }
-
         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
         final int count = sortedTargets.size();
         for (int i = 0; i < count; ++i) {
@@ -1386,7 +1333,6 @@
     static class ChangeInfo {
         // Usually "post" change state.
         WindowContainer mParent;
-        ArraySet<WindowContainer> mChildren;
 
         // State tracking
         boolean mExistenceChanged = false;
@@ -1481,19 +1427,6 @@
             }
             return flags;
         }
-
-        void addChild(@NonNull WindowContainer wc) {
-            if (mChildren == null) {
-                mChildren = new ArraySet<>();
-            }
-            mChildren.add(wc);
-        }
-        void addChildren(@NonNull ArraySet<WindowContainer> wcs) {
-            if (mChildren == null) {
-                mChildren = new ArraySet<>();
-            }
-            mChildren.addAll(wcs);
-        }
     }
 
     /**
@@ -1586,4 +1519,64 @@
             return b.toString();
         }
     }
+
+    /**
+     * The container to represent the depth relation for calculating transition targets. The window
+     * container with larger depth is put at larger index. For the same depth, higher z-order has
+     * larger index.
+     */
+    private static class Targets {
+        /** All targets. Its keys (depth) are sorted in ascending order naturally. */
+        final SparseArray<WindowContainer<?>> mArray = new SparseArray<>();
+        /** The initial participants which have changes. */
+        final ArrayList<WindowContainer<?>> mValidParticipants = new ArrayList<>();
+        /** The targets which were represented by their parent. */
+        private ArrayList<WindowContainer<?>> mRemovedTargets;
+        private int mDepthFactor;
+
+        void add(WindowContainer<?> target) {
+            // The number of slots per depth is larger than the total number of window container,
+            // so the depth score (key) won't have collision.
+            if (mDepthFactor == 0) {
+                mDepthFactor = target.mWmService.mRoot.getTreeWeight() + 1;
+            }
+            int score = target.getPrefixOrderIndex();
+            WindowContainer<?> wc = target;
+            while (wc != null) {
+                final WindowContainer<?> parent = wc.getParent();
+                if (parent != null) {
+                    score += mDepthFactor;
+                }
+                wc = parent;
+            }
+            mArray.put(score, target);
+        }
+
+        void remove(int index, WindowContainer<?> removingTarget) {
+            mArray.removeAt(index);
+            if (mRemovedTargets == null) {
+                mRemovedTargets = new ArrayList<>();
+            }
+            mRemovedTargets.add(removingTarget);
+        }
+
+        boolean wasParticipated(WindowContainer<?> wc) {
+            return mArray.indexOfValue(wc) >= 0
+                    || (mRemovedTargets != null && mRemovedTargets.contains(wc));
+        }
+
+        /** Returns the target list sorted by z-order in ascending order (index 0 is top). */
+        ArrayList<WindowContainer> getListSortedByZ() {
+            final SparseArray<WindowContainer<?>> arrayByZ = new SparseArray<>(mArray.size());
+            for (int i = mArray.size() - 1; i >= 0; --i) {
+                final int zOrder = mArray.keyAt(i) % mDepthFactor;
+                arrayByZ.put(zOrder, mArray.valueAt(i));
+            }
+            final ArrayList<WindowContainer> sortedTargets = new ArrayList<>(arrayByZ.size());
+            for (int i = arrayByZ.size() - 1; i >= 0; --i) {
+                sortedTargets.add(arrayByZ.valueAt(i));
+            }
+            return sortedTargets;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index fe968ec..7a031db 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -128,16 +128,46 @@
             throw new IllegalStateException("Shell Transitions not enabled");
         }
         if (mCollectingTransition != null) {
-            throw new IllegalStateException("Simultaneous transitions not supported yet.");
+            throw new IllegalStateException("Simultaneous transition collection not supported"
+                    + " yet. Use {@link #createPendingTransition} for explicit queueing.");
         }
+        Transition transit = new Transition(type, flags, this, mAtm.mWindowManager.mSyncEngine);
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", transit);
+        moveToCollecting(transit);
+        return transit;
+    }
+
+    /** Starts Collecting */
+    private void moveToCollecting(@NonNull Transition transition) {
+        if (mCollectingTransition != null) {
+            throw new IllegalStateException("Simultaneous transition collection not supported.");
+        }
+        mCollectingTransition = transition;
         // Distinguish change type because the response time is usually expected to be not too long.
-        final long timeoutMs = type == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
-        mCollectingTransition = new Transition(type, flags, timeoutMs, this,
-                mAtm.mWindowManager.mSyncEngine);
-        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s",
+        final long timeoutMs =
+                transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
+        mCollectingTransition.startCollecting(timeoutMs);
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
                 mCollectingTransition);
         dispatchLegacyAppTransitionPending();
-        return mCollectingTransition;
+    }
+
+    /** Creates a transition representation but doesn't start collecting. */
+    @NonNull
+    PendingStartTransition createPendingTransition(@WindowManager.TransitionType int type) {
+        if (mTransitionPlayer == null) {
+            throw new IllegalStateException("Shell Transitions not enabled");
+        }
+        final PendingStartTransition out = new PendingStartTransition(new Transition(type,
+                0 /* flags */, this, mAtm.mWindowManager.mSyncEngine));
+        // We want to start collecting immediately when the engine is free, otherwise it may
+        // be busy again.
+        out.setStartSync(() -> {
+            moveToCollecting(out.mTransition);
+        });
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating PendingTransition: %s",
+                out.mTransition);
+        return out;
     }
 
     void registerTransitionPlayer(@Nullable ITransitionPlayer player,
@@ -507,6 +537,15 @@
         proto.end(token);
     }
 
+    /** Represents a startTransition call made while there is other active BLAST SyncGroup. */
+    class PendingStartTransition extends WindowOrganizerController.PendingTransaction {
+        final Transition mTransition;
+
+        PendingStartTransition(Transition transition) {
+            mTransition = transition;
+        }
+    }
+
     static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
         private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
 
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index fc154a8..36bb55e 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -28,9 +28,6 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.view.DisplayInfo;
-import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.view.animation.Animation;
 
 import com.android.internal.protolog.common.ProtoLog;
@@ -190,23 +187,6 @@
         setVisible(visible);
     }
 
-    @Override
-    void adjustWindowParams(WindowState win, WindowManager.LayoutParams attrs) {
-        if (attrs.height == ViewGroup.LayoutParams.MATCH_PARENT
-                || attrs.width == ViewGroup.LayoutParams.MATCH_PARENT) {
-            return;
-        }
-
-        final DisplayInfo displayInfo = win.getDisplayInfo();
-
-        final float layoutScale = Math.max(
-                (float) displayInfo.logicalHeight / (float) attrs.height,
-                (float) displayInfo.logicalWidth / (float) attrs.width);
-        attrs.height = (int) (attrs.height * layoutScale);
-        attrs.width = (int) (attrs.width * layoutScale);
-        attrs.flags |= WindowManager.LayoutParams.FLAG_SCALED;
-    }
-
     boolean hasVisibleNotDrawnWallpaper() {
         if (!isVisible()) return false;
         for (int j = mChildren.size() - 1; j >= 0; --j) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4006848..11d1983 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -85,8 +85,8 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
 import android.view.SurfaceControl.Builder;
+import android.view.SurfaceControlViewHost;
 import android.view.SurfaceSession;
 import android.view.TaskTransitionSpec;
 import android.view.WindowManager;
@@ -661,6 +661,11 @@
         }
     }
 
+    /** Returns the total number of descendants, including self. */
+    int getTreeWeight() {
+        return mTreeWeight;
+    }
+
     /**
      * @return The index of this element in the hierarchy tree in prefix order.
      */
@@ -3331,7 +3336,10 @@
         ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "setSyncGroup #%d on %s", group.mSyncId, this);
         if (group != null) {
             if (mSyncGroup != null && mSyncGroup != group) {
-                throw new IllegalStateException("Can't sync on 2 engines simultaneously");
+                // This can still happen if WMCore starts a new transition when there is ongoing
+                // sync transaction from Shell. Please file a bug if it happens.
+                throw new IllegalStateException("Can't sync on 2 engines simultaneously"
+                        + " currentSyncId=" + mSyncGroup.mSyncId + " newSyncId=" + group.mSyncId);
             }
         }
         mSyncGroup = group;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 026b9e1..5397c48 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2105,6 +2105,19 @@
         Slog.i(tag, s, e);
     }
 
+    void clearTouchableRegion(Session session, IWindow client) {
+        int uid = Binder.getCallingUid();
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                WindowState w = windowForClientLocked(session, client, false);
+                w.clearClientTouchableRegion();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets,
             Rect visibleInsets, Region touchableRegion) {
         int uid = Binder.getCallingUid();
@@ -2130,6 +2143,7 @@
                     }
                     w.setDisplayLayoutNeeded();
                     mWindowPlacerLocked.performSurfacePlacement();
+                    w.getDisplayContent().getInputMonitor().updateInputWindowsLw(true);
 
                     // We need to report touchable region changes to accessibility.
                     if (mAccessibilityController.hasCallbacks()) {
@@ -2180,9 +2194,9 @@
 
     public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewVisibility, int flags,
-            long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
+            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
             SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
+            InsetsSourceControl[] outActiveControls) {
         Arrays.fill(outActiveControls, null);
         int result = 0;
         boolean configChanged;
@@ -2202,14 +2216,11 @@
                 win.setRequestedSize(requestedWidth, requestedHeight);
             }
 
-            win.setFrameNumber(frameNumber);
-
             int attrChanges = 0;
             int flagChanges = 0;
             int privateFlagChanges = 0;
             if (attrs != null) {
                 displayPolicy.adjustWindowParamsLw(win, attrs);
-                win.mToken.adjustWindowParams(win, attrs);
                 attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid);
                 attrs.inputFeatures = sanitizeSpyWindow(attrs.inputFeatures, win.getName(), uid,
                         pid);
@@ -2497,11 +2508,6 @@
                 displayContent.sendNewConfiguration();
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             }
-            if (winAnimator.mSurfaceController != null) {
-                win.calculateSurfaceBounds(win.getLayoutingAttrs(
-                        win.getWindowConfiguration().getRotation()), mTmpRect);
-                outSurfaceSize.set(mTmpRect.width(), mTmpRect.height());
-            }
             getInsetsSourceControls(win, outActiveControls);
         }
 
@@ -2580,7 +2586,7 @@
         WindowSurfaceController surfaceController;
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
-            surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type);
+            surfaceController = winAnimator.createSurfaceLocked();
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
@@ -2616,6 +2622,10 @@
 
     void finishDrawingWindow(Session session, IWindow client,
             @Nullable SurfaceControl.Transaction postDrawTransaction) {
+        if (postDrawTransaction != null) {
+            postDrawTransaction.sanitize();
+        }
+
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -6420,7 +6430,6 @@
         pw.print("  mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
         pw.print("  mHasPermanentDpad="); pw.println(mHasPermanentDpad);
         mRoot.dumpTopFocusedDisplayId(pw);
-        mRoot.dumpDefaultMinSizeOfResizableTask(pw);
         mRoot.forAllDisplays(dc -> {
             final int displayId = dc.getDisplayId();
             final InsetsControlTarget imeLayeringTarget = dc.getImeTarget(IME_TARGET_LAYERING);
@@ -6438,6 +6447,8 @@
                 pw.print("  imeControlTarget in display# "); pw.print(displayId);
                 pw.print(' '); pw.println(imeControlTarget);
             }
+            pw.print("  Minimum task size of display#"); pw.print(displayId);
+            pw.print(' '); pw.print(dc.mMinSizeOfResizeableTaskDp);
         });
         pw.print("  mInTouchMode="); pw.println(mInTouchMode);
         pw.print("  mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
@@ -6611,6 +6622,7 @@
                 pw.println("    d[isplays]: active display contents");
                 pw.println("    t[okens]: token list");
                 pw.println("    w[indows]: window list");
+                pw.println("    package-config: installed packages having app-specific config");
                 pw.println("    trace: print trace status and write Winscope trace to file");
                 pw.println("  cmd may also be a NAME to dump windows.  NAME may");
                 pw.println("    be a partial substring in a window name, a");
@@ -6697,6 +6709,9 @@
             } else if ("constants".equals(cmd)) {
                 mConstants.dump(pw);
                 return;
+            } else if ("package-config".equals(cmd)) {
+                mAtmService.dumpInstalledPackagesConfig(pw);
+                return;
             } else {
                 // Dumping a single name?
                 if (!dumpWindows(pw, cmd, args, opti, dumpAll)) {
@@ -6763,6 +6778,10 @@
             if (dumpAll) {
                 pw.println(separator);
             }
+            mAtmService.dumpInstalledPackagesConfig(pw);
+            if (dumpAll) {
+                pw.println(separator);
+            }
             mConstants.dump(pw);
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 455856c..27024ce 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -76,6 +76,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.function.pooled.PooledConsumer;
@@ -94,7 +95,7 @@
  * @see android.window.WindowOrganizer
  */
 class WindowOrganizerController extends IWindowOrganizerController.Stub
-    implements BLASTSyncEngine.TransactionReadyListener {
+        implements BLASTSyncEngine.TransactionReadyListener, BLASTSyncEngine.SyncEngineListener {
 
     private static final String TAG = "WindowOrganizerController";
 
@@ -117,6 +118,21 @@
     private final HashMap<Integer, IWindowContainerTransactionCallback>
             mTransactionCallbacksByPendingSyncId = new HashMap();
 
+    /**
+     * A queue of transaction waiting for their turn to sync. Currently {@link BLASTSyncEngine} only
+     * supports 1 sync at a time, so we have to queue them.
+     *
+     * WMCore has enough information to ensure that it won't end up collecting multiple transitions
+     * in parallel by itself; however, Shell can start transitions/apply sync transaction at
+     * arbitrary times via {@link WindowOrganizerController#startTransition} and
+     * {@link WindowOrganizerController#applySyncTransaction}, so we have to support those coming in
+     * at any time (even while already syncing).
+     *
+     * This is really just a back-up for unrealistic situations (eg. during tests). In practice,
+     * this shouldn't ever happen.
+     */
+    private final ArrayList<PendingTransaction> mPendingTransactions = new ArrayList<>();
+
     final TaskOrganizerController mTaskOrganizerController;
     final DisplayAreaOrganizerController mDisplayAreaOrganizerController;
     final TaskFragmentOrganizerController mTaskFragmentOrganizerController;
@@ -140,6 +156,7 @@
     void setWindowManager(WindowManagerService wms) {
         mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController);
         mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
+        wms.mSyncEngine.setSyncEngineListener(this);
     }
 
     TransitionController getTransitionController() {
@@ -184,6 +201,11 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
+                if (callback == null) {
+                    applyTransaction(t, -1 /* syncId*/, null /*transition*/, caller);
+                    return -1;
+                }
+
                 /**
                  * If callback is non-null we are looking to synchronize this transaction by
                  * collecting all the results in to a SurfaceFlinger transaction and then delivering
@@ -196,13 +218,25 @@
                  * all the WindowContainers will eventually finish applying their changes and notify
                  * the BLASTSyncEngine which will deliver the Transaction to the callback.
                  */
-                int syncId = -1;
-                if (callback != null) {
-                    syncId = startSyncWithOrganizer(callback);
-                }
-                applyTransaction(t, syncId, null /*transition*/, caller);
-                if (syncId >= 0) {
+                final BLASTSyncEngine.SyncGroup syncGroup = prepareSyncWithOrganizer(callback);
+                final int syncId = syncGroup.mSyncId;
+                if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) {
+                    mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup);
+                    applyTransaction(t, syncId, null /*transition*/, caller);
                     setSyncReady(syncId);
+                } else {
+                    // Because the BLAST engine only supports one sync at a time, queue the
+                    // transaction.
+                    final PendingTransaction pt = new PendingTransaction();
+                    // Start sync group immediately when the SyncEngine is free.
+                    pt.setStartSync(() ->
+                            mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup));
+                    // Those will be post so that it won't interrupt ongoing transition.
+                    pt.setStartTransaction(() -> {
+                        applyTransaction(t, syncId, null /*transition*/, caller);
+                        setSyncReady(syncId);
+                    });
+                    mPendingTransactions.add(pt);
                 }
                 return syncId;
             }
@@ -220,31 +254,49 @@
         try {
             synchronized (mGlobalLock) {
                 Transition transition = Transition.fromBinder(transitionToken);
+                if (mTransitionController.getTransitionPlayer() == null && transition == null) {
+                    Slog.w(TAG, "Using shell transitions API for legacy transitions.");
+                    if (t == null) {
+                        throw new IllegalArgumentException("Can't use legacy transitions in"
+                                + " compatibility mode with no WCT.");
+                    }
+                    applyTransaction(t, -1 /* syncId */, null, caller);
+                    return null;
+                }
                 // In cases where transition is already provided, the "readiness lifecycle" of the
                 // transition is determined outside of this transaction. However, if this is a
                 // direct call from shell, the entire transition lifecycle is contained in the
                 // provided transaction and thus we can setReady immediately after apply.
-                boolean needsSetReady = transition == null && t != null;
+                final boolean needsSetReady = transition == null && t != null;
+                final WindowContainerTransaction wct =
+                        t != null ? t : new WindowContainerTransaction();
                 if (transition == null) {
                     if (type < 0) {
                         throw new IllegalArgumentException("Can't create transition with no type");
                     }
-                    if (mTransitionController.getTransitionPlayer() == null) {
-                        Slog.w(TAG, "Using shell transitions API for legacy transitions.");
-                        if (t == null) {
-                            throw new IllegalArgumentException("Can't use legacy transitions in"
-                                    + " compatibility mode with no WCT.");
-                        }
-                        applyTransaction(t, -1 /* syncId */, null, caller);
-                        return null;
+                    // If there is already a collecting transition, queue up a new transition and
+                    // return that. The actual start and apply will then be deferred until that
+                    // transition starts collecting. This should almost never happen except during
+                    // tests.
+                    if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
+                        Slog.e(TAG, "startTransition() while one is already collecting.");
+                        final TransitionController.PendingStartTransition pt =
+                                mTransitionController.createPendingTransition(type);
+                        // Those will be post so that it won't interrupt ongoing transition.
+                        pt.setStartTransaction(() -> {
+                            pt.mTransition.start();
+                            applyTransaction(wct, -1 /*syncId*/, pt.mTransition, caller);
+                            if (needsSetReady) {
+                                pt.mTransition.setAllReady();
+                            }
+                        });
+                        mPendingTransactions.add(pt);
+                        return pt.mTransition;
                     }
                     transition = mTransitionController.createTransition(type);
                 }
                 transition.start();
-                if (t == null) {
-                    t = new WindowContainerTransaction();
-                }
-                applyTransaction(t, -1 /*syncId*/, transition, caller);
+                applyTransaction(wct, -1 /*syncId*/, transition, caller);
                 if (needsSetReady) {
                     transition.setAllReady();
                 }
@@ -1036,11 +1088,24 @@
         return mTaskFragmentOrganizerController;
     }
 
+    /**
+     * This will prepare a {@link BLASTSyncEngine.SyncGroup} for the organizer to track, but the
+     * {@link BLASTSyncEngine.SyncGroup} may not be active until the {@link BLASTSyncEngine} is
+     * free.
+     */
+    private BLASTSyncEngine.SyncGroup prepareSyncWithOrganizer(
+            IWindowContainerTransactionCallback callback) {
+        final BLASTSyncEngine.SyncGroup s = mService.mWindowManager.mSyncEngine
+                .prepareSyncSet(this, "");
+        mTransactionCallbacksByPendingSyncId.put(s.mSyncId, callback);
+        return s;
+    }
+
     @VisibleForTesting
     int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) {
-        int id = mService.mWindowManager.mSyncEngine.startSyncSet(this);
-        mTransactionCallbacksByPendingSyncId.put(id, callback);
-        return id;
+        final BLASTSyncEngine.SyncGroup s = prepareSyncWithOrganizer(callback);
+        mService.mWindowManager.mSyncEngine.startSyncSet(s);
+        return s.mSyncId;
     }
 
     @VisibleForTesting
@@ -1072,6 +1137,19 @@
     }
 
     @Override
+    public void onSyncEngineFree() {
+        if (mPendingTransactions.isEmpty()) {
+            return;
+        }
+
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "PendingStartTransaction found");
+        final PendingTransaction pt = mPendingTransactions.remove(0);
+        pt.startSync();
+        // Post this so that the now-playing transition setup isn't interrupted.
+        mService.mH.post(pt::startTransaction);
+    }
+
+    @Override
     public void registerTransitionPlayer(ITransitionPlayer player) {
         enforceTaskPermission("registerTransitionPlayer()");
         final int callerPid = Binder.getCallingPid();
@@ -1329,4 +1407,38 @@
                         + result + " when starting " + intent);
         }
     }
+
+    /**
+     *  Represents a sync {@link WindowContainerTransaction} call made while there is other active
+     *  {@link BLASTSyncEngine.SyncGroup}.
+     */
+    static class PendingTransaction {
+        private Runnable mStartSync;
+        private Runnable mStartTransaction;
+
+        /**
+         * The callback will be called immediately when the {@link BLASTSyncEngine} is free. One
+         * should call {@link BLASTSyncEngine#startSyncSet(BLASTSyncEngine.SyncGroup)} here to
+         * reserve the {@link BLASTSyncEngine}.
+         */
+        void setStartSync(@NonNull Runnable callback) {
+            mStartSync = callback;
+        }
+
+        /**
+         * The callback will be post to the main handler after the {@link BLASTSyncEngine} is free
+         * to apply the pending {@link WindowContainerTransaction}.
+         */
+        void setStartTransaction(@NonNull Runnable callback) {
+            mStartTransaction = callback;
+        }
+
+        private void startSync() {
+            mStartSync.run();
+        }
+
+        private void startTransaction() {
+            mStartTransaction.run();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1f83767..8864b98 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -163,7 +163,6 @@
 import static com.android.server.wm.WindowStateProto.ATTRIBUTES;
 import static com.android.server.wm.WindowStateProto.DESTROYING;
 import static com.android.server.wm.WindowStateProto.DISPLAY_ID;
-import static com.android.server.wm.WindowStateProto.FINISHED_SEAMLESS_ROTATION_FRAME;
 import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION;
 import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS;
 import static com.android.server.wm.WindowStateProto.GLOBAL_SCALE;
@@ -380,7 +379,6 @@
      */
     final boolean mForceSeamlesslyRotate;
     SeamlessRotator mPendingSeamlessRotate;
-    long mFinishSeamlessRotateFrameNumber;
 
     private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
 
@@ -654,6 +652,7 @@
 
     private final Rect mTmpRect = new Rect();
     private final Point mTmpPoint = new Point();
+    private final Region mTmpRegion = new Region();
 
     private final Transaction mTmpTransaction;
 
@@ -706,11 +705,6 @@
      */
     private PowerManagerWrapper mPowerManagerWrapper;
 
-    /**
-     * A frame number in which changes requested in this layout will be rendered.
-     */
-    private long mFrameNumber = -1;
-
     private static final StringBuilder sTmpSB = new StringBuilder();
 
     /**
@@ -969,7 +963,6 @@
         }
 
         mPendingSeamlessRotate.finish(t, this);
-        mFinishSeamlessRotateFrameNumber = getFrameNumber();
         mPendingSeamlessRotate = null;
 
         getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
@@ -1418,8 +1411,8 @@
         return mWindowFrames.mParentFrame;
     }
 
-    void getCompatFrameSize(Rect outFrame) {
-        outFrame.set(0, 0, mWindowFrames.mCompatFrame.width(), mWindowFrames.mCompatFrame.height());
+    Rect getCompatFrame() {
+        return mWindowFrames.mCompatFrame;
     }
 
     WindowManager.LayoutParams getAttrs() {
@@ -1563,7 +1556,7 @@
             }
         } else {
             // The orientation change is completed. If it was hidden by the animation, reshow it.
-            mDisplayContent.finishFadeRotationAnimation(mToken);
+            mDisplayContent.finishAsyncRotation(mToken);
         }
     }
 
@@ -1608,6 +1601,15 @@
         return getDisplayContent().getDisplayInfo();
     }
 
+    @Override
+    public Rect getMaxBounds() {
+        final Rect maxBounds = mToken.getFixedRotationTransformMaxBounds();
+        if (maxBounds != null) {
+            return maxBounds;
+        }
+        return super.getMaxBounds();
+    }
+
     /**
      * Returns the insets state for the window. Its sources may be the copies with visibility
      * modification according to the state of transient bars.
@@ -1729,20 +1731,6 @@
             } else {
                 intersectWithRootTaskBounds = false;
             }
-            if (inSplitScreenPrimaryWindowingMode()) {
-                // If this is in the primary split and the root home task is the top visible task in
-                // the secondary split, it means this is "minimized" and thus must prevent
-                // overlapping with home.
-                // TODO(b/158242495): get rid of this when drag/drop can use surface bounds.
-                final Task rootSecondary =
-                        task.getDisplayArea().getRootSplitScreenSecondaryTask();
-                if (rootSecondary.isActivityTypeHome() || rootSecondary.isActivityTypeRecents()) {
-                    final WindowContainer topTask = rootSecondary.getTopChild();
-                    if (topTask.isVisible()) {
-                        cutRect(mTmpRect, topTask.getBounds());
-                    }
-                }
-            }
         }
 
         bounds.set(mWindowFrames.mFrame);
@@ -2773,6 +2761,7 @@
                 region.set(-dw, -dh, dw + dw, dh + dh);
             }
             subtractTouchExcludeRegionIfNeeded(region);
+
         } else {
             // Not modal
             getTouchableRegion(region);
@@ -2783,6 +2772,14 @@
         if (frame.left != 0 || frame.top != 0) {
             region.translate(-frame.left, -frame.top);
         }
+        if (modal && mTouchableInsets == TOUCHABLE_INSETS_REGION) {
+            // The client gave us a touchable region and so first
+            // we calculate the untouchable region, then punch that out of our
+            // expanded modal region.
+            mTmpRegion.set(0, 0, frame.right, frame.bottom);
+            mTmpRegion.op(mGivenTouchableRegion, Region.Op.DIFFERENCE);
+            region.op(mTmpRegion, Region.Op.DIFFERENCE);
+        }
 
         // TODO(b/139804591): sizecompat layout needs to be reworked. Currently mFrame is post-
         // scaling but the existing logic doesn't expect that. The result is that the already-
@@ -4113,7 +4110,6 @@
         proto.write(IS_ON_SCREEN, isOnScreen());
         proto.write(IS_VISIBLE, isVisible);
         proto.write(PENDING_SEAMLESS_ROTATION, mPendingSeamlessRotate != null);
-        proto.write(FINISHED_SEAMLESS_ROTATION_FRAME, mFinishSeamlessRotateFrameNumber);
         proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
         proto.write(HAS_COMPAT_SCALE, hasCompatScale());
         proto.write(GLOBAL_SCALE, mGlobalScale);
@@ -4255,7 +4251,6 @@
         } else {
             pw.print("null");
         }
-        pw.println(" finishedFrameNumber=" + mFinishSeamlessRotateFrameNumber);
 
         if (mHScale != 1 || mVScale != 1) {
             pw.println(prefix + "mHScale=" + mHScale
@@ -4705,14 +4700,6 @@
         if (!isImeLayeringTarget()) {
             return false;
         }
-        // If we are in split screen which case we process the IME at the DisplayContent level to
-        // ensure it is above the docked divider.
-        // i.e. Like {@link DisplayContent.ImeContainer#skipImeWindowsDuringTraversal}, the IME
-        // window will be ignored to traverse when the IME target is still in split-screen mode.
-        if (mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()
-                && getTask() != null) {
-            return false;
-        }
         // Note that we don't process IME window if the IME input target is not on the screen.
         // In case some unexpected IME visibility cases happen like starting the remote
         // animation on the keyguard but seeing the IME window that originally on the app
@@ -5624,16 +5611,6 @@
         return target != null ? target.getWindow() : null;
     }
 
-    long getFrameNumber() {
-        // Return the frame number in which changes requested in this layout will be rendered or
-        // -1 if we do not expect the frame to be rendered.
-        return getFrame().isEmpty() ? -1 : mFrameNumber;
-    }
-
-    void setFrameNumber(long frameNumber) {
-        mFrameNumber = frameNumber;
-    }
-
     void forceReportingResized() {
         mWindowFrames.forceReportingResized();
     }
@@ -5820,10 +5797,10 @@
 
         boolean skipLayout = false;
         // Control the timing to switch the appearance of window with different rotations.
-        final FadeRotationAnimationController fadeRotationController =
-                mDisplayContent.getFadeRotationAnimationController();
-        if (fadeRotationController != null
-                && fadeRotationController.handleFinishDrawing(this, postDrawTransaction)) {
+        final AsyncRotationController asyncRotationController =
+                mDisplayContent.getAsyncRotationController();
+        if (asyncRotationController != null
+                && asyncRotationController.handleFinishDrawing(this, postDrawTransaction)) {
             // Consume the transaction because the controller will apply it with fade animation.
             // Layout is not needed because the window will be hidden by the fade leash. Clear
             // sync state because its sync transaction doesn't need to be merged to sync group.
@@ -5904,39 +5881,6 @@
         mRedrawForSyncReported = false;
     }
 
-    void calculateSurfaceBounds(WindowManager.LayoutParams attrs, Rect outSize) {
-        outSize.setEmpty();
-        if ((attrs.flags & FLAG_SCALED) != 0) {
-            // For a scaled surface, we always want the requested size.
-            outSize.right = mRequestedWidth;
-            outSize.bottom = mRequestedHeight;
-        } else {
-            // When we're doing a drag-resizing, request a surface that's fullscreen size,
-            // so that we don't need to reallocate during the process. This also prevents
-            // buffer drops due to size mismatch.
-            if (isDragResizing()) {
-                final DisplayInfo displayInfo = getDisplayInfo();
-                outSize.right = displayInfo.logicalWidth;
-                outSize.bottom = displayInfo.logicalHeight;
-            } else {
-                getCompatFrameSize(outSize);
-            }
-        }
-
-        // This doesn't necessarily mean that there is an error in the system. The sizes might be
-        // incorrect, because it is before the first layout or draw.
-        if (outSize.width() < 1) {
-            outSize.right = 1;
-        }
-        if (outSize.height() < 1) {
-            outSize.bottom = 1;
-        }
-
-        // Adjust for surface insets.
-        outSize.inset(-attrs.surfaceInsets.left, -attrs.surfaceInsets.top,
-                -attrs.surfaceInsets.right, -attrs.surfaceInsets.bottom);
-    }
-
     /**
      * This method is used to control whether we return the BLAST_SYNC flag
      * from relayoutWindow calls on this window (triggering the client to redirect
@@ -6062,4 +6006,9 @@
         }
         mWmService.handleTaskFocusChange(getTask(), mActivityRecord);
     }
+
+    void clearClientTouchableRegion() {
+        mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+        mGivenTouchableRegion.setEmpty();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 316051e..c17961e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -150,8 +150,6 @@
 
     int mAttrType;
 
-    private final Rect mTmpSize = new Rect();
-
     /**
      * Handles surface changes synchronized to after the client has drawn the surface. This
      * transaction is currently used to reparent the old surface children to the new surface once
@@ -291,7 +289,7 @@
         }
     }
 
-    WindowSurfaceController createSurfaceLocked(int windowType) {
+    WindowSurfaceController createSurfaceLocked() {
         final WindowState w = mWin;
 
         if (mSurfaceController != null) {
@@ -319,16 +317,9 @@
             flags |= SurfaceControl.SKIP_SCREENSHOT;
         }
 
-        w.calculateSurfaceBounds(attrs, mTmpSize);
-
-        final int width = mTmpSize.width();
-        final int height = mTmpSize.height();
-
         if (DEBUG_VISIBILITY) {
             Slog.v(TAG, "Creating surface in session "
                     + mSession.mSurfaceSession + " window " + this
-                    + " w=" + width + " h=" + height
-                    + " x=" + mTmpSize.left + " y=" + mTmpSize.top
                     + " format=" + attrs.format + " flags=" + flags);
         }
 
@@ -339,8 +330,8 @@
             final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
             final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
 
-            mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), width,
-                    height, format, flags, this, windowType);
+            mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
+                    flags, this, attrs.type);
             mSurfaceController.setColorSpaceAgnostic((attrs.privateFlags
                     & WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
 
@@ -372,8 +363,7 @@
         if (SHOW_LIGHT_TRANSACTIONS) {
             Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
             WindowManagerService.logSurface(w, "CREATE pos=("
-                    + w.getFrame().left + "," + w.getFrame().top + ") ("
-                    + width + "x" + height + ")" + " HIDE", false);
+                    + w.getFrame().left + "," + w.getFrame().top + ") HIDE", false);
         }
 
         mLastHidden = true;
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index fa0d708..665857c 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -31,7 +31,6 @@
 import static com.android.server.wm.WindowSurfaceControllerProto.LAYER;
 import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
 
-import android.graphics.Region;
 import android.os.Debug;
 import android.os.Trace;
 import android.util.Slog;
@@ -76,8 +75,8 @@
     // Used to track whether we have called detach children on the way to invisibility.
     boolean mChildrenDetached;
 
-    WindowSurfaceController(String name, int w, int h, int format,
-            int flags, WindowStateAnimator animator, int windowType) {
+    WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
+            int windowType) {
         mAnimator = animator;
 
         title = name;
@@ -91,7 +90,6 @@
         final SurfaceControl.Builder b = win.makeSurface()
                 .setParent(win.getSurfaceControl())
                 .setName(name)
-                .setBufferSize(w, h)
                 .setFormat(format)
                 .setFlags(flags)
                 .setMetadata(METADATA_WINDOW_TYPE, windowType)
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index e5a3b7a..6d8203c 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -430,6 +430,13 @@
         return isFixedRotationTransforming() ? mFixedRotationTransformState.mDisplayFrames : null;
     }
 
+    Rect getFixedRotationTransformMaxBounds() {
+        return isFixedRotationTransforming()
+                ? mFixedRotationTransformState.mRotatedOverrideConfiguration.windowConfiguration
+                .getMaxBounds()
+                : null;
+    }
+
     Rect getFixedRotationTransformDisplayBounds() {
         return isFixedRotationTransforming()
                 ? mFixedRotationTransformState.mRotatedOverrideConfiguration.windowConfiguration
@@ -646,14 +653,6 @@
         }
     }
 
-    /**
-     * Gives a chance to this {@link WindowToken} to adjust the {@link
-     * android.view.WindowManager.LayoutParams} of its windows.
-     */
-    void adjustWindowParams(WindowState win, WindowManager.LayoutParams attrs) {
-    }
-
-
     @CallSuper
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
diff --git a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
deleted file mode 100644
index 59abaab..0000000
--- a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.utils;
-
-import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Utility to compute bounds after rotating the screen.
- */
-public class DisplayRotationUtil {
-    private final Matrix mTmpMatrix = new Matrix();
-
-    private static int getRotationToBoundsOffset(int rotation) {
-        switch (rotation) {
-            case ROTATION_0:
-                return 0;
-            case ROTATION_90:
-                return -1;
-            case ROTATION_180:
-                return 2;
-            case ROTATION_270:
-                return 1;
-            default:
-                // should not happen
-                return 0;
-        }
-    }
-
-    @VisibleForTesting
-    static int getBoundIndexFromRotation(int i, int rotation) {
-        return Math.floorMod(i + getRotationToBoundsOffset(rotation),
-                BOUNDS_POSITION_LENGTH);
-    }
-
-    /**
-     * Compute bounds after rotating the screen.
-     *
-     * @param bounds Bounds before the rotation. The array must contain exactly 4 non-null elements.
-     * @param rotation rotation constant defined in android.view.Surface.
-     * @param initialDisplayWidth width of the display before the rotation.
-     * @param initialDisplayHeight height of the display before the rotation.
-     * @return Bounds after the rotation.
-     *
-     * @hide
-     */
-    public Rect[] getRotatedBounds(
-            Rect[] bounds, int rotation, int initialDisplayWidth, int initialDisplayHeight) {
-        if (bounds.length != BOUNDS_POSITION_LENGTH) {
-            throw new IllegalArgumentException(
-                    "bounds must have exactly 4 elements: bounds=" + bounds);
-        }
-        if (rotation == ROTATION_0) {
-            return bounds;
-        }
-        transformPhysicalToLogicalCoordinates(rotation, initialDisplayWidth, initialDisplayHeight,
-                mTmpMatrix);
-        Rect[] newBounds = new Rect[BOUNDS_POSITION_LENGTH];
-        for (int i = 0; i < bounds.length; i++) {
-
-            final Rect rect = bounds[i];
-            if (!rect.isEmpty()) {
-                final RectF rectF = new RectF(rect);
-                mTmpMatrix.mapRect(rectF);
-                rectF.round(rect);
-            }
-            newBounds[getBoundIndexFromRotation(i, rotation)] = rect;
-        }
-        return newBounds;
-    }
-}
diff --git a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
index ee3f4f4d..a45771e 100644
--- a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
+++ b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
@@ -19,7 +19,6 @@
 import android.graphics.Rect;
 import android.util.Size;
 import android.view.DisplayCutout;
-import android.view.Gravity;
 
 import java.util.Objects;
 
@@ -52,7 +51,7 @@
             return NO_CUTOUT;
         }
         final Size displaySize = new Size(displayWidth, displayHeight);
-        final Rect safeInsets = computeSafeInsets(displaySize, inner);
+        final Rect safeInsets = DisplayCutout.computeSafeInsets(displayWidth, displayHeight, inner);
         return new WmDisplayCutout(inner.replaceSafeInsets(safeInsets), displaySize);
     }
 
@@ -66,45 +65,6 @@
         return computeSafeInsets(mInner, width, height);
     }
 
-    private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
-        if (displaySize.getWidth() == displaySize.getHeight()) {
-            throw new UnsupportedOperationException("not implemented: display=" + displaySize +
-                    " cutout=" + cutout);
-        }
-
-        int leftInset = Math.max(cutout.getWaterfallInsets().left,
-                findCutoutInsetForSide(displaySize, cutout.getBoundingRectLeft(), Gravity.LEFT));
-        int topInset = Math.max(cutout.getWaterfallInsets().top,
-                findCutoutInsetForSide(displaySize, cutout.getBoundingRectTop(), Gravity.TOP));
-        int rightInset = Math.max(cutout.getWaterfallInsets().right,
-                findCutoutInsetForSide(displaySize, cutout.getBoundingRectRight(), Gravity.RIGHT));
-        int bottomInset = Math.max(cutout.getWaterfallInsets().bottom,
-                findCutoutInsetForSide(displaySize, cutout.getBoundingRectBottom(),
-                        Gravity.BOTTOM));
-
-        return new Rect(leftInset, topInset, rightInset, bottomInset);
-    }
-
-    private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) {
-        if (boundingRect.isEmpty()) {
-            return 0;
-        }
-
-        int inset = 0;
-        switch (gravity) {
-            case Gravity.TOP:
-                return Math.max(inset, boundingRect.bottom);
-            case Gravity.BOTTOM:
-                return Math.max(inset, display.getHeight() - boundingRect.top);
-            case Gravity.LEFT:
-                return Math.max(inset, boundingRect.right);
-            case Gravity.RIGHT:
-                return Math.max(inset, display.getWidth() - boundingRect.left);
-            default:
-                throw new IllegalArgumentException("unknown gravity: " + gravity);
-        }
-    }
-
     public DisplayCutout getDisplayCutout() {
         return mInner;
     }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 79a980f..95ef5f7 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -145,8 +145,8 @@
         "libutils",
         "libhwui",
         "libbpf_android",
-        "libnetdbpf",
         "libnetdutils",
+        "libnetworkstats",
         "libpsi",
         "libdataloader",
         "libincfs",
diff --git a/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp b/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
index 1c574fb..436ac1b 100644
--- a/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
+++ b/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
@@ -25,7 +25,6 @@
 
 #include <stdio.h>
 #include <string.h>
-#include <asm/byteorder.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
index d29d3fc..9917bcb 100644
--- a/services/core/jni/com_android_server_UsbDescriptorParser.cpp
+++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
@@ -15,16 +15,14 @@
  */
 
 #define LOG_TAG "UsbHostManagerJNI"
-#include "utils/Log.h"
-
+#include <nativehelper/JNIHelp.h>
 #include <stdlib.h>
+#include <usbhost/usbhost.h>
+#include <usbhost/usbhost_jni.h>
 
 #include "jni.h"
-#include <nativehelper/JNIHelp.h>
+#include "utils/Log.h"
 
-#include <usbhost/usbhost.h>
-
-#define MAX_DESCRIPTORS_LENGTH 4096
 static const int USB_CONTROL_TRANSFER_TIMEOUT_MS = 200;
 
 // com.android.server.usb.descriptors
@@ -41,26 +39,9 @@
     }
 
     int fd = usb_device_get_fd(device);
-    if (fd < 0) {
-        usb_device_close(device);
-        return NULL;
-    }
-
-    // from android_hardware_UsbDeviceConnection_get_desc()
-    jbyte buffer[MAX_DESCRIPTORS_LENGTH];
-    lseek(fd, 0, SEEK_SET);
-    int numBytes = read(fd, buffer, sizeof(buffer));
-    jbyteArray ret = NULL;
+    jbyteArray descriptors = usb_jni_read_descriptors(env, fd);
     usb_device_close(device);
-
-    if (numBytes > 0) {
-        ret = env->NewByteArray(numBytes);
-        env->SetByteArrayRegion(ret, 0, numBytes, buffer);
-    } else {
-        ALOGE("error reading descriptors\n");
-    }
-
-    return ret;
+    return descriptors;
 }
 
 jstring JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getDescriptorString_1native(
diff --git a/services/core/jni/com_android_server_UsbDeviceManager.cpp b/services/core/jni/com_android_server_UsbDeviceManager.cpp
index 3ab5920..0a9ce2f 100644
--- a/services/core/jni/com_android_server_UsbDeviceManager.cpp
+++ b/services/core/jni/com_android_server_UsbDeviceManager.cpp
@@ -25,7 +25,6 @@
 #include "MtpDescriptors.h"
 
 #include <stdio.h>
-#include <asm/byteorder.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp
index a629b69..e29d2ca 100644
--- a/services/core/jni/com_android_server_UsbHostManager.cpp
+++ b/services/core/jni/com_android_server_UsbHostManager.cpp
@@ -23,7 +23,6 @@
 #include "android_runtime/Log.h"
 
 #include <stdio.h>
-#include <asm/byteorder.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
@@ -31,8 +30,6 @@
 
 #include <usbhost/usbhost.h>
 
-#define MAX_DESCRIPTORS_LENGTH 4096
-
 namespace android
 {
 
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index c71686a..df5fb28 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -277,6 +277,7 @@
     void setInputDispatchMode(bool enabled, bool frozen);
     void setSystemUiLightsOut(bool lightsOut);
     void setPointerSpeed(int32_t speed);
+    void setPointerAcceleration(float acceleration);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
     void setShowTouches(bool enabled);
     void setInteractive(bool interactive);
@@ -363,6 +364,9 @@
         // Pointer speed.
         int32_t pointerSpeed;
 
+        // Pointer acceleration.
+        float pointerAcceleration;
+
         // True if pointer gestures are enabled.
         bool pointerGesturesEnabled;
 
@@ -412,6 +416,7 @@
         AutoMutex _l(mLock);
         mLocked.systemUiLightsOut = false;
         mLocked.pointerSpeed = 0;
+        mLocked.pointerAcceleration = android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION;
         mLocked.pointerGesturesEnabled = true;
         mLocked.showTouches = false;
         mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
@@ -439,6 +444,7 @@
         dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
                              toString(mLocked.systemUiLightsOut));
         dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
+        dump += StringPrintf(INDENT "Pointer Acceleration: %0.3f\n", mLocked.pointerAcceleration);
         dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
                 toString(mLocked.pointerGesturesEnabled));
         dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
@@ -628,6 +634,7 @@
 
         outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
                 * POINTER_SPEED_EXPONENT);
+        outConfig->pointerVelocityControlParameters.acceleration = mLocked.pointerAcceleration;
         outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
 
         outConfig->showTouches = mLocked.showTouches;
@@ -1066,6 +1073,22 @@
             InputReaderConfiguration::CHANGE_POINTER_SPEED);
 }
 
+void NativeInputManager::setPointerAcceleration(float acceleration) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mLocked.pointerAcceleration == acceleration) {
+            return;
+        }
+
+        ALOGI("Setting pointer acceleration to %0.3f", acceleration);
+        mLocked.pointerAcceleration = acceleration;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_POINTER_SPEED);
+}
+
 void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
     { // acquire lock
         AutoMutex _l(mLock);
@@ -1882,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);
@@ -2373,6 +2403,7 @@
          (void*)nativeTransferTouchFocus},
         {"nativeTransferTouch", "(JLandroid/os/IBinder;)Z", (void*)nativeTransferTouch},
         {"nativeSetPointerSpeed", "(JI)V", (void*)nativeSetPointerSpeed},
+        {"nativeSetPointerAcceleration", "(JF)V", (void*)nativeSetPointerAcceleration},
         {"nativeSetShowTouches", "(JZ)V", (void*)nativeSetShowTouches},
         {"nativeSetInteractive", "(JZ)V", (void*)nativeSetInteractive},
         {"nativeReloadCalibration", "(J)V", (void*)nativeReloadCalibration},
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index baf2ede..574dbfd 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -38,7 +38,8 @@
                     <xs:annotation name="nonnull"/>
                     <xs:annotation name="final"/>
                 </xs:element>
-                <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0" maxOccurs="1"/>
+                <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
+                            maxOccurs="1"/>
                 <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" />
                 <xs:element type="nonNegativeDecimal" name="screenBrightnessRampFastDecrease">
                     <xs:annotation name="final"/>
@@ -67,6 +68,19 @@
                 <xs:element type="xs:nonNegativeInteger" name="ambientLightHorizonShort">
                     <xs:annotation name="final"/>
                 </xs:element>
+
+                <!-- Set of thresholds that dictate the change needed for screen brightness
+                adaptations -->
+                <xs:element type="thresholds" name="displayBrightnessChangeThresholds">
+                    <xs:annotation name="nonnull"/>
+                    <xs:annotation name="final"/>
+                </xs:element>
+                <!-- Set of thresholds that dictate the change needed for ambient brightness
+                adaptations -->
+                <xs:element type="thresholds" name="ambientBrightnessChangeThresholds">
+                    <xs:annotation name="nonnull"/>
+                    <xs:annotation name="final"/>
+                </xs:element>
             </xs:sequence>
         </xs:complexType>
     </xs:element>
@@ -81,7 +95,8 @@
 
     <xs:complexType name="highBrightnessMode">
         <xs:all>
-            <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1" maxOccurs="1">
+            <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
+                        maxOccurs="1">
                 <xs:annotation name="nonnull"/>
                 <xs:annotation name="final"/>
             </xs:element>
@@ -110,7 +125,8 @@
 
     <xs:complexType name="hbmTiming">
         <xs:all>
-            <xs:element name="timeWindowSecs" type="xs:nonNegativeInteger" minOccurs="1" maxOccurs="1">
+            <xs:element name="timeWindowSecs" type="xs:nonNegativeInteger" minOccurs="1"
+                        maxOccurs="1">
                 <xs:annotation name="nonnull"/>
                 <xs:annotation name="final"/>
             </xs:element>
@@ -216,5 +232,33 @@
                 <xs:annotation name="final"/>
             </xs:element>
         </xs:sequence>
+      </xs:complexType>
+
+    <!-- Thresholds for brightness changes. -->
+    <xs:complexType name="thresholds">
+        <xs:sequence>
+            <!-- Brightening thresholds. -->
+            <xs:element name="brighteningThresholds" type="brightnessThresholds" minOccurs="0"
+                        maxOccurs="1" >
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <!-- Darkening thresholds. -->
+            <xs:element name="darkeningThresholds" type="brightnessThresholds" minOccurs="0"
+                        maxOccurs="1" >
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
     </xs:complexType>
+
+    <!-- Brightening and darkening minimum change thresholds. -->
+    <xs:complexType name="brightnessThresholds">
+        <!-- Minimum brightness change needed. -->
+        <xs:element name="minimum" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1" >
+            <xs:annotation name="nonnull"/>
+            <xs:annotation name="final"/>
+        </xs:element>
+    </xs:complexType>
+
 </xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 6f97431..04f0916 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -1,6 +1,12 @@
 // Signature format: 2.0
 package com.android.server.display.config {
 
+  public class BrightnessThresholds {
+    ctor public BrightnessThresholds();
+    method @NonNull public final java.math.BigDecimal getMinimum();
+    method public final void setMinimum(@NonNull java.math.BigDecimal);
+  }
+
   public class Density {
     ctor public Density();
     method @NonNull public final java.math.BigInteger getDensity();
@@ -18,9 +24,11 @@
 
   public class DisplayConfiguration {
     ctor public DisplayConfiguration();
+    method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
     method public final java.math.BigInteger getAmbientLightHorizonLong();
     method public final java.math.BigInteger getAmbientLightHorizonShort();
     method @Nullable public final com.android.server.display.config.DensityMap getDensityMap();
+    method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
     method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
     method public final com.android.server.display.config.SensorDetails getLightSensor();
     method public final com.android.server.display.config.SensorDetails getProxSensor();
@@ -31,9 +39,11 @@
     method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease();
     method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease();
     method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease();
+    method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
     method public final void setAmbientLightHorizonLong(java.math.BigInteger);
     method public final void setAmbientLightHorizonShort(java.math.BigInteger);
     method public final void setDensityMap(@Nullable com.android.server.display.config.DensityMap);
+    method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
     method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
     method public final void setLightSensor(com.android.server.display.config.SensorDetails);
     method public final void setProxSensor(com.android.server.display.config.SensorDetails);
@@ -121,6 +131,14 @@
     enum_constant public static final com.android.server.display.config.ThermalStatus shutdown;
   }
 
+  public class Thresholds {
+    ctor public Thresholds();
+    method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds();
+    method @NonNull public final com.android.server.display.config.BrightnessThresholds getDarkeningThresholds();
+    method public final void setBrighteningThresholds(@NonNull com.android.server.display.config.BrightnessThresholds);
+    method public final void setDarkeningThresholds(@NonNull com.android.server.display.config.BrightnessThresholds);
+  }
+
   public class XmlParser {
     ctor public XmlParser();
     method public static com.android.server.display.config.DisplayConfiguration read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index f19202a..2090ab3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -32,6 +32,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.PasswordPolicy;
+import android.app.admin.PreferentialNetworkServiceConfig;
 import android.graphics.Color;
 import android.os.Bundle;
 import android.os.PersistableBundle;
@@ -306,6 +307,8 @@
     public boolean mAdminCanGrantSensorsPermissions;
     public boolean mPreferentialNetworkServiceEnabled =
             DevicePolicyManager.PREFERENTIAL_NETWORK_SERVICE_ENABLED_DEFAULT;
+    public PreferentialNetworkServiceConfig mPreferentialNetworkServiceConfig =
+            PreferentialNetworkServiceConfig.DEFAULT;
 
     private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true;
     boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 9b87b9d..200b120 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -171,11 +171,11 @@
     public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables){}
 
     @Override
-    public void resetDrawables(@NonNull int[] drawableIds){}
+    public void resetDrawables(@NonNull String[] drawableIds){}
 
     @Override
     public ParcelableResource getDrawable(
-            int drawableId, int drawableStyle, int drawableSource) {
+            String drawableId, String drawableStyle, String drawableSource) {
         return null;
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
index 9a98235..e70c071 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
@@ -16,10 +16,10 @@
 
 package com.android.server.devicepolicy;
 
-import static android.app.admin.DevicePolicyResources.Drawable.Source.UPDATABLE_DRAWABLE_SOURCES;
-import static android.app.admin.DevicePolicyResources.Drawable.Style;
-import static android.app.admin.DevicePolicyResources.Drawable.Style.UPDATABLE_DRAWABLE_STYLES;
-import static android.app.admin.DevicePolicyResources.Drawable.UPDATABLE_DRAWABLE_IDS;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.UPDATABLE_DRAWABLE_SOURCES;
+import static android.app.admin.DevicePolicyResources.Drawables.Style;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.UPDATABLE_DRAWABLE_STYLES;
+import static android.app.admin.DevicePolicyResources.Drawables.UPDATABLE_DRAWABLE_IDS;
 import static android.app.admin.DevicePolicyResources.Strings.UPDATABLE_STRING_IDS;
 
 import static java.util.Objects.requireNonNull;
@@ -27,7 +27,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyDrawableResource;
-import android.app.admin.DevicePolicyResources;
+import android.app.admin.DevicePolicyResources.Drawables;
 import android.app.admin.DevicePolicyStringResource;
 import android.app.admin.ParcelableResource;
 import android.os.Environment;
@@ -72,13 +72,13 @@
     /**
      * Map of <drawable_id, <style_id, resource_value>>
      */
-    private final Map<Integer, Map<Integer, ParcelableResource>>
+    private final Map<String, Map<String, ParcelableResource>>
             mUpdatedDrawablesForStyle = new HashMap<>();
 
     /**
      * Map of <drawable_id, <source_id, resource_value>>
      */
-    private final Map<Integer, Map<Integer, ParcelableResource>>
+    private final Map<String, Map<String, ParcelableResource>>
             mUpdatedDrawablesForSource = new HashMap<>();
 
     /**
@@ -103,14 +103,17 @@
     boolean updateDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) {
         boolean updated = false;
         for (int i = 0; i < drawables.size(); i++) {
-            int drawableId = drawables.get(i).getDrawableId();
-            int drawableStyle = drawables.get(i).getDrawableStyle();
-            int drawableSource = drawables.get(i).getDrawableSource();
+            String drawableId = drawables.get(i).getDrawableId();
+            String drawableStyle = drawables.get(i).getDrawableStyle();
+            String drawableSource = drawables.get(i).getDrawableSource();
             ParcelableResource resource = drawables.get(i).getResource();
 
+            Objects.requireNonNull(drawableId, "drawableId must be provided.");
+            Objects.requireNonNull(drawableStyle, "drawableStyle must be provided.");
+            Objects.requireNonNull(drawableSource, "drawableSource must be provided.");
             Objects.requireNonNull(resource, "ParcelableResource must be provided.");
 
-            if (drawableSource == DevicePolicyResources.Drawable.Source.UNDEFINED) {
+            if (Drawables.Source.UNDEFINED.equals(drawableSource)) {
                 updated |= updateDrawable(drawableId, drawableStyle, resource);
             } else {
                 updated |= updateDrawableForSource(drawableId, drawableSource, resource);
@@ -126,7 +129,7 @@
     }
 
     private boolean updateDrawable(
-            int drawableId, int drawableStyle, ParcelableResource updatableResource) {
+            String drawableId, String drawableStyle, ParcelableResource updatableResource) {
         if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) {
             Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId);
         }
@@ -149,7 +152,7 @@
 
     // TODO(b/214576716): change this to respect style
     private boolean updateDrawableForSource(
-            int drawableId, int drawableSource, ParcelableResource updatableResource) {
+            String drawableId, String drawableSource, ParcelableResource updatableResource) {
         if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) {
             Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId);
         }
@@ -173,11 +176,11 @@
     /**
      * Returns {@code false} if no resources were removed.
      */
-    boolean removeDrawables(@NonNull int[] drawableIds) {
+    boolean removeDrawables(@NonNull String[] drawableIds) {
         synchronized (mLock) {
             boolean removed = false;
             for (int i = 0; i < drawableIds.length; i++) {
-                int drawableId = drawableIds[i];
+                String drawableId = drawableIds[i];
                 removed |= mUpdatedDrawablesForStyle.remove(drawableId) != null
                         || mUpdatedDrawablesForSource.remove(drawableId) != null;
             }
@@ -191,7 +194,7 @@
 
     @Nullable
     ParcelableResource getDrawable(
-            int drawableId, int drawableStyle, int drawableSource) {
+            String drawableId, String drawableStyle, String drawableSource) {
         if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) {
             Log.w(TAG, "Getting an updated resource for an unknown drawable id " + drawableId);
         }
@@ -231,7 +234,9 @@
             String stringId = strings.get(i).getStringId();
             ParcelableResource resource = strings.get(i).getResource();
 
+            Objects.requireNonNull(stringId, "stringId must be provided.");
             Objects.requireNonNull(resource, "ParcelableResource must be provided.");
+
             updated |= updateString(stringId, resource);
         }
         if (!updated) {
@@ -392,19 +397,19 @@
 
         void writeInner(TypedXmlSerializer out) throws IOException {
             if (mUpdatedDrawablesForStyle != null && !mUpdatedDrawablesForStyle.isEmpty()) {
-                for (Map.Entry<Integer, Map<Integer, ParcelableResource>> drawableEntry
+                for (Map.Entry<String, Map<String, ParcelableResource>> drawableEntry
                         : mUpdatedDrawablesForStyle.entrySet()) {
                     out.startTag(/* namespace= */ null, TAG_DRAWABLE_STYLE_ENTRY);
-                    out.attributeInt(
+                    out.attribute(
                             /* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey());
                     out.attributeInt(
                             /* namespace= */ null,
                             ATTR_DRAWABLE_STYLE_SIZE,
                             drawableEntry.getValue().size());
                     int counter = 0;
-                    for (Map.Entry<Integer, ParcelableResource> styleEntry
+                    for (Map.Entry<String, ParcelableResource> styleEntry
                             : drawableEntry.getValue().entrySet()) {
-                        out.attributeInt(
+                        out.attribute(
                                 /* namespace= */ null,
                                 ATTR_DRAWABLE_STYLE + (counter++),
                                 styleEntry.getKey());
@@ -414,19 +419,19 @@
                 }
             }
             if (mUpdatedDrawablesForSource != null && !mUpdatedDrawablesForSource.isEmpty()) {
-                for (Map.Entry<Integer, Map<Integer, ParcelableResource>> drawableEntry
+                for (Map.Entry<String, Map<String, ParcelableResource>> drawableEntry
                         : mUpdatedDrawablesForSource.entrySet()) {
                     out.startTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY);
-                    out.attributeInt(
+                    out.attribute(
                             /* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey());
                     out.attributeInt(
                             /* namespace= */ null,
                             ATTR_DRAWABLE_SOURCE_SIZE,
                             drawableEntry.getValue().size());
                     int counter = 0;
-                    for (Map.Entry<Integer, ParcelableResource> sourceEntry
+                    for (Map.Entry<String, ParcelableResource> sourceEntry
                             : drawableEntry.getValue().entrySet()) {
-                        out.attributeInt(
+                        out.attribute(
                                 /* namespace= */ null,
                                 ATTR_DRAWABLE_SOURCE + (counter++),
                                 sourceEntry.getKey());
@@ -457,7 +462,7 @@
             }
             switch (tag) {
                 case TAG_DRAWABLE_STYLE_ENTRY:
-                    int drawableId = parser.getAttributeInt(
+                    String drawableId = parser.getAttributeValue(
                             /* namespace= */ null, ATTR_DRAWABLE_ID);
                     mUpdatedDrawablesForStyle.put(
                             drawableId,
@@ -465,7 +470,7 @@
                     int size = parser.getAttributeInt(
                             /* namespace= */ null, ATTR_DRAWABLE_STYLE_SIZE);
                     for (int i = 0; i < size; i++) {
-                        int style = parser.getAttributeInt(
+                        String style = parser.getAttributeValue(
                                 /* namespace= */ null, ATTR_DRAWABLE_STYLE + i);
                         mUpdatedDrawablesForStyle.get(drawableId).put(
                                 style,
@@ -473,13 +478,13 @@
                     }
                     break;
                 case TAG_DRAWABLE_SOURCE_ENTRY:
-                    drawableId = parser.getAttributeInt(
+                    drawableId = parser.getAttributeValue(
                             /* namespace= */ null, ATTR_DRAWABLE_ID);
                     mUpdatedDrawablesForSource.put(drawableId, new HashMap<>());
                     size = parser.getAttributeInt(
                             /* namespace= */ null, ATTR_DRAWABLE_SOURCE_SIZE);
                     for (int i = 0; i < size; i++) {
-                        int source = parser.getAttributeInt(
+                        String source = parser.getAttributeValue(
                                 /* namespace= */ null, ATTR_DRAWABLE_SOURCE + i);
                         mUpdatedDrawablesForSource.get(drawableId).put(
                                 source,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 40196db..733cfcd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -125,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.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
@@ -199,6 +200,7 @@
 import android.app.admin.ParcelableResource;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.PasswordPolicy;
+import android.app.admin.PreferentialNetworkServiceConfig;
 import android.app.admin.SecurityLog;
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.app.admin.StartInstallingUpdateCallback;
@@ -245,6 +247,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.hardware.usb.UsbManager;
+import android.location.Location;
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.media.IAudioService;
@@ -260,6 +263,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
@@ -325,6 +329,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.LocalePicker;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.net.NetworkUtilsInternal;
@@ -404,6 +409,8 @@
 
     protected static final String LOG_TAG = "DevicePolicyManager";
 
+    private static final String ATTRIBUTION_TAG = "DevicePolicyManagerService";
+
     static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE
 
     static final String DEVICE_POLICIES_XML = "device_policies.xml";
@@ -1772,7 +1779,7 @@
      * Instantiates the service.
      */
     public DevicePolicyManagerService(Context context) {
-        this(new Injector(context));
+        this(new Injector(context.createAttributionContext(ATTRIBUTION_TAG)));
     }
 
     @VisibleForTesting
@@ -3357,14 +3364,14 @@
         updatePermissionPolicyCache(userId);
         updateAdminCanGrantSensorsPermissionCache(userId);
 
-        final boolean preferentialNetworkServiceEnabled;
+        final PreferentialNetworkServiceConfig preferentialNetworkServiceConfig;
         synchronized (getLockObject()) {
             ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
-            preferentialNetworkServiceEnabled = owner != null
-                    ? owner.mPreferentialNetworkServiceEnabled
-                             : DevicePolicyManager.PREFERENTIAL_NETWORK_SERVICE_ENABLED_DEFAULT;
+            preferentialNetworkServiceConfig = owner != null
+                    ? owner.mPreferentialNetworkServiceConfig
+                    : PreferentialNetworkServiceConfig.DEFAULT;
         }
-        updateNetworkPreferenceForUser(userId, preferentialNetworkServiceEnabled);
+        updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfig);
 
         startOwnerService(userId, "start-user");
     }
@@ -3381,7 +3388,7 @@
 
     @Override
     void handleStopUser(int userId) {
-        updateNetworkPreferenceForUser(userId, false);
+        updateNetworkPreferenceForUser(userId, PreferentialNetworkServiceConfig.DEFAULT);
         stopOwnerService(userId, "stop-user");
     }
 
@@ -7209,6 +7216,64 @@
         return getFrpManagementAgentUid() != -1;
     }
 
+    @Override
+    public void sendLostModeLocationUpdate(AndroidFuture<Boolean> future) {
+        if (!mHasFeature) {
+            future.complete(false);
+            return;
+        }
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(permission.SEND_LOST_MODE_LOCATION_UPDATES));
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                    UserHandle.USER_SYSTEM);
+            Preconditions.checkState(admin != null,
+                    "Lost mode location updates can only be sent on an organization-owned device.");
+            mInjector.binderWithCleanCallingIdentity(() -> {
+                final List<String> providers =
+                        mInjector.getLocationManager().getAllProviders().stream()
+                                .filter(mInjector.getLocationManager()::isProviderEnabled)
+                                .collect(Collectors.toList());
+                if (providers.isEmpty()) {
+                    future.complete(false);
+                    return;
+                }
+
+                final CancellationSignal cancellationSignal = new CancellationSignal();
+                List<String> providersWithNullLocation = new ArrayList<String>();
+                for (String provider : providers) {
+                    mInjector.getLocationManager().getCurrentLocation(provider, cancellationSignal,
+                            mContext.getMainExecutor(), location -> {
+                                if (cancellationSignal.isCanceled()) {
+                                    return;
+                                } else if (location != null) {
+                                    sendLostModeLocationUpdate(admin, location);
+                                    cancellationSignal.cancel();
+                                    future.complete(true);
+                                } else {
+                                    // location == null, provider wasn't able to get location, see
+                                    // if there are more providers
+                                    providersWithNullLocation.add(provider);
+                                    if (providers.size() == providersWithNullLocation.size()) {
+                                        future.complete(false);
+                                    }
+                                }
+                            }
+                    );
+                }
+            });
+        }
+    }
+
+    private void sendLostModeLocationUpdate(ActiveAdmin admin, Location location) {
+        final Intent intent = new Intent(
+                DevicePolicyManager.ACTION_LOST_MODE_LOCATION_UPDATE);
+        intent.putExtra(DevicePolicyManager.EXTRA_LOST_MODE_LOCATION, location);
+        intent.setPackage(admin.info.getPackageName());
+        mContext.sendBroadcastAsUser(intent, admin.getUserHandle());
+    }
+
     /**
      * Called by a privileged caller holding {@code BIND_DEVICE_ADMIN} permission to retrieve
      * the remove warning for the given device admin.
@@ -10977,7 +11042,8 @@
 
     @Override
     public int getLogoutUserId() {
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())
+                || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS));
 
         return getLogoutUserIdUnchecked();
     }
@@ -10992,16 +11058,7 @@
         }
     }
 
-    @Override
-    public void clearLogoutUser() {
-        CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(canManageUsers(caller));
-
-        Slogf.i(LOG_TAG, "Clearing logout user as requested by %s", caller);
-        clearLogoutUserUnchecked();
-    }
-
-    private void clearLogoutUserUnchecked() {
+    private void clearLogoutUser() {
         if (!mInjector.userManagerIsHeadlessSystemUserMode()) return; // ignore
 
         synchronized (getLockObject()) {
@@ -11102,6 +11159,21 @@
             return stopUserUnchecked(callingUserId);
         }
 
+        return logoutUserUnchecked(/* userIdToStop= */ callingUserId);
+    }
+
+    @Override
+    public int logoutUserInternal() {
+        CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                canManageUsers(caller) || hasCallingOrSelfPermission(permission.CREATE_USERS));
+
+        int result = logoutUserUnchecked(getCurrentForegroundUserId());
+        Slogf.d(LOG_TAG, "logout called by uid %d. Result: %d", caller.getUid(), result);
+        return result;
+    }
+
+    private int logoutUserUnchecked(@UserIdInt int userIdToStop) {
         int logoutUserId = getLogoutUserIdUnchecked();
         if (logoutUserId == UserHandle.USER_NULL) {
             // Could happen on devices using headless system user mode when called before calling
@@ -11117,7 +11189,7 @@
                 // This should never happen as target user is determined by getPreviousUserId()
                 return UserManager.USER_OPERATION_ERROR_UNKNOWN;
             }
-            clearLogoutUserUnchecked();
+            clearLogoutUser();
         } catch (RemoteException e) {
             // Same process, should not happen.
             return UserManager.USER_OPERATION_ERROR_UNKNOWN;
@@ -11125,7 +11197,7 @@
             mInjector.binderRestoreCallingIdentity(id);
         }
 
-        return stopUserUnchecked(callingUserId);
+        return stopUserUnchecked(userIdToStop);
     }
 
     private int stopUserUnchecked(@UserIdInt int userId) {
@@ -12105,7 +12177,7 @@
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(isProfileOwner(caller),
                 "Caller is not profile owner;"
-                        + " only profile owner may control the preferntial network service");
+                        + " only profile owner may control the preferential network service");
         synchronized (getLockObject()) {
             final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
                     caller.getUserId());
@@ -12142,6 +12214,47 @@
     }
 
     @Override
+    public void setPreferentialNetworkServiceConfig(
+            PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) {
+        if (!mHasFeature) {
+            return;
+        }
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(isProfileOwner(caller),
+                "Caller is not profile owner;"
+                        + " only profile owner may control the preferential network service");
+        synchronized (getLockObject()) {
+            final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
+                    caller.getUserId());
+            if (!requiredAdmin.mPreferentialNetworkServiceConfig.equals(
+                    preferentialNetworkServiceConfig)) {
+                requiredAdmin.mPreferentialNetworkServiceConfig = preferentialNetworkServiceConfig;
+                saveSettingsLocked(caller.getUserId());
+            }
+        }
+        updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfig);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_PREFERENTIAL_NETWORK_SERVICE_ENABLED)
+                .setBoolean(preferentialNetworkServiceConfig.isEnabled())
+                .write();
+    }
+
+    @Override
+    public PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig() {
+        if (!mHasFeature) {
+            return PreferentialNetworkServiceConfig.DEFAULT;
+        }
+
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(isProfileOwner(caller),
+                "Caller is not profile owner");
+        synchronized (getLockObject()) {
+            final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(caller.getUserId());
+            return requiredAdmin.mPreferentialNetworkServiceConfig;
+        }
+    }
+
+    @Override
     public void setLockTaskPackages(ComponentName who, String[] packages)
             throws SecurityException {
         Objects.requireNonNull(who, "ComponentName is null");
@@ -17964,8 +18077,6 @@
         if (!isManagedProfile(userId)) {
             return;
         }
-        int networkPreference = preferentialNetworkServiceEnabled
-                ? PROFILE_NETWORK_PREFERENCE_ENTERPRISE : PROFILE_NETWORK_PREFERENCE_DEFAULT;
         ProfileNetworkPreference.Builder preferenceBuilder =
                 new ProfileNetworkPreference.Builder();
         if (preferentialNetworkServiceEnabled) {
@@ -17982,6 +18093,40 @@
                         null /* executor */, null /* listener */));
     }
 
+    private void updateNetworkPreferenceForUser(int userId,
+            PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) {
+        if (!isManagedProfile(userId)) {
+            return;
+        }
+        ProfileNetworkPreference.Builder preferenceBuilder =
+                new ProfileNetworkPreference.Builder();
+        if (preferentialNetworkServiceConfig.isEnabled()) {
+            if (preferentialNetworkServiceConfig.isFallbackToDefaultConnectionAllowed()) {
+                preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+            } else {
+                preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+            }
+        } else {
+            preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+        }
+        List<Integer> allowedUids = Arrays.stream(
+                preferentialNetworkServiceConfig.getIncludedUids()).boxed().collect(
+                        Collectors.toList());
+        List<Integer> excludedUids = Arrays.stream(
+                preferentialNetworkServiceConfig.getExcludedUids()).boxed().collect(
+                Collectors.toList());
+        preferenceBuilder.setIncludedUids(allowedUids);
+        preferenceBuilder.setExcludedUids(excludedUids);
+        preferenceBuilder.setPreferenceEnterpriseId(
+                preferentialNetworkServiceConfig.getNetworkId());
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceBuilder.build());
+        mInjector.binderWithCleanCallingIdentity(() ->
+                mInjector.getConnectivityManager().setProfileNetworkPreferences(
+                        UserHandle.of(userId), preferences,
+                        null /* executor */, null /* listener */));
+    }
+
     @Override
     public boolean canAdminGrantSensorsPermissionsForUser(int userId) {
         if (!mHasFeature) {
@@ -18221,13 +18366,13 @@
         mInjector.binderWithCleanCallingIdentity(() -> {
             if (mDeviceManagementResourcesProvider.updateDrawables(drawables)) {
                 sendDrawableUpdatedBroadcast(
-                        drawables.stream().mapToInt(d -> d.getDrawableId()).toArray());
+                        drawables.stream().map(s -> s.getDrawableId()).toArray(String[]::new));
             }
         });
     }
 
     @Override
-    public void resetDrawables(@NonNull int[] drawableIds) {
+    public void resetDrawables(@NonNull String[] drawableIds) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
                 android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
 
@@ -18241,24 +18386,15 @@
     }
 
     @Override
-    public ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource) {
+    public ParcelableResource getDrawable(
+            String drawableId, String drawableStyle, String drawableSource) {
         return mInjector.binderWithCleanCallingIdentity(() ->
                 mDeviceManagementResourcesProvider.getDrawable(
                         drawableId, drawableStyle, drawableSource));
     }
 
-    private void sendDrawableUpdatedBroadcast(int[] drawableIds) {
-        final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
-        intent.putExtra(EXTRA_RESOURCE_ID, drawableIds);
-        intent.putExtra(EXTRA_RESOURCE_TYPE_DRAWABLE, /* value= */ true);
-        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
-        List<UserInfo> users = mUserManager.getAliveUsers();
-        for (int i = 0; i < users.size(); i++) {
-            UserHandle user = users.get(i).getUserHandle();
-            mContext.sendBroadcastAsUser(intent, user);
-        }
+    private void sendDrawableUpdatedBroadcast(String[] drawableIds) {
+        sendResourceUpdatedBroadcast(EXTRA_RESOURCE_TYPE_DRAWABLE, drawableIds);
     }
 
     @Override
@@ -18294,9 +18430,13 @@
     }
 
     private void sendStringsUpdatedBroadcast(String[] stringIds) {
+        sendResourceUpdatedBroadcast(EXTRA_RESOURCE_TYPE_STRING, stringIds);
+    }
+
+    private void sendResourceUpdatedBroadcast(String resourceType, String[] resourceIds) {
         final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
-        intent.putExtra(EXTRA_RESOURCE_ID, stringIds);
-        intent.putExtra(EXTRA_RESOURCE_TYPE_STRING, /* value= */ true);
+        intent.putExtra(EXTRA_RESOURCE_ID, resourceIds);
+        intent.putExtra(resourceType, /* value= */ true);
         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
 
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
index 1d4c94d..99b0f25 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
@@ -16,6 +16,8 @@
 
 package com.android.server.selectiontoolbar;
 
+import android.service.selectiontoolbar.DefaultSelectionToolbarRenderService;
+
 import com.android.server.infra.ServiceNameResolver;
 
 import java.io.PrintWriter;
@@ -24,7 +26,7 @@
 
     // TODO: move to SysUi or ExtServices
     private static final String SELECTION_TOOLBAR_SERVICE_NAME =
-            "android/com.android.server.selectiontoolbar.DefaultSelectionToolbarRenderService";
+            "android/" + DefaultSelectionToolbarRenderService.class.getName();
 
     @Override
     public String getDefaultServiceName(int userId) {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
index 57562ef..f266e76 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
@@ -31,9 +31,9 @@
     override val creator = ParsedPermissionGroupImpl.CREATOR
 
     override val subclassBaseParams = listOf(
-        ParsedPermissionGroup::getRequestDetailResourceId,
-        ParsedPermissionGroup::getBackgroundRequestDetailResourceId,
-        ParsedPermissionGroup::getBackgroundRequestResourceId,
+        ParsedPermissionGroup::getRequestDetailRes,
+        ParsedPermissionGroup::getBackgroundRequestDetailRes,
+        ParsedPermissionGroup::getBackgroundRequestRes,
         ParsedPermissionGroup::getRequestRes,
         ParsedPermissionGroup::getPriority,
     )
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
index 037da24..0302d57 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
@@ -43,10 +43,9 @@
     override fun mainComponentSubclassExtraParams() = listOf(
         getSetByValue(
             ParsedProvider::getUriPermissionPatterns,
-            ParsedProviderImpl::setUriPermissionPatterns,
+            ParsedProviderImpl::addUriPermissionPattern,
             PatternMatcher("testPattern", PatternMatcher.PATTERN_LITERAL),
-            transformGet = { it?.singleOrNull() },
-            transformSet = { arrayOf(it) },
+            transformGet = { it.singleOrNull() },
             compare = { first, second ->
                 equalBy(
                     first, second,
@@ -57,15 +56,14 @@
         ),
         getSetByValue(
             ParsedProvider::getPathPermissions,
-            ParsedProviderImpl::setPathPermissions,
+            ParsedProviderImpl::addPathPermission,
             PathPermission(
                 "testPermissionPattern",
                 PatternMatcher.PATTERN_LITERAL,
                 "test.READ_PERMISSION",
                 "test.WRITE_PERMISSION"
             ),
-            transformGet = { it?.singleOrNull() },
-            transformSet = { arrayOf(it) },
+            transformGet = { it.singleOrNull() },
             compare = { first, second ->
                 equalBy(
                     first, second,
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 36246e5..9e221be 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -62,12 +62,14 @@
         "truth-prebuilt",
         // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows
         "testng",
+        "compatibility-device-util-axt",
     ],
 
     libs: [
         "android.test.mock",
         "android.test.base",
         "android.test.runner",
+        "servicestests-core-utils",
     ],
 
     jni_libs: [
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index 40b3664..9b04ae4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -344,6 +344,30 @@
         callStart(instance);
 
         assertFalse(instance.isForceAllAppsStandbyEnabled());
+
+        when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+                .thenReturn(false);
+
+        areJobsRestricted(instance,
+                new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {false, false, false, false},
+                false);
+        areJobsRestricted(instance,
+                new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {false, false, false, false},
+                true);
+        areAlarmsRestrictedByBatterySaver(instance,
+                new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {false, false, false, false});
+
+        // Toggle the auto restricted bucket feature flag on bg restriction, shouldn't make a
+        // difference.
+        when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+                .thenReturn(true);
+
         areJobsRestricted(instance,
                 new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
                 new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
@@ -364,6 +388,9 @@
 
         assertTrue(instance.isForceAllAppsStandbyEnabled());
 
+        when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+                .thenReturn(false);
+
         areJobsRestricted(instance,
                 new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
                 new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
@@ -379,6 +406,29 @@
                 new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
                 new boolean[] {true, true, true, false});
 
+        // Toggle the auto restricted bucket feature flag on bg restriction, shouldn't make a
+        // difference.
+        when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+                .thenReturn(true);
+
+        areJobsRestricted(instance,
+                new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {true, true, true, false},
+                false);
+        areJobsRestricted(instance,
+                new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {false, false, false, false},
+                true);
+        areAlarmsRestrictedByBatterySaver(instance,
+                new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {true, true, true, false});
+
+        when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+                .thenReturn(false);
+
         // Toggle the foreground state.
 
         assertFalse(instance.isUidActive(UID_1));
@@ -500,9 +550,35 @@
                 new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
                 new boolean[] {true, false, false, true, false});
 
+        // Toggle the auto restricted bucket feature flag on bg restriction.
+        when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+                .thenReturn(true);
+
+        areJobsRestricted(instance,
+                new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {false, false, false, false, false},
+                false);
+        areJobsRestricted(instance,
+                new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {false, false, false, false, false},
+                true);
+
+        areAlarmsRestrictedByBatterySaver(instance,
+                new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {false, false, false, false, false});
+        areAlarmsRestrictedByFAS(instance,
+                new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {false, false, false, false, false});
+
         // Toggle power saver, should still be the same.
         mPowerSaveMode = true;
         mPowerSaveObserver.accept(getPowerSaveState());
+        when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+                .thenReturn(false);
 
         areJobsRestricted(instance,
                 new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
@@ -524,9 +600,36 @@
                 new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
                 new boolean[] {true, false, false, true, false});
 
+        // Toggle the auto restricted bucket feature flag on bg restriction.
+        when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+                .thenReturn(true);
+
+        areJobsRestricted(instance,
+                new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {true, true, true, true, false},
+                false);
+        areJobsRestricted(instance,
+                new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {false, false, false, false, false},
+                true);
+
+        areAlarmsRestrictedByBatterySaver(instance,
+                new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {true, true, true, true, false});
+        areAlarmsRestrictedByFAS(instance,
+                new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+                new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+                new boolean[] {false, false, false, false, false});
+
         mPowerSaveMode = false;
         mPowerSaveObserver.accept(getPowerSaveState());
 
+        when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+                .thenReturn(false);
+
         areJobsRestricted(instance,
                 new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
                 new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
new file mode 100644
index 0000000..28c91aa
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -0,0 +1,2441 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_USER_FLAG_INTERACTION;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
+import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_BG;
+import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FG;
+import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FGS;
+import static com.android.server.am.AppRestrictionController.STOCK_PM_FLAGS;
+
+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.eq;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
+import android.app.ActivityManagerInternal.BindServiceEventListener;
+import android.app.ActivityManagerInternal.BroadcastEventListener;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.role.RoleManager;
+import android.app.usage.AppStandbyInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
+import android.os.BatteryManagerInternal;
+import android.os.BatteryStatsInternal;
+import android.os.BatteryUsageStats;
+import android.os.Handler;
+import android.os.MessageQueue;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UidBatteryConsumer;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.AppStateTracker;
+import com.android.server.DeviceIdleInternal;
+import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy;
+import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates;
+import com.android.server.am.AppBatteryExemptionTracker.UidStateEventWithBattery;
+import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
+import com.android.server.am.AppBindServiceEventsTracker.AppBindServiceEventsPolicy;
+import com.android.server.am.AppBroadcastEventsTracker.AppBroadcastEventsPolicy;
+import com.android.server.am.AppFGSTracker.AppFGSPolicy;
+import com.android.server.am.AppMediaSessionTracker.AppMediaSessionPolicy;
+import com.android.server.am.AppRestrictionController.NotificationHelper;
+import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.usage.AppStandbyInternal;
+import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+
+/**
+ * Tests for {@link AppRestrictionController}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksMockingServicesTests:BackgroundRestrictionTest
+ */
+@RunWith(AndroidJUnit4.class)
+public final class BackgroundRestrictionTest {
+    private static final String TAG = BackgroundRestrictionTest.class.getSimpleName();
+
+    private static final int TEST_USER0 = UserHandle.USER_SYSTEM;
+    private static final int TEST_USER1 = UserHandle.MIN_SECONDARY_USER_ID;
+    private static final int[] TEST_USERS = new int[] {TEST_USER0, TEST_USER1};
+    private static final String TEST_PACKAGE_BASE = "test_";
+    private static final int TEST_PACKAGE_APPID_BASE = Process.FIRST_APPLICATION_UID;
+    private static final int[] TEST_PACKAGE_USER0_UIDS = new int[] {
+        UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 0),
+        UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 1),
+        UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 2),
+        UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 3),
+        UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 4),
+        UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 5),
+        UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 6),
+    };
+    private static final int[] TEST_PACKAGE_USER1_UIDS = new int[] {
+        UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 0),
+        UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 1),
+        UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 2),
+        UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 3),
+        UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 4),
+        UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 5),
+        UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 6),
+    };
+    private static final int[][] TEST_UIDS = new int[][] {
+        TEST_PACKAGE_USER0_UIDS,
+        TEST_PACKAGE_USER1_UIDS,
+    };
+    private static final int[] TEST_STANDBY_BUCKETS = new int[] {
+        STANDBY_BUCKET_EXEMPTED,
+        STANDBY_BUCKET_ACTIVE,
+        STANDBY_BUCKET_WORKING_SET,
+        STANDBY_BUCKET_FREQUENT,
+        STANDBY_BUCKET_RARE,
+        STANDBY_BUCKET_RESTRICTED,
+        STANDBY_BUCKET_NEVER,
+    };
+
+    private static final int BATTERY_FULL_CHARGE_MAH = 5_000;
+
+    @Mock private ActivityManagerInternal mActivityManagerInternal;
+    @Mock private ActivityManagerService mActivityManagerService;
+    @Mock private AppOpsManager mAppOpsManager;
+    @Mock private AppStandbyInternal mAppStandbyInternal;
+    @Mock private AppHibernationManagerInternal mAppHibernationInternal;
+    @Mock private AppStateTracker mAppStateTracker;
+    @Mock private BatteryManagerInternal mBatteryManagerInternal;
+    @Mock private BatteryStatsInternal mBatteryStatsInternal;
+    @Mock private DeviceIdleInternal mDeviceIdleInternal;
+    @Mock private IActivityManager mIActivityManager;
+    @Mock private UserManagerInternal mUserManagerInternal;
+    @Mock private PackageManager mPackageManager;
+    @Mock private PackageManagerInternal mPackageManagerInternal;
+    @Mock private NotificationManager mNotificationManager;
+    @Mock private PermissionManagerServiceInternal mPermissionManagerServiceInternal;
+    @Mock private MediaSessionManager mMediaSessionManager;
+    @Mock private RoleManager mRoleManager;
+
+    private long mCurrentTimeMillis;
+
+    @Captor private ArgumentCaptor<AppStateTracker.BackgroundRestrictedAppListener> mFasListenerCap;
+    private AppStateTracker.BackgroundRestrictedAppListener mFasListener;
+
+    @Captor private ArgumentCaptor<AppIdleStateChangeListener> mIdleStateListenerCap;
+    private AppIdleStateChangeListener mIdleStateListener;
+
+    @Captor private ArgumentCaptor<IUidObserver> mUidObserversCap;
+    private IUidObserver mUidObservers;
+
+    @Captor private ArgumentCaptor<OnActiveSessionsChangedListener> mActiveSessionListenerCap;
+    private OnActiveSessionsChangedListener mActiveSessionListener;
+
+    @Captor private ArgumentCaptor<BroadcastEventListener> mBroadcastEventListenerCap;
+    private BroadcastEventListener mBroadcastEventListener;
+
+    @Captor private ArgumentCaptor<BindServiceEventListener> mBindServiceEventListenerCap;
+    private BindServiceEventListener mBindServiceEventListener;
+
+    private Context mContext = getInstrumentation().getTargetContext();
+    private TestBgRestrictionInjector mInjector;
+    private AppRestrictionController mBgRestrictionController;
+    private AppBatteryTracker mAppBatteryTracker;
+    private AppBatteryPolicy mAppBatteryPolicy;
+    private AppBatteryExemptionTracker mAppBatteryExemptionTracker;
+    private AppBroadcastEventsTracker mAppBroadcastEventsTracker;
+    private AppBindServiceEventsTracker mAppBindServiceEventsTracker;
+    private AppFGSTracker mAppFGSTracker;
+    private AppMediaSessionTracker mAppMediaSessionTracker;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        initController();
+    }
+
+    private void initController() throws Exception {
+        mInjector = spy(new TestBgRestrictionInjector(mContext));
+        mBgRestrictionController = spy(new AppRestrictionController(mInjector,
+                    mActivityManagerService));
+
+        doReturn(PROCESS_STATE_FOREGROUND_SERVICE).when(mActivityManagerInternal)
+                .getUidProcessState(anyInt());
+        doReturn(TEST_USERS).when(mUserManagerInternal).getUserIds();
+        for (int userId: TEST_USERS) {
+            final ArrayList<AppStandbyInfo> appStandbyInfoList = new ArrayList<>();
+            for (int i = 0; i < TEST_STANDBY_BUCKETS.length; i++) {
+                final String packageName = TEST_PACKAGE_BASE + i;
+                final int uid = UserHandle.getUid(userId, TEST_PACKAGE_APPID_BASE + i);
+                appStandbyInfoList.add(new AppStandbyInfo(packageName, TEST_STANDBY_BUCKETS[i]));
+                doReturn(uid)
+                        .when(mPackageManagerInternal)
+                        .getPackageUid(packageName, STOCK_PM_FLAGS, userId);
+                doReturn(false)
+                        .when(mAppStateTracker)
+                        .isAppBackgroundRestricted(uid, packageName);
+                doReturn(TEST_STANDBY_BUCKETS[i])
+                        .when(mAppStandbyInternal)
+                        .getAppStandbyBucket(eq(packageName), eq(userId), anyLong(), anyBoolean());
+                doReturn(new String[]{packageName})
+                        .when(mPackageManager)
+                        .getPackagesForUid(eq(uid));
+                doReturn(AppOpsManager.MODE_IGNORED)
+                        .when(mAppOpsManager)
+                        .checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, uid, packageName);
+                doReturn(AppOpsManager.MODE_IGNORED)
+                        .when(mAppOpsManager)
+                        .checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, uid, packageName);
+                doReturn(PERMISSION_DENIED)
+                        .when(mPermissionManagerServiceInternal)
+                        .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION);
+                doReturn(PERMISSION_DENIED)
+                        .when(mPermissionManagerServiceInternal)
+                        .checkPermission(packageName, ACCESS_BACKGROUND_LOCATION, userId);
+            }
+            doReturn(appStandbyInfoList).when(mAppStandbyInternal).getAppStandbyBuckets(userId);
+        }
+
+        doReturn(BATTERY_FULL_CHARGE_MAH * 1000).when(mBatteryManagerInternal)
+                .getBatteryFullCharge();
+
+        mBgRestrictionController.onSystemReady();
+
+        verify(mInjector.getAppStateTracker())
+                .addBackgroundRestrictedAppListener(mFasListenerCap.capture());
+        mFasListener = mFasListenerCap.getValue();
+        verify(mInjector.getAppStandbyInternal())
+                .addListener(mIdleStateListenerCap.capture());
+        mIdleStateListener = mIdleStateListenerCap.getValue();
+        verify(mInjector.getIActivityManager())
+                .registerUidObserver(mUidObserversCap.capture(),
+                    anyInt(), anyInt(), anyString());
+        mUidObservers = mUidObserversCap.getValue();
+        verify(mAppMediaSessionTracker.mInjector.getMediaSessionManager())
+                .addOnActiveSessionsChangedListener(any(), any(), any(),
+                        mActiveSessionListenerCap.capture());
+        mActiveSessionListener = mActiveSessionListenerCap.getValue();
+        verify(mAppBroadcastEventsTracker.mInjector.getActivityManagerInternal())
+                .addBroadcastEventListener(mBroadcastEventListenerCap.capture());
+        mBroadcastEventListener = mBroadcastEventListenerCap.getValue();
+        verify(mAppBindServiceEventsTracker.mInjector.getActivityManagerInternal())
+                .addBindServiceEventListener(mBindServiceEventListenerCap.capture());
+        mBindServiceEventListener = mBindServiceEventListenerCap.getValue();
+    }
+
+    @After
+    public void tearDown() {
+        mBgRestrictionController.getBackgroundHandlerThread().quitSafely();
+    }
+
+    @Test
+    public void testInitialLevels() throws Exception {
+        final int[] expectedLevels = {
+            RESTRICTION_LEVEL_EXEMPTED,
+            RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
+            RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
+            RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
+            RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
+            RESTRICTION_LEVEL_RESTRICTED_BUCKET,
+            RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
+        };
+        for (int i = 0; i < TEST_UIDS.length; i++) {
+            final int[] uids = TEST_UIDS[i];
+            for (int j = 0; j < uids.length; j++) {
+                assertEquals(expectedLevels[j],
+                        mBgRestrictionController.getRestrictionLevel(uids[j]));
+                assertEquals(expectedLevels[j],
+                        mBgRestrictionController.getRestrictionLevel(uids[j],
+                                TEST_PACKAGE_BASE + j));
+            }
+        }
+    }
+
+    @Test
+    public void testTogglingBackgroundRestrict() throws Exception {
+        final int testPkgIndex = 2;
+        final String testPkgName = TEST_PACKAGE_BASE + testPkgIndex;
+        final int testUser = TEST_USER0;
+        final int testUid = UserHandle.getUid(testUser, TEST_PACKAGE_APPID_BASE + testPkgIndex);
+        final TestAppRestrictionLevelListener listener = new TestAppRestrictionLevelListener();
+        final long timeout = 1_000; // ms
+
+        mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
+
+        setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+        // Verify the current settings.
+        verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+        assertEquals(STANDBY_BUCKET_WORKING_SET, mInjector.getAppStandbyInternal()
+                .getAppStandbyBucket(testPkgName, testUser, SystemClock.elapsedRealtime(), false));
+
+        // Now toggling ON the background restrict.
+        setBackgroundRestrict(testPkgName, testUid, true, listener);
+
+        // We should have been in the background restricted level.
+        verifyRestrictionLevel(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, testPkgName, testUid);
+
+        listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+
+        // The app should have been put into the restricted standby bucket.
+        verify(mInjector.getAppStandbyInternal(), atLeast(1)).restrictApp(
+                eq(testPkgName),
+                eq(testUser),
+                eq(REASON_MAIN_FORCED_BY_USER),
+                eq(REASON_SUB_FORCED_USER_FLAG_INTERACTION));
+
+        // Changing to the restricted standby bucket won't make a difference.
+        listener.mLatchHolder[0] = new CountDownLatch(1);
+        mIdleStateListener.onAppIdleStateChanged(testPkgName, testUser, false,
+                STANDBY_BUCKET_RESTRICTED, REASON_MAIN_USAGE);
+        waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+        verifyRestrictionLevel(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, testPkgName, testUid);
+        try {
+            listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+            fail("There shouldn't be any level change events");
+        } catch (Exception e) {
+            // Expected.
+        }
+
+        clearInvocations(mInjector.getAppStandbyInternal());
+
+        // Toggling back.
+        setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+        // It should have gone back to adaptive level.
+        verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+
+        // The app standby bucket should be the rare.
+        verify(mInjector.getAppStandbyInternal(), atLeast(1)).maybeUnrestrictApp(
+                eq(testPkgName),
+                eq(testUser),
+                eq(REASON_MAIN_FORCED_BY_USER),
+                eq(REASON_SUB_FORCED_USER_FLAG_INTERACTION),
+                eq(REASON_MAIN_USAGE),
+                eq(REASON_SUB_USAGE_USER_INTERACTION));
+
+        listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+
+        clearInvocations(mInjector.getAppStandbyInternal());
+
+        // Now set its UID state active.
+        mUidObservers.onUidActive(testUid);
+
+        // Now toggling ON the background restrict.
+        setBackgroundRestrict(testPkgName, testUid, true, listener);
+
+        // We should have been in the background restricted level.
+        verifyRestrictionLevel(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, testPkgName, testUid);
+
+        listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+
+        // The app should have NOT been put into the restricted standby bucket.
+        verify(mInjector.getAppStandbyInternal(), never()).restrictApp(
+                eq(testPkgName),
+                eq(testUser),
+                eq(REASON_MAIN_FORCED_BY_USER),
+                eq(REASON_SUB_FORCED_USER_FLAG_INTERACTION));
+
+        // Now set its UID to idle.
+        mUidObservers.onUidIdle(testUid, false);
+
+        // The app should have been put into the restricted standby bucket because we're idle now.
+        verify(mInjector.getAppStandbyInternal(), timeout(timeout).times(1)).restrictApp(
+                eq(testPkgName),
+                eq(testUser),
+                eq(REASON_MAIN_FORCED_BY_USER),
+                eq(REASON_SUB_FORCED_USER_FLAG_INTERACTION));
+    }
+
+    @Test
+    public void testTogglingStandbyBucket() throws Exception {
+        final int testPkgIndex = 2;
+        final String testPkgName = TEST_PACKAGE_BASE + testPkgIndex;
+        final int testUser = TEST_USER0;
+        final int testUid = UserHandle.getUid(testUser, TEST_PACKAGE_APPID_BASE + testPkgIndex);
+        final TestAppRestrictionLevelListener listener = new TestAppRestrictionLevelListener();
+        final long timeout = 1_000; // ms
+
+        mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
+
+        setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+        // Verify the current settings.
+        verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+
+        for (int bucket: Arrays.asList(STANDBY_BUCKET_ACTIVE, STANDBY_BUCKET_WORKING_SET,
+                STANDBY_BUCKET_FREQUENT, STANDBY_BUCKET_RARE)) {
+            listener.mLatchHolder[0] = new CountDownLatch(1);
+            mIdleStateListener.onAppIdleStateChanged(testPkgName, testUser, false,
+                    bucket, REASON_MAIN_USAGE);
+            waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+            verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+
+            try {
+                listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+                fail("There shouldn't be any level change events");
+            } catch (Exception e) {
+                // Expected.
+            }
+        }
+
+        // Toggling restricted bucket.
+        listener.mLatchHolder[0] = new CountDownLatch(1);
+        mIdleStateListener.onAppIdleStateChanged(testPkgName, testUser, false,
+                STANDBY_BUCKET_RESTRICTED, REASON_MAIN_USAGE);
+        waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+        verifyRestrictionLevel(RESTRICTION_LEVEL_RESTRICTED_BUCKET, testPkgName, testUid);
+        listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+
+        // Toggling exempted bucket.
+        listener.mLatchHolder[0] = new CountDownLatch(1);
+        mIdleStateListener.onAppIdleStateChanged(testPkgName, testUser, false,
+                STANDBY_BUCKET_EXEMPTED, REASON_MAIN_FORCED_BY_SYSTEM);
+        waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+        verifyRestrictionLevel(RESTRICTION_LEVEL_EXEMPTED, testPkgName, testUid);
+        listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_EXEMPTED);
+    }
+
+    @Test
+    public void testBgCurrentDrainMonitor() throws Exception {
+        final BatteryUsageStats stats = mock(BatteryUsageStats.class);
+        final List<BatteryUsageStats> statsList = Arrays.asList(stats);
+        final int testPkgIndex = 2;
+        final String testPkgName = TEST_PACKAGE_BASE + testPkgIndex;
+        final int testUser = TEST_USER0;
+        final int testUid = UserHandle.getUid(testUser,
+                TEST_PACKAGE_APPID_BASE + testPkgIndex);
+        final int testUid2 = UserHandle.getUid(testUser,
+                TEST_PACKAGE_APPID_BASE + testPkgIndex + 1);
+        final TestAppRestrictionLevelListener listener = new TestAppRestrictionLevelListener();
+        final long timeout =
+                AppBatteryTracker.BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG * 2;
+        final long windowMs = 2_000;
+        final float restrictBucketThreshold = 2.0f;
+        final float restrictBucketThresholdMah =
+                BATTERY_FULL_CHARGE_MAH * restrictBucketThreshold / 100.0f;
+        final float bgRestrictedThreshold = 4.0f;
+        final float bgRestrictedThresholdMah =
+                BATTERY_FULL_CHARGE_MAH * bgRestrictedThreshold / 100.0f;
+
+        DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null;
+        DeviceConfigSession<Long> bgCurrentDrainWindow = null;
+        DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null;
+        DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null;
+
+        mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
+
+        setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+        // Verify the current settings.
+        verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+
+        final double[] zeros = new double[]{0.0f, 0.0f};
+        final int[] uids = new int[]{testUid, testUid2};
+
+        try {
+            bgCurrentDrainMonitor = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
+                    DeviceConfig::getBoolean,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+            bgCurrentDrainMonitor.set(true);
+
+            bgCurrentDrainWindow = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
+                    DeviceConfig::getLong,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+            bgCurrentDrainWindow.set(windowMs);
+
+            bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
+                    DeviceConfig::getFloat,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+            bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold);
+
+            bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
+                    DeviceConfig::getFloat,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+            bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold);
+
+            mCurrentTimeMillis = 10_000L;
+            doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp();
+            doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(anyObject());
+
+            runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah - 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    () -> {
+                        doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+                        mCurrentTimeMillis += windowMs + 1;
+                        try {
+                            listener.verify(timeout, testUid, testPkgName,
+                                    RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+                            fail("There shouldn't be any level change events");
+                        } catch (Exception e) {
+                            // Expected.
+                        }
+                    });
+
+            runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    () -> {
+                        doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+                        mCurrentTimeMillis += windowMs + 1;
+                        // It should have gone to the restricted bucket.
+                        listener.verify(timeout, testUid, testPkgName,
+                                RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+                        verify(mInjector.getAppStandbyInternal()).restrictApp(
+                                eq(testPkgName),
+                                eq(testUser),
+                                anyInt(), anyInt());
+                    });
+
+
+            runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah - 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    () -> {
+                        doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+                        mCurrentTimeMillis += windowMs + 1;
+                        // We won't change restriction level until user interactions.
+                        try {
+                            listener.verify(timeout, testUid, testPkgName,
+                                    RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+                            fail("There shouldn't be any level change events");
+                        } catch (Exception e) {
+                            // Expected.
+                        }
+                        verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket(
+                                eq(testPkgName),
+                                eq(STANDBY_BUCKET_RARE),
+                                eq(testUser),
+                                anyInt(), anyInt());
+                    });
+
+            // Trigger user interaction.
+            runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah - 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    () -> {
+                        doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+                        mCurrentTimeMillis += windowMs + 1;
+                        mIdleStateListener.onUserInteractionStarted(testPkgName, testUser);
+                        waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+                        // It should have been back to normal.
+                        listener.verify(timeout, testUid, testPkgName,
+                                RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+                        verify(mInjector.getAppStandbyInternal(), atLeast(1)).maybeUnrestrictApp(
+                                eq(testPkgName),
+                                eq(testUser),
+                                eq(REASON_MAIN_FORCED_BY_SYSTEM),
+                                eq(REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE),
+                                eq(REASON_MAIN_USAGE),
+                                eq(REASON_SUB_USAGE_USER_INTERACTION));
+                    });
+
+            clearInvocations(mInjector.getAppStandbyInternal());
+
+            runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    () -> {
+                        doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+                        mCurrentTimeMillis += windowMs + 1;
+                        // It should have gone to the restricted bucket.
+                        listener.verify(timeout, testUid, testPkgName,
+                                RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+                        verify(mInjector.getAppStandbyInternal(), times(1)).restrictApp(
+                                eq(testPkgName),
+                                eq(testUser),
+                                anyInt(), anyInt());
+                    });
+
+            clearInvocations(mInjector.getAppStandbyInternal());
+            // Drain a bit more, there shouldn't be any level changes.
+            runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah + 2, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    () -> {
+                        doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+                        mCurrentTimeMillis += windowMs + 1;
+                        // We won't change restriction level until user interactions.
+                        try {
+                            listener.verify(timeout, testUid, testPkgName,
+                                    RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+                            fail("There shouldn't be any level change events");
+                        } catch (Exception e) {
+                            // Expected.
+                        }
+                        verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket(
+                                eq(testPkgName),
+                                eq(STANDBY_BUCKET_RARE),
+                                eq(testUser),
+                                anyInt(), anyInt());
+                    });
+
+            // Sleep a while and set a higher drain
+            Thread.sleep(windowMs);
+            clearInvocations(mInjector.getAppStandbyInternal());
+            clearInvocations(mBgRestrictionController);
+            runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+                    new double[]{bgRestrictedThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    () -> {
+                        doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+                        mCurrentTimeMillis += windowMs + 1;
+                        // We won't change restriction level automatically because it needs
+                        // user consent.
+                        try {
+                            listener.verify(timeout, testUid, testPkgName,
+                                    RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+                            fail("There shouldn't be level change event like this");
+                        } catch (Exception e) {
+                            // Expected.
+                        }
+                        verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket(
+                                eq(testPkgName),
+                                eq(STANDBY_BUCKET_RARE),
+                                eq(testUser),
+                                anyInt(), anyInt());
+                        // We should have requested to goto background restricted level.
+                        verify(mBgRestrictionController, times(1)).handleRequestBgRestricted(
+                                eq(testPkgName),
+                                eq(testUid));
+                        // Verify we have the notification posted.
+                        checkNotificationShown(new String[] {testPkgName}, atLeast(1), true);
+                    });
+
+            // Turn ON the FAS for real.
+            setBackgroundRestrict(testPkgName, testUid, true, listener);
+
+            // Verify it's background restricted now.
+            verifyRestrictionLevel(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, testPkgName, testUid);
+            listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+
+            // Trigger user interaction.
+            mIdleStateListener.onUserInteractionStarted(testPkgName, testUser);
+            waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+
+            listener.mLatchHolder[0] = new CountDownLatch(1);
+            try {
+                listener.verify(timeout, testUid, testPkgName,
+                        RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+                fail("There shouldn't be level change event like this");
+            } catch (Exception e) {
+                // Expected.
+            }
+
+            // Turn OFF the FAS.
+            listener.mLatchHolder[0] = new CountDownLatch(1);
+            clearInvocations(mInjector.getAppStandbyInternal());
+            clearInvocations(mBgRestrictionController);
+            setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+            // It'll go back to restricted bucket because it used to behave poorly.
+            listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+            verifyRestrictionLevel(RESTRICTION_LEVEL_RESTRICTED_BUCKET, testPkgName, testUid);
+        } finally {
+            closeIfNotNull(bgCurrentDrainMonitor);
+            closeIfNotNull(bgCurrentDrainWindow);
+            closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold);
+            closeIfNotNull(bgCurrentDrainBgRestrictedThreshold);
+        }
+    }
+
+    @Test
+    public void testLongFGSMonitor() throws Exception {
+        final int testPkgIndex1 = 1;
+        final String testPkgName1 = TEST_PACKAGE_BASE + testPkgIndex1;
+        final int testUser1 = TEST_USER0;
+        final int testUid1 = UserHandle.getUid(testUser1, TEST_PACKAGE_APPID_BASE + testPkgIndex1);
+        final int testPid1 = 1234;
+
+        final int testPkgIndex2 = 2;
+        final String testPkgName2 = TEST_PACKAGE_BASE + testPkgIndex2;
+        final int testUser2 = TEST_USER0;
+        final int testUid2 = UserHandle.getUid(testUser2, TEST_PACKAGE_APPID_BASE + testPkgIndex2);
+        final int testPid2 = 1235;
+
+        final long windowMs = 2_000;
+        final long thresholdMs = 1_000;
+        final long shortMs = 100;
+
+        DeviceConfigSession<Boolean> longRunningFGSMonitor = null;
+        DeviceConfigSession<Long> longRunningFGSWindow = null;
+        DeviceConfigSession<Long> longRunningFGSThreshold = null;
+
+        try {
+            longRunningFGSMonitor = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppFGSPolicy.KEY_BG_FGS_MONITOR_ENABLED,
+                    DeviceConfig::getBoolean,
+                    AppFGSPolicy.DEFAULT_BG_FGS_MONITOR_ENABLED);
+            longRunningFGSMonitor.set(true);
+
+            longRunningFGSWindow = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppFGSPolicy.KEY_BG_FGS_LONG_RUNNING_WINDOW,
+                    DeviceConfig::getLong,
+                    AppFGSPolicy.DEFAULT_BG_FGS_LONG_RUNNING_WINDOW);
+            longRunningFGSWindow.set(windowMs);
+
+            longRunningFGSThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppFGSPolicy.KEY_BG_FGS_LONG_RUNNING_THRESHOLD,
+                    DeviceConfig::getLong,
+                    AppFGSPolicy.DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD);
+            longRunningFGSThreshold.set(thresholdMs);
+
+            // Basic case
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+                    testPid1, true);
+            // Verify we have the notification, it'll include the summary notification though.
+            int notificationId = checkNotificationShown(
+                    new String[] {testPkgName1}, timeout(windowMs * 2).times(2), true)[0];
+
+            clearInvocations(mInjector.getNotificationManager());
+            // Sleep a while, verify it won't show another notification.
+            Thread.sleep(windowMs * 2);
+            checkNotificationShown(
+                    new String[] {testPkgName1}, timeout(windowMs * 2).times(0), false);
+
+            // Stop this FGS
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+                    testPid1, false);
+            checkNotificationGone(testPkgName1, timeout(windowMs), notificationId);
+
+            clearInvocations(mInjector.getNotificationManager());
+            // Start another one and stop it.
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+                    testPid2, true);
+            Thread.sleep(shortMs);
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+                    testPid2, false);
+
+            // Not long enough, it shouldn't show notification in this case.
+            checkNotificationShown(
+                    new String[] {testPkgName2}, timeout(windowMs * 2).times(0), false);
+
+            clearInvocations(mInjector.getNotificationManager());
+            // Start the FGS again.
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+                    testPid2, true);
+            // Verify we have the notification.
+            notificationId = checkNotificationShown(
+                    new String[] {testPkgName2}, timeout(windowMs * 2).times(2), true)[0];
+
+            // Stop this FGS
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+                    testPid2, false);
+            checkNotificationGone(testPkgName2, timeout(windowMs), notificationId);
+
+            // Start over with concurrent cases.
+            clearInvocations(mInjector.getNotificationManager());
+            mBgRestrictionController.resetRestrictionSettings();
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+                    testPid2, true);
+            Thread.sleep(shortMs);
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+                    testPid1, true);
+
+            // Verify we've seen both notifications, and test pkg2 should be shown before test pkg1.
+            int[] notificationIds = checkNotificationShown(
+                    new String[] {testPkgName2, testPkgName1},
+                    timeout(windowMs * 2).times(4), true);
+
+            // Stop both of them.
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+                    testPid1, false);
+            checkNotificationGone(testPkgName1, timeout(windowMs), notificationIds[1]);
+            clearInvocations(mInjector.getNotificationManager());
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+                    testPid2, false);
+            checkNotificationGone(testPkgName2, timeout(windowMs), notificationIds[0]);
+
+            // Test the interlaced case.
+            clearInvocations(mInjector.getNotificationManager());
+            mBgRestrictionController.resetRestrictionSettings();
+            mAppFGSTracker.reset();
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+                    testPid1, true);
+
+            final long initialWaitMs = thresholdMs / 2;
+            Thread.sleep(initialWaitMs);
+
+            for (long remaining = thresholdMs - initialWaitMs; remaining > 0;) {
+                mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+                        testPid1, false);
+                mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+                        testPid2, true);
+                Thread.sleep(shortMs);
+                mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+                        testPid1, true);
+                mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+                        testPid2, false);
+                Thread.sleep(shortMs);
+                remaining -= shortMs;
+            }
+
+            // Verify test pkg1 got the notification, but not test pkg2.
+            notificationId = checkNotificationShown(
+                    new String[] {testPkgName1}, timeout(windowMs).times(2), true)[0];
+
+            clearInvocations(mInjector.getNotificationManager());
+            // Stop the FGS.
+            mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+                    testPid1, false);
+            checkNotificationGone(testPkgName1, timeout(windowMs), notificationId);
+        } finally {
+            closeIfNotNull(longRunningFGSMonitor);
+            closeIfNotNull(longRunningFGSWindow);
+            closeIfNotNull(longRunningFGSThreshold);
+        }
+    }
+
+    @Test
+    public void testLongFGSExemptions() throws Exception {
+        final int testPkgIndex1 = 1;
+        final String testPkgName1 = TEST_PACKAGE_BASE + testPkgIndex1;
+        final int testUser1 = TEST_USER0;
+        final int testUid1 = UserHandle.getUid(testUser1, TEST_PACKAGE_APPID_BASE + testPkgIndex1);
+        final int testPid1 = 1234;
+
+        final int testPkgIndex2 = 2;
+        final String testPkgName2 = TEST_PACKAGE_BASE + testPkgIndex2;
+        final int testUser2 = TEST_USER0;
+        final int testUid2 = UserHandle.getUid(testUser2, TEST_PACKAGE_APPID_BASE + testPkgIndex2);
+        final int testPid2 = 1235;
+
+        final long windowMs = 2_000;
+        final long thresholdMs = 1_000;
+
+        DeviceConfigSession<Boolean> longRunningFGSMonitor = null;
+        DeviceConfigSession<Long> longRunningFGSWindow = null;
+        DeviceConfigSession<Long> longRunningFGSThreshold = null;
+        DeviceConfigSession<Long> mediaPlaybackFGSThreshold = null;
+        DeviceConfigSession<Long> locationFGSThreshold = null;
+
+        doReturn(testPkgName1).when(mInjector).getPackageName(testPid1);
+        doReturn(testPkgName2).when(mInjector).getPackageName(testPid2);
+
+        try {
+            longRunningFGSMonitor = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppFGSPolicy.KEY_BG_FGS_MONITOR_ENABLED,
+                    DeviceConfig::getBoolean,
+                    AppFGSPolicy.DEFAULT_BG_FGS_MONITOR_ENABLED);
+            longRunningFGSMonitor.set(true);
+
+            longRunningFGSWindow = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppFGSPolicy.KEY_BG_FGS_LONG_RUNNING_WINDOW,
+                    DeviceConfig::getLong,
+                    AppFGSPolicy.DEFAULT_BG_FGS_LONG_RUNNING_WINDOW);
+            longRunningFGSWindow.set(windowMs);
+
+            longRunningFGSThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppFGSPolicy.KEY_BG_FGS_LONG_RUNNING_THRESHOLD,
+                    DeviceConfig::getLong,
+                    AppFGSPolicy.DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD);
+            longRunningFGSThreshold.set(thresholdMs);
+
+            mediaPlaybackFGSThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppFGSPolicy.KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD,
+                    DeviceConfig::getLong,
+                    AppFGSPolicy.DEFAULT_BG_FGS_MEDIA_PLAYBACK_THRESHOLD);
+            mediaPlaybackFGSThreshold.set(thresholdMs);
+
+            locationFGSThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppFGSPolicy.KEY_BG_FGS_LOCATION_THRESHOLD,
+                    DeviceConfig::getLong,
+                    AppFGSPolicy.DEFAULT_BG_FGS_LOCATION_THRESHOLD);
+            locationFGSThreshold.set(thresholdMs);
+
+            // Long-running FGS with type "location", but ran for a very short time.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_LOCATION, 0, null, null, null,
+                    timeout(windowMs * 2).times(2));
+
+            // Long-running FGS with type "location", and ran for a while.
+            // We shouldn't see notifications in this case.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_LOCATION, thresholdMs * 2, null, null, null,
+                    timeout(windowMs * 2).times(0));
+
+            // Long-running FGS with background location permission.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_LOCATION, 0, ACCESS_BACKGROUND_LOCATION, null, null,
+                    timeout(windowMs * 2).times(0));
+
+            // Long-running FGS with type "mediaPlayback", but ran for a very short time.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, null, null, null,
+                    timeout(windowMs * 2).times(2));
+
+            // Long-running FGS with type "mediaPlayback", and ran for a while.
+            // We shouldn't see notifications in this case.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, thresholdMs * 2, null, null, null,
+                    timeout(windowMs * 2).times(0));
+
+            // Long-running FGS with type "camera", and ran for a while.
+            // We shouldn't see notifications in this case.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_CAMERA, thresholdMs * 2, null, null, null,
+                    timeout(windowMs * 2).times(0));
+
+            // Long-running FGS with type "location|mediaPlayback", but ran for a very short time.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+                    0, null, null, null, timeout(windowMs * 2).times(2));
+
+            // Long-running FGS with type "location|mediaPlayback", and ran for a while.
+            // We shouldn't see notifications in this case.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+                    thresholdMs * 2, null, null, null, timeout(windowMs * 2).times(0));
+
+            // Long-running FGS with a media session starts/stops right away.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, 0, null,
+                    List.of(Pair.create(createMediaControllers(
+                            new String[] {testPkgName1}, new int[] {testUid1}), 0L)), null,
+                    timeout(windowMs * 2).times(2));
+
+            // Long-running FGS with media session, and ran for a while.
+            // We shouldn't see notifications in this case.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, thresholdMs * 2, null,
+                    List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+                            new int[] {testUid1}), thresholdMs * 2)), null,
+                    timeout(windowMs * 2).times(0));
+
+            // Long-running FGS with 2 media sessions start/stop right away
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, 0, null,
+                    List.of(Pair.create(createMediaControllers(
+                            new String[] {testPkgName1, testPkgName2},
+                            new int[] {testUid1, testUid2}), 0L)), null,
+                    timeout(windowMs * 2).times(2));
+
+            // Long-running FGS with 2 media sessions start/stop interlaced.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, 0, null,
+                    List.of(Pair.create(createMediaControllers(
+                                    new String[] {testPkgName1, testPkgName2},
+                                    new int[] {testUid1, testUid2}), thresholdMs),
+                            Pair.create(createMediaControllers(
+                                    new String[] {testPkgName1},
+                                    new int[] {testUid1}), thresholdMs / 10),
+                            Pair.create(createMediaControllers(
+                                    new String[] {testPkgName2},
+                                    new int[] {testUid2}), thresholdMs / 10),
+                            Pair.create(createMediaControllers(
+                                    new String[] {testPkgName1},
+                                    new int[] {testUid1}), thresholdMs / 10)
+                            ), null,
+                    timeout(windowMs * 2).times(0));
+
+            // Long-running FGS with top state for a very short time.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, 0, null, null, List.of(0L),
+                    timeout(windowMs * 2).times(2));
+
+            // Long-running FGS with top state for extended time.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, 0, null, null, List.of(0L, windowMs * 2, 0L),
+                    timeout(windowMs * 2).times(0));
+
+            // Long-running FGS with top state, on and off frequently.
+            runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, 0, null, null,
+                    List.of(0L, thresholdMs / 10, thresholdMs / 10, thresholdMs / 10,
+                            thresholdMs / 10, thresholdMs / 10, thresholdMs / 10),
+                    timeout(windowMs * 2).times(2));
+        } finally {
+            closeIfNotNull(longRunningFGSMonitor);
+            closeIfNotNull(longRunningFGSWindow);
+            closeIfNotNull(longRunningFGSThreshold);
+            closeIfNotNull(mediaPlaybackFGSThreshold);
+            closeIfNotNull(locationFGSThreshold);
+        }
+    }
+
+    private void resetBgRestrictionController() {
+        mBgRestrictionController.resetRestrictionSettings();
+        waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+    }
+
+    private void runTestLongFGSExemptionOnce(String packageName, int uid, int pid,
+            int serviceType, long sleepMs, String perm,
+            List<Pair<List<MediaController>, Long>> mediaControllers, List<Long> topStateChanges,
+            VerificationMode mode) throws Exception {
+        runExemptionTestOnce(
+                packageName, uid, pid, serviceType, sleepMs, true, perm, mediaControllers,
+                topStateChanges, true, true,
+                () -> checkNotificationShown(new String[] {packageName}, mode, false)
+        );
+    }
+
+    private void runExemptionTestOnce(String packageName, int uid, int pid,
+            int serviceType, long sleepMs, boolean stopAfterSleep, String perm,
+            List<Pair<List<MediaController>, Long>> mediaControllers,
+            List<Long> topStateChanges, boolean resetFGSTracker, boolean resetController,
+            RunnableWithException r) throws Exception {
+        if (resetFGSTracker) {
+            mAppFGSTracker.reset();
+            mAppMediaSessionTracker.reset();
+        }
+        if (resetController) {
+            resetBgRestrictionController();
+        }
+        clearInvocations(mInjector.getNotificationManager());
+
+        Thread topStateThread = null;
+        if (topStateChanges != null) {
+            final CountDownLatch latch = new CountDownLatch(1);
+            topStateThread = new Thread(() -> {
+                try {
+                    latch.await();
+                    boolean top = false;
+                    for (long l: topStateChanges) {
+                        mUidObservers.onUidStateChanged(uid,
+                                top ? PROCESS_STATE_TOP : PROCESS_STATE_FOREGROUND_SERVICE,
+                                0, 0);
+                        top = !top;
+                        Thread.sleep(l);
+                    }
+                    mUidObservers.onUidGone(uid, false);
+                } catch (InterruptedException | RemoteException e) {
+                }
+            });
+            topStateThread.start();
+            latch.countDown();
+        }
+
+        mAppFGSTracker.onForegroundServiceStateChanged(packageName, uid, pid, true);
+        if (serviceType != FOREGROUND_SERVICE_TYPE_NONE) {
+            mAppFGSTracker.mProcessObserver.onForegroundServicesChanged(pid, uid, serviceType);
+            Thread.sleep(sleepMs);
+            if (stopAfterSleep) {
+                // Stop it now.
+                mAppFGSTracker.mProcessObserver.onForegroundServicesChanged(pid, uid,
+                        FOREGROUND_SERVICE_TYPE_NONE);
+            }
+        }
+
+        if (perm != null) {
+            doReturn(PERMISSION_GRANTED)
+                    .when(mPermissionManagerServiceInternal)
+                    .checkPermission(packageName, perm, UserHandle.getUserId(uid));
+            doReturn(PERMISSION_GRANTED)
+                    .when(mPermissionManagerServiceInternal)
+                    .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION);
+        }
+
+        if (mediaControllers != null) {
+            for (Pair<List<MediaController>, Long> entry: mediaControllers) {
+                mActiveSessionListener.onActiveSessionsChanged(entry.first);
+                Thread.sleep(entry.second);
+            }
+            if (stopAfterSleep) {
+                // Stop it now.
+                mActiveSessionListener.onActiveSessionsChanged(null);
+            }
+        }
+
+        r.run();
+
+        // Stop this FGS
+        mAppFGSTracker.onForegroundServiceStateChanged(packageName, uid, pid, false);
+
+        if (perm != null) {
+            doReturn(PERMISSION_DENIED)
+                    .when(mPermissionManagerServiceInternal)
+                    .checkPermission(packageName, perm, UserHandle.getUserId(uid));
+            doReturn(PERMISSION_DENIED)
+                    .when(mPermissionManagerServiceInternal)
+                    .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION);
+        }
+        if (topStateThread != null) {
+            topStateThread.join();
+        }
+    }
+
+    private List<MediaController> createMediaControllers(String[] packageNames, int[] uids) {
+        final ArrayList<MediaController> controllers = new ArrayList<>();
+        for (int i = 0; i < packageNames.length; i++) {
+            controllers.add(createMediaController(packageNames[i], uids[i]));
+        }
+        return controllers;
+    }
+
+    private MediaController createMediaController(String packageName, int uid) {
+        final MediaController controller = mock(MediaController.class);
+        final MediaSession.Token token = mock(MediaSession.Token.class);
+        doReturn(packageName).when(controller).getPackageName();
+        doReturn(token).when(controller).getSessionToken();
+        doReturn(uid).when(token).getUid();
+        return controller;
+    }
+
+    @Test
+    public void testBgCurrentDrainMonitorExemptions() throws Exception {
+        final BatteryUsageStats stats = mock(BatteryUsageStats.class);
+        final List<BatteryUsageStats> statsList = Arrays.asList(stats);
+        final int testPkgIndex1 = 1;
+        final String testPkgName1 = TEST_PACKAGE_BASE + testPkgIndex1;
+        final int testUser = TEST_USER0;
+        final int testUid1 = UserHandle.getUid(testUser,
+                TEST_PACKAGE_APPID_BASE + testPkgIndex1);
+        final int testPid1 = 1234;
+        final int testPkgIndex2 = 2;
+        final String testPkgName2 = TEST_PACKAGE_BASE + testPkgIndex2;
+        final int testUid2 = UserHandle.getUid(testUser,
+                TEST_PACKAGE_APPID_BASE + testPkgIndex2);
+        final int testPid2 = 1235;
+        final TestAppRestrictionLevelListener listener = new TestAppRestrictionLevelListener();
+        final long timeout =
+                AppBatteryTracker.BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG * 2;
+        final long windowMs = 2_000;
+        final float restrictBucketThreshold = 2.0f;
+        final float restrictBucketThresholdMah =
+                BATTERY_FULL_CHARGE_MAH * restrictBucketThreshold / 100.0f;
+        final float bgRestrictedThreshold = 4.0f;
+        final float bgRestrictedThresholdMah =
+                BATTERY_FULL_CHARGE_MAH * bgRestrictedThreshold / 100.0f;
+        final float restrictBucketHighThreshold = 25.0f;
+        final float restrictBucketHighThresholdMah =
+                BATTERY_FULL_CHARGE_MAH * restrictBucketHighThreshold / 100.0f;
+        final float bgRestrictedHighThreshold = 25.0f;
+        final float bgRestrictedHighThresholdMah =
+                BATTERY_FULL_CHARGE_MAH * bgRestrictedHighThreshold / 100.0f;
+        final long bgMediaPlaybackMinDuration = 1_000L;
+        final long bgLocationMinDuration = 1_000L;
+
+        DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null;
+        DeviceConfigSession<Long> bgCurrentDrainWindow = null;
+        DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null;
+        DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null;
+        DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketHighThreshold = null;
+        DeviceConfigSession<Float> bgCurrentDrainBgRestrictedHighThreshold = null;
+        DeviceConfigSession<Long> bgMediaPlaybackMinDurationThreshold = null;
+        DeviceConfigSession<Long> bgLocationMinDurationThreshold = null;
+        DeviceConfigSession<Boolean> bgCurrentDrainEventDurationBasedThresholdEnabled = null;
+        DeviceConfigSession<Boolean> bgBatteryExemptionEnabled = null;
+
+        mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
+
+        setBackgroundRestrict(testPkgName1, testUid1, false, listener);
+
+        // Verify the current settings.
+        verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName1, testUid1);
+
+        final double[] zeros = new double[]{0.0f, 0.0f};
+        final int[] uids = new int[]{testUid1, testUid2};
+
+        doReturn(testPkgName1).when(mInjector).getPackageName(testPid1);
+        doReturn(testPkgName2).when(mInjector).getPackageName(testPid2);
+
+        try {
+            bgCurrentDrainMonitor = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
+                    DeviceConfig::getBoolean,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+            bgCurrentDrainMonitor.set(true);
+
+            bgCurrentDrainWindow = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
+                    DeviceConfig::getLong,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+            bgCurrentDrainWindow.set(windowMs);
+
+            bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
+                    DeviceConfig::getFloat,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+            bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold);
+
+            bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
+                    DeviceConfig::getFloat,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+            bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold);
+
+            bgCurrentDrainRestrictedBucketHighThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET,
+                    DeviceConfig::getFloat,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD);
+            bgCurrentDrainRestrictedBucketHighThreshold.set(restrictBucketHighThreshold);
+
+            bgCurrentDrainBgRestrictedHighThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED,
+                    DeviceConfig::getFloat,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD);
+            bgCurrentDrainBgRestrictedHighThreshold.set(bgRestrictedHighThreshold);
+
+            bgMediaPlaybackMinDurationThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION,
+                    DeviceConfig::getLong,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
+            bgMediaPlaybackMinDurationThreshold.set(bgMediaPlaybackMinDuration);
+
+            bgLocationMinDurationThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION,
+                    DeviceConfig::getLong,
+                    AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION);
+            bgLocationMinDurationThreshold.set(bgLocationMinDuration);
+
+            bgCurrentDrainEventDurationBasedThresholdEnabled = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED,
+                    DeviceConfig::getBoolean,
+                    AppBatteryPolicy
+                            .DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
+            bgCurrentDrainEventDurationBasedThresholdEnabled.set(true);
+
+            bgBatteryExemptionEnabled = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryExemptionPolicy.KEY_BG_BATTERY_EXEMPTION_ENABLED,
+                    DeviceConfig::getBoolean,
+                    AppBatteryExemptionPolicy.DEFAULT_BG_BATTERY_EXEMPTION_ENABLED);
+            bgBatteryExemptionEnabled.set(false);
+
+            mCurrentTimeMillis = 10_000L;
+            doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp();
+            doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(anyObject());
+
+            // Run with a media playback service which starts/stops immediately, we should
+            // goto the restricted bucket.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, true,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, null, null, null);
+
+            // Run with a media playback service with extended time. We should be back to normal.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_ADAPTIVE_BUCKET, timeout, false,
+                    () -> {
+                        // A user interaction will bring it back to normal.
+                        mIdleStateListener.onUserInteractionStarted(testPkgName1,
+                                UserHandle.getUserId(testUid1));
+                        waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+                        // It should have been back to normal.
+                        listener.verify(timeout, testUid1, testPkgName1,
+                                RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+                        verify(mInjector.getAppStandbyInternal(), times(1)).maybeUnrestrictApp(
+                                eq(testPkgName1),
+                                eq(UserHandle.getUserId(testUid1)),
+                                eq(REASON_MAIN_FORCED_BY_SYSTEM),
+                                eq(REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE),
+                                eq(REASON_MAIN_USAGE),
+                                eq(REASON_SUB_USAGE_USER_INTERACTION));
+                    }, windowMs, null, null, null);
+
+            // Start over.
+            resetBgRestrictionController();
+            setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+            mAppBatteryPolicy.reset();
+
+            // Run with a media playback service with extended time, with higher current drain.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah - 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, null, null, null);
+
+            // Run with a media playback service with extended time, with even higher current drain.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+                    null, windowMs, null, null, null);
+
+            // Start over.
+            resetBgRestrictionController();
+            setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+            mAppBatteryPolicy.reset();
+
+            // Run with a media session with extended time, with higher current drain.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
+                    List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+                                new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
+                    null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah - 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, null, null, null);
+
+            // Run with a media session with extended time, with even higher current drain.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
+                    List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+                                new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
+                    null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+                    null, windowMs, null, null, null);
+
+            // Start over.
+            resetBgRestrictionController();
+            setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+            mAppBatteryPolicy.reset();
+
+            // Run with a media session with extended time, with moderate current drain,
+            // but it ran on the top when the location service is active.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
+                    List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+                                new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
+                    List.of(0L, timeout * 2), listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, null, null, null);
+
+            // Start over.
+            resetBgRestrictionController();
+            setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+            mAppBatteryPolicy.reset();
+
+            // Run with a location service with extended time, with higher current drain.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah - 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, null, null, null);
+
+            // Run with a location service with extended time, with even higher current drain.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+                    null, windowMs, null, null, null);
+
+            // Start over.
+            resetBgRestrictionController();
+            setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+            mAppBatteryPolicy.reset();
+
+            // Run with a location service with extended time, with moderate current drain,
+            // but it ran on the top when the location service is active.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
+                    null, null, List.of(0L, timeout * 2), listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, null, null, null);
+
+            // Start over.
+            resetBgRestrictionController();
+            setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+            mAppBatteryPolicy.reset();
+
+            // Run with bg location permission, with higher current drain.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, 0, false,
+                    ACCESS_BACKGROUND_LOCATION, null, null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah - 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, null, null, null);
+
+            // Run with bg location permission, with even higher current drain.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, 0, false,
+                    ACCESS_BACKGROUND_LOCATION , null, null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+                    null, windowMs, null,  null, null);
+
+            // Now turn off the event duration based feature flag.
+            bgCurrentDrainEventDurationBasedThresholdEnabled.set(false);
+            // Turn on the battery exemption feature flag.
+            bgBatteryExemptionEnabled.set(true);
+
+            // Start over.
+            resetBgRestrictionController();
+            setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+            mAppBatteryPolicy.reset();
+
+            waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+
+            // Run with a media playback service which starts/stops immediately, we should
+            // goto the restricted bucket.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, true,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, null, null, null);
+
+            // Run with a media playback service with extended time. We should be back to normal.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketThresholdMah + 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_ADAPTIVE_BUCKET, timeout, false,
+                    () -> {
+                        // A user interaction will bring it back to normal.
+                        mIdleStateListener.onUserInteractionStarted(testPkgName1,
+                                UserHandle.getUserId(testUid1));
+                        waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+                        // It should have been back to normal.
+                        listener.verify(timeout, testUid1, testPkgName1,
+                                RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+                        verify(mInjector.getAppStandbyInternal(), times(1)).maybeUnrestrictApp(
+                                eq(testPkgName1),
+                                eq(UserHandle.getUserId(testUid1)),
+                                eq(REASON_MAIN_FORCED_BY_SYSTEM),
+                                eq(REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE),
+                                eq(REASON_MAIN_USAGE),
+                                eq(REASON_SUB_USAGE_USER_INTERACTION));
+                    }, windowMs, null, null, null);
+
+            // Start over.
+            resetBgRestrictionController();
+            setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+            mAppBatteryPolicy.reset();
+
+            final double[] initialBg = {1, 1}, initialFgs = {1, 1}, initialFg = zeros;
+
+            // Run with a media playback service with extended time, with higher current drain.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah - 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, initialBg, initialFgs, initialFg);
+
+            // Run with a media playback service with extended time, with even higher current drain,
+            // it still should stay in the current restriction level as we exempt the media
+            // playback.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah + 100, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+                    null, windowMs, initialBg, initialFgs, initialFg);
+
+            // Start over.
+            resetBgRestrictionController();
+            setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+            mAppBatteryPolicy.reset();
+
+            // Run with a media session with extended time, with higher current drain.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
+                    List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+                                new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
+                    null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah - 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, initialBg, initialFgs, initialFg);
+
+            // Run with a media session with extended time, with even higher current drain.
+            // it still should stay in the current restriction level as we exempt the media
+            // session.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
+                    List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+                                new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
+                    null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah + 100, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, initialBg, initialFgs, initialFg);
+
+            // Start over.
+            resetBgRestrictionController();
+            setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+            mAppBatteryPolicy.reset();
+
+            // Run with a location service with extended time, with higher current drain.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah - 1, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+                    null, windowMs, initialBg, initialFgs, initialFg);
+
+            // Run with a location service with extended time, with even higher current drain.
+            // it still should stay in the current restriction level as we exempt the location.
+            runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+                    FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
+                    null, null, null, listener, stats, uids,
+                    new double[]{restrictBucketHighThresholdMah + 100, 0},
+                    new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+                    true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+                    null, windowMs, initialBg, initialFgs, initialFg);
+        } finally {
+            closeIfNotNull(bgCurrentDrainMonitor);
+            closeIfNotNull(bgCurrentDrainWindow);
+            closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold);
+            closeIfNotNull(bgCurrentDrainBgRestrictedThreshold);
+            closeIfNotNull(bgCurrentDrainRestrictedBucketHighThreshold);
+            closeIfNotNull(bgCurrentDrainBgRestrictedHighThreshold);
+            closeIfNotNull(bgMediaPlaybackMinDurationThreshold);
+            closeIfNotNull(bgLocationMinDurationThreshold);
+            closeIfNotNull(bgCurrentDrainEventDurationBasedThresholdEnabled);
+            closeIfNotNull(bgBatteryExemptionEnabled);
+        }
+    }
+
+    private void runTestBgCurrentDrainExemptionOnce(String packageName, int uid, int pid,
+            int serviceType, long sleepMs, boolean stopAfterSleep, String perm,
+            List<Pair<List<MediaController>, Long>> mediaControllers,
+            List<Long> topStateChanges, TestAppRestrictionLevelListener listener,
+            BatteryUsageStats stats, int[] uids, double[] bg, double[] fgs, double[] fg,
+            boolean expectingTimeout, int expectingLevel, long timeout, boolean resetFGSTracker,
+            RunnableWithException extraVerifiers, long windowMs,
+            double[] initialBg, double[] initialFgs, double[] initialFg) throws Exception {
+        listener.mLatchHolder[0] = new CountDownLatch(1);
+        if (initialBg != null) {
+            doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+            mCurrentTimeMillis += windowMs + 1;
+            setUidBatteryConsumptions(stats, uids, initialBg, initialFgs, initialFg);
+            mAppBatteryExemptionTracker.reset();
+            mAppBatteryPolicy.reset();
+        }
+        runExemptionTestOnce(
+                packageName, uid, pid, serviceType, sleepMs, stopAfterSleep,
+                perm, mediaControllers, topStateChanges, resetFGSTracker, false,
+                () -> {
+                    clearInvocations(mInjector.getAppStandbyInternal());
+                    clearInvocations(mBgRestrictionController);
+                    runTestBgCurrentDrainMonitorOnce(listener, stats, uids, bg, fgs, fg, false,
+                            () -> {
+                                doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+                                mCurrentTimeMillis += windowMs + 1;
+                                if (expectingTimeout) {
+                                    try {
+                                        listener.verify(timeout, uid, packageName, expectingLevel);
+                                        fail("There shouldn't be any level change events");
+                                    } catch (Exception e) {
+                                        // Expected.
+                                    }
+                                } else {
+                                    listener.verify(timeout, uid, packageName, expectingLevel);
+                                }
+                                if (expectingLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+                                    verify(mInjector.getAppStandbyInternal(),
+                                            expectingTimeout ? never() : atLeast(1)).restrictApp(
+                                            eq(packageName),
+                                            eq(UserHandle.getUserId(uid)),
+                                            anyInt(), anyInt());
+                                } else if (expectingLevel
+                                         == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+                                    verify(mBgRestrictionController,
+                                            expectingTimeout ? never() : atLeast(1))
+                                            .handleRequestBgRestricted(eq(packageName), eq(uid));
+                                } else {
+                                    verify(mInjector.getAppStandbyInternal(),
+                                            expectingTimeout ? never() : atLeast(1))
+                                            .setAppStandbyBucket(
+                                                   eq(packageName),
+                                                   eq(STANDBY_BUCKET_RARE),
+                                                   eq(UserHandle.getUserId(uid)),
+                                                   anyInt(), anyInt());
+                                }
+                                if (extraVerifiers != null) {
+                                    extraVerifiers.run();
+                                }
+                            }
+                    );
+                }
+        );
+    }
+
+    @Test
+    public void testExcessiveBroadcasts() throws Exception {
+        final long windowMs = 5_000;
+        final int threshold = 10;
+        runTestExcessiveEvent(AppBroadcastEventsPolicy.KEY_BG_BROADCAST_MONITOR_ENABLED,
+                AppBroadcastEventsPolicy.DEFAULT_BG_BROADCAST_MONITOR_ENABLED,
+                AppBroadcastEventsPolicy.KEY_BG_BROADCAST_WINDOW,
+                AppBroadcastEventsPolicy.DEFAULT_BG_BROADCAST_WINDOW,
+                AppBroadcastEventsPolicy.KEY_BG_EX_BROADCAST_THRESHOLD,
+                AppBroadcastEventsPolicy.DEFAULT_BG_EX_BROADCAST_THRESHOLD,
+                windowMs, threshold, mBroadcastEventListener::onSendingBroadcast,
+                mAppBroadcastEventsTracker,
+                new long[][] {
+                    new long[] {1_000L, 2_000L, 2_000L},
+                    new long[] {2_000L, 2_000L, 1_000L},
+                },
+                new int[][] {
+                    new int[] {3, 3, 3},
+                    new int[] {3, 3, 4},
+                },
+                new boolean[] {
+                    true,
+                    false,
+                }
+        );
+    }
+
+    @Test
+    public void testExcessiveBindServices() throws Exception {
+        final long windowMs = 5_000;
+        final int threshold = 10;
+        runTestExcessiveEvent(AppBindServiceEventsPolicy.KEY_BG_BIND_SVC_MONITOR_ENABLED,
+                AppBindServiceEventsPolicy.DEFAULT_BG_BIND_SVC_MONITOR_ENABLED,
+                AppBindServiceEventsPolicy.KEY_BG_BIND_SVC_WINDOW,
+                AppBindServiceEventsPolicy.DEFAULT_BG_BIND_SVC_WINDOW,
+                AppBindServiceEventsPolicy.KEY_BG_EX_BIND_SVC_THRESHOLD,
+                AppBindServiceEventsPolicy.DEFAULT_BG_EX_BIND_SVC_THRESHOLD,
+                windowMs, threshold, mBindServiceEventListener::onBindingService,
+                mAppBindServiceEventsTracker,
+                new long[][] {
+                    new long[] {0L, 2_000L, 4_000L, 1_000L},
+                    new long[] {2_000L, 2_000L, 2_000L, 2_000L},
+                },
+                new int[][] {
+                    new int[] {8, 3, 1, 0}, // Will goto restricted bucket.
+                    new int[] {3, 3, 3, 3},
+                },
+                new boolean[] {
+                    false,
+                    true,
+                }
+        );
+    }
+
+    private void runTestExcessiveEvent(String keyEnable, boolean defaultEnable,
+            String keyWindow, long defaultWindow, String keyThreshold, int defaultThreshold,
+            long windowMs, int threshold, BiConsumer<String, Integer> eventEmitter,
+            BaseAppStateEventsTracker tracker, long[][] waitMs, int[][] events,
+            boolean[] expectingTimeout) throws Exception {
+        final int testPkgIndex = 1;
+        final String testPkgName = TEST_PACKAGE_BASE + testPkgIndex;
+        final int testUser = TEST_USER0;
+        final int testUid = UserHandle.getUid(testUser, TEST_PACKAGE_APPID_BASE + testPkgIndex);
+        final int testPid = 1234;
+
+        final long timeoutMs = 2_000;
+
+        final TestAppRestrictionLevelListener listener = new TestAppRestrictionLevelListener();
+
+        mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
+        setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+        DeviceConfigSession<Boolean> enableMonitor = null;
+        DeviceConfigSession<Long> eventsWindow = null;
+        DeviceConfigSession<Integer> eventsThreshold = null;
+
+        doReturn(testPkgName).when(mInjector).getPackageName(testPid);
+
+        verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+
+        try {
+            enableMonitor = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    keyEnable,
+                    DeviceConfig::getBoolean,
+                    defaultEnable);
+            enableMonitor.set(true);
+
+            eventsWindow = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    keyWindow,
+                    DeviceConfig::getLong,
+                    defaultWindow);
+            eventsWindow.set(windowMs);
+
+            eventsThreshold = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    keyThreshold,
+                    DeviceConfig::getInt,
+                    defaultThreshold);
+            eventsThreshold.set(threshold);
+
+            for (int i = 0; i < waitMs.length; i++) {
+                resetBgRestrictionController();
+                listener.mLatchHolder[0] = new CountDownLatch(1);
+                tracker.reset();
+                clearInvocations(mInjector.getAppStandbyInternal());
+                clearInvocations(mBgRestrictionController);
+                for (int j = 0; j < waitMs[i].length; j++) {
+                    for (int k = 0; k < events[i][j]; k++) {
+                        eventEmitter.accept(testPkgName, testUid);
+                    }
+                    Thread.sleep(waitMs[i][j]);
+                }
+                waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+                if (expectingTimeout[i]) {
+                    verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+                    try {
+                        listener.verify(timeoutMs, testUid, testPkgName,
+                                RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+                        fail("There shouldn't be any level change events");
+                    } catch (TimeoutException e) {
+                        // expected.
+                    }
+                } else {
+                    verifyRestrictionLevel(RESTRICTION_LEVEL_RESTRICTED_BUCKET,
+                            testPkgName, testUid);
+                    listener.verify(timeoutMs, testUid, testPkgName,
+                            RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+                }
+            }
+        } finally {
+            closeIfNotNull(enableMonitor);
+            closeIfNotNull(eventsWindow);
+            closeIfNotNull(eventsThreshold);
+        }
+    }
+
+    private int[] checkNotificationShown(String[] packageName, VerificationMode mode,
+            boolean verifyNotification) throws Exception {
+        final ArgumentCaptor<Integer> notificationIdCaptor =
+                ArgumentCaptor.forClass(Integer.class);
+        final ArgumentCaptor<Notification> notificationCaptor =
+                ArgumentCaptor.forClass(Notification.class);
+        verify(mInjector.getNotificationManager(), mode).notifyAsUser(any(),
+                notificationIdCaptor.capture(), notificationCaptor.capture(), any());
+        final int[] notificationId = new int[packageName.length];
+        if (verifyNotification) {
+            for (int i = 0, j = 0; i < packageName.length; j++) {
+                final int id = notificationIdCaptor.getAllValues().get(j);
+                if (id == NotificationHelper.SUMMARY_NOTIFICATION_ID) {
+                    continue;
+                }
+                final Notification n = notificationCaptor.getAllValues().get(j);
+                notificationId[i] = id;
+                assertTrue(NotificationHelper.SUMMARY_NOTIFICATION_ID < notificationId[i]);
+                assertEquals(NotificationHelper.GROUP_KEY, n.getGroup());
+                assertEquals(ABUSIVE_BACKGROUND_APPS, n.getChannelId());
+                assertEquals(packageName[i], n.extras.getString(Intent.EXTRA_PACKAGE_NAME));
+                i++;
+            }
+        }
+        return notificationId;
+    }
+
+    private void checkNotificationGone(String packageName, VerificationMode mode,
+            int notificationId) throws Exception {
+        final ArgumentCaptor<Integer> notificationIdCaptor =
+                ArgumentCaptor.forClass(Integer.class);
+        verify(mInjector.getNotificationManager(), mode).cancel(notificationIdCaptor.capture());
+        assertEquals(notificationId, notificationIdCaptor.getValue().intValue());
+    }
+
+    private void closeIfNotNull(DeviceConfigSession<?> config) throws Exception {
+        if (config != null) {
+            config.close();
+        }
+    }
+
+    private interface RunnableWithException {
+        void run() throws Exception;
+    }
+
+    private void runTestBgCurrentDrainMonitorOnce(TestAppRestrictionLevelListener listener,
+            BatteryUsageStats stats, int[] uids, double[] bg, double[] fgs, double[] fg,
+            RunnableWithException runnable) throws Exception {
+        runTestBgCurrentDrainMonitorOnce(listener, stats, uids, bg, fgs, fg, true, runnable);
+    }
+
+    private void runTestBgCurrentDrainMonitorOnce(TestAppRestrictionLevelListener listener,
+            BatteryUsageStats stats, int[] uids, double[] bg, double[] fgs, double[] fg,
+            boolean resetListener, RunnableWithException runnable) throws Exception {
+        if (resetListener) {
+            listener.mLatchHolder[0] = new CountDownLatch(1);
+        }
+        setUidBatteryConsumptions(stats, uids, bg, fgs, fg);
+        runnable.run();
+    }
+
+    private void setUidBatteryConsumptions(BatteryUsageStats stats, int[] uids, double[] bg,
+            double[] fgs, double[] fg) {
+        ArrayList<UidBatteryConsumer> consumers = new ArrayList<>();
+        for (int i = 0; i < uids.length; i++) {
+            consumers.add(mockUidBatteryConsumer(uids[i], bg[i], fgs[i], fg[i]));
+        }
+        doReturn(consumers).when(stats).getUidBatteryConsumers();
+    }
+
+    private UidBatteryConsumer mockUidBatteryConsumer(int uid, double bg, double fgs, double fg) {
+        UidBatteryConsumer uidConsumer = mock(UidBatteryConsumer.class);
+        doReturn(uid).when(uidConsumer).getUid();
+        doReturn(bg).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_BG));
+        doReturn(fgs).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_FGS));
+        doReturn(fg).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_FG));
+        return uidConsumer;
+    }
+
+    private void setBackgroundRestrict(String pkgName, int uid, boolean restricted,
+            TestAppRestrictionLevelListener listener) throws Exception {
+        Log.i(TAG, "Setting background restrict to " + restricted + " for " + pkgName + " " + uid);
+        listener.mLatchHolder[0] = new CountDownLatch(1);
+        doReturn(restricted).when(mAppStateTracker).isAppBackgroundRestricted(uid, pkgName);
+        mFasListener.updateBackgroundRestrictedForUidPackage(uid, pkgName, restricted);
+        waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+    }
+
+    private class TestAppRestrictionLevelListener implements AppBackgroundRestrictionListener {
+        private final CountDownLatch[] mLatchHolder = new CountDownLatch[1];
+        final int[] mUidHolder = new int[1];
+        final String[] mPkgNameHolder = new String[1];
+        final int[] mLevelHolder = new int[1];
+
+        @Override
+        public void onRestrictionLevelChanged(int uid, String packageName, int newLevel) {
+            mUidHolder[0] = uid;
+            mPkgNameHolder[0] = packageName;
+            mLevelHolder[0] = newLevel;
+            mLatchHolder[0].countDown();
+        };
+
+        void verify(long timeout, int uid, String pkgName, int level) throws Exception {
+            if (!mLatchHolder[0].await(timeout, TimeUnit.MILLISECONDS)) {
+                throw new TimeoutException();
+            }
+            assertEquals(uid, mUidHolder[0]);
+            assertEquals(pkgName, mPkgNameHolder[0]);
+            assertEquals(level, mLevelHolder[0]);
+        }
+    }
+
+    private void verifyRestrictionLevel(int level, String pkgName, int uid) {
+        assertEquals(level, mBgRestrictionController.getRestrictionLevel(uid));
+        assertEquals(level, mBgRestrictionController.getRestrictionLevel(uid, pkgName));
+    }
+
+    private void waitForIdleHandler(Handler handler) {
+        waitForIdleHandler(handler, Duration.ofSeconds(1));
+    }
+
+    private void waitForIdleHandler(Handler handler, Duration timeout) {
+        final MessageQueue queue = handler.getLooper().getQueue();
+        final CountDownLatch latch = new CountDownLatch(1);
+        queue.addIdleHandler(() -> {
+            latch.countDown();
+            // Remove idle handler
+            return false;
+        });
+        try {
+            latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("Interrupted unexpectedly: " + e);
+        }
+    }
+
+    @Test
+    public void testMergeAppStateDurations() throws Exception {
+        final BaseAppStateDurations testObj = new BaseAppStateDurations(0, "", 1, "", null) {};
+        assertAppStateDurations(null, testObj.add(null, null));
+        assertAppStateDurations(new LinkedList<BaseTimeEvent>(), testObj.add(
+                null, new LinkedList<BaseTimeEvent>()));
+        assertAppStateDurations(new LinkedList<BaseTimeEvent>(), testObj.add(
+                new LinkedList<BaseTimeEvent>(), null));
+        assertAppStateDurations(createDurations(1), testObj.add(
+                createDurations(1), new LinkedList<BaseTimeEvent>()));
+        assertAppStateDurations(createDurations(1), testObj.add(
+                new LinkedList<BaseTimeEvent>(), createDurations(1)));
+        assertAppStateDurations(createDurations(1, 4, 5, 8, 9), testObj.add(
+                createDurations(1, 3, 5, 7, 9), createDurations(2, 4, 6, 8, 10)));
+        assertAppStateDurations(createDurations(1, 5), testObj.add(
+                createDurations(1, 2, 3, 4), createDurations(2, 3, 4, 5)));
+        assertAppStateDurations(createDurations(1, 4, 6, 9), testObj.add(
+                createDurations(2, 4, 6, 9), createDurations(1, 4, 7, 8)));
+        assertAppStateDurations(createDurations(1, 4, 5, 8, 9, 10), testObj.add(
+                createDurations(1, 4, 6, 8), createDurations(1, 3, 5, 8, 9, 10)));
+    }
+
+    @Test
+    public void testSubtractAppStateDurations() throws Exception {
+        final BaseAppStateDurations testObj = new BaseAppStateDurations(0, "", 1, "", null) {};
+        assertAppStateDurations(null, testObj.subtract(null, null));
+        assertAppStateDurations(null, testObj.subtract(null, new LinkedList<BaseTimeEvent>()));
+        assertAppStateDurations(new LinkedList<BaseTimeEvent>(), testObj.subtract(
+                new LinkedList<BaseTimeEvent>(), null));
+        assertAppStateDurations(createDurations(1), testObj.subtract(
+                createDurations(1), new LinkedList<BaseTimeEvent>()));
+        assertAppStateDurations(new LinkedList<BaseTimeEvent>(), testObj.subtract(
+                new LinkedList<BaseTimeEvent>(), createDurations(1)));
+        assertAppStateDurations(new LinkedList<BaseTimeEvent>(), testObj.subtract(
+                createDurations(1), createDurations(1)));
+        assertAppStateDurations(createDurations(1, 2, 5, 6, 9, 10), testObj.subtract(
+                createDurations(1, 3, 5, 7, 9), createDurations(2, 4, 6, 8, 10)));
+        assertAppStateDurations(createDurations(1, 2, 3, 4), testObj.subtract(
+                createDurations(1, 4, 6, 7, 9, 10), createDurations(2, 3, 5, 8, 9, 10)));
+        assertAppStateDurations(createDurations(3, 4, 9, 10), testObj.subtract(
+                createDurations(1, 4, 6, 8, 9, 10), createDurations(1, 3, 5, 8)));
+        assertAppStateDurations(createDurations(1, 2, 3, 4, 5, 6, 7, 8), testObj.subtract(
+                createDurations(1, 6, 7, 8), createDurations(2, 3, 4, 5, 8, 10)));
+        assertAppStateDurations(createDurations(5, 6), testObj.subtract(
+                createDurations(2, 3, 5, 6), createDurations(1, 4, 7, 8)));
+        assertAppStateDurations(createDurations(2, 3, 4, 5, 6, 7, 8), testObj.subtract(
+                createDurations(1), createDurations(1, 2, 3, 4, 5, 6, 7, 8)));
+    }
+
+    private void assertAppStateDurations(LinkedList<BaseTimeEvent> expected,
+            LinkedList<BaseTimeEvent> actual) throws Exception {
+        assertListEquals(expected, actual);
+    }
+
+    private <T> void assertListEquals(LinkedList<T> expected, LinkedList<T> actual) {
+        assertEquals(expected == null || expected.isEmpty(), actual == null || actual.isEmpty());
+        if (expected != null) {
+            if (expected.size() > 0) {
+                assertEquals(expected.size(), actual.size());
+            }
+            while (expected.peek() != null) {
+                assertTrue(expected.poll().equals(actual.poll()));
+            }
+        }
+    }
+
+    private LinkedList<BaseTimeEvent> createDurations(long... timestamps) {
+        return Arrays.stream(timestamps).mapToObj(BaseTimeEvent::new)
+                .collect(LinkedList<BaseTimeEvent>::new, LinkedList<BaseTimeEvent>::add,
+                (a, b) -> a.addAll(b));
+    }
+
+    private LinkedList<Integer> createIntLinkedList(int[] vals) {
+        return Arrays.stream(vals).collect(LinkedList<Integer>::new, LinkedList<Integer>::add,
+                (a, b) -> a.addAll(b));
+    }
+
+    @Test
+    public void testAppStateTimeSlotEvents() throws Exception {
+        final long maxTrackingDuration = 5_000L;
+        assertAppStateTimeSlotEvents(new int[] {2, 2, 0, 0, 1},
+                new long[] {1_500, 1_500, 2_100, 2_999, 5_999}, 5_000);
+        assertAppStateTimeSlotEvents(new int[] {2, 2, 0, 0, 1, 1},
+                new long[] {1_500, 1_500, 2_100, 2_999, 5_999, 6_000}, 6_000);
+        assertAppStateTimeSlotEvents(new int[] {2, 0, 0, 1, 1, 1},
+                new long[] {1_500, 1_500, 2_100, 2_999, 5_999, 6_000, 7_000}, 7_000);
+        assertMergeAppStateTimeSlotEvents(new int[] {}, new long[] {}, new long[] {}, 0);
+        assertMergeAppStateTimeSlotEvents(new int[] {1}, new long[] {}, new long[] {1_500}, 1_000);
+        assertMergeAppStateTimeSlotEvents(new int[] {1}, new long[] {1_500}, new long[] {}, 1_000);
+        assertMergeAppStateTimeSlotEvents(new int[] {1, 1},
+                new long[] {1_500}, new long[] {2_500}, 2_000);
+        assertMergeAppStateTimeSlotEvents(new int[] {1, 1},
+                new long[] {2_500}, new long[] {1_500}, 2_000);
+        assertMergeAppStateTimeSlotEvents(new int[] {1, 2, 1},
+                new long[] {1_500, 2_500}, new long[] {2_600, 3_000}, 3_000);
+        assertMergeAppStateTimeSlotEvents(new int[] {2, 1, 1},
+                new long[] {2_600, 3_500}, new long[] {1_500, 1_600}, 3_000);
+        assertMergeAppStateTimeSlotEvents(new int[] {1, 2, 1},
+                new long[] {1_500, 3_500}, new long[] {2_600, 2_700}, 3_000);
+        assertMergeAppStateTimeSlotEvents(new int[] {1, 2, 1},
+                new long[] {2_500, 2_600}, new long[] {1_500, 3_700}, 3_000);
+        assertMergeAppStateTimeSlotEvents(new int[] {1, 0, 0, 0, 0, 1},
+                new long[] {2_500, 8_600}, new long[] {1_500, 3_700}, 8_000);
+    }
+
+    private BaseAppStateTimeSlotEvents createBaseAppStateTimeSlotEvents(
+            long slotSize, long maxTrackingDuration, long[] timestamps) {
+        final BaseAppStateTimeSlotEvents testObj = new BaseAppStateTimeSlotEvents(
+                0, "", 1, slotSize, "", () -> maxTrackingDuration) {};
+        for (int i = 0; i < timestamps.length; i++) {
+            testObj.addEvent(timestamps[i], 0);
+        }
+        return testObj;
+    }
+
+    private void assertAppStateTimeSlotEvents(int[] expectedEvents, long[] timestamps,
+            long expectedCurTimeslot) {
+        final BaseAppStateTimeSlotEvents testObj = createBaseAppStateTimeSlotEvents(1_000L,
+                5_000L, timestamps);
+        assertEquals(expectedCurTimeslot, testObj.getCurrentSlotStartTime(0));
+        assertListEquals(createIntLinkedList(expectedEvents), testObj.getRawEvents(0));
+    }
+
+    private void assertMergeAppStateTimeSlotEvents(int[] expectedEvents, long[] timestamps1,
+            long[] timestamps2, long expectedCurTimeslot) {
+        final BaseAppStateTimeSlotEvents testObj1 = createBaseAppStateTimeSlotEvents(1_000L,
+                5_000L, timestamps1);
+        final BaseAppStateTimeSlotEvents testObj2 = createBaseAppStateTimeSlotEvents(1_000L,
+                5_000L, timestamps2);
+        testObj1.add(testObj2);
+        assertEquals(expectedCurTimeslot, testObj1.getCurrentSlotStartTime(0));
+        assertListEquals(createIntLinkedList(expectedEvents), testObj1.getRawEvents(0));
+    }
+
+    @Test
+    public void testMergeUidBatteryUsage() throws Exception {
+        final UidBatteryStates testObj = new UidBatteryStates(0, "", null);
+        assertListEquals(null, testObj.add(null, null));
+        assertListEquals(new LinkedList<UidStateEventWithBattery>(), testObj.add(
+                null, new LinkedList<UidStateEventWithBattery>()));
+        assertListEquals(new LinkedList<UidStateEventWithBattery>(), testObj.add(
+                new LinkedList<UidStateEventWithBattery>(), null));
+        assertListEquals(createUidStateEventWithBatteryList(
+                new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+                testObj.add(createUidStateEventWithBatteryList(
+                new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+                new LinkedList<UidStateEventWithBattery>()));
+        assertListEquals(createUidStateEventWithBatteryList(
+                new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+                testObj.add(new LinkedList<UidStateEventWithBattery>(),
+                createUidStateEventWithBatteryList(
+                new boolean[] {true}, new long[] {10L}, new double[] {10.0d})));
+        assertListEquals(createUidStateEventWithBatteryList(
+                new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+                testObj.add(createUidStateEventWithBatteryList(
+                new boolean[] {true}, new long[] {11L}, new double[] {11.0d}),
+                createUidStateEventWithBatteryList(
+                new boolean[] {true}, new long[] {10L}, new double[] {10.0d})));
+        assertListEquals(createUidStateEventWithBatteryList(
+                new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+                testObj.add(createUidStateEventWithBatteryList(
+                new boolean[] {true, false}, new long[] {11L, 12L}, new double[] {11.0d, 1.0d}),
+                createUidStateEventWithBatteryList(
+                new boolean[] {true}, new long[] {10L}, new double[] {10.0d})));
+        assertListEquals(createUidStateEventWithBatteryList(
+                new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+                testObj.add(createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true}, new long[] {11L, 12L, 13L},
+                new double[] {11.0d, 1.0d, 13.0d}),
+                createUidStateEventWithBatteryList(
+                new boolean[] {true}, new long[] {10L}, new double[] {10.0d})));
+        assertListEquals(createUidStateEventWithBatteryList(
+                new boolean[] {true, false}, new long[] {10L, 13L}, new double[] {10.0d, 3.0d}),
+                testObj.add(createUidStateEventWithBatteryList(
+                new boolean[] {true, false}, new long[] {11L, 13L}, new double[] {11.0d, 2.0d}),
+                createUidStateEventWithBatteryList(
+                new boolean[] {true, false}, new long[] {10L, 12L}, new double[] {10.0d, 2.0d})));
+        assertListEquals(createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true}, new long[] {10L, 13L, 14L},
+                new double[] {10.0d, 3.0d, 14.0d}),
+                testObj.add(createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true}, new long[] {11L, 13L, 14L},
+                new double[] {11.0d, 2.0d, 14.0d}),
+                createUidStateEventWithBatteryList(
+                new boolean[] {true, false}, new long[] {10L, 12L}, new double[] {10.0d, 2.0d})));
+        assertListEquals(createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false, true, false},
+                new long[] {10L, 13L, 14L, 17L, 18L, 21L},
+                new double[] {10.0d, 3.0d, 14.0d, 3.0d, 18.0d, 3.0d}),
+                testObj.add(createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false, true, false},
+                new long[] {11L, 13L, 15L, 17L, 19L, 21L},
+                new double[] {11.0d, 2.0d, 15.0d, 2.0d, 19.0d, 2.0d}),
+                createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false, true, false},
+                new long[] {10L, 12L, 14L, 16L, 18L, 20L},
+                new double[] {10.0d, 2.0d, 14.0d, 2.0d, 18.0d, 2.0d})));
+        assertListEquals(createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false, true, false, true, false, true, false},
+                new long[] {10L, 11L, 12L, 13L, 14L, 15L, 16L, 17L, 18L, 19L},
+                new double[] {10.0d, 1.0d, 12.0d, 1.0d, 14.0d, 1.0d, 16.0d, 1.0d, 18.0d, 1.0d}),
+                testObj.add(createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false},
+                new long[] {12L, 13L, 16L, 17L},
+                new double[] {12.0d, 1.0d, 16.0d, 1.0d}),
+                createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false, true, false},
+                new long[] {10L, 11L, 14L, 15L, 18L, 19L},
+                new double[] {10.0d, 1.0d, 14.0d, 1.0d, 18.0d, 1.0d})));
+        assertListEquals(createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false},
+                new long[] {10L, 14L, 18L, 19L},
+                new double[] {10.0d, 4.0d, 18.0d, 1.0d}),
+                testObj.add(createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false},
+                new long[] {11L, 12L, 13L, 14L},
+                new double[] {11.0d, 1.0d, 13.0d, 1.0d}),
+                createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false, true, false},
+                new long[] {10L, 11L, 12L, 13L, 18L, 19L},
+                new double[] {10.0d, 1.0d, 12.0d, 1.0d, 18.0d, 1.0d})));
+        assertListEquals(createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false},
+                new long[] {10L, 14L, 18L, 19L},
+                new double[] {10.0d, 4.0d, 18.0d, 1.0d}),
+                testObj.add(createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false},
+                new long[] {10L, 14L, 18L, 19L},
+                new double[] {10.0d, 4.0d, 18.0d, 1.0d}),
+                createUidStateEventWithBatteryList(
+                new boolean[] {true, false, true, false, true, false},
+                new long[] {10L, 11L, 12L, 13L, 18L, 19L},
+                new double[] {10.0d, 1.0d, 12.0d, 1.0d, 18.0d, 1.0d})));
+    }
+
+    private LinkedList<UidStateEventWithBattery> createUidStateEventWithBatteryList(
+            boolean[] isStart, long[] timestamps, double[] batteryUsage) {
+        final LinkedList<UidStateEventWithBattery> result = new LinkedList<>();
+        for (int i = 0; i < isStart.length; i++) {
+            result.add(new UidStateEventWithBattery(
+                    isStart[i], timestamps[i], batteryUsage[i], null));
+        }
+        return result;
+    }
+
+    private class TestBgRestrictionInjector extends AppRestrictionController.Injector {
+        private Context mContext;
+
+        TestBgRestrictionInjector(Context context) {
+            super(context);
+            mContext = context;
+        }
+
+        @Override
+        void initAppStateTrackers(AppRestrictionController controller) {
+            try {
+                mAppBatteryTracker = new AppBatteryTracker(mContext, controller,
+                        TestAppBatteryTrackerInjector.class.getDeclaredConstructor(
+                                BackgroundRestrictionTest.class),
+                        BackgroundRestrictionTest.this);
+                controller.addAppStateTracker(mAppBatteryTracker);
+                mAppBatteryExemptionTracker = new AppBatteryExemptionTracker(mContext, controller,
+                        TestAppBatteryExemptionTrackerInjector.class.getDeclaredConstructor(
+                                BackgroundRestrictionTest.class),
+                        BackgroundRestrictionTest.this);
+                controller.addAppStateTracker(mAppBatteryExemptionTracker);
+                mAppFGSTracker = new AppFGSTracker(mContext, controller,
+                        TestAppFGSTrackerInjector.class.getDeclaredConstructor(
+                                BackgroundRestrictionTest.class),
+                        BackgroundRestrictionTest.this);
+                controller.addAppStateTracker(mAppFGSTracker);
+                mAppMediaSessionTracker = new AppMediaSessionTracker(mContext, controller,
+                        TestAppMediaSessionTrackerInjector.class.getDeclaredConstructor(
+                                BackgroundRestrictionTest.class),
+                        BackgroundRestrictionTest.this);
+                controller.addAppStateTracker(mAppMediaSessionTracker);
+                mAppBroadcastEventsTracker = new AppBroadcastEventsTracker(mContext, controller,
+                        TestAppBroadcastEventsTrackerInjector.class.getDeclaredConstructor(
+                                BackgroundRestrictionTest.class),
+                        BackgroundRestrictionTest.this);
+                controller.addAppStateTracker(mAppBroadcastEventsTracker);
+                mAppBindServiceEventsTracker = new AppBindServiceEventsTracker(mContext, controller,
+                        TestAppBindServiceEventsTrackerInjector.class.getDeclaredConstructor(
+                                BackgroundRestrictionTest.class),
+                        BackgroundRestrictionTest.this);
+                controller.addAppStateTracker(mAppBindServiceEventsTracker);
+            } catch (NoSuchMethodException e) {
+                // Won't happen.
+            }
+        }
+
+        @Override
+        ActivityManagerInternal getActivityManagerInternal() {
+            return mActivityManagerInternal;
+        }
+
+        @Override
+        AppRestrictionController getAppRestrictionController() {
+            return mBgRestrictionController;
+        }
+
+        @Override
+        AppOpsManager getAppOpsManager() {
+            return mAppOpsManager;
+        }
+
+        @Override
+        AppStandbyInternal getAppStandbyInternal() {
+            return mAppStandbyInternal;
+        }
+
+        @Override
+        AppHibernationManagerInternal getAppHibernationInternal() {
+            return mAppHibernationInternal;
+        }
+
+        @Override
+        AppStateTracker getAppStateTracker() {
+            return mAppStateTracker;
+        }
+
+        @Override
+        IActivityManager getIActivityManager() {
+            return mIActivityManager;
+        }
+
+        @Override
+        UserManagerInternal getUserManagerInternal() {
+            return mUserManagerInternal;
+        }
+
+        @Override
+        PackageManagerInternal getPackageManagerInternal() {
+            return mPackageManagerInternal;
+        }
+
+        @Override
+        PackageManager getPackageManager() {
+            return mPackageManager;
+        }
+
+        @Override
+        NotificationManager getNotificationManager() {
+            return mNotificationManager;
+        }
+
+        @Override
+        RoleManager getRoleManager() {
+            return mRoleManager;
+        }
+
+        @Override
+        AppFGSTracker getAppFGSTracker() {
+            return mAppFGSTracker;
+        }
+
+        @Override
+        AppMediaSessionTracker getAppMediaSessionTracker() {
+            return mAppMediaSessionTracker;
+        }
+
+        @Override
+        ActivityManagerService getActivityManagerService() {
+            return mActivityManagerService;
+        }
+
+        @Override
+        UidBatteryUsageProvider getUidBatteryUsageProvider() {
+            return mAppBatteryTracker;
+        }
+
+        @Override
+        AppBatteryExemptionTracker getAppBatteryExemptionTracker() {
+            return mAppBatteryExemptionTracker;
+        }
+    }
+
+    private class TestBaseTrackerInjector<T extends BaseAppStatePolicy>
+            extends BaseAppStateTracker.Injector<T> {
+        @Override
+        void onSystemReady() {
+            getPolicy().onSystemReady();
+        }
+
+        @Override
+        ActivityManagerInternal getActivityManagerInternal() {
+            return BackgroundRestrictionTest.this.mActivityManagerInternal;
+        }
+
+        @Override
+        BatteryManagerInternal getBatteryManagerInternal() {
+            return BackgroundRestrictionTest.this.mBatteryManagerInternal;
+        }
+
+        @Override
+        BatteryStatsInternal getBatteryStatsInternal() {
+            return BackgroundRestrictionTest.this.mBatteryStatsInternal;
+        }
+
+        @Override
+        DeviceIdleInternal getDeviceIdleInternal() {
+            return BackgroundRestrictionTest.this.mDeviceIdleInternal;
+        }
+
+        @Override
+        UserManagerInternal getUserManagerInternal() {
+            return BackgroundRestrictionTest.this.mUserManagerInternal;
+        }
+
+        @Override
+        long currentTimeMillis() {
+            return BackgroundRestrictionTest.this.mCurrentTimeMillis;
+        }
+
+        @Override
+        PackageManager getPackageManager() {
+            return BackgroundRestrictionTest.this.mPackageManager;
+        }
+
+        @Override
+        PermissionManagerServiceInternal getPermissionManagerServiceInternal() {
+            return BackgroundRestrictionTest.this.mPermissionManagerServiceInternal;
+        }
+
+        @Override
+        AppOpsManager getAppOpsManager() {
+            return BackgroundRestrictionTest.this.mAppOpsManager;
+        }
+
+        @Override
+        MediaSessionManager getMediaSessionManager() {
+            return BackgroundRestrictionTest.this.mMediaSessionManager;
+        }
+
+        @Override
+        long getServiceStartForegroundTimeout() {
+            return 1_000; // ms
+        }
+
+        @Override
+        RoleManager getRoleManager() {
+            return BackgroundRestrictionTest.this.mRoleManager;
+        }
+    }
+
+    private class TestAppBatteryTrackerInjector extends TestBaseTrackerInjector<AppBatteryPolicy> {
+        @Override
+        void setPolicy(AppBatteryPolicy policy) {
+            super.setPolicy(policy);
+            BackgroundRestrictionTest.this.mAppBatteryPolicy = policy;
+        }
+    }
+
+    private class TestAppBatteryExemptionTrackerInjector
+            extends TestBaseTrackerInjector<AppBatteryExemptionPolicy> {
+    }
+
+    private class TestAppFGSTrackerInjector extends TestBaseTrackerInjector<AppFGSPolicy> {
+    }
+
+    private class TestAppMediaSessionTrackerInjector
+            extends TestBaseTrackerInjector<AppMediaSessionPolicy> {
+    }
+
+    private class TestAppBroadcastEventsTrackerInjector
+            extends TestBaseTrackerInjector<AppBroadcastEventsPolicy> {
+        @Override
+        void setPolicy(AppBroadcastEventsPolicy policy) {
+            super.setPolicy(policy);
+            policy.setTimeSlotSize(1_000L);
+        }
+    }
+
+    private class TestAppBindServiceEventsTrackerInjector
+            extends TestBaseTrackerInjector<AppBindServiceEventsPolicy> {
+        @Override
+        void setPolicy(AppBindServiceEventsPolicy policy) {
+            super.setPolicy(policy);
+            policy.setTimeSlotSize(1_000L);
+        }
+    }
+}
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 bdfa3bf..d5e4710 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -18,6 +18,8 @@
 
 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.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.app.GameServiceProviderInstanceImplTest.FakeGameService.GameServiceState;
 
 import static com.google.common.collect.Iterables.getOnlyElement;
@@ -28,16 +30,19 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
+import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.app.ITaskStackListener;
 import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
@@ -57,6 +62,7 @@
 import android.view.SurfaceControlViewHost.SurfacePackage;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.infra.AndroidFuture;
@@ -99,6 +105,10 @@
     private static final ComponentName GAME_A_MAIN_ACTIVITY =
             new ComponentName(GAME_A_PACKAGE, "com.package.game.a.MainActivity");
 
+    private static final String GAME_B_PACKAGE = "com.package.game.b";
+    private static final ComponentName GAME_B_MAIN_ACTIVITY =
+            new ComponentName(GAME_B_PACKAGE, "com.package.game.b.MainActivity");
+
     private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
 
     private MockitoSession mMockingSession;
@@ -109,6 +119,9 @@
     private WindowManagerService mMockWindowManagerService;
     @Mock
     private WindowManagerInternal mMockWindowManagerInternal;
+    @Mock
+    private IActivityManager mMockActivityManager;
+    private FakeContext mFakeContext;
     private FakeGameClassifier mFakeGameClassifier;
     private FakeGameService mFakeGameService;
     private FakeServiceConnector<IGameService> mFakeGameServiceConnector;
@@ -117,6 +130,9 @@
     private ArrayList<ITaskStackListener> mTaskStackListeners;
     private ArrayList<RunningTaskInfo> mRunningTaskInfos;
 
+    @Mock
+    private PackageManager mMockPackageManager;
+
     @Before
     public void setUp() throws PackageManager.NameNotFoundException, RemoteException {
         mMockingSession = mockitoSession()
@@ -124,8 +140,11 @@
                 .strictness(Strictness.LENIENT)
                 .startMocking();
 
+        mFakeContext = new FakeContext(InstrumentationRegistry.getInstrumentation().getContext());
+
         mFakeGameClassifier = new FakeGameClassifier();
         mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE);
+        mFakeGameClassifier.recordGamePackage(GAME_B_PACKAGE);
 
         mFakeGameService = new FakeGameService();
         mFakeGameServiceConnector = new FakeServiceConnector<>(mFakeGameService);
@@ -150,7 +169,9 @@
         mGameServiceProviderInstance = new GameServiceProviderInstanceImpl(
                 new UserHandle(USER_ID),
                 ConcurrentUtils.DIRECT_EXECUTOR,
+                mFakeContext,
                 mFakeGameClassifier,
+                mMockActivityManager,
                 mMockActivityTaskManager,
                 mMockWindowManagerService,
                 mMockWindowManagerInternal,
@@ -660,6 +681,58 @@
         assertEquals(TEST_BITMAP, result.getBitmap());
     }
 
+    @Test
+    public void restartGame_taskIdAssociatedWithGame_restartsTargetGame() throws Exception {
+        Intent launchIntent = new Intent("com.test.ACTION_LAUNCH_GAME_PACKAGE")
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        when(mMockPackageManager.getLaunchIntentForPackage(GAME_A_PACKAGE))
+                .thenReturn(launchIntent);
+
+        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));
+
+        startTask(11, GAME_B_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(11);
+
+        FakeGameSession gameSession11 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(11)
+                .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
+
+        mFakeGameSessionService.getCapturedCreateInvocations().get(0)
+                .mGameSessionController.restartGame(10);
+
+        verify(mMockActivityManager).forceStopPackage(GAME_A_PACKAGE, UserHandle.USER_CURRENT);
+        assertThat(mFakeContext.getLastStartedIntent()).isEqualTo(launchIntent);
+    }
+
+    @Test
+    public void restartGame_taskIdNotAssociatedWithGame_noOp() 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));
+
+        getOnlyElement(
+                mFakeGameSessionService.getCapturedCreateInvocations())
+                .mGameSessionController.restartGame(11);
+
+        verifyZeroInteractions(mMockActivityManager);
+        assertThat(mFakeContext.getLastStartedIntent()).isNull();
+    }
+
     private void startTask(int taskId, ComponentName componentName) {
         RunningTaskInfo runningTaskInfo = new RunningTaskInfo();
         runningTaskInfo.taskId = taskId;
@@ -826,4 +899,32 @@
             mIsFocused = focused;
         }
     }
-}
\ No newline at end of file
+
+    private final class FakeContext extends ContextWrapper {
+        private Intent mLastStartedIntent;
+
+        FakeContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mMockPackageManager;
+        }
+
+        @Override
+        public void startActivity(Intent intent) {
+            mLastStartedIntent = intent;
+        }
+
+        @Override
+        public void enforceCallingPermission(String permission, @Nullable String message) {
+            // Do nothing.
+        }
+
+        Intent getLastStartedIntent() {
+            return mLastStartedIntent;
+        }
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 555f4b8..1e0f30e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -521,6 +521,8 @@
         val parsedPackage = parseResult.hideAsParsed() as ParsedPackage
         whenever(mocks.packageParser.parsePackage(
                 or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage }
+        whenever(mocks.packageParser.parsePackage(
+                or(eq(path), eq(basePath)), anyInt(), anyBoolean(), any())) { parsedPackage }
         return parsedPackage
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index dbd5403..0820a3c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -33,6 +33,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.argThat
@@ -121,7 +122,8 @@
         whenever(rule.mocks().packageParser.parsePackage(
                 argThat { path: File -> path.path.contains("a.data.package") },
                 anyInt(),
-                anyBoolean()))
+                anyBoolean(),
+                any()))
                 .thenThrow(PackageManagerException(
                         PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!"))
         val pm = createPackageManagerService()
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index f24059c..a6c81a0 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -133,9 +133,11 @@
     name: "servicestests-core-utils",
     srcs: [
         "src/com/android/server/pm/PackageSettingBuilder.java",
+        "src/com/android/server/am/DeviceConfigSession.java",
     ],
     static_libs: [
         "services.core",
+        "compatibility-device-util-axt",
     ],
 }
 
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 677f0f6..36c37c4 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -541,14 +541,11 @@
                     | 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 */,
-                    mContext.getOpPackageName());
+                    ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */);
 
             // 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
@@ -677,12 +674,10 @@
         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 */,
-                mContext.getOpPackageName());
+                ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, null /* callingPackage */);
         // 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.
@@ -776,9 +771,7 @@
 
         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, 0, 0, mContext.getOpPackageName());
+        mAms.registerUidObserver(observer, 0, 0, null);
         // 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/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 6818d1f..49635a9 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -33,6 +33,7 @@
 import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
 import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
 import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG;
+import static com.android.server.am.UserController.USER_COMPLETED_EVENT_MSG;
 import static com.android.server.am.UserController.USER_CURRENT_MSG;
 import static com.android.server.am.UserController.USER_START_MSG;
 import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
@@ -45,6 +46,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.any;
@@ -91,6 +93,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.FgThread;
+import com.android.server.SystemService;
 import com.android.server.am.UserState.KeyEvictedCallback;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
@@ -167,6 +170,8 @@
             doReturn(false).when(mInjector).taskSupervisorSwitchUser(anyInt(), any());
             doNothing().when(mInjector).taskSupervisorResumeFocusedStackTopActivity();
             doNothing().when(mInjector).systemServiceManagerOnUserStopped(anyInt());
+            doNothing().when(mInjector).systemServiceManagerOnUserCompletedEvent(
+                    anyInt(), anyInt());
             doNothing().when(mInjector).activityManagerForceStopPackage(anyInt(), anyString());
             doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
             doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
@@ -719,6 +724,54 @@
         }
     }
 
+    @Test
+    public void testScheduleOnUserCompletedEvent() throws Exception {
+        // user1 is starting, switching, and unlocked, but not scheduled unlocked yet
+        // user2 is starting and had unlocked but isn't unlocked anymore for whatever reason
+
+        final int user1 = 101;
+        final int user2 = 102;
+        setUpUser(user1, 0);
+        setUpUser(user2, 0);
+
+        mUserController.startUser(user1, /* foreground= */ true);
+        mUserController.getStartedUserState(user1).setState(UserState.STATE_RUNNING_UNLOCKED);
+
+        mUserController.startUser(user2, /* foreground= */ false);
+        mUserController.getStartedUserState(user2).setState(UserState.STATE_RUNNING_LOCKED);
+
+        final int event1a = SystemService.UserCompletedEventType.EVENT_TYPE_USER_STARTING;
+        final int event1b = SystemService.UserCompletedEventType.EVENT_TYPE_USER_SWITCHING;
+
+        final int event2a = SystemService.UserCompletedEventType.EVENT_TYPE_USER_STARTING;
+        final int event2b = SystemService.UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED;
+
+
+        mUserController.scheduleOnUserCompletedEvent(user1, event1a, 2000);
+        assertNotNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user1));
+        assertNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user2));
+
+        mUserController.scheduleOnUserCompletedEvent(user2, event2a, 2000);
+        assertNotNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user1));
+        assertNotNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user2));
+
+        mUserController.scheduleOnUserCompletedEvent(user2, event2b, 2000);
+        mUserController.scheduleOnUserCompletedEvent(user1, event1b, 2000);
+        mUserController.scheduleOnUserCompletedEvent(user1, 0, 2000);
+
+        assertNotNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user1));
+        assertNotNull(mInjector.mHandler.getMessageForCode(USER_COMPLETED_EVENT_MSG, user2));
+
+        mUserController.reportOnUserCompletedEvent(user1);
+        verify(mInjector, times(1))
+                .systemServiceManagerOnUserCompletedEvent(eq(user1), eq(event1a | event1b));
+        verify(mInjector, never()).systemServiceManagerOnUserCompletedEvent(eq(user2), anyInt());
+
+        mUserController.reportOnUserCompletedEvent(user2);
+        verify(mInjector, times(1))
+                .systemServiceManagerOnUserCompletedEvent(eq(user2), eq(event2a));
+    }
+
     private void setUpAndStartUserInBackground(int userId) throws Exception {
         setUpUser(userId, 0);
         mUserController.startUser(userId, /* foreground= */ false);
@@ -969,8 +1022,12 @@
         }
 
         Message getMessageForCode(int what) {
+            return getMessageForCode(what, null);
+        }
+
+        Message getMessageForCode(int what, Object obj) {
             for (Message msg : mMessages) {
-                if (msg.what == what) {
+                if (msg.what == what && (obj == null || obj.equals(msg.obj))) {
                     return msg;
                 }
             }
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 fc55a9f..9bb722f 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
@@ -39,6 +39,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.Supplier;
+
 @Presubmit
 @SmallTest
 public class AcquisitionClientTest {
@@ -87,7 +89,7 @@
         boolean mHalOperationRunning;
 
         public TestAcquisitionClient(@NonNull Context context,
-                @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
+                @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
                 @NonNull ClientMonitorCallbackConverter callback) {
             super(context, lazyDaemon, token, callback, 0 /* userId */, "Test", 0 /* cookie */,
                     TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */, 0 /* statsModality */,
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 c99d656..ecd9abc 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
@@ -61,6 +61,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.Supplier;
+
 @Presubmit
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -93,7 +95,7 @@
 
     @Test
     public void testClientDuplicateFinish_ignoredBySchedulerAndDoesNotCrash() {
-        final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
+        final Supplier<Object> nonNullDaemon = () -> mock(Object.class);
 
         final HalClientMonitor<Object> client1 =
                 new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
@@ -184,7 +186,7 @@
 
     @Test
     public void testCancelNotInvoked_whenOperationWaitingForCookie() {
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> mock(Object.class);
+        final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
                 lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class));
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
@@ -296,7 +298,7 @@
 
     @Test
     public void testCancelPendingAuth() throws RemoteException {
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+        final Supplier<Object> lazyDaemon = () -> mock(Object.class);
         final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
         final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
@@ -360,7 +362,7 @@
 
     private void testCancelsAuthDetectWhenRequestId(@Nullable Long requestId, long cancelRequestId,
             boolean started) {
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+        final Supplier<Object> lazyDaemon = () -> mock(Object.class);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
         testCancelsWhenRequestId(requestId, cancelRequestId, started,
                 new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback));
@@ -383,7 +385,7 @@
 
     private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
             boolean started) {
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+        final Supplier<Object> lazyDaemon = () -> mock(Object.class);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
         testCancelsWhenRequestId(requestId, cancelRequestId, started,
                 new TestEnrollClient(mContext, lazyDaemon, mToken, callback));
@@ -441,7 +443,7 @@
     public void testCancelsPending_whenAuthRequestIdsSet() {
         final long requestId1 = 10;
         final long requestId2 = 20;
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+        final Supplier<Object> lazyDaemon = () -> mock(Object.class);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(
                 mContext, lazyDaemon, mToken, callback);
@@ -500,7 +502,7 @@
 
     @Test
     public void testClientDestroyed_afterFinish() {
-        final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
+        final Supplier<Object> nonNullDaemon = () -> mock(Object.class);
         final TestHalClientMonitor client =
                 new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
         mScheduler.scheduleClientMonitor(client);
@@ -520,7 +522,7 @@
         int mNumCancels = 0;
 
         public TestAuthenticationClient(@NonNull Context context,
-                @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
+                @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
                 @NonNull ClientMonitorCallbackConverter listener) {
             super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
                     false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
@@ -567,7 +569,7 @@
         int mNumCancels = 0;
 
         TestEnrollClient(@NonNull Context context,
-                @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
+                @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
                 @NonNull ClientMonitorCallbackConverter listener) {
             super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
                     "test" /* owner */, mock(BiometricUtils.class),
@@ -604,12 +606,12 @@
         private boolean mDestroyed;
 
         TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
-                @NonNull LazyDaemon<Object> lazyDaemon) {
+                @NonNull Supplier<Object> lazyDaemon) {
             this(context, token, lazyDaemon, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER);
         }
 
         TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
-                @NonNull LazyDaemon<Object> lazyDaemon, int cookie, int protoEnum) {
+                @NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum) {
             super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
                     TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
                     0 /* statsAction */, 0 /* statsClient */);
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 a11709a..30777cd 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
@@ -47,6 +47,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.Supplier;
+
 @Presubmit
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -215,7 +217,7 @@
         int numInvocations;
 
         @Override
-        public void onUserStarted(int newUserId, Object newObject) {
+        public void onUserStarted(int newUserId, Object newObject, int halInterfaceVersion) {
             numInvocations++;
             mCurrentUserId = newUserId;
         }
@@ -223,7 +225,7 @@
 
     private static class TestStopUserClient extends StopUserClient<Object> {
         public TestStopUserClient(@NonNull Context context,
-                @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
+                @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
                 int sensorId, @NonNull UserStoppedCallback callback) {
             super(context, lazyDaemon, token, userId, sensorId, callback);
         }
@@ -251,7 +253,7 @@
         ClientMonitorCallback mCallback;
 
         public TestStartUserClient(@NonNull Context context,
-                @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
+                @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
                 int sensorId, @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
             super(context, lazyDaemon, token, userId, sensorId, callback);
             mShouldFinish = shouldFinish;
@@ -268,7 +270,8 @@
 
             mCallback = callback;
             if (mShouldFinish) {
-                mUserStartedCallback.onUserStarted(getTargetUserId(), new Object());
+                mUserStartedCallback.onUserStarted(
+                        getTargetUserId(), new Object(), 1 /* halInterfaceVersion */);
                 callback.onClientFinished(this, true /* success */);
             }
         }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 2718bf9..61e4776 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -101,8 +101,8 @@
         mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
 
         mScheduler.scheduleClientMonitor(new FaceResetLockoutClient(mContext,
-                () -> mSession, USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache,
-                mLockoutResetDispatcher));
+                () -> new AidlSession(1, mSession, USER_ID, mHalCallback),
+                USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache, mLockoutResetDispatcher));
         mLooper.dispatchAll();
 
         verifyNotLocked();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index d4609b5..8b7b484 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -101,8 +101,8 @@
         mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
 
         mScheduler.scheduleClientMonitor(new FingerprintResetLockoutClient(mContext,
-                () -> mSession, USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache,
-                mLockoutResetDispatcher));
+                () -> new AidlSession(1, mSession, USER_ID, mHalCallback),
+                USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache, mLockoutResetDispatcher));
         mLooper.dispatchAll();
 
         verifyNotLocked();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 53468c8..ff1b6f6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -19,11 +19,13 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.hardware.input.InputManagerInternal;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.IInputConstants;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 
@@ -64,9 +66,13 @@
         final IBinder deviceToken = new Binder();
         mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
+        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
+        verify(mInputManagerInternalMock).setPointerAcceleration(eq(1f));
         mInputController.unregisterInputDevice(deviceToken);
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(
                 eq(Display.INVALID_DISPLAY));
+        verify(mInputManagerInternalMock).setPointerAcceleration(
+                eq((float) IInputConstants.DEFAULT_POINTER_ACCELERATION));
     }
 
     @Test
@@ -75,10 +81,14 @@
         mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
-        mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
+        verify(mInputManagerInternalMock).setPointerAcceleration(eq(1f));
+        final IBinder deviceToken2 = new Binder();
+        mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
                 /* displayId= */ 2);
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
         mInputController.unregisterInputDevice(deviceToken);
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
+        verify(mInputManagerInternalMock, times(0)).setPointerAcceleration(
+                eq((float) IInputConstants.DEFAULT_POINTER_ACCELERATION));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 72100e44..e36263e 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -18,17 +18,21 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
 import android.Manifest;
 import android.app.admin.DevicePolicyManager;
+import android.companion.AssociationInfo;
 import android.companion.virtual.IVirtualDeviceActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
 import android.content.Context;
@@ -41,24 +45,35 @@
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
 import android.hardware.input.VirtualTouchEvent;
+import android.net.MacAddress;
 import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.WorkSource;
 import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.KeyEvent;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 @Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class VirtualDeviceManagerServiceTest {
 
     private static final String DEVICE_NAME = "device name";
@@ -84,6 +99,11 @@
     private InputManagerInternal mInputManagerInternalMock;
     @Mock
     private IVirtualDeviceActivityListener mActivityListener;
+    @Mock
+    IPowerManager mIPowerManagerMock;
+    @Mock
+    IThermalService mIThermalServiceMock;
+    private PowerManager mPowerManager;
 
     @Before
     public void setUp() {
@@ -102,10 +122,17 @@
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
                 mDevicePolicyManagerMock);
 
+        mPowerManager = new PowerManager(mContext, mIPowerManagerMock, mIThermalServiceMock,
+                new Handler(TestableLooper.get(this).getLooper()));
+        when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+
         mInputController = new InputController(new Object(), mNativeWrapperMock);
+        AssociationInfo associationInfo = new AssociationInfo(1, 0, null,
+                MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
         mDeviceImpl = new VirtualDeviceImpl(mContext,
-                /* association info */ null, new Binder(), /* uid */ 0, mInputController,
-                (int associationId) -> {}, mPendingTrampolineCallback, mActivityListener,
+                associationInfo, new Binder(), /* uid */ 0, mInputController,
+                (int associationId) -> {
+                }, mPendingTrampolineCallback, mActivityListener,
                 new VirtualDeviceParams.Builder().build());
     }
 
@@ -118,6 +145,72 @@
     }
 
     @Test
+    public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
+        final int displayId = 2;
+        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
+        verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
+                nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+                nullable(String.class), anyInt());
+        TestableLooper.get(this).processAllMessages();
+        verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
+                nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+                nullable(String.class), eq(displayId));
+    }
+
+    @Test
+    public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
+            throws RemoteException {
+        final int displayId = 2;
+        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
+        assertThrows(IllegalStateException.class,
+                () -> mDeviceImpl.onVirtualDisplayCreatedLocked(displayId));
+        TestableLooper.get(this).processAllMessages();
+        verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
+                nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+                nullable(String.class), eq(displayId));
+    }
+
+    @Test
+    public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() {
+        final int unknownDisplayId = 999;
+        assertThrows(IllegalStateException.class,
+                () -> mDeviceImpl.onVirtualDisplayRemovedLocked(unknownDisplayId));
+    }
+
+    @Test
+    public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
+        final int displayId = 2;
+        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
+        ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
+        TestableLooper.get(this).processAllMessages();
+        verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
+                anyInt(),
+                nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+                nullable(String.class), eq(displayId));
+
+        IBinder wakeLock = wakeLockCaptor.getValue();
+        mDeviceImpl.onVirtualDisplayRemovedLocked(displayId);
+        verify(mIPowerManagerMock, Mockito.times(1)).releaseWakeLock(eq(wakeLock), anyInt());
+    }
+
+    @Test
+    public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
+        final int displayId = 2;
+        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
+        ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
+        TestableLooper.get(this).processAllMessages();
+        verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
+                anyInt(),
+                nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+                nullable(String.class), eq(displayId));
+        IBinder wakeLock = wakeLockCaptor.getValue();
+
+        // Close the VirtualDevice without first notifying it of the VirtualDisplay removal.
+        mDeviceImpl.close();
+        verify(mIPowerManagerMock, Mockito.times(1)).releaseWakeLock(eq(wakeLock), anyInt());
+    }
+
+    @Test
     public void createVirtualKeyboard_noDisplay_failsSecurityException() {
         assertThrows(
                 SecurityException.class,
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 842a438..c0ad69f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -42,6 +42,7 @@
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 
@@ -94,6 +95,7 @@
 import android.app.admin.DevicePolicyManagerLiteInternal;
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.PasswordMetrics;
+import android.app.admin.PreferentialNetworkServiceConfig;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.admin.WifiSsidPolicy;
 import android.content.BroadcastReceiver;
@@ -4146,6 +4148,164 @@
     }
 
     @Test
+    public void testSetPreferentialNetworkServiceConfig_noProfileOwner() throws Exception {
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.setPreferentialNetworkServiceConfig(
+                        PreferentialNetworkServiceConfig.DEFAULT));
+    }
+
+    @Test
+    public void testIsPreferentialNetworkServiceEnabled_noProfileOwner() throws Exception {
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.isPreferentialNetworkServiceEnabled());
+    }
+
+    @Test
+    public void testSetPreferentialNetworkServiceConfig_invalidConfig() throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        PreferentialNetworkServiceConfig.Builder preferentialNetworkServiceConfigBuilder =
+                new PreferentialNetworkServiceConfig.Builder();
+        assertExpectException(NullPointerException.class, null,
+                () -> preferentialNetworkServiceConfigBuilder.setIncludedUids(null));
+        assertExpectException(NullPointerException.class, null,
+                () -> preferentialNetworkServiceConfigBuilder.setExcludedUids(null));
+        assertExpectException(IllegalArgumentException.class, null,
+                () -> preferentialNetworkServiceConfigBuilder.setNetworkId(6));
+        int[] includedUids = new int[]{1, 2};
+        int[] excludedUids = new int[]{3, 4};
+        preferentialNetworkServiceConfigBuilder.setIncludedUids(includedUids);
+        preferentialNetworkServiceConfigBuilder.setExcludedUids(excludedUids);
+
+        assertExpectException(IllegalStateException.class, null,
+                () -> preferentialNetworkServiceConfigBuilder.build());
+    }
+
+    @Test
+    public void testSetPreferentialNetworkServiceConfig_defaultPreference() throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        dpm.setPreferentialNetworkServiceConfig(PreferentialNetworkServiceConfig.DEFAULT);
+        assertThat(dpm.isPreferentialNetworkServiceEnabled()).isFalse();
+        assertThat(dpm.getPreferentialNetworkServiceConfig()
+                .isEnabled()).isFalse();
+
+        ProfileNetworkPreference preferenceDetails =
+                new ProfileNetworkPreference.Builder()
+                        .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT)
+                        .build();
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+                        null, null);
+    }
+
+    @Test
+    public void testSetPreferentialNetworkServiceConfig_enterprisePreference() throws Exception {
+        PreferentialNetworkServiceConfig preferentialNetworkServiceConfigEnabled =
+                (new PreferentialNetworkServiceConfig.Builder())
+                        .setEnabled(true)
+                        .setNetworkId(NET_ENTERPRISE_ID_1)
+                        .build();
+
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled);
+        assertThat(dpm.getPreferentialNetworkServiceConfig()
+                .isEnabled()).isTrue();
+        ProfileNetworkPreference preferenceDetails =
+                new ProfileNetworkPreference.Builder()
+                        .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE)
+                        .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1)
+                        .build();
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+                        null, null);
+    }
+
+    @Test
+    public void testSetPreferentialNetworkServiceConfig_enterprisePreferenceIncludedUids()
+            throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        PreferentialNetworkServiceConfig preferentialNetworkServiceConfigEnabled =
+                (new PreferentialNetworkServiceConfig.Builder())
+                        .setEnabled(true)
+                        .setNetworkId(NET_ENTERPRISE_ID_1)
+                        .setFallbackToDefaultConnectionAllowed(false)
+                        .setIncludedUids(new int[]{1, 2})
+                        .build();
+        dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled);
+        assertThat(dpm.getPreferentialNetworkServiceConfig()
+                .isEnabled()).isTrue();
+        List<Integer> includedList = new ArrayList<>();
+        includedList.add(1);
+        includedList.add(2);
+        ProfileNetworkPreference preferenceDetails =
+                new ProfileNetworkPreference.Builder()
+                        .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK)
+                        .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1)
+                        .setIncludedUids(includedList)
+                        .build();
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+                        null, null);
+    }
+
+    @Test
+    public void testSetPreferentialNetworkServiceConfig_enterprisePreferenceExcludedUids()
+            throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        PreferentialNetworkServiceConfig preferentialNetworkServiceConfigEnabled =
+                (new PreferentialNetworkServiceConfig.Builder())
+                        .setEnabled(true)
+                        .setNetworkId(NET_ENTERPRISE_ID_1)
+                        .setFallbackToDefaultConnectionAllowed(false)
+                        .setExcludedUids(new int[]{1, 2})
+                        .build();
+
+        dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled);
+        assertThat(dpm.getPreferentialNetworkServiceConfig()
+                .isEnabled()).isTrue();
+        List<Integer> excludedUids = new ArrayList<>();
+        excludedUids.add(1);
+        excludedUids.add(2);
+        ProfileNetworkPreference preferenceDetails =
+                new ProfileNetworkPreference.Builder()
+                        .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK)
+                        .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1)
+                        .setExcludedUids(excludedUids)
+                        .build();
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.clear();
+        preferences.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+                        null, null);
+    }
+
+    @Test
     public void testSetSystemSettingFailWithNonWhitelistedSettings() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -7962,6 +8122,51 @@
                 () -> WifiSsidPolicy.createDenylistPolicy(ssids));
     }
 
+    @Test
+    public void testSendLostModeLocationUpdate_noPermission() {
+        assertThrows(SecurityException.class, () -> dpm.sendLostModeLocationUpdate(
+                getServices().executor, /* empty callback */ result -> {}));
+    }
+
+    @Test
+    public void testSendLostModeLocationUpdate_notOrganizationOwnedDevice() {
+        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        assertThrows(IllegalStateException.class, () -> dpm.sendLostModeLocationUpdate(
+                getServices().executor, /* empty callback */ result -> {}));
+    }
+
+    @Test
+    public void testSendLostModeLocationUpdate_asDeviceOwner() throws Exception {
+        final String TEST_PROVIDER = "network";
+        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        setDeviceOwner();
+        when(getServices().locationManager.getAllProviders()).thenReturn(List.of(TEST_PROVIDER));
+        when(getServices().locationManager.isProviderEnabled(TEST_PROVIDER)).thenReturn(true);
+
+        dpm.sendLostModeLocationUpdate(getServices().executor, /* empty callback */ result -> {});
+
+        verify(getServices().locationManager, times(1)).getCurrentLocation(
+                eq(TEST_PROVIDER), any(), eq(getServices().executor), any());
+    }
+
+    @Test
+    public void testSendLostModeLocationUpdate_asProfileOwnerOfOrgOwnedDevice() throws Exception {
+        final String TEST_PROVIDER = "network";
+        final int MANAGED_PROFILE_ADMIN_UID =
+                UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
+        when(getServices().locationManager.getAllProviders()).thenReturn(List.of(TEST_PROVIDER));
+        when(getServices().locationManager.isProviderEnabled(TEST_PROVIDER)).thenReturn(true);
+
+        dpm.sendLostModeLocationUpdate(getServices().executor, /* empty callback */ result -> {});
+
+        verify(getServices().locationManager, times(1)).getCurrentLocation(
+                eq(TEST_PROVIDER), any(), eq(getServices().executor), any());
+    }
+
     private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
         final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage,
                 userVpnUid, List.of(new AppOpsManager.OpEntry(
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 6eb2085..2cf67f8 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -47,6 +47,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * Context used throughout DPMS tests.
@@ -234,6 +235,8 @@
                 return mMockSystemServices.vpnManager;
             case Context.DEVICE_POLICY_SERVICE:
                 return mMockSystemServices.devicePolicyManager;
+            case Context.LOCATION_SERVICE:
+                return mMockSystemServices.locationManager;
         }
         throw new UnsupportedOperationException();
     }
@@ -493,6 +496,11 @@
     }
 
     @Override
+    public Executor getMainExecutor() {
+        return mMockSystemServices.executor;
+    }
+
+    @Override
     public int checkCallingPermission(String permission) {
         return checkPermission(permission);
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 597a165..21fb2da 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -47,6 +47,7 @@
 import android.content.pm.UserInfo;
 import android.database.Cursor;
 import android.hardware.usb.UsbManager;
+import android.location.LocationManager;
 import android.media.IAudioService;
 import android.net.ConnectivityManager;
 import android.net.IIpConnectivityMetrics;
@@ -81,6 +82,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -90,6 +92,7 @@
     public final File systemUserDataDir;
     public final EnvironmentForMock environment;
     public final SystemPropertiesForMock systemProperties;
+    public final Executor executor;
     public final UserManager userManager;
     public final UserManagerInternal userManagerInternal;
     public final UsageStatsManagerInternal usageStatsManagerInternal;
@@ -127,6 +130,7 @@
     public final UsbManager usbManager;
     public final VpnManager vpnManager;
     public final DevicePolicyManager devicePolicyManager;
+    public final LocationManager locationManager;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
     public final BuildMock buildMock = new BuildMock();
@@ -138,6 +142,7 @@
 
         environment = mock(EnvironmentForMock.class);
         systemProperties = mock(SystemPropertiesForMock.class);
+        executor = mock(Executor.class);
         userManager = mock(UserManager.class);
         userManagerInternal = mock(UserManagerInternal.class);
         usageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
@@ -175,6 +180,7 @@
         usbManager = mock(UsbManager.class);
         vpnManager = mock(VpnManager.class);
         devicePolicyManager = mock(DevicePolicyManager.class);
+        locationManager = mock(LocationManager.class);
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 03eba9b..d2cff0e 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -551,13 +551,14 @@
         Assert.assertTrue(Arrays.equals(expected, actual));
     }
 
-    private static final class TestDeviceStatePolicy implements DeviceStatePolicy {
+    private static final class TestDeviceStatePolicy extends DeviceStatePolicy {
         private final DeviceStateProvider mProvider;
         private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;
         private boolean mConfigureBlocked = false;
         private Runnable mPendingConfigureCompleteRunnable;
 
         TestDeviceStatePolicy(DeviceStateProvider provider) {
+            super(InstrumentationRegistry.getContext());
             mProvider = provider;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java
new file mode 100644
index 0000000..0bd81b7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicestate;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Unit tests for the {@link DeviceStatePolicy.Provider}
+ * <p/>
+ * Build/Install/Run:
+ *  <code>atest DeviceStatePolicyProviderTest</code>
+ */
+@Presubmit
+public class DeviceStatePolicyProviderTest {
+
+    @Test
+    public void test_emptyPolicyProvider() {
+        Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider("")),
+                Matchers.instanceOf(DeviceStatePolicy.DefaultProvider.class));
+    }
+
+    @Test
+    public void test_nullPolicyProvider() {
+        Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(null)),
+                Matchers.instanceOf(DeviceStatePolicy.DefaultProvider.class));
+    }
+
+    @Test
+    public void test_customPolicyProvider() {
+        Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
+                TestProvider.class.getName())),
+                Matchers.instanceOf(TestProvider.class));
+    }
+
+    @Test
+    public void test_badPolicyProvider_notImplementingProviderInterface() {
+        assertThrows(IllegalStateException.class, () -> {
+            DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
+                    Object.class.getName()));
+        });
+    }
+
+    @Test
+    public void test_badPolicyProvider_doesntExist() {
+        assertThrows(IllegalStateException.class, () -> {
+            DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
+                    "com.android.devicestate.nonexistent.policy"));
+        });
+    }
+
+    private static Resources resourcesWithProvider(String provider) {
+        final Resources mockResources = mock(Resources.class);
+        when(mockResources.getString(
+                com.android.internal.R.string.config_deviceSpecificDeviceStatePolicyProvider))
+                .thenReturn(provider);
+        return mockResources;
+    }
+
+    // Stub implementation of DeviceStatePolicy.Provider for testing
+    static class TestProvider implements DeviceStatePolicy.Provider {
+        @Override
+        public DeviceStatePolicy instantiate(Context context) {
+            throw new RuntimeException("test stub");
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 54945e4..4caa85c 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -391,4 +391,33 @@
         listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0));
         assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
     }
+
+    @Test
+    public void testHysteresisLevels() {
+        int[] ambientBrighteningThresholds = {100, 200};
+        int[] ambientDarkeningThresholds = {400, 500};
+        int[] ambientThresholdLevels = {500};
+        float ambientDarkeningMinChangeThreshold = 3.0f;
+        float ambientBrighteningMinChangeThreshold = 1.5f;
+        HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
+                ambientDarkeningThresholds, ambientThresholdLevels,
+                ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
+
+        // test low, activate minimum change thresholds.
+        assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), EPSILON);
+        assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), EPSILON);
+        assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
+
+        // test max
+        assertEquals(12000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON);
+        assertEquals(5000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
+
+        // test just below threshold
+        assertEquals(548.9f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
+        assertEquals(299.4f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
+
+        // test at (considered above) threshold
+        assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
+        assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index c675726..24a4751 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -393,7 +393,8 @@
 
         // Create an idle mode bms
         // This will fail if it tries to fetch the wrong configuration.
-        BrightnessMappingStrategy bms = BrightnessMappingStrategy.createForIdleMode(res, ddc);
+        BrightnessMappingStrategy bms = BrightnessMappingStrategy.createForIdleMode(res, ddc,
+                null);
         assertNotNull("BrightnessMappingStrategy should not be null", bms);
 
         // Ensure that the config is the one we set
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index eaa271a..1fb5898 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.PropertyInvalidatedCache;
+import android.companion.virtual.IVirtualDevice;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -73,6 +74,7 @@
 
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayManagerService.SyncRoot;
 import com.android.server.lights.LightsManager;
 import com.android.server.sensors.SensorManagerInternal;
@@ -167,6 +169,7 @@
             };
 
     @Mock InputManagerInternal mMockInputManagerInternal;
+    @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
     @Mock IVirtualDisplayCallback.Stub mMockAppToken;
     @Mock IVirtualDisplayCallback.Stub mMockAppToken2;
     @Mock WindowManagerInternal mMockWindowManagerInternal;
@@ -187,6 +190,9 @@
         LocalServices.addService(LightsManager.class, mMockLightsManager);
         LocalServices.removeServiceForTest(SensorManagerInternal.class);
         LocalServices.addService(SensorManagerInternal.class, mMockSensorManagerInternal);
+        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+        LocalServices.addService(
+                VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
 
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mDeviceConfig = new FakeDeviceConfigInterface();
@@ -661,6 +667,101 @@
     }
 
     /**
+     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when the permission
+     * ADD_TRUSTED_DISPLAY is granted.
+     */
+    @Test
+    public void testOwnDisplayGroup_allowCreationWithAddTrustedDisplayPermission() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
+        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
+
+        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                null /* projection */, null /* virtualDeviceToken */, PACKAGE_NAME);
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+    }
+
+    /**
+     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is blocked when the permission
+     * ADD_TRUSTED_DISPLAY is denied.
+     */
+    @Test
+    public void testOwnDisplayGroup_withoutAddTrustedDisplayPermission_throwsSecurityException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
+        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
+
+        try {
+            bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                    null /* projection */, null /* virtualDeviceToken */, PACKAGE_NAME);
+            fail("Creating virtual display with VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP without "
+                    + "ADD_TRUSTED_DISPLAY permission should throw SecurityException.");
+        } catch (SecurityException e) {
+            // SecurityException is expected
+        }
+    }
+
+    /**
+     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when called with
+     * a virtual device, even if ADD_TRUSTED_DISPLAY is not granted.
+     */
+    @Test
+    public void testOwnDisplayGroup_allowCreationWithVirtualDevice() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
+        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
+
+        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+        when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
+            .thenReturn(true);
+
+        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                null /* projection */, virtualDevice /* virtualDeviceToken */, PACKAGE_NAME);
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+    }
+
+    /**
      * Tests that there is a display change notification if the frame rate override
      * list is updated.
      */
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index 2565ae3..0f3742f 100644
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -37,7 +38,9 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.R;
+import com.android.server.LocalServices;
 import com.android.server.display.TestUtils;
+import com.android.server.display.color.ColorDisplayService;
 import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.AmbientFilterStubber;
 
@@ -75,6 +78,7 @@
     @Mock private TypedArray mHighLightBiases;
     @Mock private TypedArray mAmbientColorTemperatures;
     @Mock private TypedArray mDisplayColorTemperatures;
+    @Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock;
 
     @Before
     public void setUp() throws Exception {
@@ -120,6 +124,18 @@
                 R.array.config_displayWhiteBalanceHighLightAmbientBiases))
                 .thenReturn(mHighLightBiases);
         mockThrottler();
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mColorDisplayServiceInternalMock);
+    }
+
+    @Test
+    public void testCalculateAdjustedBrightnessNits() {
+        doReturn(0.9f).when(mColorDisplayServiceInternalMock).getDisplayWhiteBalanceLuminance();
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float adjustedNits = controller.calculateAdjustedBrightnessNits(500f);
+        assertEquals(/* expected= */ 550f, adjustedNits, /* delta= */ 0.001);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 81c9871..e80721a 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -51,6 +51,7 @@
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.NetworkStats.METERED_NO;
 import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
 import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.MB_IN_BYTES;
@@ -71,7 +72,6 @@
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
 import static com.android.server.net.NetworkPolicyManagerService.UidBlockedState.getEffectiveBlockedReasons;
-import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -95,6 +95,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -495,8 +496,14 @@
         verify(mNetworkManager).registerObserver(networkObserver.capture());
         mNetworkObserver = networkObserver.getValue();
 
-        // Simulate NetworkStatsService broadcast stats updated to signal its readiness.
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_UPDATED));
+        // Catch UsageCallback during systemReady(). Simulate NetworkStatsService triggered
+        // stats updated callback to signal its readiness.
+        final ArgumentCaptor<NetworkStatsManager.UsageCallback> usageObserver =
+                ArgumentCaptor.forClass(NetworkStatsManager.UsageCallback.class);
+        verify(mStatsManager, times(2))
+                .registerUsageCallback(any(), anyLong(), any(), usageObserver.capture());
+        usageObserver.getValue().onThresholdReached(
+                new NetworkTemplate.Builder(MATCH_MOBILE).build());
 
         NetworkPolicy defaultPolicy = mService.buildDefaultCarrierPolicy(0, "");
         mDefaultWarningBytes = defaultPolicy.warningBytes;
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index d8ecf20..31bdec1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -65,7 +65,7 @@
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.permission.CompatibilityPermissionInfo;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.pkg.component.ParsedActivityImpl;
 import com.android.server.pm.pkg.component.ParsedApexSystemService;
@@ -673,9 +673,9 @@
         assertArrayEquals(a.getSplitFlags(), b.getSplitFlags());
 
         PackageInfo aInfo = PackageInfoUtils.generate(a, new int[]{}, 0, 0, 0,
-                Collections.emptySet(), PackageUserState.DEFAULT, 0, mockPkgSetting(a));
+                Collections.emptySet(), PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(a));
         PackageInfo bInfo = PackageInfoUtils.generate(b, new int[]{}, 0, 0, 0,
-                Collections.emptySet(), PackageUserState.DEFAULT, 0, mockPkgSetting(b));
+                Collections.emptySet(), PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(b));
         assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
 
         assertEquals(ArrayUtils.size(a.getPermissions()), ArrayUtils.size(b.getPermissions()));
@@ -831,9 +831,9 @@
 
         // Validity check for ServiceInfo.
         ServiceInfo aInfo = PackageInfoUtils.generateServiceInfo(aPkg, a, 0,
-                PackageUserState.DEFAULT, 0, mockPkgSetting(aPkg));
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(aPkg));
         ServiceInfo bInfo = PackageInfoUtils.generateServiceInfo(bPkg, b, 0,
-                PackageUserState.DEFAULT, 0, mockPkgSetting(bPkg));
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(bPkg));
         assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
         assertEquals(a.getName(), b.getName());
     }
@@ -858,9 +858,9 @@
 
         // Validity check for ActivityInfo.
         ActivityInfo aInfo = PackageInfoUtils.generateActivityInfo(aPkg, a, 0,
-                PackageUserState.DEFAULT, 0, mockPkgSetting(aPkg));
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(aPkg));
         ActivityInfo bInfo = PackageInfoUtils.generateActivityInfo(bPkg, b, 0,
-                PackageUserState.DEFAULT, 0, mockPkgSetting(bPkg));
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(bPkg));
         assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
         assertEquals(a.getName(), b.getName());
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 7ff8eec..4a24bbd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -574,7 +574,7 @@
         assertBasicPackageSetting(scanResult, packageName, isInstant, pkgSetting);
 
         final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
-                pkgSetting.getPkg(), 0, pkgSetting.readUserState(0), 0, pkgSetting);
+                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
         assertBasicApplicationInfo(scanResult, applicationInfo);
     }
 
@@ -612,7 +612,7 @@
     private static void assertAbiAndPathssDerived(ScanResult scanResult) {
         PackageSetting pkgSetting = scanResult.mPkgSetting;
         final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
-                pkgSetting.getPkg(), 0, pkgSetting.readUserState(0), 0, pkgSetting);
+                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
         assertThat(applicationInfo.primaryCpuAbi, is("derivedPrimary"));
         assertThat(applicationInfo.secondaryCpuAbi, is("derivedSecondary"));
 
@@ -626,7 +626,7 @@
     private static void assertPathsNotDerived(ScanResult scanResult) {
         PackageSetting pkgSetting = scanResult.mPkgSetting;
         final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
-                pkgSetting.getPkg(), 0, pkgSetting.readUserState(0), 0, pkgSetting);
+                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
         assertThat(applicationInfo.nativeLibraryRootDir, is("getRootDir"));
         assertThat(pkgSetting.getLegacyNativeLibraryPath(), is("getRootDir"));
         assertThat(applicationInfo.nativeLibraryRootRequiresIsa, is(true));
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 7e5fe04..401cd7f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -309,7 +309,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_restrictedReturnsError() throws Exception {
+    public void testRemoveUserWhenPossible_restrictedReturnsError() throws Exception {
         final int currentUser = ActivityManager.getCurrentUser();
         final UserInfo user1 = createUser("User 1", /* flags= */ 0);
         mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
@@ -328,7 +328,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_evenWhenRestricted() throws Exception {
+    public void testRemoveUserWhenPossible_evenWhenRestricted() throws Exception {
         final int currentUser = ActivityManager.getCurrentUser();
         final UserInfo user1 = createUser("User 1", /* flags= */ 0);
         mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
@@ -351,7 +351,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_systemUserReturnsError() throws Exception {
+    public void testRemoveUserWhenPossible_systemUserReturnsError() throws Exception {
         assertThat(mUserManager.removeUserWhenPossible(UserHandle.SYSTEM,
                 /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
 
@@ -360,7 +360,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_invalidUserReturnsError() throws Exception {
+    public void testRemoveUserWhenPossible_invalidUserReturnsError() throws Exception {
         assertThat(hasUser(Integer.MAX_VALUE)).isFalse();
         assertThat(mUserManager.removeUserWhenPossible(UserHandle.of(Integer.MAX_VALUE),
                 /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
@@ -368,7 +368,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral() throws Exception {
+    public void testRemoveUserWhenPossible_currentUserSetEphemeral() throws Exception {
         final int startUser = ActivityManager.getCurrentUser();
         final UserInfo user1 = createUser("User 1", /* flags= */ 0);
         // Switch to the user just created.
@@ -392,7 +392,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_nonCurrentUserRemoved() throws Exception {
+    public void testRemoveUserWhenPossible_nonCurrentUserRemoved() throws Exception {
         final UserInfo user1 = createUser("User 1", /* flags= */ 0);
         synchronized (mUserRemoveLock) {
             assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
new file mode 100644
index 0000000..c59b58d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.power;
+
+
+import static android.os.PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
+import static android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN;
+import static android.os.PowerManager.GO_TO_SLEEP_REASON_TIMEOUT;
+import static android.os.PowerManager.WAKE_REASON_GESTURE;
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
+import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link com.android.server.power.PowerGroup}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:PowerManagerServiceTest
+ */
+public class PowerGroupTest {
+
+    private static final int GROUP_ID = 0;
+    private static final long TIMESTAMP_CREATE = 1;
+    private static final long TIMESTAMP1 = 999;
+    private static final long TIMESTAMP2 = TIMESTAMP1 + 10;
+    private static final long TIMESTAMP3 = TIMESTAMP2 + 10;
+    private static final int UID = 11;
+
+    private PowerGroup mPowerGroup;
+    @Mock
+    private PowerGroup.PowerGroupListener mWakefulnessCallbackMock;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mPowerGroup = new PowerGroup(GROUP_ID, mWakefulnessCallbackMock, new DisplayPowerRequest(),
+                WAKEFULNESS_AWAKE, /* ready= */ true, /* supportsSandman= */true, TIMESTAMP_CREATE);
+    }
+
+    @Test
+    public void testDreamPowerGroupTriggersOnWakefulnessChangedCallback() {
+        mPowerGroup.dreamLocked(TIMESTAMP1, UID);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_APPLICATION),
+                eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), /* details= */
+                isNull());
+    }
+
+    @Test
+    public void testLastWakeAndSleepTimeIsUpdated() {
+        assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
+        assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
+
+        // Verify that the transition to WAKEFULNESS_DOZING updates the last sleep time
+        String details = "PowerGroup1 Timeout";
+        mPowerGroup.setWakefulnessLocked(WAKEFULNESS_DOZING, TIMESTAMP1, UID,
+                GO_TO_SLEEP_REASON_TIMEOUT, /* opUid= */ 0, /* opPackageName= */ null, details);
+        assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP1);
+        assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_DOZING), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_TIMEOUT),
+                eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), eq(details));
+
+        // Verify that the transition to WAKEFULNESS_ASLEEP after dozing does not update the last
+        // wake or sleep time
+        mPowerGroup.setWakefulnessLocked(WAKEFULNESS_ASLEEP, TIMESTAMP2, UID,
+                GO_TO_SLEEP_REASON_DEVICE_ADMIN, /* opUid= */ 0, /* opPackageName= */ null,
+                details);
+        assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP1);
+        assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_ASLEEP), eq(TIMESTAMP2), eq(GO_TO_SLEEP_REASON_DEVICE_ADMIN),
+                eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), eq(details));
+
+        // Verify that waking up the power group only updates the last wake time
+        details = "PowerGroup1 Gesture";
+        mPowerGroup.setWakefulnessLocked(WAKEFULNESS_AWAKE, TIMESTAMP2, UID,
+                WAKE_REASON_GESTURE, /* opUid= */ 0, /* opPackageName= */ null, details);
+        assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP2);
+        assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP1);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_AWAKE), eq(TIMESTAMP2), eq(WAKE_REASON_GESTURE),
+                eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(), eq(details));
+
+        // Verify that a transition to WAKEFULNESS_ASLEEP from an interactive state updates the last
+        // sleep time
+        mPowerGroup.setWakefulnessLocked(WAKEFULNESS_ASLEEP, TIMESTAMP3, UID,
+                GO_TO_SLEEP_REASON_DEVICE_ADMIN, /* opUid= */ 0, /* opPackageName= */ null,
+                details);
+        assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP3);
+        assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP2);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_ASLEEP), eq(TIMESTAMP3), eq(GO_TO_SLEEP_REASON_DEVICE_ADMIN),
+                eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), eq(details));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index c832a3e..d35c679 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -111,6 +111,7 @@
  * Build/Install/Run:
  *  atest FrameworksServicesTests:PowerManagerServiceTest
  */
+@SuppressWarnings("GuardedBy")
 public class PowerManagerServiceTest {
     private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
     private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason";
@@ -437,7 +438,7 @@
     @Test
     public void testWakefulnessAwake_InitialValue() {
         createService();
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
     }
 
     @Test
@@ -445,12 +446,12 @@
         createService();
         // Start with AWAKE state
         startSystem();
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         // Take a nap and verify.
         mService.getBinderServiceInstance().goToSleep(mClock.now(),
                 PowerManager.GO_TO_SLEEP_REASON_APPLICATION, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
     }
 
     @Test
@@ -467,21 +468,21 @@
         int flags = PowerManager.FULL_WAKE_LOCK;
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
                 null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
 
         // Ensure that the flag does *NOT* work with a partial wake lock.
         flags = PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
                 null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
 
         // Verify that flag forces a wakeup when paired to a FULL_WAKE_LOCK
         flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
                 null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
     }
 
@@ -492,7 +493,7 @@
         forceSleep();
         mService.getBinderServiceInstance().wakeUp(mClock.now(),
                 PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
     }
 
     /**
@@ -511,7 +512,7 @@
                 .thenReturn(false);
         mService.readConfigurationLocked();
         setPluggedIn(true);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
                 .thenReturn(true);
         mService.readConfigurationLocked();
@@ -526,20 +527,20 @@
         when(mWirelessChargerDetectorMock.update(true /* isPowered */,
                 BatteryManager.BATTERY_PLUGGED_WIRELESS)).thenReturn(false);
         setPluggedIn(true);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
 
         // Test 3:
         // Do not wake up if the phone is being REMOVED from a wireless charger
         when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
         setPluggedIn(false);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
 
         // Test 4:
         // Do not wake if we are dreaming.
         forceAwake();  // Needs to be awake first before it can dream.
         forceDream();
         setPluggedIn(true);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
         forceSleep();
 
         // Test 5:
@@ -552,7 +553,7 @@
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug))
                 .thenReturn(false);
         setPluggedIn(false);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         Settings.Global.putInt(
                 mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0);
         mUserSwitchedReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED));
@@ -565,14 +566,14 @@
         forceAwake();
         forceDozing();
         setPluggedIn(true);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
 
         // Test 7:
         // Finally, take away all the factors above and ensure the device wakes up!
         forceAwake();
         forceSleep();
         setPluggedIn(false);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
     }
 
     @Test
@@ -580,12 +581,12 @@
         createService();
         // Start with AWAKE state
         startSystem();
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         // Take a nap and verify.
         mService.getBinderServiceInstance().goToSleep(mClock.now(),
                 PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
     }
 
     @Test
@@ -616,12 +617,12 @@
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
 
         // Verify that we start awake
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         // Grab the wakefulness value when PowerManager finally calls into the
         // native component to actually perform the suspend.
         when(mNativeWrapperMock.nativeForceSuspend()).then(inv -> {
-            assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+            assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
             return true;
         });
 
@@ -629,7 +630,7 @@
         assertThat(retval).isTrue();
 
         // Still asleep when the function returns.
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
     }
 
     @Test
@@ -662,7 +663,7 @@
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
 
         // Verify that we start awake
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         // Create a wakelock
         mService.getBinderServiceInstance().acquireWakeLock(new Binder(), flags, tag, pkg,
@@ -718,7 +719,7 @@
 
         // Start with AWAKE state
         startSystem();
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         assertTrue(isAcquired[0]);
 
         // Take a nap and verify we no longer hold the blocker
@@ -728,7 +729,7 @@
         when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
         mService.getBinderServiceInstance().goToSleep(mClock.now(),
                 PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
         assertFalse(isAcquired[0]);
 
         // Override the display state by DreamManager and verify is reacquires the blocker.
@@ -841,9 +842,9 @@
         verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).show();
         when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
         advanceTime(70);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         forceAwake();
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss(false);
     }
 
@@ -862,7 +863,7 @@
         createService();
         startSystem();
         advanceTime(20);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
                 PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
     }
@@ -882,9 +883,9 @@
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
                 null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
 
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         advanceTime(60);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
                 PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
     }
@@ -912,7 +913,7 @@
         mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
 
         advanceTime(520);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
                 PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
     }
@@ -934,7 +935,7 @@
                 PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS);
 
         advanceTime(520);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
                 PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
     }
@@ -955,7 +956,7 @@
                 PowerManager.USER_ACTIVITY_EVENT_OTHER, 0 /* flags */);
 
         advanceTime(520);
-        assertThat(mService.getWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_ASLEEP);
     }
 
     @Test
@@ -984,14 +985,14 @@
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
                 null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY);
 
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
                 WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
                 WAKEFULNESS_AWAKE);
 
         advanceTime(15000);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
                 WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
@@ -1024,14 +1025,14 @@
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
                 null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
 
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
                 WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
                 WAKEFULNESS_AWAKE);
 
         advanceTime(15000);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
                 WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
@@ -1069,14 +1070,14 @@
                 WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
                 WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         listener.get().onDisplayGroupRemoved(nonDefaultDisplayGroupId);
 
         advanceTime(15000);
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
                 WAKEFULNESS_DOZING);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
     }
 
     @Test
@@ -1084,7 +1085,7 @@
         createService();
         startSystem();
 
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         verify(mNotifierMock, never()).onWakefulnessChangeStarted(anyInt(), anyInt(), anyLong());
     }
 
@@ -1103,7 +1104,7 @@
         createService();
         startSystem();
 
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         verify(mNotifierMock).onWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP), anyInt(),
                 anyLong());
     }
@@ -1137,7 +1138,7 @@
                 PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
 
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
                 DisplayPowerRequest.POLICY_BRIGHT);
     }
@@ -1418,23 +1419,23 @@
         startSystem();
         listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
 
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
                 null, null);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
                 null, null);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
 
         mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
                 null, null);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
     }
 
     @Test
-    public void testMultiDisplay_addDisplayGroup_preservesWakefulness() {
+    public void testMultiDisplay_addDisplayGroup_wakesDeviceUp() {
         final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
         final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
                 new AtomicReference<>();
@@ -1446,15 +1447,15 @@
         createService();
         startSystem();
 
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
                 null, null);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
 
         listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
 
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
     }
 
     @Test
@@ -1471,18 +1472,79 @@
         startSystem();
         listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
 
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
                 null, null);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         listener.get().onDisplayGroupRemoved(nonDefaultDisplayGroupId);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
 
         mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
                 null, null);
-        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testMultiDisplay_updatesLastGlobalWakeTime() {
+        final int nonDefaultPowerGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        long eventTime1 = 10;
+        long eventTime2 = eventTime1 + 1;
+        long eventTime3 = eventTime2 + 1;
+        long eventTime4 = eventTime3 + 1;
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultPowerGroupId);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_DOZING, eventTime1,
+                0, PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, 0, null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(nonDefaultPowerGroupId, WAKEFULNESS_DOZING, eventTime2,
+                0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE,
+                eventTime3, /* uid= */ 0, PowerManager.WAKE_REASON_PLUGGED_IN, /* opUid= */
+                0, /* opPackageName= */ null, /* details= */ null);
+        PowerManager.WakeData wakeData = mService.getLocalServiceInstance().getLastWakeup();
+        assertThat(wakeData.wakeTime).isEqualTo(eventTime3);
+        assertThat(wakeData.wakeReason).isEqualTo(PowerManager.WAKE_REASON_PLUGGED_IN);
+        assertThat(wakeData.sleepDuration).isEqualTo(eventTime3 - eventTime2);
+
+        // The global wake time and reason as well as sleep duration shouldn't change when another
+        // PowerGroup wakes up.
+        mService.setWakefulnessLocked(nonDefaultPowerGroupId, WAKEFULNESS_AWAKE,
+                eventTime4, /* uid= */ 0, PowerManager.WAKE_REASON_CAMERA_LAUNCH, /* opUid= */
+                0, /* opPackageName= */ null, /* details= */ null);
+        assertThat(wakeData.wakeTime).isEqualTo(eventTime3);
+        assertThat(wakeData.wakeReason).isEqualTo(PowerManager.WAKE_REASON_PLUGGED_IN);
+        assertThat(wakeData.sleepDuration).isEqualTo(eventTime3 - eventTime2);
+    }
+
+    @Test
+    public void testLastSleepTime_notUpdatedWhenDreaming() {
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        PowerManager.WakeData initialWakeData = mService.getLocalServiceInstance().getLastWakeup();
+
+        forceDream();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        assertThat(mService.getLocalServiceInstance().getLastWakeup()).isEqualTo(initialWakeData);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 5dd44ff..020d9f8 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
+import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -69,6 +72,7 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -525,7 +529,8 @@
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK), /* delay= */ 100)
+                .addOffDuration(Duration.ofMillis(100))
+                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .compose();
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
@@ -558,11 +563,12 @@
                 0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
 
         long vibrationId = 1;
-        VibrationEffect effect = VibrationEffect.startWaveform()
-                .addStep(1, 10)
-                .addRamp(0, 20)
-                .addStep(0.8f, 100, 30)
-                .addRamp(0.6f, 200, 40)
+        VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
+                .addSustain(Duration.ofMillis(10))
+                .addTransition(Duration.ofMillis(20), targetAmplitude(0))
+                .addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
+                .addSustain(Duration.ofMillis(30))
+                .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
                 .build();
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
@@ -595,11 +601,12 @@
         fakeVibrator.setPwleSizeMax(2);
 
         long vibrationId = 1;
-        VibrationEffect effect = VibrationEffect.startWaveform()
-                .addStep(1, 10)
-                .addRamp(0, 20)
-                .addStep(0.8f, 10, 30)
-                .addRamp(0.6f, 100, 40)
+        VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
+                .addSustain(Duration.ofMillis(10))
+                .addTransition(Duration.ofMillis(20), targetAmplitude(0))
+                .addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
+                .addSustain(Duration.ofMillis(30))
+                .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
                 .build();
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
@@ -1261,7 +1268,9 @@
         fakeVibrator.setPwleSizeMax(2);
 
         long vibrationId = 1;
-        VibrationEffect effect = VibrationEffect.startWaveform().addRamp(1, 1).build();
+        VibrationEffect effect = VibrationEffect.startWaveform()
+                .addTransition(Duration.ofMillis(1), targetAmplitude(1))
+                .build();
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 774e5b9..30ad1f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -40,6 +40,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.Process.NOBODY_UID;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.InsetsState.ITYPE_IME;
@@ -83,6 +84,7 @@
 import static com.android.server.wm.ActivityRecord.State.STARTED;
 import static com.android.server.wm.ActivityRecord.State.STOPPED;
 import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
@@ -3305,6 +3307,29 @@
                 CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
     }
 
+    @Test // b/162542125
+    public void testInputDispatchTimeout() throws RemoteException {
+        final ActivityRecord activity = createActivityWithTask();
+        final WindowProcessController wpc = activity.app;
+        spyOn(wpc);
+        doReturn(true).when(wpc).isInstrumenting();
+        final ActivityRecord instrumentingActivity = createActivityOnDisplay(
+                true /* defaultDisplay */, wpc);
+        assertEquals(INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS,
+                instrumentingActivity.mInputDispatchingTimeoutMillis);
+
+        doReturn(false).when(wpc).isInstrumenting();
+        final ActivityRecord nonInstrumentingActivity = createActivityOnDisplay(
+                true /* defaultDisplay */, wpc);
+        assertEquals(DEFAULT_DISPATCHING_TIMEOUT_MILLIS,
+                nonInstrumentingActivity.mInputDispatchingTimeoutMillis);
+
+        final ActivityRecord noProcActivity = createActivityOnDisplay(true /* defaultDisplay */,
+                null);
+        assertEquals(DEFAULT_DISPATCHING_TIMEOUT_MILLIS,
+                noProcActivity.mInputDispatchingTimeoutMillis);
+    }
+
     private ICompatCameraControlCallback getCompatCameraControlCallback() {
         return new ICompatCameraControlCallback.Stub() {
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index fdc8982..c58bf3b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -340,7 +340,7 @@
             doReturn(stack).when(mRootWindowContainer)
                     .getLaunchRootTask(any(), any(), any(), anyBoolean());
             doReturn(stack).when(mRootWindowContainer).getLaunchRootTask(any(), any(), any(), any(),
-                    anyBoolean(), any(), anyInt(), anyInt(), anyInt());
+                    anyBoolean(), any(), anyInt());
         }
 
         // Set up mock package manager internal and make sure no unmocked methods are called
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 8d58ec0..ea03250 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1340,7 +1340,7 @@
         displayContent.getDisplayRotation().setRotation((displayContent.getRotation() + 1) % 4);
         displayContent.setRotationAnimation(rotationAnim);
         // The fade rotation animation also starts to hide some non-app windows.
-        assertNotNull(displayContent.getFadeRotationAnimationController());
+        assertNotNull(displayContent.getAsyncRotationController());
         assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
 
         for (WindowState w : windows) {
@@ -1354,7 +1354,7 @@
         // the animation controller should be cleared.
         statusBar.setOrientationChanging(false);
         navBar.setOrientationChanging(false);
-        assertNull(displayContent.getFadeRotationAnimationController());
+        assertNull(displayContent.getAsyncRotationController());
     }
 
     @UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
@@ -1392,7 +1392,7 @@
                 ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
                 false /* forceUpdate */));
 
-        assertNotNull(mDisplayContent.getFadeRotationAnimationController());
+        assertNotNull(mDisplayContent.getAsyncRotationController());
         assertTrue(mStatusBarWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
         assertTrue(mNavBarWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
         // Notification shade may have its own view animation in real case so do not fade out it.
@@ -1482,7 +1482,7 @@
         assertFalse(app.hasFixedRotationTransform());
         assertFalse(app2.hasFixedRotationTransform());
         assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
-        assertNull(mDisplayContent.getFadeRotationAnimationController());
+        assertNull(mDisplayContent.getAsyncRotationController());
     }
 
     @Test
@@ -1598,6 +1598,30 @@
         assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
     }
 
+    /**
+     * Creates different types of displays, verifies that minimal task size doesn't change
+     * with density of display.
+     */
+    @Test
+    public void testCalculatesDisplaySpecificMinTaskSizes() {
+        DisplayContent defaultTestDisplay =
+                new TestDisplayContent.Builder(mAtm, 1000, 2000).build();
+        final int defaultMinTaskSize = defaultTestDisplay.mMinSizeOfResizeableTaskDp;
+        DisplayContent firstDisplay = new TestDisplayContent.Builder(mAtm, 1000, 2000)
+                .setDensityDpi(300)
+                .updateDisplayMetrics()
+                .setDefaultMinTaskSizeDp(defaultMinTaskSize + 10)
+                .build();
+        assertEquals(defaultMinTaskSize + 10, firstDisplay.mMinSizeOfResizeableTaskDp);
+
+        DisplayContent secondDisplay = new TestDisplayContent.Builder(mAtm, 200, 200)
+                .setDensityDpi(320)
+                .updateDisplayMetrics()
+                .setDefaultMinTaskSizeDp(defaultMinTaskSize + 20)
+                .build();
+        assertEquals(defaultMinTaskSize + 20, secondDisplay.mMinSizeOfResizeableTaskDp);
+    }
+
     @Test
     public void testRecentsNotRotatingWithFixedRotation() {
         unblockDisplayRotation(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 6342183..25cff61c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DISABLED;
@@ -546,6 +547,80 @@
                 SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
     }
 
+    @Test
+    public void testReturnsSensorRotation_180degrees_allRotationsAllowed()
+            throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        enableOrientationSensor();
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+
+        assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testReturnLastRotation_sensor180_allRotationsNotAllowed()
+            throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(false);
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        enableOrientationSensor();
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testAllowRotationsIsCached()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        enableOrientationSensor();
+
+        // Rotate once to read the resource
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(true);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+        mTarget.rotationForOrientation(SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0);
+
+        // Change resource to disallow all rotations.
+        // Rotate again and 180 degrees rotation should still be returned even if "disallowed".
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(false);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+        assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testResetAllowRotations()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        enableOrientationSensor();
+
+        // Rotate once to read the resource
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(true);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+        mTarget.rotationForOrientation(SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0);
+
+        // Change resource to disallow all rotations.
+        // Reset "allowAllRotations".
+        // Rotate again and 180 degrees rotation should not be allowed anymore.
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(false);
+        mTarget.resetAllowAllRotations();
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+    }
+
     // =================================
     // Tests for Policy based Rotation
     // =================================
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index 365e749..093be82 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -20,10 +20,10 @@
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DISABLED;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -260,6 +260,17 @@
     }
 
     @Test
+    public void testResetAllowAllRotations() {
+        final DisplayRotation displayRotation = mock(DisplayRotation.class);
+        spyOn(mPrimaryDisplay);
+        doReturn(displayRotation).when(mPrimaryDisplay).getDisplayRotation();
+
+        mDisplayWindowSettings.applyRotationSettingsToDisplayLocked(mPrimaryDisplay);
+
+        verify(displayRotation).resetAllowAllRotations();
+    }
+
+    @Test
     public void testDefaultToFreeUserRotation() {
         mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
similarity index 83%
rename from services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
rename to services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index d64bf12..d487113 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -30,9 +30,9 @@
 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.clearInvocations;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityThread;
@@ -48,8 +48,7 @@
 import android.view.WindowManagerGlobal;
 import android.window.WindowTokenClient;
 
-import com.android.server.inputmethod.InputMethodManagerService;
-import com.android.server.inputmethod.InputMethodMenuController;
+import com.android.server.inputmethod.InputMethodDialogWindowContext;
 
 import org.junit.After;
 import org.junit.Before;
@@ -61,18 +60,20 @@
 //  scenario there.
 /**
  * Build/Install/Run:
- *  atest WmTests:InputMethodMenuControllerTest
+ *  atest WmTests:InputMethodDialogWindowContextTest
  */
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class InputMethodMenuControllerTest extends WindowTestsBase {
+public class InputMethodDialogWindowContextTest extends WindowTestsBase {
 
-    private InputMethodMenuController mController;
+    private InputMethodDialogWindowContext mWindowContext;
     private DualDisplayAreaGroupPolicyTest.DualDisplayContent mSecondaryDisplay;
 
     private IWindowManager mIWindowManager;
     private DisplayManagerGlobal mDisplayManagerGlobal;
 
+    private static final int WAIT_TIMEOUT_MS = 1000;
+
     @Before
     public void setUp() throws Exception {
         // Let the Display be created with the DualDisplay policy.
@@ -80,7 +81,7 @@
                 new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider();
         Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
 
-        mController = new InputMethodMenuController(mock(InputMethodManagerService.class));
+        mWindowContext = new InputMethodDialogWindowContext();
         mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent
                 .Builder(mAtm, 1000, 1000).build();
         mSecondaryDisplay.getDisplayInfo().state = STATE_ON;
@@ -119,21 +120,21 @@
 
     @Test
     public void testGetSettingsContext() {
-        final Context contextOnDefaultDisplay = mController.getSettingsContext(DEFAULT_DISPLAY);
+        final Context contextOnDefaultDisplay = mWindowContext.get(DEFAULT_DISPLAY);
 
         assertImeSwitchContextMetricsValidity(contextOnDefaultDisplay, mDefaultDisplay);
 
         // Obtain the context again and check if the window metrics match the IME container bounds
         // of the secondary display.
-        final Context contextOnSecondaryDisplay = mController.getSettingsContext(
-                mSecondaryDisplay.getDisplayId());
+        final Context contextOnSecondaryDisplay =
+                mWindowContext.get(mSecondaryDisplay.getDisplayId());
 
         assertImeSwitchContextMetricsValidity(contextOnSecondaryDisplay, mSecondaryDisplay);
     }
 
     @Test
     public void testGetSettingsContextOnDualDisplayContent() {
-        final Context context = mController.getSettingsContext(mSecondaryDisplay.getDisplayId());
+        final Context context = mWindowContext.get(mSecondaryDisplay.getDisplayId());
         final WindowTokenClient tokenClient = (WindowTokenClient) context.getWindowContextToken();
         assertNotNull(tokenClient);
         spyOn(tokenClient);
@@ -144,19 +145,22 @@
 
         mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer);
 
-        verify(imeContainer, atLeastOnce()).onConfigurationChanged(
+        verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
                 eq(mSecondaryDisplay.mFirstRoot.getConfiguration()));
-        verify(tokenClient, atLeastOnce()).onConfigurationChanged(
+        verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
                 eq(mSecondaryDisplay.mFirstRoot.getConfiguration()),
                 eq(mSecondaryDisplay.mDisplayId));
         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot);
         assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
 
+        // Clear the previous invocation histories in case we may count the previous
+        // onConfigurationChanged invocation into the next verification.
+        clearInvocations(tokenClient, imeContainer);
         mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer);
 
-        verify(imeContainer, atLeastOnce()).onConfigurationChanged(
+        verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
                 eq(mSecondaryDisplay.mSecondRoot.getConfiguration()));
-        verify(tokenClient, atLeastOnce()).onConfigurationChanged(
+        verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
                 eq(mSecondaryDisplay.mSecondRoot.getConfiguration()),
                 eq(mSecondaryDisplay.mDisplayId));
         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 632a59d..87f76fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -621,9 +621,9 @@
     @Test
     public void testNotAttachNavigationBar_controlledByFadeRotationAnimation() {
         setupForShouldAttachNavBarDuringTransition();
-        FadeRotationAnimationController mockController =
-                mock(FadeRotationAnimationController.class);
-        doReturn(mockController).when(mDefaultDisplay).getFadeRotationAnimationController();
+        AsyncRotationController mockController =
+                mock(AsyncRotationController.class);
+        doReturn(mockController).when(mDefaultDisplay).getAsyncRotationController();
         final ActivityRecord homeActivity = createHomeActivity();
         initializeRecentsAnimationController(mController, homeActivity);
         assertFalse(mController.isNavigationBarAttachedToApp());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 575e082..a4851ad5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -665,9 +665,9 @@
 
     @Test
     public void testNonAppTarget_notSendNavBar_controlledByFadeRotation() throws Exception {
-        final FadeRotationAnimationController mockController =
-                mock(FadeRotationAnimationController.class);
-        doReturn(mockController).when(mDisplayContent).getFadeRotationAnimationController();
+        final AsyncRotationController mockController =
+                mock(AsyncRotationController.class);
+        doReturn(mockController).when(mDisplayContent).getAsyncRotationController();
         final int transit = TRANSIT_OLD_TASK_OPEN;
         setupForNonAppTargetNavBar(transit, true);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index f4abf88..ee17f52 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -25,7 +25,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.TYPE_VIRTUAL;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -935,39 +934,6 @@
         assertEquals(infoFake1.activityInfo.name, resolvedInfo.first.name);
     }
 
-    /**
-     * Test that {@link RootWindowContainer#getLaunchRootTask} with the real caller id will get the
-     * expected root task when requesting the activity launch on the secondary display.
-     */
-    @Test
-    public void testGetLaunchRootTaskWithRealCallerId() {
-        // Create a non-system owned virtual display.
-        final TestDisplayContent secondaryDisplay =
-                new TestDisplayContent.Builder(mAtm, 1000, 1500)
-                        .setType(TYPE_VIRTUAL).setOwnerUid(100).build();
-
-        // Create an activity with specify the original launch pid / uid.
-        final ActivityRecord r = new ActivityBuilder(mAtm).setLaunchedFromPid(200)
-                .setLaunchedFromUid(200).build();
-
-        // Simulate ActivityStarter to find a launch root task for requesting the activity to launch
-        // on the secondary display with realCallerId.
-        final ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchDisplayId(secondaryDisplay.mDisplayId);
-        options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        doReturn(true).when(mSupervisor).canPlaceEntityOnDisplay(secondaryDisplay.mDisplayId,
-                300 /* test realCallerPid */, 300 /* test realCallerUid */, r.info);
-        final Task result = mRootWindowContainer.getLaunchRootTask(r, options,
-                null /* task */, null /* sourceTask */, true /* onTop */, null /* launchParams */,
-                0 /* launchFlags */, 300 /* test realCallerPid */,
-                300 /* test realCallerUid */);
-
-        // Assert that the root task is returned as expected.
-        assertNotNull(result);
-        assertEquals("The display ID of the root task should same as secondary display ",
-                secondaryDisplay.mDisplayId, result.getDisplayId());
-    }
-
     @Test
     public void testGetValidLaunchRootTaskOnDisplayWithCandidateRootTask() {
         // Create a root task with an activity on secondary display.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 8b0716c..1e64e46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -29,18 +29,26 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
+import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
 
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
 class TestDisplayContent extends DisplayContent {
 
     public static final int DEFAULT_LOGICAL_DISPLAY_DENSITY = 300;
@@ -85,6 +93,11 @@
         private boolean mSystemDecorations = false;
         private int mStatusBarHeight = 0;
         private SettingsEntry mOverrideSettings;
+        private DisplayMetrics mDisplayMetrics;
+        @Mock
+        Context mMockContext;
+        @Mock
+        Resources mResources;
 
         Builder(ActivityTaskManagerService service, int width, int height) {
             mService = service;
@@ -97,6 +110,8 @@
             // Set unique ID so physical display overrides are not inheritted from
             // DisplayWindowSettings.
             mInfo.uniqueId = generateUniqueId();
+            mDisplayMetrics = new DisplayMetrics();
+            updateDisplayMetrics();
         }
         Builder(ActivityTaskManagerService service, DisplayInfo info) {
             mService = service;
@@ -153,6 +168,20 @@
             mInfo.logicalDensityDpi = dpi;
             return this;
         }
+        Builder updateDisplayMetrics() {
+            mInfo.getAppMetrics(mDisplayMetrics);
+            return this;
+        }
+        Builder setDefaultMinTaskSizeDp(int valueDp) {
+            MockitoAnnotations.initMocks(this);
+            doReturn(mMockContext).when(mService.mContext).createConfigurationContext(any());
+            doReturn(mResources).when(mMockContext).getResources();
+            doReturn(valueDp * mDisplayMetrics.density)
+                    .when(mResources)
+                    .getDimension(
+                        com.android.internal.R.dimen.default_minimal_size_resizable_task);
+            return this;
+        }
         TestDisplayContent createInternal(Display display) {
             return new TestDisplayContent(mService.mRootWindowContainer, display);
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 141588a..fd523f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -50,6 +50,7 @@
 import android.util.ArraySet;
 import android.view.SurfaceControl;
 import android.view.TransactionCommittedListener;
+import android.window.IDisplayAreaOrganizer;
 import android.window.ITaskOrganizer;
 import android.window.ITransitionPlayer;
 import android.window.TransitionInfo;
@@ -60,6 +61,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -75,7 +78,9 @@
     private Transition createTestTransition(int transitType) {
         TransitionController controller = mock(TransitionController.class);
         final BLASTSyncEngine sync = createTestBLASTSyncEngine();
-        return new Transition(transitType, 0 /* flags */, 0 /* timeoutMs */, controller, sync);
+        final Transition t = new Transition(transitType, 0 /* flags */, controller, sync);
+        t.startCollecting(0 /* timeoutMs */);
+        return t;
     }
 
     @Test
@@ -104,7 +109,7 @@
         // Check basic both tasks participating
         participants.add(oldTask);
         participants.add(newTask);
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
         TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
         assertEquals(2, info.getChanges().size());
         assertEquals(transit, info.getType());
@@ -169,7 +174,7 @@
         participants.add(oldTask);
         participants.add(opening);
         participants.add(opening2);
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
         TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
         assertEquals(2, info.getChanges().size());
         assertEquals(transit, info.getType());
@@ -215,15 +220,14 @@
         // Check promotion to DisplayArea
         participants.add(showing);
         participants.add(showing2);
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
         TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
         assertEquals(1, info.getChanges().size());
         assertEquals(transit, info.getType());
         assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
 
-        ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
         // Check that organized tasks get reported even if not top
-        showTask.mTaskOrganizer = mockOrg;
+        makeTaskOrganized(showTask);
         targets = Transition.calculateTargets(participants, changes);
         info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
         assertEquals(2, info.getChanges().size());
@@ -253,7 +257,7 @@
         opening.mVisibleRequested = true;
         closing.mVisibleRequested = false;
 
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(
                 transition.mParticipants, transition.mChanges);
         TransitionInfo info = Transition.calculateTransitionInfo(
                 0, 0, targets, transition.mChanges);
@@ -290,7 +294,7 @@
             tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
         }
 
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(
                 transition.mParticipants, transition.mChanges);
         TransitionInfo info = Transition.calculateTransitionInfo(
                 0, 0, targets, transition.mChanges);
@@ -339,7 +343,7 @@
             tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
         }
 
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(
                 transition.mParticipants, transition.mChanges);
         TransitionInfo info = Transition.calculateTransitionInfo(
                 0, 0, targets, transition.mChanges);
@@ -361,13 +365,7 @@
                 mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
         // Make DA organized so we can check that they don't get included.
         WindowContainer parent = wallpaperWindowToken.getParent();
-        while (parent != null && parent != mDisplayContent) {
-            if (parent.asDisplayArea() != null) {
-                parent.asDisplayArea().setOrganizer(
-                        mock(android.window.IDisplayAreaOrganizer.class), true /* skipAppear */);
-            }
-            parent = parent.getParent();
-        }
+        makeDisplayAreaOrganized(parent, mDisplayContent);
         final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
                 "wallpaperWindow");
         wallpaperWindowToken.setVisibleRequested(false);
@@ -379,7 +377,7 @@
         mDisplayContent.getWindowConfiguration().setRotation(
                 (mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
 
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(
                 transition.mParticipants, transition.mChanges);
         TransitionInfo info = Transition.calculateTransitionInfo(
                 0, 0, targets, transition.mChanges);
@@ -392,11 +390,37 @@
     }
 
     @Test
+    public void testOpenActivityInTheSameTaskWithDisplayChange() {
+        final ActivityRecord closing = createActivityRecord(mDisplayContent);
+        closing.mVisibleRequested = true;
+        final Task task = closing.getTask();
+        makeTaskOrganized(task);
+        final ActivityRecord opening = createActivityRecord(task);
+        opening.mVisibleRequested = false;
+        makeDisplayAreaOrganized(mDisplayContent.getDefaultTaskDisplayArea(), mDisplayContent);
+        final WindowContainer<?>[] wcs = { closing, opening, task, mDisplayContent };
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        for (WindowContainer<?> wc : wcs) {
+            transition.collect(wc);
+        }
+        closing.mVisibleRequested = false;
+        opening.mVisibleRequested = true;
+        final int newRotation = mDisplayContent.getWindowConfiguration().getRotation() + 1;
+        for (WindowContainer<?> wc : wcs) {
+            wc.getWindowConfiguration().setRotation(newRotation);
+        }
+
+        final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+                transition.mParticipants, transition.mChanges);
+        // Especially the activities must be in the targets.
+        assertTrue(targets.containsAll(Arrays.asList(wcs)));
+    }
+
+    @Test
     public void testIndependent() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
         ArraySet<WindowContainer> participants = transition.mParticipants;
-        ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
 
         final Task openTask = createTask(mDisplayContent);
         final Task openInOpenTask = createTaskInRootTask(openTask, 0);
@@ -408,10 +432,8 @@
         final ActivityRecord changeInChange = createActivityRecord(changeInChangeTask);
         final ActivityRecord openInChange = createActivityRecord(openInChangeTask);
         // set organizer for everything so that they all get added to transition info
-        for (Task t : new Task[]{
-                openTask, openInOpenTask, changeTask, changeInChangeTask, openInChangeTask}) {
-            t.mTaskOrganizer = mockOrg;
-        }
+        makeTaskOrganized(openTask, openInOpenTask, changeTask, changeInChangeTask,
+                openInChangeTask);
 
         // Start states.
         changes.put(openTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
@@ -438,7 +460,8 @@
         participants.add(openInChange);
         // Explicitly add changeTask (to test independence with parents)
         participants.add(changeTask);
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        final ArrayList<WindowContainer> targets =
+                Transition.calculateTargets(participants, changes);
         TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
         // Root changes should always be considered independent
         assertTrue(isIndependent(
@@ -466,12 +489,13 @@
         final BLASTSyncEngine sync = new BLASTSyncEngine(mWm);
         final CountDownLatch latch = new CountDownLatch(1);
         // When the timeout is reached, it will finish the sync-group and notify transaction ready.
-        new Transition(TRANSIT_OPEN, 0 /* flags */, 10 /* timeoutMs */, controller, sync) {
+        final Transition t = new Transition(TRANSIT_OPEN, 0 /* flags */, controller, sync) {
             @Override
             public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
                 latch.countDown();
             }
         };
+        t.startCollecting(10 /* timeoutMs */);
         assertTrue(awaitInWmLock(() -> latch.await(3, TimeUnit.SECONDS)));
     }
 
@@ -491,9 +515,9 @@
         mDisplayContent.setLastHasContent();
         mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */,
                 null /* displayChange */);
-        final FadeRotationAnimationController fadeController =
-                mDisplayContent.getFadeRotationAnimationController();
-        assertNotNull(fadeController);
+        final AsyncRotationController asyncRotationController =
+                mDisplayContent.getAsyncRotationController();
+        assertNotNull(asyncRotationController);
         for (WindowState w : windows) {
             w.setOrientationChanging(true);
         }
@@ -506,7 +530,7 @@
         // Status bar finishes drawing before the start transaction. Its fade-in animation will be
         // executed until the transaction is committed, so it is still in target tokens.
         statusBar.setOrientationChanging(false);
-        assertTrue(fadeController.isTargetToken(statusBar.mToken));
+        assertTrue(asyncRotationController.isTargetToken(statusBar.mToken));
 
         final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
         final ArgumentCaptor<TransactionCommittedListener> listenerCaptor =
@@ -516,13 +540,13 @@
         verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture());
         // The transaction is committed, so fade-in animation for status bar is consumed.
         listenerCaptor.getValue().onTransactionCommitted();
-        assertFalse(fadeController.isTargetToken(statusBar.mToken));
+        assertFalse(asyncRotationController.isTargetToken(statusBar.mToken));
 
         // Status bar finishes drawing after the start transaction, so its fade-in animation can
         // execute directly.
         navBar.setOrientationChanging(false);
-        assertFalse(fadeController.isTargetToken(navBar.mToken));
-        assertNull(mDisplayContent.getFadeRotationAnimationController());
+        assertFalse(asyncRotationController.isTargetToken(navBar.mToken));
+        assertNull(mDisplayContent.getAsyncRotationController());
     }
 
     @Test
@@ -540,10 +564,10 @@
         mDisplayContent.setLastHasContent();
         mDisplayContent.requestChangeTransitionIfNeeded(anyChanges, null /* displayChange */);
         transition.setKnownConfigChanges(mDisplayContent, anyChanges);
-        final FadeRotationAnimationController fadeController =
-                mDisplayContent.getFadeRotationAnimationController();
-        assertNotNull(fadeController);
-        assertTrue(fadeController.shouldFreezeInsetsPosition(statusBar));
+        final AsyncRotationController asyncRotationController =
+                mDisplayContent.getAsyncRotationController();
+        assertNotNull(asyncRotationController);
+        assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar));
 
         statusBar.setOrientationChanging(true);
         player.startTransition();
@@ -569,7 +593,7 @@
         // The controller should capture the draw transaction and merge it when preparing to run
         // fade-in animation.
         verify(mDisplayContent.getPendingTransaction()).merge(eq(postDrawTransaction));
-        assertNull(mDisplayContent.getFadeRotationAnimationController());
+        assertNull(mDisplayContent.getAsyncRotationController());
     }
 
     @Test
@@ -578,17 +602,15 @@
         final TransitionController controller = new TransitionController(mAtm, snapshotController);
         final ITransitionPlayer player = new ITransitionPlayer.Default();
         controller.registerTransitionPlayer(player, null /* appThread */);
-        ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
         final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
 
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
         final Task task1 = createTask(mDisplayContent);
-        task1.mTaskOrganizer = mockOrg;
         final ActivityRecord activity1 = createActivityRecord(task1);
         activity1.mVisibleRequested = false;
         activity1.setVisible(false);
         final Task task2 = createTask(mDisplayContent);
-        task2.mTaskOrganizer = mockOrg;
+        makeTaskOrganized(task1, task2);
         final ActivityRecord activity2 = createActivityRecord(task1);
         activity2.mVisibleRequested = true;
         activity2.setVisible(true);
@@ -644,17 +666,15 @@
         final TransitionController controller = new TransitionController(mAtm, snapshotController);
         final ITransitionPlayer player = new ITransitionPlayer.Default();
         controller.registerTransitionPlayer(player, null /* appThread */);
-        ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
         final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
 
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
         final Task task1 = createTask(mDisplayContent);
-        task1.mTaskOrganizer = mockOrg;
         final ActivityRecord activity1 = createActivityRecord(task1);
         activity1.mVisibleRequested = false;
         activity1.setVisible(false);
         final Task task2 = createTask(mDisplayContent);
-        task2.mTaskOrganizer = mockOrg;
+        makeTaskOrganized(task1, task2);
         final ActivityRecord activity2 = createActivityRecord(task2);
         activity2.mVisibleRequested = true;
         activity2.setVisible(true);
@@ -703,6 +723,24 @@
         verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false));
     }
 
+    private static void makeTaskOrganized(Task... tasks) {
+        final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
+        for (Task t : tasks) {
+            t.mTaskOrganizer = organizer;
+        }
+    }
+
+    private static void makeDisplayAreaOrganized(WindowContainer<?> from,
+            WindowContainer<?> end) {
+        final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class);
+        while (from != null && from != end) {
+            if (from.asDisplayArea() != null) {
+                from.asDisplayArea().mOrganizer = organizer;
+            }
+            from = from.getParent();
+        }
+    }
+
     /** Fill the change map with all the parents of top. Change maps are usually fully populated */
     private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes,
             WindowContainer top) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index caaf4e4..1f68608 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -116,6 +117,7 @@
 
         WindowManager.LayoutParams attrs = wallpaperWindow.getAttrs();
         Rect bounds = dc.getBounds();
+        int displayWidth = dc.getBounds().width();
         int displayHeight = dc.getBounds().height();
 
         // Use a wallpaper with a different ratio than the display
@@ -123,20 +125,18 @@
         int wallpaperHeight = (int) (bounds.height() * 1.10);
 
         // Simulate what would be done on the client's side
-        attrs.width = wallpaperWidth;
-        attrs.height = wallpaperHeight;
-        attrs.flags |= FLAG_LAYOUT_NO_LIMITS;
+        final float layoutScale = Math.max(
+                displayWidth / (float) wallpaperWidth, displayHeight / (float) wallpaperHeight);
+        attrs.width = (int) (wallpaperWidth * layoutScale + .5f);
+        attrs.height = (int) (wallpaperHeight * layoutScale + .5f);
+        attrs.flags |= FLAG_LAYOUT_NO_LIMITS | FLAG_SCALED;
         attrs.gravity = Gravity.TOP | Gravity.LEFT;
         wallpaperWindow.getWindowFrames().mParentFrame.set(dc.getBounds());
 
-        // Calling layoutWindowLw a first time, so adjustWindowParams gets the correct data
-        dc.getDisplayPolicy().layoutWindowLw(wallpaperWindow, null, dc.mDisplayFrames);
-
-        wallpaperWindowToken.adjustWindowParams(wallpaperWindow, attrs);
         dc.getDisplayPolicy().layoutWindowLw(wallpaperWindow, null, dc.mDisplayFrames);
 
         assertEquals(Configuration.ORIENTATION_PORTRAIT, dc.getConfiguration().orientation);
-        int expectedWidth = (int) (wallpaperWidth * (displayHeight / (double) wallpaperHeight));
+        int expectedWidth = (int) (wallpaperWidth * layoutScale + .5f);
 
         // Check that the wallpaper is correctly scaled
         assertEquals(expectedWidth, wallpaperWindow.getFrame().width());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 62c1067..4095728 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -883,7 +883,8 @@
 
     /** Sets the default minimum task size to 1 so that tests can use small task sizes */
     public void removeGlobalMinSizeRestriction() {
-        mAtm.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
+        mAtm.mRootWindowContainer.forAllDisplays(
+                displayContent -> displayContent.mMinSizeOfResizeableTaskDp = 1);
     }
 
     /** Mocks the behavior of taking a snapshot. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java
deleted file mode 100644
index 926153d..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.utils;
-
-import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
-import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
-import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
-import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static com.android.server.wm.utils.DisplayRotationUtil.getBoundIndexFromRotation;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-
-/**
- * Tests for {@link DisplayRotationUtil}
- *
- * Build/Install/Run:
- *  atest WmTests:DisplayRotationUtilTest
- */
-@SmallTest
-@Presubmit
-public class DisplayRotationUtilTest {
-    private static final Rect ZERO_RECT = new Rect();
-
-    @Test
-    public void testGetBoundIndexFromRotation_rot0() {
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_0),
-                equalTo(BOUNDS_POSITION_LEFT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_0),
-                equalTo(BOUNDS_POSITION_TOP));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_0),
-                equalTo(BOUNDS_POSITION_RIGHT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_0),
-                equalTo(BOUNDS_POSITION_BOTTOM));
-    }
-
-    @Test
-    public void testGetBoundIndexFromRotation_rot90() {
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_90),
-                equalTo(BOUNDS_POSITION_BOTTOM));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_90),
-                equalTo(BOUNDS_POSITION_LEFT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_90),
-                equalTo(BOUNDS_POSITION_TOP));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_90),
-                equalTo(BOUNDS_POSITION_RIGHT));
-    }
-
-    @Test
-    public void testGetBoundIndexFromRotation_rot180() {
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_180),
-                equalTo(BOUNDS_POSITION_RIGHT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_180),
-                equalTo(BOUNDS_POSITION_BOTTOM));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_180),
-                equalTo(BOUNDS_POSITION_LEFT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_180),
-                equalTo(BOUNDS_POSITION_TOP));
-    }
-
-    @Test
-    public void testGetBoundIndexFromRotation_rot270() {
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_270),
-                equalTo(BOUNDS_POSITION_TOP));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_270),
-                equalTo(BOUNDS_POSITION_RIGHT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_270),
-                equalTo(BOUNDS_POSITION_BOTTOM));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_270),
-                equalTo(BOUNDS_POSITION_LEFT));
-
-    }
-
-    @Test
-    public void testGetRotatedBounds_top_rot0() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_0, 200, 300),
-                equalTo(bounds));
-    }
-
-    @Test
-    public void testGetRotatedBounds_top_rot90() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_90, 200, 300),
-                equalTo(new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT}));
-    }
-
-    @Test
-    public void testGetRotatedBounds_top_rot180() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_180, 200, 300),
-                equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(50, 290, 150, 300)}));
-    }
-
-    @Test
-    public void testGetRotatedBounds_top_rot270() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_270, 200, 300),
-                equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(290, 50, 300, 150), ZERO_RECT}));
-    }
-
-    @Test
-    public void testGetRotatedBounds_left_rot0() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_0, 300, 200),
-                equalTo(bounds));
-    }
-
-    @Test
-    public void testGetRotatedBounds_left_rot90() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_90, 300, 200),
-                equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(50, 290, 150, 300)}));
-    }
-
-    @Test
-    public void testGetRotatedBounds_left_rot180() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_180, 300, 200),
-                equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(290, 50, 300, 150), ZERO_RECT}));
-    }
-
-    @Test
-    public void testGetRotatedBounds_left_rot270() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_270, 300, 200),
-                equalTo(new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT}));
-    }
-}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
index 85b1de5..9d4db00 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -41,7 +41,6 @@
 
     private final boolean mIsInputHeadset;
     private final boolean mIsOutputHeadset;
-    private final boolean mIsDock;
 
     private boolean mSelected = false;
     private int mOutputState;
@@ -54,7 +53,7 @@
 
     public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress,
             boolean hasOutput, boolean hasInput,
-            boolean isInputHeadset, boolean isOutputHeadset, boolean isDock) {
+            boolean isInputHeadset, boolean isOutputHeadset) {
         mAudioService = audioService;
         mCardNum = card;
         mDeviceNum = device;
@@ -63,32 +62,31 @@
         mHasInput = hasInput;
         mIsInputHeadset = isInputHeadset;
         mIsOutputHeadset = isOutputHeadset;
-        mIsDock = isDock;
     }
 
     /**
-     * @return the ALSA card number associated with this peripheral.
+     * @returns the ALSA card number associated with this peripheral.
      */
     public int getCardNum() {
         return mCardNum;
     }
 
     /**
-     * @return the ALSA device number associated with this peripheral.
+     * @returns the ALSA device number associated with this peripheral.
      */
     public int getDeviceNum() {
         return mDeviceNum;
     }
 
     /**
-     * @return the USB device device address associated with this peripheral.
+     * @returns the USB device device address associated with this peripheral.
      */
     public String getDeviceAddress() {
         return mDeviceAddress;
     }
 
     /**
-     * @return the ALSA card/device address string.
+     * @returns the ALSA card/device address string.
      */
     public String getAlsaCardDeviceString() {
         if (mCardNum < 0 || mDeviceNum < 0) {
@@ -100,42 +98,35 @@
     }
 
     /**
-     * @return true if the device supports output.
+     * @returns true if the device supports output.
      */
     public boolean hasOutput() {
         return mHasOutput;
     }
 
     /**
-     * @return true if the device supports input (recording).
+     * @returns true if the device supports input (recording).
      */
     public boolean hasInput() {
         return mHasInput;
     }
 
     /**
-     * @return true if the device is a headset for purposes of input.
+     * @returns true if the device is a headset for purposes of input.
      */
     public boolean isInputHeadset() {
         return mIsInputHeadset;
     }
 
     /**
-     * @return true if the device is a headset for purposes of output.
+     * @returns true if the device is a headset for purposes of output.
      */
     public boolean isOutputHeadset() {
         return mIsOutputHeadset;
     }
 
     /**
-     * @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.
+     * @returns true if input jack is detected or jack detection is not supported.
      */
     private synchronized boolean isInputJackConnected() {
         if (mJackDetector == null) {
@@ -145,7 +136,7 @@
     }
 
     /**
-     * @return true if input jack is detected or jack detection is not supported.
+     * @returns true if input jack is detected or jack detection is not supported.
      */
     private synchronized boolean isOutputJackConnected() {
         if (mJackDetector == null) {
@@ -199,10 +190,9 @@
         try {
             // Output Device
             if (mHasOutput) {
-                int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
-                        : (mIsOutputHeadset
-                            ? AudioSystem.DEVICE_OUT_USB_HEADSET
-                            : AudioSystem.DEVICE_OUT_USB_DEVICE);
+                int device = 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
@@ -241,7 +231,7 @@
 
     /**
      * @Override
-     * @return a string representation of the object.
+     * @returns a string representation of the object.
      */
     public synchronized String toString() {
         return "UsbAlsaDevice: [card: " + mCardNum
@@ -283,7 +273,7 @@
 
     /**
      * @Override
-     * @return true if the objects are equivalent.
+     * @returns true if the objects are equivalent.
      */
     public boolean equals(Object obj) {
         if (!(obj instanceof UsbAlsaDevice)) {
@@ -295,13 +285,12 @@
                 && mHasOutput == other.mHasOutput
                 && mHasInput == other.mHasInput
                 && mIsInputHeadset == other.mIsInputHeadset
-                && mIsOutputHeadset == other.mIsOutputHeadset
-                && mIsDock == other.mIsDock);
+                && mIsOutputHeadset == other.mIsOutputHeadset);
     }
 
     /**
      * @Override
-     * @return a hash code generated from the object contents.
+     * @returns a hash code generated from the object contents.
      */
     public int hashCode() {
         final int prime = 31;
@@ -312,7 +301,6 @@
         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 fd9b995..1c72eb8 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -237,7 +237,6 @@
         if (hasInput || hasOutput) {
             boolean isInputHeadset = parser.isInputHeadset();
             boolean isOutputHeadset = parser.isOutputHeadset();
-            boolean isDock = parser.isDock();
 
             if (mAudioService == null) {
                 Slog.e(TAG, "no AudioService");
@@ -247,7 +246,7 @@
             UsbAlsaDevice alsaDevice =
                     new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
                                       deviceAddress, hasOutput, hasInput,
-                                      isInputHeadset, isOutputHeadset, isDock);
+                                      isInputHeadset, isOutputHeadset);
             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 94cc826..9ac270f 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() + "], isDock: " + parser.isDock());
+                        + " , out: " + parser.isOutputHeadset() + "]");
             } else {
                 pw.println(formatTime() + " Disconnect " + mDeviceAddress);
             }
@@ -179,8 +179,9 @@
                 UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
                 descriptorTree.parse(parser);
                 descriptorTree.report(new TextReportCanvas(parser, stringBuilder));
+
                 stringBuilder.append("isHeadset[in: " + parser.isInputHeadset()
-                        + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock());
+                        + " , out: " + parser.isOutputHeadset() + "]");
                 pw.println(stringBuilder.toString());
             } else {
                 pw.println(formatTime() + " Disconnect " + mDeviceAddress);
@@ -197,8 +198,9 @@
                     descriptor.report(canvas);
                 }
                 pw.println(stringBuilder.toString());
+
                 pw.println("isHeadset[in: " + parser.isInputHeadset()
-                        + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock());
+                        + " , out: " + parser.isOutputHeadset() + "]");
             } 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 65b79bf..d0825ba 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -503,6 +503,49 @@
             return HAL_MODE_DFP;
     }
 
+    /**
+     * Reset USB port.
+     *
+     * @param portId port identifier.
+     */
+    public boolean resetUsbPort(@NonNull String portId, int transactionId,
+            @NonNull IUsbOperationInternal callback, IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            Objects.requireNonNull(callback);
+            Objects.requireNonNull(portId);
+            final PortInfo portInfo = mPorts.get(portId);
+            if (portInfo == null) {
+                logAndPrint(Log.ERROR, pw, "resetUsbPort: No such port: " + portId
+                    + " opId:" + transactionId);
+                try {
+                    callback.onOperationComplete(
+                            USB_OPERATION_ERROR_PORT_MISMATCH);
+                } catch (RemoteException e) {
+                    logAndPrintException(pw,
+                            "resetUsbPort: Failed to call OperationComplete. opId:"
+                            + transactionId, e);
+                }
+                return false;
+            }
+
+            try {
+                try {
+                    return mUsbPortHal.resetUsbPort(portId, transactionId, callback);
+                } catch (Exception e) {
+                    logAndPrintException(pw,
+                        "reseetUsbPort: Failed to resetUsbPort. opId:"
+                        + transactionId , e);
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                }
+            } catch (RemoteException e) {
+                logAndPrintException(pw,
+                        "resetUsbPort: Failed to call onOperationComplete. opId:"
+                        + transactionId, e);
+            }
+            return false;
+        }
+    }
+
     public void setPortRoles(String portId, int newPowerRole, int newDataRole,
             IndentingPrintWriter pw) {
         synchronized (mLock) {
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 88ffc7d61..f3308bb 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -680,6 +680,35 @@
     }
 
     @Override
+    public boolean resetUsbPort(String portId, int operationId,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(portId, "resetUsbPort: portId must not be null. opId:"
+                + operationId);
+        Objects.requireNonNull(callback, "resetUsbPort: 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) {
+                wait = mPortManager.resetUsbPort(portId, operationId, callback, null);
+            } else {
+                wait = false;
+                try {
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "resetUsbPort: Failed to call onOperationComplete", e);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return wait;
+    }
+
+    @Override
     public List<ParcelableUsbPort> getPorts() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
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 6e68a91..3412a6f 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -870,35 +870,4 @@
         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/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index 5582600..f468db3 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -238,6 +238,50 @@
     }
 
     @Override
+    public boolean resetUsbPort(String portName, long operationID,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(portName);
+        Objects.requireNonNull(callback);
+        long key = operationID;
+        synchronized (mLock) {
+            try {
+                if (mProxy == null) {
+                    logAndPrint(Log.ERROR, mPw,
+                            "resetUsbPort: 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, "resetUsbPort: operationID exists ! opID:"
+                            + operationID + " key:" + key);
+                }
+                try {
+                    sCallbacks.put(operationID, callback);
+                    mProxy.resetUsbPort(portName, operationID);
+                } catch (RemoteException e) {
+                    logAndPrintException(mPw,
+                            "resetUsbPort: Failed to resetUsbPort: portID="
+                            + portName + "opId:" + operationID, e);
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                    sCallbacks.remove(key);
+                    return false;
+                }
+            } catch (RemoteException e) {
+                logAndPrintException(mPw,
+                        "resetUsbPort: Failed to call onOperationComplete portID="
+                        + portName + "opID:" + operationID, e);
+                sCallbacks.remove(key);
+                return false;
+            }
+            return true;
+        }
+    }
+
+    @Override
     public boolean enableUsbData(String portName, boolean enable, long operationID,
             IUsbOperationInternal callback) {
         Objects.requireNonNull(portName);
@@ -605,5 +649,27 @@
                         e);
             }
         }
+
+        @Override
+        public void notifyResetUsbPortStatus(String portName, int retval,
+                long operationID) {
+            if (retval == Status.SUCCESS) {
+                UsbPortManager.logAndPrint(Log.INFO, mPw, "notifyResetUsbPortStatus:"
+                        + portName + ": opID:" + operationID);
+            } 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,
+                        "notifyResetUsbPortStatus: Failed to call onOperationComplete",
+                        e);
+            }
+        }
     }
 }
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
index abfdd6f..4fa296d 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
@@ -193,4 +193,18 @@
      */
     public void enableLimitPowerTransfer(String portName, boolean limit, long transactionId,
             IUsbOperationInternal callback);
+
+    /**
+     * Invoked to reset UsbData on the specified port.
+     *
+     * @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.
+     * @param callback callback object to be invoked to invoke the status of the operation upon
+     *                 completion.
+     */
+    public boolean resetUsbPort(String portName, long transactionId,
+            IUsbOperationInternal callback);
 }
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
index c1d7635..64e8adc 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
@@ -318,6 +318,19 @@
     }
 
     @Override
+    public boolean resetUsbPort(String portName, long transactionId,
+            IUsbOperationInternal callback) {
+        try {
+            callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED);
+        } catch (RemoteException e) {
+            logAndPrintException(mPw, "Failed to call onOperationComplete. opID:"
+                    + transactionId
+                    + " portId:" + portName, e);
+        }
+        return false;
+    }
+
+    @Override
     public boolean enableUsbData(String portName, boolean enable, long transactionId,
             IUsbOperationInternal callback) {
         int halVersion;
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index e332d3f..ec18c6a 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -380,7 +380,45 @@
      */
     public static final int CAPABILITY_CALL_COMPOSER = 0x8000;
 
-    /* NEXT CAPABILITY: 0x10000 */
+    /**
+     * Flag indicating that this {@link PhoneAccount} provides SIM-based voice calls, potentially as
+     * an over-the-top solution such as wi-fi calling.
+     *
+     * <p>Similar to {@link #CAPABILITY_SUPPORTS_VIDEO_CALLING}, this capability indicates this
+     * {@link PhoneAccount} has the ability to make voice calls (but not necessarily at this time).
+     * Whether this {@link PhoneAccount} can make a voice call is ultimately controlled by {@link
+     * #CAPABILITY_VOICE_CALLING_AVAILABLE}, which indicates whether this {@link PhoneAccount} is
+     * currently capable of making a voice call. Consider a case where, for example, a {@link
+     * PhoneAccount} supports making voice calls (e.g. {@link
+     * #CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS}), but a current lack of network connectivity
+     * prevents voice calls from being made (e.g. {@link #CAPABILITY_VOICE_CALLING_AVAILABLE}).
+     *
+     * <p>In order to declare this capability, this {@link PhoneAccount} must also declare {@link
+     * #CAPABILITY_SIM_SUBSCRIPTION} or {@link #CAPABILITY_CONNECTION_MANAGER} and satisfy the
+     * associated requirements.
+     *
+     * @see #CAPABILITY_VOICE_CALLING_AVAILABLE
+     * @see #getCapabilities
+     */
+    public static final int CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS = 0x10000;
+
+    /**
+     * Flag indicating that this {@link PhoneAccount} is <em>currently</em> able to place SIM-based
+     * voice calls, similar to {@link #CAPABILITY_VIDEO_CALLING}.
+     *
+     * <p>See also {@link #CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS}, which indicates whether
+     * the {@code PhoneAccount} supports placing SIM-based voice calls or not.
+     *
+     * <p>In order to declare this capability, this {@link PhoneAccount} must also declare {@link
+     * #CAPABILITY_SIM_SUBSCRIPTION} or {@link #CAPABILITY_CONNECTION_MANAGER} and satisfy the
+     * associated requirements.
+     *
+     * @see #CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS
+     * @see #getCapabilities
+     */
+    public static final int CAPABILITY_VOICE_CALLING_AVAILABLE = 0x20000;
+
+    /* NEXT CAPABILITY: 0x40000 */
 
     /**
      * URI scheme for telephone number URIs.
@@ -1102,14 +1140,20 @@
             sb.append("SimSub ");
         }
         if (hasCapabilities(CAPABILITY_RTT)) {
-            sb.append("Rtt");
+            sb.append("Rtt ");
         }
         if (hasCapabilities(CAPABILITY_ADHOC_CONFERENCE_CALLING)) {
-            sb.append("AdhocConf");
+            sb.append("AdhocConf ");
         }
         if (hasCapabilities(CAPABILITY_CALL_COMPOSER)) {
             sb.append("CallComposer ");
         }
+        if (hasCapabilities(CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS)) {
+            sb.append("SuppVoice ");
+        }
+        if (hasCapabilities(CAPABILITY_VOICE_CALLING_AVAILABLE)) {
+            sb.append("Voice ");
+        }
         return sb.toString();
     }
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d5c846d..7ba4b11 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5186,6 +5186,20 @@
         public static final int E911_RTP_INACTIVITY_ON_CONNECTED = 4;
 
         /**
+         * List of different RAT technologies on which IMS
+         * is supported.
+         *
+         * <p>Possible values are,
+         * {@link AccessNetworkConstants.AccessNetworkType#NGRAN}
+         * {@link AccessNetworkConstants.AccessNetworkType#EUTRAN}
+         * {@link AccessNetworkConstants.AccessNetworkType#IWLAN}
+         * {@link AccessNetworkConstants.AccessNetworkType#UTRAN}
+         * {@link AccessNetworkConstants.AccessNetworkType#GERAN}
+         */
+        public static final String KEY_SUPPORTED_RATS_INT_ARRAY =
+                KEY_PREFIX + "supported_rats_int_array";
+
+        /**
          * 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
@@ -5375,6 +5389,13 @@
                     new int[] {
                         GEOLOCATION_PIDF_FOR_EMERGENCY_ON_WIFI
                     });
+            defaults.putIntArray(
+                    KEY_SUPPORTED_RATS_INT_ARRAY,
+                    new int[] {
+                        AccessNetworkType.NGRAN,
+                        AccessNetworkType.EUTRAN,
+                        AccessNetworkType.IWLAN
+                    });
 
             defaults.putString(KEY_PHONE_CONTEXT_DOMAIN_NAME_STRING, "");
             defaults.putString(KEY_IMS_USER_AGENT_STRING,
@@ -5499,6 +5520,44 @@
         public static final String KEY_SESSION_REFRESHER_TYPE_INT =
                 KEY_PREFIX + "session_refresher_type_int";
 
+
+        /** @hide */
+        @IntDef({
+            SESSION_PRIVACY_TYPE_HEADER,
+            SESSION_PRIVACY_TYPE_NONE,
+            SESSION_PRIVACY_TYPE_ID
+        })
+
+        public @interface SessionPrivacyType {}
+
+        /**
+         * Session privacy type is HEADER as per RFC 3323 Section 4.2.
+         */
+        public static final int SESSION_PRIVACY_TYPE_HEADER = 0;
+
+        /**
+         * Session privacy type is NONE as per RFC 3323 Section 4.2.
+         */
+        public static final int SESSION_PRIVACY_TYPE_NONE = 1;
+
+        /**
+         * Session privacy type is ID as per RFC 3325 Section 9.3.
+         */
+        public static final int SESSION_PRIVACY_TYPE_ID = 2;
+
+        /**
+         * Specify the session privacy type.
+         *
+         * <p>Reference: RFC 3323 Section 4.2, RFC 3325 Section 9.3.
+         *
+         * <p>Possible values are,
+         * {@link #SESSION_PRIVACY_TYPE_HEADER},
+         * {@link #SESSION_PRIVACY_TYPE_NONE},
+         * {@link #SESSION_PRIVACY_TYPE_ID}
+         */
+        public static final String KEY_SESSION_PRIVACY_TYPE_INT =
+                KEY_PREFIX + "session_privacy_type_int";
+
         /**
          * Flag indicating whether PRACK must be enabled for all 18x messages.
          *
@@ -6320,6 +6379,7 @@
             defaults.putBoolean(KEY_VOICE_ON_DEFAULT_BEARER_SUPPORTED_BOOL, false);
 
             defaults.putInt(KEY_SESSION_REFRESHER_TYPE_INT, SESSION_REFRESHER_TYPE_UNKNOWN);
+            defaults.putInt(KEY_SESSION_PRIVACY_TYPE_INT, SESSION_PRIVACY_TYPE_HEADER);
             defaults.putInt(KEY_SESSION_REFRESH_METHOD_INT,
                             SESSION_REFRESH_METHOD_UPDATE_PREFERRED);
             defaults.putInt(KEY_CONFERENCE_SUBSCRIBE_TYPE_INT,
@@ -6567,22 +6627,6 @@
                 KEY_PREFIX + "text_rr_bandwidth_bps_int";
 
         /**
-         * List of various reasons for RTT call to end due to
-         * media inactivity.
-         *
-         * <p>Possible values are,
-         * <UL>
-         *     <LI>{@link Ims#RTCP_INACTIVITY_ON_HOLD}</LI>
-         *     <LI>{@link Ims#RTCP_INACTIVITY_ON_CONNECTED}</LI>
-         *     <LI>{@link Ims#RTP_INACTIVITY_ON_CONNECTED}</LI>
-         *     <LI>{@link Ims#E911_RTCP_INACTIVITY_ON_CONNECTED}</LI>
-         *     <LI>{@link Ims#E911_RTP_INACTIVITY_ON_CONNECTED}</LI>
-         * </UL>
-         */
-        public static final String KEY_TEXT_INACTIVITY_CALL_END_REASONS_INT_ARRAY =
-                KEY_PREFIX + "text_inactivity_call_end_reasons_int_array";
-
-        /**
          * Specifies the Text Codec capability.
          *
          * <p>Possible keys in this bundle are,
@@ -6617,13 +6661,6 @@
             defaults.putInt(KEY_TEXT_RS_BANDWIDTH_BPS_INT, 100);
             defaults.putInt(KEY_TEXT_RR_BANDWIDTH_BPS_INT, 300);
 
-            defaults.putIntArray(
-                    KEY_TEXT_INACTIVITY_CALL_END_REASONS_INT_ARRAY,
-                    new int[] {
-                        Ims.RTCP_INACTIVITY_ON_CONNECTED,
-                        Ims.RTP_INACTIVITY_ON_CONNECTED
-                    });
-
             PersistableBundle text_codec_capability_payload_types = new PersistableBundle();
 
             text_codec_capability_payload_types.putInt(
@@ -6707,6 +6744,13 @@
         public static final String KEY_EMERGENCY_REGISTRATION_TIMER_MILLIS_INT =
                 KEY_PREFIX + "emergency_registration_timer_millis_int";
 
+        /**
+         * This setting will be specify the wait time for refreshing
+         * geolocation information before dialing emergency call.
+         */
+        public static final String KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT =
+                KEY_PREFIX + "refresh_geolocation_timeout_millis_int";
+
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
             defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false);
@@ -6721,6 +6765,7 @@
                     });
 
             defaults.putInt(KEY_EMERGENCY_REGISTRATION_TIMER_MILLIS_INT, 20000);
+            defaults.putInt(KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT, 5000);
 
             return defaults;
         }
@@ -7035,6 +7080,17 @@
                 KEY_PREFIX + "ut_requires_ims_registration_bool";
 
         /**
+         * Flag that controls whether XCAP over UT is supported
+         * when on roaming network.
+         *
+         * <p>If {@code true}: XCAP over UT is supported when on
+         * roaming network.
+         * {@code false} otherwise.
+         */
+        public static final String KEY_UT_SUPPORTED_WHEN_ROAMING_BOOL =
+                KEY_PREFIX + "ut_supported_when_roaming_bool";
+
+        /**
          * Flag that controls whether Circuit Switched Fallback (CSFB)
          * option is available when XCAP over UT fails.
          *
@@ -7385,6 +7441,7 @@
             defaults.putBoolean(KEY_USE_CSFB_ON_XCAP_OVER_UT_FAILURE_BOOL, true);
             defaults.putBoolean(KEY_UT_SUPPORTED_WHEN_PS_DATA_OFF_BOOL, true);
             defaults.putBoolean(KEY_NETWORK_INITIATED_USSD_OVER_IMS_SUPPORTED_BOOL, true);
+            defaults.putBoolean(KEY_UT_SUPPORTED_WHEN_ROAMING_BOOL, true);
 
             defaults.putInt(KEY_UT_IPTYPE_HOME_INT, ApnSetting.PROTOCOL_IPV4V6);
             defaults.putInt(KEY_UT_IPTYPE_ROAMING_INT, ApnSetting.PROTOCOL_IPV4V6);
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 56bf303..3a3b363 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1083,6 +1083,13 @@
      */
     public static final int REQUEST_NOT_SUPPORTED = 0x1000A;
 
+    /**
+     * An internal setup data error initiated by telephony that no retry should be performed.
+     *
+     * @hide
+     */
+    public static final int NO_RETRY_FAILURE = 0x1000B;
+
     private static final Map<Integer, String> sFailCauseMap;
     static {
         sFailCauseMap = new HashMap<>();
@@ -1515,6 +1522,8 @@
         sFailCauseMap.put(DUPLICATE_CID, "DUPLICATE_CID");
         sFailCauseMap.put(NO_DEFAULT_DATA, "NO_DEFAULT_DATA");
         sFailCauseMap.put(SERVICE_TEMPORARILY_UNAVAILABLE, "SERVICE_TEMPORARILY_UNAVAILABLE");
+        sFailCauseMap.put(REQUEST_NOT_SUPPORTED, "REQUEST_NOT_SUPPORTED");
+        sFailCauseMap.put(NO_RETRY_FAILURE, "NO_RETRY_FAILURE");
     }
 
     private DataFailCause() {
@@ -1565,6 +1574,7 @@
     }
 
     /** @hide */
+    // TODO: Migrated to DataConfigManager
     public static boolean isPermanentFailure(@NonNull Context context,
                                              @DataFailureCause int failCause,
                                              int subId) {
@@ -1621,6 +1631,7 @@
                     };
                 }
 
+                permanentFailureSet.add(NO_RETRY_FAILURE);
                 sPermanentFailureCache.put(subId, permanentFailureSet);
             }
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 536517c..baccb26 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -150,7 +150,6 @@
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
-
 /**
  * Provides access to information about the telephony services on
  * the device. Applications can use the methods in this class to
@@ -11966,7 +11965,11 @@
 
         private final int mErrorCode;
 
-        /** @hide */
+        /**
+         * An exception with ModemActivityInfo specific error codes.
+         *
+         * @param errorCode a ModemActivityInfoError code.
+         */
         public ModemActivityInfoException(@ModemActivityInfoError int errorCode) {
             mErrorCode = errorCode;
         }
@@ -16805,4 +16808,37 @@
         }
         mTelephonyRegistryMgr.removeCarrierPrivilegesListener(listener);
     }
+
+    /**
+     * Sets a voice service state override from telecom based on the current {@link PhoneAccount}s
+     * registered. See {@link PhoneAccount#CAPABILITY_VOICE_CALLING_AVAILABLE}.
+     *
+     * <p>Currently, this API is only called to indicate over-the-top voice calling capability of
+     * the SIM call manager, which will get merged into {@link ServiceState#getState} and propagated
+     * to interested callers via {@link #getServiceState} and {@link
+     * TelephonyCallback.ServiceStateListener}.
+     *
+     * <p>If callers are truly interested in the actual device <-> tower connection status and not
+     * an overall "device can make voice calls" boolean, they can use {@link
+     * ServiceState#getNetworkRegistrationInfo} to check CS registration state.
+     *
+     * <p>TODO(b/215240050) In the future, this API will be removed and replaced with a new superset
+     * API to disentangle the "true" {@link ServiceState} meaning of "this is the connection status
+     * to the tower" from IMS registration state and over-the-top voice calling capabilities.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE)
+    public void setVoiceServiceStateOverride(boolean hasService) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) {
+                throw new IllegalStateException("Telephony service is null");
+            }
+            telephony.setVoiceServiceStateOverride(getSubId(), hasService, getOpPackageName());
+        } catch (RemoteException ex) {
+            ex.rethrowAsRuntimeException();
+        }
+    }
 }
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/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 409c838..2470887 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -114,6 +114,7 @@
     public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 54;
     public static final int EVENT_SIM_STATE_UPDATED = BASE + 55;
     public static final int EVENT_APN_UNTHROTTLED = BASE + 56;
+    public static final int EVENT_TRAFFIC_DESCRIPTORS_UPDATED = BASE + 57;
 
     /***** Constants *****/
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index bce7a24..dc96b35 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2543,4 +2543,10 @@
      * registration technology specified, false if it is not required.
      */
     boolean isRcsProvisioningRequiredForCapability(int subId, int capability, int tech);
+
+    /**
+     * Sets a voice service state from telecom based on the current PhoneAccounts registered. See
+     * PhoneAccount#CAPABILITY_VOICE_CALLING_AVAILABLE.
+     */
+    void setVoiceServiceStateOverride(int subId, boolean hasService, String callingPackage);
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 8de38f6..294a220 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -27,6 +27,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -91,6 +92,13 @@
         super.statusBarLayerRotatesScales()
     }
 
+    @FlakyTest(bugId = 214452854)
+    @Test
+    fun statusBarLayerRotatesScales_shellTransit() {
+        assumeTrue(isShellTransitionsEnabled)
+        super.statusBarLayerRotatesScales()
+    }
+
     /** {@inheritDoc} */
     @Presubmit
     @Test
@@ -100,6 +108,13 @@
         super.launcherLayerReplacesApp()
     }
 
+    @FlakyTest(bugId = 214452854)
+    @Test
+    fun launcherLayerReplacesApp_shellTransit() {
+        assumeTrue(isShellTransitionsEnabled)
+        super.launcherLayerReplacesApp()
+    }
+
     /** {@inheritDoc} */
     @Presubmit
     @Test
@@ -109,6 +124,13 @@
         super.entireScreenCovered()
     }
 
+    @FlakyTest(bugId = 214452854)
+    @Test
+    fun entireScreenCovered_shellTransit() {
+        assumeTrue(isShellTransitionsEnabled)
+        super.entireScreenCovered()
+    }
+
     companion object {
         /**
          * Creates the test configurations.
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 d960e94..519bd56 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
@@ -26,6 +26,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -90,6 +91,13 @@
         super.statusBarLayerRotatesScales()
     }
 
+    @FlakyTest(bugId = 214452854)
+    @Test
+    fun statusBarLayerRotatesScales_shellTransit() {
+        assumeTrue(isShellTransitionsEnabled)
+        super.statusBarLayerRotatesScales()
+    }
+
     /** {@inheritDoc} */
     @Presubmit
     @Test
@@ -99,6 +107,13 @@
         super.launcherLayerReplacesApp()
     }
 
+    @FlakyTest(bugId = 214452854)
+    @Test
+    fun launcherLayerReplacesApp_shellTransit() {
+        assumeTrue(isShellTransitionsEnabled)
+        super.launcherLayerReplacesApp()
+    }
+
     /** {@inheritDoc} */
     @Presubmit
     @Test
@@ -108,6 +123,13 @@
         super.entireScreenCovered()
     }
 
+    @FlakyTest(bugId = 214452854)
+    @Test
+    fun entireScreenCovered_shellTransit() {
+        assumeTrue(isShellTransitionsEnabled)
+        super.entireScreenCovered()
+    }
+
     /** {@inheritDoc} */
     @Presubmit
     @Test
@@ -117,6 +139,13 @@
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
     }
 
+    @FlakyTest(bugId = 214452854)
+    @Test
+    fun visibleLayersShownMoreThanOneConsecutiveEntry_shellTransit() {
+        assumeTrue(isShellTransitionsEnabled)
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 5450610..d38a719 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -25,9 +25,7 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -83,11 +81,7 @@
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /** {@inheritDoc} */
     @FlakyTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index aac4812..7443f0b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -28,9 +28,7 @@
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -100,47 +98,12 @@
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun entireScreenCovered() {
-        // This test doesn't work in shell transitions because of b/204570898
-        assumeFalse(isShellTransitionsEnabled)
-        super.entireScreenCovered()
-    }
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appWindowReplacesLauncherAsTopWindow() {
-        // This test doesn't work in shell transitions because of b/206085788
-        assumeFalse(isShellTransitionsEnabled)
-        super.appWindowReplacesLauncherAsTopWindow()
-    }
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appLayerReplacesLauncher() {
-        // This test doesn't work in shell transitions because of b/206085788
-        assumeFalse(isShellTransitionsEnabled)
-        super.appLayerReplacesLauncher()
-    }
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        // This test doesn't work in shell transitions because of b/206090480
-        assumeFalse(isShellTransitionsEnabled)
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-    }
+    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
 
     /** {@inheritDoc} */
     @FlakyTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index a85dcc5..12177ed 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -28,11 +28,9 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.google.common.truth.Truth
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -181,18 +179,12 @@
     /** {@inheritDoc} */
     @FlakyTest(bugId = 202936526)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
     fun statusBarLayerPositionAtEnd() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayersEnd {
             val display = this.entry.displays.minByOrNull { it.id }
                 ?: error("There is no display!")
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index cd209b2..304e516 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -25,8 +25,6 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -87,11 +85,7 @@
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /** {@inheritDoc} */
     @Presubmit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 01fce05..42941c2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -40,6 +40,7 @@
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.server.wm.traces.common.Rect
 import org.junit.Assume
+import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -62,7 +63,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group1
-class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestParameter) {
+open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
 
     private val testApp1 = SimpleAppHelper(instrumentation)
@@ -85,9 +86,9 @@
                         ?: error("Display not found")
 
                     // Swipe right from bottom to quick switch back
-                    // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle
-                    // as to not accidentally trigger a swipe back or forward action which would result
-                    // in the same behavior but not testing quick swap.
+                    // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the
+                    // middle as to not accidentally trigger a swipe back or forward action which
+                    // would result in the same behavior but not testing quick swap.
                     device.swipe(
                             startDisplayBounds.right / 3,
                             startDisplayBounds.bottom,
@@ -126,15 +127,19 @@
         }
     }
 
+    @Before
+    open fun setup() {
+        // This test doesn't work in shell transitions because of b/213867585
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
     /**
      * Checks that the transition starts with [testApp1]'s windows filling/covering exactly the
      * entirety of the display.
      */
     @Presubmit
     @Test
-    fun startsWithApp1WindowsCoverFullScreen() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun startsWithApp1WindowsCoverFullScreen() {
         testSpec.assertWmStart {
             this.frameRegion(testApp1.component, FlickerComponentName.LETTERBOX)
                 .coversExactly(startDisplayBounds)
@@ -147,9 +152,7 @@
      */
     @Presubmit
     @Test
-    fun startsWithApp1LayersCoverFullScreen() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun startsWithApp1LayersCoverFullScreen() {
         testSpec.assertLayersStart {
             this.visibleRegion(testApp1.component).coversExactly(startDisplayBounds)
         }
@@ -160,23 +163,19 @@
      */
     @Presubmit
     @Test
-    fun startsWithApp1WindowBeingOnTop() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun startsWithApp1WindowBeingOnTop() {
         testSpec.assertWmStart {
             this.isAppWindowOnTop(testApp1.component)
         }
     }
 
     /**
-     * Checks that [testApp2] windows fill the entire screen (i.e. is "fullscreen") at the end of the
-     * transition once we have fully quick switched from [testApp1] back to the [testApp2].
+     * Checks that [testApp2] windows fill the entire screen (i.e. is "fullscreen") at the end of
+     * the transition once we have fully quick switched from [testApp1] back to the [testApp2].
      */
     @Presubmit
     @Test
-    fun endsWithApp2WindowsCoveringFullScreen() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun endsWithApp2WindowsCoveringFullScreen() {
         testSpec.assertWmEnd {
             this.frameRegion(testApp2.component).coversExactly(startDisplayBounds)
         }
@@ -188,9 +187,7 @@
      */
     @Presubmit
     @Test
-    fun endsWithApp2LayersCoveringFullScreen() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun endsWithApp2LayersCoveringFullScreen() {
         testSpec.assertLayersEnd {
             this.visibleRegion(testApp2.component, FlickerComponentName.LETTERBOX)
                 .coversExactly(startDisplayBounds)
@@ -198,14 +195,12 @@
     }
 
     /**
-     * Checks that [testApp2] is the top window at the end of the transition once we have fully quick
-     * switched from [testApp1] back to the [testApp2].
+     * Checks that [testApp2] is the top window at the end of the transition once we have fully
+     * quick switched from [testApp1] back to the [testApp2].
      */
     @Presubmit
     @Test
-    fun endsWithApp2BeingOnTop() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun endsWithApp2BeingOnTop() {
         testSpec.assertWmEnd {
             this.isAppWindowOnTop(testApp2.component)
         }
@@ -217,9 +212,7 @@
      */
     @Presubmit
     @Test
-    fun app2WindowBecomesAndStaysVisible() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun app2WindowBecomesAndStaysVisible() {
         testSpec.assertWm {
             this.isAppWindowInvisible(testApp2.component)
                     .then()
@@ -235,9 +228,7 @@
      */
     @Presubmit
     @Test
-    fun app2LayerBecomesAndStaysVisible() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun app2LayerBecomesAndStaysVisible() {
         testSpec.assertLayers {
             this.isInvisible(testApp2.component)
                     .then()
@@ -251,9 +242,7 @@
      */
     @Presubmit
     @Test
-    fun app1WindowBecomesAndStaysInvisible() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun app1WindowBecomesAndStaysInvisible() {
         testSpec.assertWm {
             this.isAppWindowVisible(testApp1.component)
                     .then()
@@ -267,9 +256,7 @@
      */
     @Presubmit
     @Test
-    fun app1LayerBecomesAndStaysInvisible() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun app1LayerBecomesAndStaysInvisible() {
         testSpec.assertLayers {
             this.isVisible(testApp1.component)
                     .then()
@@ -284,9 +271,7 @@
      */
     @Presubmit
     @Test
-    fun app2WindowIsVisibleOnceApp1WindowIsInvisible() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun app2WindowIsVisibleOnceApp1WindowIsInvisible() {
         testSpec.assertWm {
             this.isAppWindowVisible(testApp1.component)
                     .then()
@@ -305,9 +290,7 @@
      */
     @Presubmit
     @Test
-    fun app2LayerIsVisibleOnceApp1LayerIsInvisible() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun app2LayerIsVisibleOnceApp1LayerIsInvisible() {
         testSpec.assertLayers {
             this.isVisible(testApp1.component)
                     .then()
@@ -324,9 +307,7 @@
      */
     @Presubmit
     @Test
-    fun navBarWindowIsAlwaysVisible() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun navBarWindowIsAlwaysVisible() {
         testSpec.navBarWindowIsVisible()
     }
 
@@ -335,9 +316,7 @@
      */
     @Presubmit
     @Test
-    fun navBarLayerAlwaysIsVisible() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun navBarLayerAlwaysIsVisible() {
         testSpec.navBarLayerIsVisible()
     }
 
@@ -348,9 +327,7 @@
      */
     @Presubmit
     @Test
-    fun navbarIsAlwaysInRightPosition() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun navbarIsAlwaysInRightPosition() {
         testSpec.navBarLayerRotatesAndScales()
     }
 
@@ -359,9 +336,7 @@
      */
     @Presubmit
     @Test
-    fun statusBarWindowIsAlwaysVisible() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun statusBarWindowIsAlwaysVisible() {
         testSpec.statusBarWindowIsVisible()
     }
 
@@ -370,9 +345,7 @@
      */
     @Presubmit
     @Test
-    fun statusBarLayerIsAlwaysVisible() {
-        // This test doesn't work in shell transitions because of b/209936664
-        Assume.assumeFalse(isShellTransitionsEnabled)
+    open fun statusBarLayerIsAlwaysVisible() {
         testSpec.statusBarLayerIsVisible()
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestShellTransit.kt
new file mode 100644
index 0000000..49b9733
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestShellTransit.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.quickswitch
+
+import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.FlakyTest
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switching back to previous app from last opened app
+ *
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTestShellTransit`
+ *
+ * Actions:
+ *     Launch an app [testApp1]
+ *     Launch another app [testApp2]
+ *     Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
+ *     Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2]
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class QuickSwitchBetweenTwoAppsForwardTestShellTransit(private val testSpec: FlickerTestParameter)
+    : QuickSwitchBetweenTwoAppsForwardTest(testSpec) {
+
+    @Before
+    override fun setup() {
+        // This test class should be removed after b/213867585 is fixed.
+        Assume.assumeTrue(isShellTransitionsEnabled)
+    }
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun startsWithApp1WindowsCoverFullScreen() =
+            super.startsWithApp1WindowsCoverFullScreen()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun startsWithApp1LayersCoverFullScreen() = super.startsWithApp1LayersCoverFullScreen()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun startsWithApp1WindowBeingOnTop() = super.startsWithApp1WindowBeingOnTop()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun endsWithApp2WindowsCoveringFullScreen() =
+            super.endsWithApp2WindowsCoveringFullScreen()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun endsWithApp2LayersCoveringFullScreen() =
+            super.endsWithApp2LayersCoveringFullScreen()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun endsWithApp2BeingOnTop() = super.endsWithApp2BeingOnTop()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun app2WindowBecomesAndStaysVisible() = super.app2WindowBecomesAndStaysVisible()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun app2LayerBecomesAndStaysVisible() = super.app2LayerBecomesAndStaysVisible()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun app1WindowBecomesAndStaysInvisible() = super.app1WindowBecomesAndStaysInvisible()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun app1LayerBecomesAndStaysInvisible() = super.app1LayerBecomesAndStaysInvisible()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun app2WindowIsVisibleOnceApp1WindowIsInvisible() =
+            super.app2WindowIsVisibleOnceApp1WindowIsInvisible()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun app2LayerIsVisibleOnceApp1LayerIsInvisible() =
+            super.app2LayerIsVisibleOnceApp1LayerIsInvisible()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun navBarLayerAlwaysIsVisible() = super.navBarLayerAlwaysIsVisible()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun navbarIsAlwaysInRightPosition() = super.navbarIsAlwaysInRightPosition()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 214452854)
+    @Test
+    override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
+}
\ No newline at end of file
diff --git a/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
index bb0cc97..21700d5 100644
--- a/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
+++ b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
@@ -42,8 +42,10 @@
     private boolean m80211nSupported;
     private boolean m80211acSupported;
     private boolean m80211axSupported;
+    private boolean m80211beSupported;
     private boolean mChannelWidth160MhzSupported;
     private boolean mChannelWidth80p80MhzSupported;
+    private boolean mChannelWidth320MhzSupported;
     private int mMaxNumberTxSpatialStreams;
     private int mMaxNumberRxSpatialStreams;
 
@@ -53,8 +55,10 @@
         m80211nSupported = false;
         m80211acSupported = false;
         m80211axSupported = false;
+        m80211beSupported = false;
         mChannelWidth160MhzSupported = false;
         mChannelWidth80p80MhzSupported = false;
+        mChannelWidth320MhzSupported = false;
         mMaxNumberTxSpatialStreams = 1;
         mMaxNumberRxSpatialStreams = 1;
     }
@@ -76,6 +80,8 @@
                 return m80211acSupported;
             case ScanResult.WIFI_STANDARD_11AX:
                 return m80211axSupported;
+            case ScanResult.WIFI_STANDARD_11BE:
+                return m80211beSupported;
             default:
                 Log.e(TAG, "isWifiStandardSupported called with invalid standard: " + standard);
                 return false;
@@ -100,6 +106,9 @@
             case ScanResult.WIFI_STANDARD_11AX:
                 m80211axSupported = support;
                 break;
+            case ScanResult.WIFI_STANDARD_11BE:
+                m80211beSupported = support;
+                break;
             default:
                 Log.e(TAG, "setWifiStandardSupport called with invalid standard: " + standard);
         }
@@ -117,13 +126,16 @@
             case ScanResult.CHANNEL_WIDTH_20MHZ:
                 return true;
             case ScanResult.CHANNEL_WIDTH_40MHZ:
-                return (m80211nSupported || m80211acSupported || m80211axSupported);
+                return (m80211nSupported || m80211acSupported || m80211axSupported
+                    || m80211beSupported);
             case ScanResult.CHANNEL_WIDTH_80MHZ:
-                return (m80211acSupported || m80211axSupported);
+                return (m80211acSupported || m80211axSupported || m80211beSupported);
             case ScanResult.CHANNEL_WIDTH_160MHZ:
                 return mChannelWidth160MhzSupported;
             case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
                 return mChannelWidth80p80MhzSupported;
+            case ScanResult.CHANNEL_WIDTH_320MHZ:
+                return mChannelWidth320MhzSupported;
             default:
                 Log.e(TAG, "isChannelWidthSupported called with invalid channel width: " + chWidth);
         }
@@ -133,8 +145,9 @@
     /**
      * Set support for channel bandwidth
      *
-     * @param chWidth valid values are {@link ScanResult#CHANNEL_WIDTH_160MHZ} and
-     *        {@link ScanResult#CHANNEL_WIDTH_80MHZ_PLUS_MHZ}
+     * @param chWidth valid values are {@link ScanResult#CHANNEL_WIDTH_160MHZ},
+     *        {@link ScanResult#CHANNEL_WIDTH_80MHZ_PLUS_MHZ} and
+     *        {@link ScanResult#CHANNEL_WIDTH_320MHZ}
      * @param support {@code true} if supported, {@code false} otherwise.
      *
      * @hide
@@ -147,6 +160,9 @@
             case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
                 mChannelWidth80p80MhzSupported = support;
                 break;
+            case ScanResult.CHANNEL_WIDTH_320MHZ:
+                mChannelWidth320MhzSupported = support;
+                break;
             default:
                 Log.e(TAG, "setChannelWidthSupported called with Invalid channel width: "
                         + chWidth);
@@ -205,8 +221,10 @@
         return m80211nSupported == capa.m80211nSupported
                 && m80211acSupported == capa.m80211acSupported
                 && m80211axSupported == capa.m80211axSupported
+                && m80211beSupported == capa.m80211beSupported
                 && mChannelWidth160MhzSupported == capa.mChannelWidth160MhzSupported
                 && mChannelWidth80p80MhzSupported == capa.mChannelWidth80p80MhzSupported
+                && mChannelWidth320MhzSupported == capa.mChannelWidth320MhzSupported
                 && mMaxNumberTxSpatialStreams == capa.mMaxNumberTxSpatialStreams
                 && mMaxNumberRxSpatialStreams == capa.mMaxNumberRxSpatialStreams;
     }
@@ -215,8 +233,9 @@
     @Override
     public int hashCode() {
         return Objects.hash(m80211nSupported, m80211acSupported, m80211axSupported,
-                mChannelWidth160MhzSupported, mChannelWidth80p80MhzSupported,
-                mMaxNumberTxSpatialStreams, mMaxNumberRxSpatialStreams);
+                m80211beSupported, mChannelWidth160MhzSupported, mChannelWidth80p80MhzSupported,
+                mChannelWidth320MhzSupported, mMaxNumberTxSpatialStreams,
+                mMaxNumberRxSpatialStreams);
     }
 
     /** implement Parcelable interface */
@@ -234,8 +253,10 @@
         out.writeBoolean(m80211nSupported);
         out.writeBoolean(m80211acSupported);
         out.writeBoolean(m80211axSupported);
+        out.writeBoolean(m80211beSupported);
         out.writeBoolean(mChannelWidth160MhzSupported);
         out.writeBoolean(mChannelWidth80p80MhzSupported);
+        out.writeBoolean(mChannelWidth320MhzSupported);
         out.writeInt(mMaxNumberTxSpatialStreams);
         out.writeInt(mMaxNumberRxSpatialStreams);
     }
@@ -246,10 +267,13 @@
         sb.append("m80211nSupported:").append(m80211nSupported ? "Yes" : "No");
         sb.append("m80211acSupported:").append(m80211acSupported ? "Yes" : "No");
         sb.append("m80211axSupported:").append(m80211axSupported ? "Yes" : "No");
+        sb.append("m80211beSupported:").append(m80211beSupported ? "Yes" : "No");
         sb.append("mChannelWidth160MhzSupported: ")
                 .append(mChannelWidth160MhzSupported ? "Yes" : "No");
         sb.append("mChannelWidth80p80MhzSupported: ")
                 .append(mChannelWidth80p80MhzSupported ? "Yes" : "No");
+        sb.append("mChannelWidth320MhzSupported: ")
+                .append(mChannelWidth320MhzSupported ? "Yes" : "No");
         sb.append("mMaxNumberTxSpatialStreams: ").append(mMaxNumberTxSpatialStreams);
         sb.append("mMaxNumberRxSpatialStreams: ").append(mMaxNumberRxSpatialStreams);
 
@@ -268,8 +292,10 @@
             capabilities.m80211nSupported = in.readBoolean();
             capabilities.m80211acSupported = in.readBoolean();
             capabilities.m80211axSupported = in.readBoolean();
+            capabilities.m80211beSupported = in.readBoolean();
             capabilities.mChannelWidth160MhzSupported = in.readBoolean();
             capabilities.mChannelWidth80p80MhzSupported = in.readBoolean();
+            capabilities.mChannelWidth320MhzSupported = in.readBoolean();
             capabilities.mMaxNumberTxSpatialStreams = in.readInt();
             capabilities.mMaxNumberRxSpatialStreams = in.readInt();
             return capabilities;
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 459696e..d3eb8e0 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -493,6 +493,8 @@
                     return SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
                 case IApInterfaceEventCallback.BANDWIDTH_160:
                     return SoftApInfo.CHANNEL_WIDTH_160MHZ;
+                case IApInterfaceEventCallback.BANDWIDTH_320:
+                    return SoftApInfo.CHANNEL_WIDTH_320MHZ;
                 default:
                     return SoftApInfo.CHANNEL_WIDTH_INVALID;
             }