Merge "Update TaskLaunchParamsModifier.java to use DA instead DC" into sc-dev
diff --git a/Android.bp b/Android.bp
index 20ca1b7..7099291 100644
--- a/Android.bp
+++ b/Android.bp
@@ -584,6 +584,7 @@
         "android.security.apc-java",
         "android.security.authorization-java",
         "android.security.usermanager-java",
+        "android.security.vpnprofilestore-java",
         "android.system.keystore2-V1-java",
         "android.system.suspend.control.internal-java",
         "cameraprotosnano",
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
index e83c64c..5a45961 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
@@ -44,7 +44,7 @@
 @RunWith(AndroidJUnit4.class)
 public class TypefaceCreatePerfTest {
     // A font file name in asset directory.
-    private static final String TEST_FONT_NAME = "DancingScript-Regular.ttf";
+    private static final String TEST_FONT_NAME = "DancingScript.ttf";
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
index df690d0..b1b733a 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
@@ -108,6 +108,8 @@
      * @hide
      */
     public static final int REASON_DENIED = -1;
+
+    /* Reason code range 0-9 are reserved for default reasons */
     /**
      * The default reason code if reason is unknown.
      */
@@ -116,6 +118,8 @@
      * Use REASON_OTHER if there is no better choice.
      */
     public static final int REASON_OTHER = 1;
+
+    /* Reason code range 10-49 are reserved for BG-FGS-launch allowed proc states */
     /** @hide */
     public static final int REASON_PROC_STATE_PERSISTENT = 10;
     /** @hide */
@@ -128,6 +132,8 @@
     public static final int REASON_PROC_STATE_FGS = 14;
     /** @hide */
     public static final int REASON_PROC_STATE_BFGS = 15;
+
+    /* Reason code range 50-99 are reserved for BG-FGS-launch allowed reasons */
     /** @hide */
     public static final int REASON_UID_VISIBLE = 50;
     /** @hide */
@@ -166,114 +172,126 @@
     public static final int REASON_EXEMPTED_PACKAGE = 64;
     /** @hide */
     public static final int REASON_ALLOWLISTED_PACKAGE  = 65;
-    /**
-     * If it's because of a role,
-     * @hide
-     */
+    /** @hide */
     public static final int REASON_APPOP = 66;
 
     /* BG-FGS-launch is allowed by temp-allowlist or system-allowlist.
-    Reason code for temp and system allowlist starts here.
-    */
+       Reason code for temp and system allowlist starts here.
+       Reason code range 100-199 are reserved for public reasons. */
+    /**
+     * Set temp-allowlist for location geofence purpose.
+     */
     public static final int REASON_GEOFENCING = 100;
+    /**
+     * Set temp-allowlist for server push messaging.
+     */
     public static final int REASON_PUSH_MESSAGING = 101;
-    public static final int REASON_ACTIVITY_RECOGNITION = 102;
+    /**
+     * Set temp-allowlist for server push messaging over the quota.
+     */
+    public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102;
+    /**
+     * Set temp-allowlist for activity recognition.
+     */
+    public static final int REASON_ACTIVITY_RECOGNITION = 103;
 
+    /* Reason code range 200-299 are reserved for broadcast actions */
     /**
      * Broadcast ACTION_BOOT_COMPLETED.
      * @hide
      */
-    public static final int REASON_BOOT_COMPLETED = 103;
+    public static final int REASON_BOOT_COMPLETED = 200;
     /**
      * Broadcast ACTION_PRE_BOOT_COMPLETED.
      * @hide
      */
-    public static final int REASON_PRE_BOOT_COMPLETED = 104;
-
+    public static final int REASON_PRE_BOOT_COMPLETED = 201;
     /**
      * Broadcast ACTION_LOCKED_BOOT_COMPLETED.
      * @hide
      */
-    public static final int REASON_LOCKED_BOOT_COMPLETED = 105;
+    public static final int REASON_LOCKED_BOOT_COMPLETED = 202;
+
+    /* Reason code range 300-399 are reserved for other internal reasons */
     /**
      * Device idle system allowlist, including EXCEPT-IDLE
      * @hide
      */
-    public static final int REASON_SYSTEM_ALLOW_LISTED  = 106;
+    public static final int REASON_SYSTEM_ALLOW_LISTED  = 300;
     /** @hide */
-    public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 107;
+    public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 301;
     /**
      * AlarmManagerService.
      * @hide
      */
-    public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 108;
+    public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 302;
     /**
      * ActiveServices.
      * @hide
      */
-    public static final int REASON_SERVICE_LAUNCH = 109;
+    public static final int REASON_SERVICE_LAUNCH = 303;
     /**
      * KeyChainSystemService.
      * @hide
      */
-    public static final int REASON_KEY_CHAIN = 110;
+    public static final int REASON_KEY_CHAIN = 304;
     /**
      * PackageManagerService.
      * @hide
      */
-    public static final int REASON_PACKAGE_VERIFIER = 111;
+    public static final int REASON_PACKAGE_VERIFIER = 305;
     /**
      * SyncManager.
      * @hide
      */
-    public static final int REASON_SYNC_MANAGER = 112;
+    public static final int REASON_SYNC_MANAGER = 306;
     /**
      * DomainVerificationProxyV1.
      * @hide
      */
-    public static final int REASON_DOMAIN_VERIFICATION_V1 = 113;
+    public static final int REASON_DOMAIN_VERIFICATION_V1 = 307;
     /**
      * DomainVerificationProxyV2.
      * @hide
      */
-    public static final int REASON_DOMAIN_VERIFICATION_V2 = 114;
+    public static final int REASON_DOMAIN_VERIFICATION_V2 = 308;
     /** @hide */
-    public static final int REASON_VPN = 115;
+    public static final int REASON_VPN = 309;
     /**
      * NotificationManagerService.
      * @hide
      */
-    public static final int REASON_NOTIFICATION_SERVICE = 116;
+    public static final int REASON_NOTIFICATION_SERVICE = 310;
     /**
      * Broadcast ACTION_MY_PACKAGE_REPLACED.
      * @hide
      */
-    public static final int REASON_PACKAGE_REPLACED = 117;
+    public static final int REASON_PACKAGE_REPLACED = 311;
     /**
      * LocationProviderManager.
      * @hide
      */
-    public static final int REASON_LOCATION_PROVIDER = 118;
+    public static final int REASON_LOCATION_PROVIDER = 312;
     /**
      * MediaButtonReceiver.
      * @hide
      */
-    public static final int REASON_MEDIA_BUTTON = 119;
+    public static final int REASON_MEDIA_BUTTON = 313;
     /**
      * InboundSmsHandler.
      * @hide
      */
-    public static final int REASON_EVENT_SMS = 120;
+    public static final int REASON_EVENT_SMS = 314;
     /**
      * InboundSmsHandler.
      * @hide
      */
-    public static final int REASON_EVENT_MMS = 121;
+    public static final int REASON_EVENT_MMS = 315;
     /**
      * Shell app.
      * @hide
      */
-    public static final int REASON_SHELL = 122;
+    public static final int REASON_SHELL = 316;
 
     /**
      * The list of BG-FGS-Launch and temp-allowlist reason code.
@@ -310,6 +328,7 @@
             // temp and system allowlist reasons.
             REASON_GEOFENCING,
             REASON_PUSH_MESSAGING,
+            REASON_PUSH_MESSAGING_OVER_QUOTA,
             REASON_ACTIVITY_RECOGNITION,
             REASON_BOOT_COMPLETED,
             REASON_PRE_BOOT_COMPLETED,
@@ -589,6 +608,8 @@
                 return "GEOFENCING";
             case REASON_PUSH_MESSAGING:
                 return "PUSH_MESSAGING";
+            case REASON_PUSH_MESSAGING_OVER_QUOTA:
+                return "PUSH_MESSAGING_OVER_QUOTA";
             case REASON_ACTIVITY_RECOGNITION:
                 return "ACTIVITY_RECOGNITION";
             case REASON_BOOT_COMPLETED:
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index e8e2c27..0308d68 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -73,20 +73,48 @@
             CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
     private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000;
 
-    // Try to give higher priority types lower values.
+    /**
+     * Set of possible execution types that a job can have. The actual type(s) of a job are based
+     * on the {@link JobStatus#lastEvaluatedPriority}, which is typically evaluated right before
+     * execution (when we're trying to determine which jobs to run next) and won't change after the
+     * job has started executing.
+     *
+     * Try to give higher priority types lower values.
+     *
+     * @see #getJobWorkTypes(JobStatus)
+     */
+
+    /** Job shouldn't run or qualify as any other work type. */
     static final int WORK_TYPE_NONE = 0;
+    /** The job is for an app in the TOP state for a currently active user. */
     static final int WORK_TYPE_TOP = 1 << 0;
-    static final int WORK_TYPE_EJ = 1 << 1;
-    static final int WORK_TYPE_BG = 1 << 2;
-    static final int WORK_TYPE_BGUSER = 1 << 3;
+    /**
+     * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher
+     * state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user.
+     */
+    static final int WORK_TYPE_FGS = 1 << 1;
+    /** The job is allowed to run as an expedited job for a currently active user. */
+    static final int WORK_TYPE_EJ = 1 << 2;
+    /**
+     * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
+     * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so
+     * can run as a background job.
+     */
+    static final int WORK_TYPE_BG = 1 << 3;
+    /**
+     * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
+     * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user,
+     * so can run as a background user job.
+     */
+    static final int WORK_TYPE_BGUSER = 1 << 4;
     @VisibleForTesting
-    static final int NUM_WORK_TYPES = 4;
-    private static final int ALL_WORK_TYPES =
-            WORK_TYPE_TOP | WORK_TYPE_EJ | WORK_TYPE_BG | WORK_TYPE_BGUSER;
+    static final int NUM_WORK_TYPES = 5;
+    private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1;
 
     @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
             WORK_TYPE_NONE,
             WORK_TYPE_TOP,
+            WORK_TYPE_FGS,
             WORK_TYPE_EJ,
             WORK_TYPE_BG,
             WORK_TYPE_BGUSER
@@ -95,12 +123,15 @@
     public @interface WorkType {
     }
 
-    private static String workTypeToString(@WorkType int workType) {
+    @VisibleForTesting
+    static String workTypeToString(@WorkType int workType) {
         switch (workType) {
             case WORK_TYPE_NONE:
                 return "NONE";
             case WORK_TYPE_TOP:
                 return "TOP";
+            case WORK_TYPE_FGS:
+                return "FGS";
             case WORK_TYPE_EJ:
                 return "EJ";
             case WORK_TYPE_BG:
@@ -131,58 +162,60 @@
             new WorkConfigLimitsPerMemoryTrimLevel(
                     new WorkTypeConfig("screen_on_normal", 11,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_EJ, 3),
-                                    Pair.create(WORK_TYPE_BG, 2)),
+                            List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_FGS, 1),
+                                    Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4))
                     ),
                     new WorkTypeConfig("screen_on_moderate", 9,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 2),
-                                    Pair.create(WORK_TYPE_BG, 2)),
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
+                                    Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2))
                     ),
                     new WorkTypeConfig("screen_on_low", 6,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1),
-                                    Pair.create(WORK_TYPE_BG, 1)),
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
+                                    Pair.create(WORK_TYPE_EJ, 1)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
                     ),
-                    new WorkTypeConfig("screen_on_critical", 5,
+                    new WorkTypeConfig("screen_on_critical", 6,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1)),
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
+                                    Pair.create(WORK_TYPE_EJ, 1)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
                     )
             );
     private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF =
             new WorkConfigLimitsPerMemoryTrimLevel(
-                    new WorkTypeConfig("screen_off_normal", 13,
+                    new WorkTypeConfig("screen_off_normal", 15,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 3),
-                                    Pair.create(WORK_TYPE_BG, 2)),
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2),
+                                    Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4))
                     ),
-                    new WorkTypeConfig("screen_off_moderate", 13,
+                    new WorkTypeConfig("screen_off_moderate", 15,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_EJ, 3),
-                                    Pair.create(WORK_TYPE_BG, 2)),
+                            List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_FGS, 2),
+                                    Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2))
                     ),
-                    new WorkTypeConfig("screen_off_low", 7,
+                    new WorkTypeConfig("screen_off_low", 9,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 2),
-                                    Pair.create(WORK_TYPE_BG, 1)),
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
+                                    Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
                     ),
-                    new WorkTypeConfig("screen_off_critical", 5,
+                    new WorkTypeConfig("screen_off_critical", 6,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1)),
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
+                                    Pair.create(WORK_TYPE_EJ, 1)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
                     )
@@ -977,10 +1010,11 @@
 
     int getJobWorkTypes(@NonNull JobStatus js) {
         int classification = 0;
-        // TODO: create dedicated work type for FGS
         if (shouldRunAsFgUserJob(js)) {
             if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
                 classification |= WORK_TYPE_TOP;
+            } else if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE) {
+                classification |= WORK_TYPE_FGS;
             } else {
                 classification |= WORK_TYPE_BG;
             }
@@ -1001,11 +1035,13 @@
         private static final String KEY_PREFIX_MAX_TOTAL =
                 CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_";
         private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_";
+        private static final String KEY_PREFIX_MAX_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "max_fgs_";
         private static final String KEY_PREFIX_MAX_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "max_ej_";
         private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_";
         private static final String KEY_PREFIX_MAX_BGUSER =
                 CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_";
         private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_";
+        private static final String KEY_PREFIX_MIN_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "min_fgs_";
         private static final String KEY_PREFIX_MIN_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "min_ej_";
         private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_";
         private static final String KEY_PREFIX_MIN_BGUSER =
@@ -1053,6 +1089,10 @@
                     properties.getInt(KEY_PREFIX_MAX_TOP + mConfigIdentifier,
                             mDefaultMaxAllowedSlots.get(WORK_TYPE_TOP, mMaxTotal))));
             mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop);
+            final int maxFgs = Math.max(1, Math.min(mMaxTotal,
+                    properties.getInt(KEY_PREFIX_MAX_FGS + mConfigIdentifier,
+                            mDefaultMaxAllowedSlots.get(WORK_TYPE_FGS, mMaxTotal))));
+            mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs);
             final int maxEj = Math.max(1, Math.min(mMaxTotal,
                     properties.getInt(KEY_PREFIX_MAX_EJ + mConfigIdentifier,
                             mDefaultMaxAllowedSlots.get(WORK_TYPE_EJ, mMaxTotal))));
@@ -1074,6 +1114,12 @@
                             mDefaultMinReservedSlots.get(WORK_TYPE_TOP))));
             mMinReservedSlots.put(WORK_TYPE_TOP, minTop);
             remaining -= minTop;
+            // Ensure fgs is in the range [0, min(maxFgs, remaining)]
+            final int minFgs = Math.max(0, Math.min(Math.min(maxFgs, remaining),
+                    properties.getInt(KEY_PREFIX_MIN_FGS + mConfigIdentifier,
+                            mDefaultMinReservedSlots.get(WORK_TYPE_FGS))));
+            mMinReservedSlots.put(WORK_TYPE_FGS, minFgs);
+            remaining -= minFgs;
             // Ensure ej is in the range [0, min(maxEj, remaining)]
             final int minEj = Math.max(0, Math.min(Math.min(maxEj, remaining),
                     properties.getInt(KEY_PREFIX_MIN_EJ + mConfigIdentifier,
@@ -1111,6 +1157,10 @@
                     .println();
             pw.print(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_TOP))
                     .println();
+            pw.print(KEY_PREFIX_MIN_FGS + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_FGS))
+                    .println();
+            pw.print(KEY_PREFIX_MAX_FGS + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_FGS))
+                    .println();
             pw.print(KEY_PREFIX_MIN_EJ + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_EJ))
                     .println();
             pw.print(KEY_PREFIX_MAX_EJ + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_EJ))
@@ -1205,6 +1255,7 @@
         private int mConfigMaxTotal;
         private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
         private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mRecycledReserved = new SparseIntArray(NUM_WORK_TYPES);
 
         /**
          * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't
@@ -1220,11 +1271,14 @@
             mConfigMaxTotal = workTypeConfig.getMaxTotal();
             mConfigNumReservedSlots.put(WORK_TYPE_TOP,
                     workTypeConfig.getMinReserved(WORK_TYPE_TOP));
+            mConfigNumReservedSlots.put(WORK_TYPE_FGS,
+                    workTypeConfig.getMinReserved(WORK_TYPE_FGS));
             mConfigNumReservedSlots.put(WORK_TYPE_EJ, workTypeConfig.getMinReserved(WORK_TYPE_EJ));
             mConfigNumReservedSlots.put(WORK_TYPE_BG, workTypeConfig.getMinReserved(WORK_TYPE_BG));
             mConfigNumReservedSlots.put(WORK_TYPE_BGUSER,
                     workTypeConfig.getMinReserved(WORK_TYPE_BGUSER));
             mConfigAbsoluteMaxSlots.put(WORK_TYPE_TOP, workTypeConfig.getMax(WORK_TYPE_TOP));
+            mConfigAbsoluteMaxSlots.put(WORK_TYPE_FGS, workTypeConfig.getMax(WORK_TYPE_FGS));
             mConfigAbsoluteMaxSlots.put(WORK_TYPE_EJ, workTypeConfig.getMax(WORK_TYPE_EJ));
             mConfigAbsoluteMaxSlots.put(WORK_TYPE_BG, workTypeConfig.getMax(WORK_TYPE_BG));
             mConfigAbsoluteMaxSlots.put(WORK_TYPE_BGUSER, workTypeConfig.getMax(WORK_TYPE_BGUSER));
@@ -1260,15 +1314,10 @@
                 // We don't need to adjust reservations if only one work type was modified
                 // because that work type is the one we're using.
 
-                // 0 is WORK_TYPE_NONE.
-                int workType = 1;
-                int rem = workTypes;
-                while (rem > 0) {
-                    if ((rem & 1) != 0) {
+                for (int workType = 1; workType <= workTypes; workType <<= 1) {
+                    if ((workType & workTypes) == workType) {
                         maybeAdjustReservations(workType);
                     }
-                    rem = rem >>> 1;
-                    workType = workType << 1;
                 }
             }
         }
@@ -1280,21 +1329,11 @@
             int numAdj = 0;
             // We don't know which type we'll classify the job as when we run it yet, so make sure
             // we have space in all applicable slots.
-            if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
-                mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + adj);
-                numAdj++;
-            }
-            if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) {
-                mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + adj);
-                numAdj++;
-            }
-            if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
-                mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + adj);
-                numAdj++;
-            }
-            if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
-                mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + adj);
-                numAdj++;
+            for (int workType = 1; workType <= workTypes; workType <<= 1) {
+                if ((workTypes & workType) == workType) {
+                    mNumPendingJobs.put(workType, mNumPendingJobs.get(workType) + adj);
+                    numAdj++;
+                }
             }
 
             return numAdj;
@@ -1388,105 +1427,45 @@
             mNumUnspecializedRemaining = mConfigMaxTotal;
 
             // Step 1
-            int runTop = mNumRunningJobs.get(WORK_TYPE_TOP);
-            int resTop = runTop;
-            mNumUnspecializedRemaining -= resTop;
-            int runEj = mNumRunningJobs.get(WORK_TYPE_EJ);
-            int resEj = runEj;
-            mNumUnspecializedRemaining -= resEj;
-            int runBg = mNumRunningJobs.get(WORK_TYPE_BG);
-            int resBg = runBg;
-            mNumUnspecializedRemaining -= resBg;
-            int runBgUser = mNumRunningJobs.get(WORK_TYPE_BGUSER);
-            int resBgUser = runBgUser;
-            mNumUnspecializedRemaining -= resBgUser;
+            for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
+                int run = mNumRunningJobs.get(workType);
+                mRecycledReserved.put(workType, run);
+                mNumUnspecializedRemaining -= run;
+            }
 
             // Step 2
-            final int numTop = runTop + mNumPendingJobs.get(WORK_TYPE_TOP);
-            int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
-                    Math.min(numTop, mConfigNumReservedSlots.get(WORK_TYPE_TOP) - resTop)));
-            resTop += fillUp;
-            mNumUnspecializedRemaining -= fillUp;
-            final int numEj = runEj + mNumPendingJobs.get(WORK_TYPE_EJ);
-            fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
-                    Math.min(numEj, mConfigNumReservedSlots.get(WORK_TYPE_EJ) - resEj)));
-            resEj += fillUp;
-            mNumUnspecializedRemaining -= fillUp;
-            final int numBg = runBg + mNumPendingJobs.get(WORK_TYPE_BG);
-            fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
-                    Math.min(numBg, mConfigNumReservedSlots.get(WORK_TYPE_BG) - resBg)));
-            resBg += fillUp;
-            mNumUnspecializedRemaining -= fillUp;
-            final int numBgUser = runBgUser + mNumPendingJobs.get(WORK_TYPE_BGUSER);
-            fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
-                    Math.min(numBgUser,
-                            mConfigNumReservedSlots.get(WORK_TYPE_BGUSER) - resBgUser)));
-            resBgUser += fillUp;
-            mNumUnspecializedRemaining -= fillUp;
+            for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
+                int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
+                int res = mRecycledReserved.get(workType);
+                int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
+                        Math.min(num, mConfigNumReservedSlots.get(workType) - res)));
+                res += fillUp;
+                mRecycledReserved.put(workType, res);
+                mNumUnspecializedRemaining -= fillUp;
+            }
 
             // Step 3
-            int unspecializedAssigned = Math.max(0,
-                    Math.min(mNumUnspecializedRemaining,
-                            Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), numTop) - resTop));
-            mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned);
-            mNumUnspecializedRemaining -= unspecializedAssigned;
-
-            unspecializedAssigned = Math.max(0,
-                    Math.min(mNumUnspecializedRemaining,
-                            Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_EJ), numEj) - resEj));
-            mNumActuallyReservedSlots.put(WORK_TYPE_EJ, resEj + unspecializedAssigned);
-            mNumUnspecializedRemaining -= unspecializedAssigned;
-
-            unspecializedAssigned = Math.max(0,
-                    Math.min(mNumUnspecializedRemaining,
-                            Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg));
-            mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned);
-            mNumUnspecializedRemaining -= unspecializedAssigned;
-
-            unspecializedAssigned = Math.max(0,
-                    Math.min(mNumUnspecializedRemaining,
-                            Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER), numBgUser)
-                                    - resBgUser));
-            mNumActuallyReservedSlots.put(WORK_TYPE_BGUSER, resBgUser + unspecializedAssigned);
-            mNumUnspecializedRemaining -= unspecializedAssigned;
+            for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
+                int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
+                int res = mRecycledReserved.get(workType);
+                int unspecializedAssigned = Math.max(0,
+                        Math.min(mNumUnspecializedRemaining,
+                                Math.min(mConfigAbsoluteMaxSlots.get(workType), num) - res));
+                mNumActuallyReservedSlots.put(workType, res + unspecializedAssigned);
+                mNumUnspecializedRemaining -= unspecializedAssigned;
+            }
         }
 
         int canJobStart(int workTypes) {
-            if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
-                final int maxAllowed = Math.min(
-                        mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP),
-                        mNumActuallyReservedSlots.get(WORK_TYPE_TOP) + mNumUnspecializedRemaining);
-                if (mNumRunningJobs.get(WORK_TYPE_TOP) + mNumStartingJobs.get(WORK_TYPE_TOP)
-                        < maxAllowed) {
-                    return WORK_TYPE_TOP;
-                }
-            }
-            if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) {
-                final int maxAllowed = Math.min(
-                        mConfigAbsoluteMaxSlots.get(WORK_TYPE_EJ),
-                        mNumActuallyReservedSlots.get(WORK_TYPE_EJ) + mNumUnspecializedRemaining);
-                if (mNumRunningJobs.get(WORK_TYPE_EJ) + mNumStartingJobs.get(WORK_TYPE_EJ)
-                        < maxAllowed) {
-                    return WORK_TYPE_EJ;
-                }
-            }
-            if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
-                final int maxAllowed = Math.min(
-                        mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG),
-                        mNumActuallyReservedSlots.get(WORK_TYPE_BG) + mNumUnspecializedRemaining);
-                if (mNumRunningJobs.get(WORK_TYPE_BG) + mNumStartingJobs.get(WORK_TYPE_BG)
-                        < maxAllowed) {
-                    return WORK_TYPE_BG;
-                }
-            }
-            if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
-                final int maxAllowed = Math.min(
-                        mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER),
-                        mNumActuallyReservedSlots.get(WORK_TYPE_BGUSER)
-                                + mNumUnspecializedRemaining);
-                if (mNumRunningJobs.get(WORK_TYPE_BGUSER) + mNumStartingJobs.get(WORK_TYPE_BGUSER)
-                        < maxAllowed) {
-                    return WORK_TYPE_BGUSER;
+            for (int workType = 1; workType <= workTypes; workType <<= 1) {
+                if ((workTypes & workType) == workType) {
+                    final int maxAllowed = Math.min(
+                            mConfigAbsoluteMaxSlots.get(workType),
+                            mNumActuallyReservedSlots.get(workType) + mNumUnspecializedRemaining);
+                    if (mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
+                            < maxAllowed) {
+                        return workType;
+                    }
                 }
             }
             return WORK_TYPE_NONE;
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index 685cf0d..906071f 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -39,10 +39,12 @@
  for handling newer video codec format and media features.
 
  <p>
- Android 12 introduces seamless media transcoding feature. By default, Android assumes apps can
- support playback of all media formats. Apps that would like to request that media be transcoded
- into a more compatible format should declare their media capabilities in a media_capabilities
- .xml resource file and add it as a property tag in the AndroidManifest.xml file. Here is a example:
+ Android 12 introduces Compatible media transcoding feature.  See
+ <a href="https://developer.android.com/about/versions/12/features#compatible_media_transcoding">
+ Compatible media transcoding</a>. By default, Android assumes apps can support playback of all
+ media formats. Apps that would like to request that media be transcoded into a more compatible
+ format should declare their media capabilities in a media_capabilities.xml resource file and add it
+ as a property tag in the AndroidManifest.xml file. Here is a example:
  <pre>
  {@code
  <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index f85e30d..9c044b5 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -66,6 +66,7 @@
     private static final String COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT =
             "set-phone-acct-suggestion-component";
     private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account";
+    private static final String COMMAND_SET_CALL_DIAGNOSTIC_SERVICE = "set-call-diagnostic-service";
     private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer";
     private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
     private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression";
@@ -112,6 +113,7 @@
                 + "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN>"
                 + " <LABEL> <ADDRESS>\n"
                 + "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n"
+                + "usage: telecom set-call-diagnostic-service <PACKAGE>\n"
                 + "usage: telecom set-default-dialer <PACKAGE>\n"
                 + "usage: telecom get-default-dialer\n"
                 + "usage: telecom get-system-dialer\n"
@@ -131,6 +133,7 @@
                 + "telecom set-phone-account-disabled: Disables the given phone account, if it"
                         + " has already been registered with telecom.\n"
                 + "\n"
+                + "telecom set-call-diagnostic-service: overrides call diagnostic service.\n"
                 + "telecom set-default-dialer: Sets the override default dialer to the given"
                         + " component; this will override whatever the dialer role is set to.\n"
                 + "\n"
@@ -206,6 +209,9 @@
             case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT:
                 runSetTestPhoneAcctSuggestionComponent();
                 break;
+            case COMMAND_SET_CALL_DIAGNOSTIC_SERVICE:
+                runSetCallDiagnosticService();
+                break;
             case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
                 runRegisterSimPhoneAccount();
                 break;
@@ -323,6 +329,13 @@
         mTelecomService.addOrRemoveTestCallCompanionApp(packageName, isAddedBool);
     }
 
+    private void runSetCallDiagnosticService() throws RemoteException {
+        String packageName = nextArg();
+        if ("default".equals(packageName)) packageName = null;
+        mTelecomService.setTestCallDiagnosticService(packageName);
+        System.out.println("Success - " + packageName + " set as call diagnostic service.");
+    }
+
     private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException {
         final String componentName = nextArg();
         mTelecomService.setTestPhoneAcctSuggestionComponent(componentName);
diff --git a/core/api/current.txt b/core/api/current.txt
index 4f1235c..8e91ddb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -20300,6 +20300,10 @@
     field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_BIT_WIDTH;
     field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_CHANNEL_MASK;
     field @NonNull public static final android.media.AudioMetadata.Key<java.lang.String> KEY_MIME;
+    field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER;
+    field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PRESENTATION_ID;
+    field @NonNull public static final android.media.AudioMetadata.Key<java.lang.String> KEY_PRESENTATION_LANGUAGE;
+    field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_PROGRAM_ID;
     field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_SAMPLE_RATE;
   }
 
@@ -20354,6 +20358,15 @@
     method public boolean hasAudioDescription();
     method public boolean hasDialogueEnhancement();
     method public boolean hasSpokenSubtitles();
+    field public static final int CONTENT_COMMENTARY = 5; // 0x5
+    field public static final int CONTENT_DIALOG = 4; // 0x4
+    field public static final int CONTENT_EMERGENCY = 6; // 0x6
+    field public static final int CONTENT_HEARING_IMPAIRED = 3; // 0x3
+    field public static final int CONTENT_MAIN = 0; // 0x0
+    field public static final int CONTENT_MUSIC_AND_EFFECTS = 1; // 0x1
+    field public static final int CONTENT_UNKNOWN = -1; // 0xffffffff
+    field public static final int CONTENT_VISUALLY_IMPAIRED = 2; // 0x2
+    field public static final int CONTENT_VOICEOVER = 7; // 0x7
     field public static final int MASTERED_FOR_3D = 3; // 0x3
     field public static final int MASTERED_FOR_HEADPHONE = 4; // 0x4
     field public static final int MASTERED_FOR_STEREO = 1; // 0x1
@@ -30418,19 +30431,10 @@
   public abstract class CombinedVibrationEffect implements android.os.Parcelable {
     method @NonNull public static android.os.CombinedVibrationEffect createSynced(@NonNull android.os.VibrationEffect);
     method public int describeContents();
-    method @NonNull public static android.os.CombinedVibrationEffect.SequentialCombination startSequential();
     method @NonNull public static android.os.CombinedVibrationEffect.SyncedCombination startSynced();
     field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect> CREATOR;
   }
 
-  public static final class CombinedVibrationEffect.SequentialCombination {
-    method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect);
-    method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect, int);
-    method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect);
-    method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect, int);
-    method @NonNull public android.os.CombinedVibrationEffect combine();
-  }
-
   public static final class CombinedVibrationEffect.SyncedCombination {
     method @NonNull public android.os.CombinedVibrationEffect.SyncedCombination addVibrator(int, @NonNull android.os.VibrationEffect);
     method @NonNull public android.os.CombinedVibrationEffect combine();
@@ -31855,6 +31859,7 @@
     method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID) throws java.io.IOException;
     method @WorkerThread public long getCacheQuotaBytes(@NonNull java.util.UUID) throws java.io.IOException;
     method @WorkerThread public long getCacheSizeBytes(@NonNull java.util.UUID) throws java.io.IOException;
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public android.app.PendingIntent getManageSpaceActivityIntent(@NonNull String, int);
     method public String getMountedObbPath(String);
     method @NonNull public android.os.storage.StorageVolume getPrimaryStorageVolume();
     method @NonNull public java.util.List<android.os.storage.StorageVolume> getRecentStorageVolumes();
@@ -38980,6 +38985,10 @@
     method public void unhold();
     method public void unregisterCallback(android.telecom.Call.Callback);
     field @Deprecated public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
+    field public static final String EVENT_CLEAR_DIAGNOSTIC_MESSAGE = "android.telecom.event.CLEAR_DIAGNOSTIC_MESSAGE";
+    field public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE = "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE";
+    field public static final String EXTRA_DIAGNOSTIC_MESSAGE = "android.telecom.extra.DIAGNOSTIC_MESSAGE";
+    field public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID = "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID";
     field public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
     field public static final String EXTRA_SILENT_RINGING_REQUESTED = "android.telecom.extra.SILENT_RINGING_REQUESTED";
     field public static final String EXTRA_SUGGESTED_PHONE_ACCOUNTS = "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS";
@@ -42230,7 +42239,6 @@
     field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80
     field public static final int CALL_COMPOSER_STATUS_OFF = 0; // 0x0
     field public static final int CALL_COMPOSER_STATUS_ON = 1; // 0x1
-    field public static final int CALL_COMPOSER_STATUS_ON_NO_PICTURES = 2; // 0x2
     field public static final int CALL_STATE_IDLE = 0; // 0x0
     field public static final int CALL_STATE_OFFHOOK = 2; // 0x2
     field public static final int CALL_STATE_RINGING = 1; // 0x1
@@ -50064,7 +50072,7 @@
     method public boolean canOpenPopup();
     method public int describeContents();
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String);
-    method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String);
+    method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(@NonNull String);
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
     method public android.view.accessibility.AccessibilityNodeInfo focusSearch(int);
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getActionList();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 60325b3..d6786f8 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -44,6 +44,14 @@
 
 }
 
+package android.app.usage {
+
+  public class NetworkStatsManager {
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>);
+  }
+
+}
+
 package android.content {
 
   public abstract class Context {
@@ -233,8 +241,8 @@
 package android.os.storage {
 
   public class StorageManager {
-    method public void notifyAppIoBlocked(@NonNull String, int, int, int);
-    method public void notifyAppIoResumed(@NonNull String, int, int, int);
+    method public void notifyAppIoBlocked(@NonNull java.util.UUID, int, int, int);
+    method public void notifyAppIoResumed(@NonNull java.util.UUID, int, int, int);
     field public static final int APP_IO_BLOCKED_REASON_TRANSCODING = 0; // 0x0
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 02925c5..998b114 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -32,6 +32,7 @@
     field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION";
     field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE";
     field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE";
+    field public static final String BIND_CALL_DIAGNOSTIC_SERVICE = "android.permission.BIND_CALL_DIAGNOSTIC_SERVICE";
     field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
     field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE";
     field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
@@ -153,6 +154,7 @@
     field public static final String MANAGE_USB = "android.permission.MANAGE_USB";
     field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
     field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
+    field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
     field public static final String MODIFY_APPWIDGET_BIND_PERMISSIONS = "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS";
     field public static final String MODIFY_AUDIO_ROUTING = "android.permission.MODIFY_AUDIO_ROUTING";
     field public static final String MODIFY_CELL_BROADCASTS = "android.permission.MODIFY_CELL_BROADCASTS";
@@ -422,6 +424,9 @@
     method @Nullable public static String opToPermission(@NonNull String);
     method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int);
     method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int);
+    field public static final int HISTORY_FLAGS_ALL = 3; // 0x3
+    field public static final int HISTORY_FLAG_AGGREGATE = 1; // 0x1
+    field public static final int HISTORY_FLAG_DISCRETE = 2; // 0x2
     field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
     field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
     field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
@@ -535,9 +540,14 @@
     method public long getAccessDuration(int, int, int);
     method public long getBackgroundAccessCount(int);
     method public long getBackgroundAccessDuration(int);
+    method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getBackgroundDiscreteAccesses(int);
     method public long getBackgroundRejectCount(int);
+    method @NonNull public android.app.AppOpsManager.AttributedOpEntry getDiscreteAccessAt(@IntRange(from=0) int);
+    method @IntRange(from=0) public int getDiscreteAccessCount();
+    method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getDiscreteAccesses(int, int, int);
     method public long getForegroundAccessCount(int);
     method public long getForegroundAccessDuration(int);
+    method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getForegroundDiscreteAccesses(int);
     method public long getForegroundRejectCount(int);
     method @NonNull public String getOpName();
     method public long getRejectCount(int, int, int);
@@ -564,6 +574,7 @@
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build();
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setAttributionTag(@Nullable String);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int);
+    method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setHistoryFlags(int);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setUid(int);
@@ -1887,11 +1898,18 @@
     field public static final int ACCESS_REJECTED = 2; // 0x2
     field public static final int ACCESS_UNKNOWN = 0; // 0x0
     field 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
@@ -1899,12 +1917,15 @@
     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 {
@@ -8204,10 +8225,11 @@
     field public static final int EVENT_MMS = 2; // 0x2
     field public static final int EVENT_SMS = 1; // 0x1
     field public static final int EVENT_UNSPECIFIED = 0; // 0x0
-    field public static final int REASON_ACTIVITY_RECOGNITION = 102; // 0x66
+    field public static final int REASON_ACTIVITY_RECOGNITION = 103; // 0x67
     field public static final int REASON_GEOFENCING = 100; // 0x64
     field public static final int REASON_OTHER = 1; // 0x1
     field public static final int REASON_PUSH_MESSAGING = 101; // 0x65
+    field public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; // 0x66
     field public static final int REASON_UNKNOWN = 0; // 0x0
     field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0
     field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1
@@ -10338,6 +10360,16 @@
     ctor @Deprecated public Call.Listener();
   }
 
+  public abstract class CallDiagnosticService extends android.app.Service {
+    ctor public CallDiagnosticService();
+    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onBluetoothCallQualityReportReceived(@NonNull android.telecom.BluetoothCallQualityReport);
+    method public abstract void onCallAudioStateChanged(@NonNull android.telecom.CallAudioState);
+    method @NonNull public abstract android.telecom.DiagnosticCall onInitializeDiagnosticCall(@NonNull android.telecom.Call.Details);
+    method public abstract void onRemoveDiagnosticCall(@NonNull android.telecom.DiagnosticCall);
+    field public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService";
+  }
+
   public static class CallScreeningService.CallResponse.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public android.telecom.CallScreeningService.CallResponse.Builder setShouldScreenCallViaAudioProcessing(boolean);
   }
@@ -10370,6 +10402,9 @@
     method public void setTelecomCallId(@NonNull String);
     field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000
     field public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 262144; // 0x40000
+    field public static final String EVENT_DEVICE_TO_DEVICE_MESSAGE = "android.telecom.event.DEVICE_TO_DEVICE_MESSAGE";
+    field public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE = "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_TYPE";
+    field public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE = "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_VALUE";
     field public static final String EXTRA_DISABLE_ADD_CALL = "android.telecom.extra.DISABLE_ADD_CALL";
     field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 1; // 0x1
     field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2
@@ -10395,6 +10430,34 @@
     method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
   }
 
+  public abstract class DiagnosticCall {
+    ctor public DiagnosticCall();
+    method public final void clearDiagnosticMessage(int);
+    method public final void displayDiagnosticMessage(int, @NonNull CharSequence);
+    method @NonNull public android.telecom.Call.Details getCallDetails();
+    method public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details);
+    method @Nullable public abstract CharSequence onCallDisconnected(int, int);
+    method @Nullable public abstract CharSequence onCallDisconnected(@NonNull android.telephony.ims.ImsReasonInfo);
+    method public abstract void onCallQualityReceived(@NonNull android.telephony.CallQuality);
+    method public abstract void onReceiveDeviceToDeviceMessage(int, int);
+    method public final void sendDeviceToDeviceMessage(int, int);
+    field public static final int AUDIO_CODEC_AMR_NB = 3; // 0x3
+    field public static final int AUDIO_CODEC_AMR_WB = 2; // 0x2
+    field public static final int AUDIO_CODEC_EVS = 1; // 0x1
+    field public static final int BATTERY_STATE_CHARGING = 3; // 0x3
+    field public static final int BATTERY_STATE_GOOD = 2; // 0x2
+    field public static final int BATTERY_STATE_LOW = 1; // 0x1
+    field public static final int COVERAGE_GOOD = 2; // 0x2
+    field public static final int COVERAGE_POOR = 1; // 0x1
+    field public static final int MESSAGE_CALL_AUDIO_CODEC = 2; // 0x2
+    field public static final int MESSAGE_CALL_NETWORK_TYPE = 1; // 0x1
+    field public static final int MESSAGE_DEVICE_BATTERY_STATE = 3; // 0x3
+    field public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4; // 0x4
+    field public static final int NETWORK_TYPE_IWLAN = 2; // 0x2
+    field public static final int NETWORK_TYPE_LTE = 1; // 0x1
+    field public static final int NETWORK_TYPE_NR = 3; // 0x3
+  }
+
   public abstract class InCallService extends android.app.Service {
     method @Deprecated public android.telecom.Phone getPhone();
     method @Deprecated public void onPhoneCreated(android.telecom.Phone);
@@ -14024,9 +14087,13 @@
 
   public final class UiTranslationManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(@NonNull android.app.assist.ActivityId);
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(@NonNull android.app.assist.ActivityId);
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(@NonNull android.app.assist.ActivityId);
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, @NonNull android.app.assist.ActivityId);
   }
 
 }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1e5a6f1..e4757e6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -224,6 +224,9 @@
     field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0
     field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1
     field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2
+    field public static final int HISTORY_FLAGS_ALL = 3; // 0x3
+    field public static final int HISTORY_FLAG_AGGREGATE = 1; // 0x1
+    field public static final int HISTORY_FLAG_DISCRETE = 2; // 0x2
     field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time";
     field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time";
     field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time";
@@ -238,6 +241,7 @@
 
   public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable {
     ctor public AppOpsManager.HistoricalOps(long, long);
+    method public void addDiscreteAccess(int, int, @NonNull String, @Nullable String, int, int, long, long);
     method public void increaseAccessCount(int, int, @NonNull String, @Nullable String, int, int, long);
     method public void increaseAccessDuration(int, int, @NonNull String, @Nullable String, int, int, long);
     method public void increaseRejectCount(int, int, @NonNull String, @Nullable String, int, int, long);
@@ -1485,6 +1489,7 @@
 
   public abstract class CombinedVibrationEffect implements android.os.Parcelable {
     method public abstract long getDuration();
+    method @NonNull public static android.os.CombinedVibrationEffect.SequentialCombination startSequential();
   }
 
   public static final class CombinedVibrationEffect.Mono extends android.os.CombinedVibrationEffect {
@@ -1502,6 +1507,14 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Sequential> CREATOR;
   }
 
+  public static final class CombinedVibrationEffect.SequentialCombination {
+    method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect);
+    method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect, int);
+    method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect);
+    method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect, int);
+    method @NonNull public android.os.CombinedVibrationEffect combine();
+  }
+
   public static final class CombinedVibrationEffect.Stereo extends android.os.CombinedVibrationEffect {
     method public long getDuration();
     method @NonNull public android.util.SparseArray<android.os.VibrationEffect> getEffects();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 160844a..dd1bc7c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static java.lang.Long.max;
+
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
@@ -3385,6 +3387,13 @@
         @DataClass.ParcelWith(LongSparseArrayParceling.class)
         private final @Nullable LongSparseArray<NoteOpEvent> mRejectEvents;
 
+        private AttributedOpEntry(@NonNull AttributedOpEntry other) {
+            mOp = other.mOp;
+            mRunning = other.mRunning;
+            mAccessEvents = other.mAccessEvents == null ? null : other.mAccessEvents.clone();
+            mRejectEvents = other.mRejectEvents == null ? null : other.mRejectEvents.clone();
+        }
+
         /**
          * Returns all keys for which we have events.
          *
@@ -3749,6 +3758,15 @@
             return lastEvent.getProxy();
         }
 
+        @NonNull
+        String getOpName() {
+            return AppOpsManager.opToPublicName(mOp);
+        }
+
+        int getOp() {
+            return mOp;
+        }
+
         private static class LongSparseArrayParceling implements
                 Parcelling<LongSparseArray<NoteOpEvent>> {
             @Override
@@ -4571,6 +4589,50 @@
     }
 
     /**
+     * Flag for querying app op history: get only aggregate information and no
+     * discrete accesses.
+     *
+     * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int HISTORY_FLAG_AGGREGATE = 1 << 0;
+
+    /**
+     * Flag for querying app op history: get only discrete information and no
+     * aggregate accesses.
+     *
+     * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int HISTORY_FLAG_DISCRETE = 1 << 1;
+
+    /**
+     * Flag for querying app op history: get all types of historical accesses.
+     *
+     * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int HISTORY_FLAGS_ALL = HISTORY_FLAG_AGGREGATE
+            | HISTORY_FLAG_DISCRETE;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "HISTORY_FLAG_" }, value = {
+            HISTORY_FLAG_AGGREGATE,
+            HISTORY_FLAG_DISCRETE
+    })
+    public @interface OpHistoryFlags {}
+
+    /**
      * Specifies what parameters to filter historical appop requests for
      *
      * @hide
@@ -4625,6 +4687,7 @@
         private final @Nullable String mPackageName;
         private final @Nullable String mAttributionTag;
         private final @Nullable List<String> mOpNames;
+        private final @OpHistoryFlags int mHistoryFlags;
         private final @HistoricalOpsRequestFilter int mFilter;
         private final long mBeginTimeMillis;
         private final long mEndTimeMillis;
@@ -4632,12 +4695,13 @@
 
         private HistoricalOpsRequest(int uid, @Nullable String packageName,
                 @Nullable String attributionTag, @Nullable List<String> opNames,
-                @HistoricalOpsRequestFilter int filter, long beginTimeMillis,
-                long endTimeMillis, @OpFlags int flags) {
+                @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter,
+                long beginTimeMillis, long endTimeMillis, @OpFlags int flags) {
             mUid = uid;
             mPackageName = packageName;
             mAttributionTag = attributionTag;
             mOpNames = opNames;
+            mHistoryFlags = historyFlags;
             mFilter = filter;
             mBeginTimeMillis = beginTimeMillis;
             mEndTimeMillis = endTimeMillis;
@@ -4655,6 +4719,7 @@
             private @Nullable String mPackageName;
             private @Nullable String mAttributionTag;
             private @Nullable List<String> mOpNames;
+            private @OpHistoryFlags int mHistoryFlags;
             private @HistoricalOpsRequestFilter int mFilter;
             private final long mBeginTimeMillis;
             private final long mEndTimeMillis;
@@ -4676,6 +4741,7 @@
                         "beginTimeMillis must be non negative and lesser than endTimeMillis");
                 mBeginTimeMillis = beginTimeMillis;
                 mEndTimeMillis = endTimeMillis;
+                mHistoryFlags = HISTORY_FLAG_AGGREGATE;
             }
 
             /**
@@ -4772,11 +4838,25 @@
             }
 
             /**
+             * Specifies what type of historical information to query.
+             *
+             * @param flags Flags for the historical types to fetch which are any
+             * combination of {@link #HISTORY_FLAG_AGGREGATE}, {@link #HISTORY_FLAG_DISCRETE},
+             * {@link #HISTORY_FLAGS_ALL}. The default is {@link #HISTORY_FLAG_AGGREGATE}.
+             * @return This builder.
+             */
+            public @NonNull Builder setHistoryFlags(@OpHistoryFlags int flags) {
+                Preconditions.checkFlagsArgument(flags, HISTORY_FLAGS_ALL);
+                mHistoryFlags = flags;
+                return this;
+            }
+
+            /**
              * @return a new {@link HistoricalOpsRequest}.
              */
             public @NonNull HistoricalOpsRequest build() {
                 return new HistoricalOpsRequest(mUid, mPackageName, mAttributionTag, mOpNames,
-                        mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags);
+                        mHistoryFlags, mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags);
             }
         }
     }
@@ -4943,7 +5023,8 @@
          * @hide
          */
         public void filter(int uid, @Nullable String packageName, @Nullable String attributionTag,
-                @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
+                @Nullable String[] opNames, @OpHistoryFlags int historyFilter,
+                @HistoricalOpsRequestFilter int filter,
                 long beginTimeMillis, long endTimeMillis) {
             final long durationMillis = getDurationMillis();
             mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis);
@@ -4956,7 +5037,8 @@
                 if ((filter & FILTER_BY_UID) != 0 && uid != uidOp.getUid()) {
                     mHistoricalUidOps.removeAt(i);
                 } else {
-                    uidOp.filter(packageName, attributionTag, opNames, filter, scaleFactor);
+                    uidOp.filter(packageName, attributionTag, opNames, filter, historyFilter,
+                            scaleFactor, mBeginTimeMillis, mEndTimeMillis);
                     if (uidOp.getPackageCount() == 0) {
                         mHistoricalUidOps.removeAt(i);
                     }
@@ -5013,6 +5095,16 @@
 
         /** @hide */
         @TestApi
+        public void addDiscreteAccess(int opCode, int uid, @NonNull String packageName,
+                @Nullable String attributionTag, @UidState int uidState, @OpFlags int opFlag,
+                long discreteAccessTime, long discreteAccessDuration) {
+            getOrCreateHistoricalUidOps(uid).addDiscreteAccess(opCode, packageName, attributionTag,
+                    uidState, opFlag, discreteAccessTime, discreteAccessDuration);
+        };
+
+
+        /** @hide */
+        @TestApi
         public void offsetBeginAndEndTime(long offsetMillis) {
             mBeginTimeMillis += offsetMillis;
             mEndTimeMillis += offsetMillis;
@@ -5288,7 +5380,8 @@
 
         private void filter(@Nullable String packageName, @Nullable String attributionTag,
                 @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
-                double fractionToRemove) {
+                @OpHistoryFlags int historyFilter, double fractionToRemove, long beginTimeMillis,
+                long endTimeMillis) {
             final int packageCount = getPackageCount();
             for (int i = packageCount - 1; i >= 0; i--) {
                 final HistoricalPackageOps packageOps = getPackageOpsAt(i);
@@ -5296,7 +5389,8 @@
                         packageOps.getPackageName())) {
                     mHistoricalPackageOps.removeAt(i);
                 } else {
-                    packageOps.filter(attributionTag, opNames, filter, fractionToRemove);
+                    packageOps.filter(attributionTag, opNames, filter, historyFilter,
+                            fractionToRemove, beginTimeMillis, endTimeMillis);
                     if (packageOps.getAttributedOpsCount() == 0) {
                         mHistoricalPackageOps.removeAt(i);
                     }
@@ -5336,6 +5430,13 @@
                     opCode, attributionTag, uidState, flags, increment);
         }
 
+        private void addDiscreteAccess(int opCode, @NonNull String packageName,
+                @Nullable String attributionTag, @UidState int uidState,
+                @OpFlags int flag, long discreteAccessTime, long discreteAccessDuration) {
+            getOrCreateHistoricalPackageOps(packageName).addDiscreteAccess(opCode, attributionTag,
+                    uidState, flag, discreteAccessTime, discreteAccessDuration);
+        };
+
         /**
          * @return The UID for which the data is related.
          */
@@ -5540,7 +5641,8 @@
         }
 
         private void filter(@Nullable String attributionTag, @Nullable String[] opNames,
-                @HistoricalOpsRequestFilter int filter, double fractionToRemove) {
+                @HistoricalOpsRequestFilter int filter, @OpHistoryFlags int historyFilter,
+                double fractionToRemove, long beginTimeMillis, long endTimeMillis) {
             final int attributionCount = getAttributedOpsCount();
             for (int i = attributionCount - 1; i >= 0; i--) {
                 final AttributedHistoricalOps attributionOps = getAttributedOpsAt(i);
@@ -5548,7 +5650,8 @@
                         attributionOps.getTag())) {
                     mAttributedHistoricalOps.removeAt(i);
                 } else {
-                    attributionOps.filter(opNames, filter, fractionToRemove);
+                    attributionOps.filter(opNames, filter, historyFilter, fractionToRemove,
+                            beginTimeMillis, endTimeMillis);
                     if (attributionOps.getOpCount() == 0) {
                         mAttributedHistoricalOps.removeAt(i);
                     }
@@ -5593,6 +5696,13 @@
                     opCode, uidState, flags, increment);
         }
 
+        private void addDiscreteAccess(int opCode, @Nullable String attributionTag,
+                @UidState int uidState, @OpFlags int flag, long discreteAccessTime,
+                long discreteAccessDuration) {
+            getOrCreateAttributedHistoricalOps(attributionTag).addDiscreteAccess(opCode, uidState,
+                    flag, discreteAccessTime, discreteAccessDuration);
+        }
+
         /**
          * Gets the package name which the data represents.
          *
@@ -5870,7 +5980,8 @@
         }
 
         private void filter(@Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
-                double scaleFactor) {
+                @OpHistoryFlags int historyFilter, double scaleFactor, long beginTimeMillis,
+                long endTimeMillis) {
             final int opCount = getOpCount();
             for (int i = opCount - 1; i >= 0; i--) {
                 final HistoricalOp op = mHistoricalOps.valueAt(i);
@@ -5878,7 +5989,7 @@
                         op.getOpName())) {
                     mHistoricalOps.removeAt(i);
                 } else {
-                    op.filter(scaleFactor);
+                    op.filter(historyFilter, scaleFactor, beginTimeMillis, endTimeMillis);
                 }
             }
         }
@@ -5909,6 +6020,12 @@
             getOrCreateHistoricalOp(opCode).increaseAccessDuration(uidState, flags, increment);
         }
 
+        private void addDiscreteAccess(int opCode, @UidState int uidState, @OpFlags int flag,
+                long discreteAccessTime, long discreteAccessDuration) {
+            getOrCreateHistoricalOp(opCode).addDiscreteAccess(uidState,flag, discreteAccessTime,
+                    discreteAccessDuration);
+        }
+
         /**
          * Gets number historical app ops.
          *
@@ -5970,8 +6087,6 @@
             return op;
         }
 
-
-
         // Code below generated by codegen v1.0.14.
         //
         // DO NOT MODIFY!
@@ -6121,6 +6236,9 @@
         private @Nullable LongSparseLongArray mRejectCount;
         private @Nullable LongSparseLongArray mAccessDuration;
 
+        /** Discrete Ops for this Op */
+        private @Nullable List<AttributedOpEntry> mDiscreteAccesses;
+
         /** @hide */
         public HistoricalOp(int op) {
             mOp = op;
@@ -6137,6 +6255,12 @@
             if (other.mAccessDuration != null) {
                 mAccessDuration = other.mAccessDuration.clone();
             }
+            final int historicalOpCount = other.getDiscreteAccessCount();
+            for (int i = 0; i < historicalOpCount; i++) {
+                final AttributedOpEntry origOp = other.getDiscreteAccessAt(i);
+                final AttributedOpEntry cloneOp = new AttributedOpEntry(origOp);
+                getOrCreateDiscreteAccesses().add(cloneOp);
+            }
         }
 
         private HistoricalOp(@NonNull Parcel parcel) {
@@ -6144,22 +6268,45 @@
             mAccessCount = readLongSparseLongArrayFromParcel(parcel);
             mRejectCount = readLongSparseLongArrayFromParcel(parcel);
             mAccessDuration = readLongSparseLongArrayFromParcel(parcel);
+            mDiscreteAccesses = readDiscreteAccessArrayFromParcel(parcel);
         }
 
-        private void filter(double scaleFactor) {
-            scale(mAccessCount, scaleFactor);
-            scale(mRejectCount, scaleFactor);
-            scale(mAccessDuration, scaleFactor);
+        private void filter(@OpHistoryFlags int historyFlag, double scaleFactor,
+                long beginTimeMillis, long endTimeMillis) {
+            if ((historyFlag & HISTORY_FLAG_AGGREGATE) == 0) {
+                mAccessCount = null;
+                mRejectCount = null;
+                mAccessDuration = null;
+            } else {
+                scale(mAccessCount, scaleFactor);
+                scale(mRejectCount, scaleFactor);
+                scale(mAccessDuration, scaleFactor);
+            }
+            if ((historyFlag & HISTORY_FLAG_DISCRETE) == 0) {
+                mDiscreteAccesses = null;
+                return;
+            }
+            final int discreteOpCount = getDiscreteAccessCount();
+            for (int i = discreteOpCount - 1; i >= 0; i--) {
+                final AttributedOpEntry op = mDiscreteAccesses.get(i);
+                long opBeginTime = op.getLastAccessTime(OP_FLAGS_ALL);
+                long opEndTime = opBeginTime + op.getLastDuration(OP_FLAGS_ALL);
+                opEndTime = max(opBeginTime, opEndTime);
+                if (opEndTime < beginTimeMillis || opBeginTime > endTimeMillis) {
+                    mDiscreteAccesses.remove(i);
+                }
+            }
         }
 
         private boolean isEmpty() {
             return !hasData(mAccessCount)
                     && !hasData(mRejectCount)
-                    && !hasData(mAccessDuration);
+                    && !hasData(mAccessDuration)
+                    && (mDiscreteAccesses == null);
         }
 
         private boolean hasData(@NonNull LongSparseLongArray array) {
-            return (array != null && array.size() > 0);
+            return array != null && array.size() > 0;
         }
 
         private @Nullable HistoricalOp splice(double fractionToRemove) {
@@ -6191,6 +6338,32 @@
             merge(this::getOrCreateAccessCount, other.mAccessCount);
             merge(this::getOrCreateRejectCount, other.mRejectCount);
             merge(this::getOrCreateAccessDuration, other.mAccessDuration);
+
+            if (other.mDiscreteAccesses == null) {
+                return;
+            }
+            if (mDiscreteAccesses == null) {
+                mDiscreteAccesses = new ArrayList(other.mDiscreteAccesses);
+                return;
+            }
+            List<AttributedOpEntry> historicalDiscreteAccesses = new ArrayList<>();
+            final int otherHistoricalOpCount = other.getDiscreteAccessCount();
+            final int historicalOpCount = getDiscreteAccessCount();
+            int i = 0;
+            int j = 0;
+            while (i < otherHistoricalOpCount || j < historicalOpCount) {
+                if (i == otherHistoricalOpCount) {
+                    historicalDiscreteAccesses.add(mDiscreteAccesses.get(j++));
+                } else if (j == historicalOpCount) {
+                    historicalDiscreteAccesses.add(other.mDiscreteAccesses.get(i++));
+                } else if (mDiscreteAccesses.get(j).getLastAccessTime(OP_FLAGS_ALL)
+                        < other.mDiscreteAccesses.get(i).getLastAccessTime(OP_FLAGS_ALL)) {
+                    historicalDiscreteAccesses.add(mDiscreteAccesses.get(j++));
+                } else {
+                    historicalDiscreteAccesses.add(other.mDiscreteAccesses.get(i++));
+                }
+            }
+            mDiscreteAccesses = historicalDiscreteAccesses;
         }
 
         private void increaseAccessCount(@UidState int uidState, @OpFlags int flags,
@@ -6218,6 +6391,23 @@
             }
         }
 
+        private void addDiscreteAccess(@UidState int uidState, @OpFlags int flag,
+                long discreteAccessTime, long discreteAccessDuration) {
+            List<AttributedOpEntry> discreteAccesses = getOrCreateDiscreteAccesses();
+            LongSparseArray<NoteOpEvent> accessEvents = new LongSparseArray<>();
+            long key = makeKey(uidState, flag);
+            NoteOpEvent note = new NoteOpEvent(discreteAccessTime, discreteAccessDuration, null);
+            accessEvents.append(key, note);
+            AttributedOpEntry access = new AttributedOpEntry(mOp, false, accessEvents, null);
+            for (int i = discreteAccesses.size() - 1; i >= 0; i--) {
+                if (discreteAccesses.get(i).getLastAccessTime(OP_FLAGS_ALL) < discreteAccessTime) {
+                    discreteAccesses.add(i + 1, access);
+                    return;
+                }
+            }
+            discreteAccesses.add(0, access);
+        }
+
         /**
          * Gets the op name.
          *
@@ -6233,6 +6423,33 @@
         }
 
         /**
+         * Gets number of discrete historical app ops.
+         *
+         * @return The number historical app ops.
+         * @see #getOpAt(int)
+         */
+        public @IntRange(from = 0) int getDiscreteAccessCount() {
+            if (mDiscreteAccesses == null) {
+                return 0;
+            }
+            return mDiscreteAccesses.size();
+        }
+
+        /**
+         * Gets the historical op at a given index.
+         *
+         * @param index The index to lookup.
+         * @return The op at the given index.
+         * @see #getOpCount()
+         */
+        public @NonNull AttributedOpEntry getDiscreteAccessAt(@IntRange(from = 0) int index) {
+            if (mDiscreteAccesses == null) {
+                throw new IndexOutOfBoundsException();
+            }
+            return mDiscreteAccesses.get(index);
+        }
+
+        /**
          * Gets the number times the op was accessed (performed) in the foreground.
          *
          * @param flags The flags which are any combination of
@@ -6251,6 +6468,25 @@
         }
 
         /**
+         * Gets the discrete events the op was accessed (performed) in the foreground.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return The list of discrete ops accessed in the foreground.
+         *
+         * @see #getBackgroundDiscreteAccesses(int)
+         * @see #getDiscreteAccesses(int, int, int)
+         */
+        @NonNull
+        public List<AttributedOpEntry> getForegroundDiscreteAccesses(@OpFlags int flags) {
+            return listForFlagsInStates(mDiscreteAccesses, MAX_PRIORITY_UID_STATE,
+                    resolveFirstUnrestrictedUidState(mOp), flags);
+        }
+
+        /**
          * Gets the number times the op was accessed (performed) in the background.
          *
          * @param flags The flags which are any combination of
@@ -6269,6 +6505,25 @@
         }
 
         /**
+         * Gets the discrete events the op was accessed (performed) in the background.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return The list of discrete ops accessed in the background.
+         *
+         * @see #getForegroundDiscreteAccesses(int)
+         * @see #getDiscreteAccesses(int, int, int)
+         */
+        @NonNull
+        public List<AttributedOpEntry> getBackgroundDiscreteAccesses(@OpFlags int flags) {
+            return listForFlagsInStates(mDiscreteAccesses, resolveLastRestrictedUidState(mOp),
+                    MIN_PRIORITY_UID_STATE, flags);
+        }
+
+        /**
          * Gets the number times the op was accessed (performed) for a
          * range of uid states.
          *
@@ -6294,6 +6549,26 @@
         }
 
         /**
+         * Gets the discrete events the op was accessed (performed) for a
+         * range of uid states.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return The discrete the op was accessed in the background.
+         *
+         * @see #getBackgroundDiscreteAccesses(int)
+         * @see #getForegroundDiscreteAccesses(int)
+         */
+        @NonNull
+        public List<AttributedOpEntry> getDiscreteAccesses(@UidState int fromUidState,
+                @UidState int toUidState, @OpFlags int flags) {
+            return listForFlagsInStates(mDiscreteAccesses, fromUidState, toUidState, flags);
+        }
+
+        /**
          * Gets the number times the op was rejected in the foreground.
          *
          * @param flags The flags which are any combination of
@@ -6427,6 +6702,7 @@
             writeLongSparseLongArrayToParcel(mAccessCount, parcel);
             writeLongSparseLongArrayToParcel(mRejectCount, parcel);
             writeLongSparseLongArrayToParcel(mAccessDuration, parcel);
+            writeDiscreteAccessArrayToParcel(mDiscreteAccesses, parcel);
         }
 
         @Override
@@ -6447,7 +6723,11 @@
             if (!equalsLongSparseLongArray(mRejectCount, other.mRejectCount)) {
                 return false;
             }
-            return equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration);
+            if (!equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration)) {
+                return false;
+            }
+            return mDiscreteAccesses == null ? (other.mDiscreteAccesses == null ? true
+                    : false) : mDiscreteAccesses.equals(other.mDiscreteAccesses);
         }
 
         @Override
@@ -6456,6 +6736,7 @@
             result = 31 * result + Objects.hashCode(mAccessCount);
             result = 31 * result + Objects.hashCode(mRejectCount);
             result = 31 * result + Objects.hashCode(mAccessDuration);
+            result = 31 * result + Objects.hashCode(mDiscreteAccesses);
             return result;
         }
 
@@ -6484,6 +6765,13 @@
             return mAccessDuration;
         }
 
+        private @NonNull List<AttributedOpEntry> getOrCreateDiscreteAccesses() {
+            if (mDiscreteAccesses == null) {
+                mDiscreteAccesses = new ArrayList<>();
+            }
+            return mDiscreteAccesses;
+        }
+
         /**
          * Multiplies the entries in the array with the passed in scale factor and
          * rounds the result at up 0.5 boundary.
@@ -6574,6 +6862,32 @@
     }
 
     /**
+     * Returns list of events filtered by UidState and UID flags.
+     *
+     * @param accesses The events list.
+     * @param beginUidState The beginning UID state (inclusive).
+     * @param endUidState The end UID state (inclusive).
+     * @param flags The UID flags.
+     * @return filtered list of events.
+     */
+    private static List<AttributedOpEntry> listForFlagsInStates(List<AttributedOpEntry> accesses,
+            @UidState int beginUidState, @UidState int endUidState, @OpFlags int flags) {
+        List<AttributedOpEntry> result = new ArrayList<>();
+        if (accesses == null) {
+            return result;
+        }
+        int nAccesses = accesses.size();
+        for (int i = 0; i < nAccesses; i++) {
+            AttributedOpEntry entry = accesses.get(i);
+            if (entry.getLastAccessTime(beginUidState, endUidState, flags) == -1) {
+                continue;
+            }
+            result.add(entry);
+        }
+        return result;
+    }
+
+    /**
      * Callback for notification of changes to operation state.
      */
     public interface OnOpChangedListener {
@@ -6796,8 +7110,9 @@
         Objects.requireNonNull(callback, "callback cannot be null");
         try {
             mService.getHistoricalOps(request.mUid, request.mPackageName, request.mAttributionTag,
-                    request.mOpNames, request.mFilter, request.mBeginTimeMillis,
-                    request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> {
+                    request.mOpNames, request.mHistoryFlags, request.mFilter,
+                    request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags,
+                    new RemoteCallback((result) -> {
                 final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
                 final long identity = Binder.clearCallingIdentity();
                 try {
@@ -6835,9 +7150,9 @@
         Objects.requireNonNull(callback, "callback cannot be null");
         try {
             mService.getHistoricalOpsFromDiskRaw(request.mUid, request.mPackageName,
-                    request.mAttributionTag, request.mOpNames, request.mFilter,
-                    request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags,
-                    new RemoteCallback((result) -> {
+                    request.mAttributionTag, request.mOpNames, request.mHistoryFlags,
+                    request.mFilter, request.mBeginTimeMillis, request.mEndTimeMillis,
+                    request.mFlags, new RemoteCallback((result) -> {
                 final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
                 final long identity = Binder.clearCallingIdentity();
                 try {
@@ -9072,6 +9387,32 @@
         return array;
     }
 
+    private static void writeDiscreteAccessArrayToParcel(
+            @Nullable List<AttributedOpEntry> array, @NonNull Parcel parcel) {
+        if (array != null) {
+            final int size = array.size();
+            parcel.writeInt(size);
+            for (int i = 0; i < size; i++) {
+                array.get(i).writeToParcel(parcel, 0);
+            }
+        } else {
+            parcel.writeInt(-1);
+        }
+    }
+
+    private static @Nullable List<AttributedOpEntry> readDiscreteAccessArrayFromParcel(
+            @NonNull Parcel parcel) {
+        final int size = parcel.readInt();
+        if (size < 0) {
+            return null;
+        }
+        final List<AttributedOpEntry> array = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            array.add(new AttributedOpEntry(parcel));
+        }
+        return array;
+    }
+
     /**
      * Collects the keys from an array to the result creating the result if needed.
      *
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8167622..bc24e97 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -24,6 +24,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.AttrRes;
 import android.annotation.ColorInt;
 import android.annotation.ColorRes;
 import android.annotation.DimenRes;
@@ -98,6 +99,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
 
@@ -3649,11 +3651,6 @@
         private int mCachedContrastColorIsFor = COLOR_INVALID;
 
         /**
-         * A neutral color color that can be used for icons.
-         */
-        private int mNeutralColor = COLOR_INVALID;
-
-        /**
          * Caches an instance of StandardTemplateParams. Note that this may have been used before,
          * so make sure to call {@link StandardTemplateParams#reset()} before using it.
          */
@@ -3666,6 +3663,7 @@
         private boolean mRebuildStyledRemoteViews;
 
         private boolean mTintActionButtons;
+        private boolean mTintWithThemeAccent;
         private boolean mInNightMode;
 
         /**
@@ -3701,6 +3699,7 @@
             mContext = context;
             Resources res = mContext.getResources();
             mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);
+            mTintWithThemeAccent = res.getBoolean(R.bool.config_tintNotificationsWithTheme);
 
             if (res.getBoolean(R.bool.config_enableNightMode)) {
                 Configuration currentConfig = res.getConfiguration();
@@ -4891,12 +4890,10 @@
         }
 
         private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) {
-            // TODO(b/180334837): Get buy-in on this color, or make sure to give this the
-            //  accent color, while still accommodating the colorized state.
             contentView.setDrawableTint(
                     R.id.phishing_alert,
                     false /* targetBackground */,
-                    getPrimaryTextColor(p),
+                    getErrorColor(p),
                     PorterDuff.Mode.SRC_ATOP);
         }
 
@@ -4943,7 +4940,7 @@
             contentView.setDrawableTint(
                     R.id.alerted_icon,
                     false /* targetBackground */,
-                    getNeutralColor(p),
+                    getHeaderIconColor(p),
                     PorterDuff.Mode.SRC_ATOP);
         }
 
@@ -5057,10 +5054,9 @@
             return text;
         }
 
-        private void setTextViewColorPrimary(RemoteViews contentView, int id,
+        private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id,
                 StandardTemplateParams p) {
-            ensureColors(p);
-            contentView.setTextColor(id, mPrimaryTextColor);
+            contentView.setTextColor(id, getPrimaryTextColor(p));
         }
 
         private boolean hasForegroundColor() {
@@ -5068,53 +5064,34 @@
         }
 
         /**
-         * Return the primary text color using the existing template params
-         * @hide
-         */
-        @VisibleForTesting
-        public int getPrimaryTextColor() {
-            return getPrimaryTextColor(mParams);
-        }
-
-        /**
          * @param p the template params to inflate this with
          * @return the primary text color
          * @hide
          */
         @VisibleForTesting
-        public int getPrimaryTextColor(StandardTemplateParams p) {
+        public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) {
             ensureColors(p);
             return mPrimaryTextColor;
         }
 
         /**
-         * Return the secondary text color using the existing template params
-         * @hide
-         */
-        @VisibleForTesting
-        public int getSecondaryTextColor() {
-            return getSecondaryTextColor(mParams);
-        }
-
-        /**
          * @param p the template params to inflate this with
          * @return the secondary text color
          * @hide
          */
         @VisibleForTesting
-        public int getSecondaryTextColor(StandardTemplateParams p) {
+        public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) {
             ensureColors(p);
             return mSecondaryTextColor;
         }
 
-        private void setTextViewColorSecondary(RemoteViews contentView, int id,
+        private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id,
                 StandardTemplateParams p) {
-            ensureColors(p);
-            contentView.setTextColor(id, mSecondaryTextColor);
+            contentView.setTextColor(id, getSecondaryTextColor(p));
         }
 
         private void ensureColors(StandardTemplateParams p) {
-            int backgroundColor = getBackgroundColor(p);
+            int backgroundColor = getUnresolvedBackgroundColor(p);
             if (mPrimaryTextColor == COLOR_INVALID
                     || mSecondaryTextColor == COLOR_INVALID
                     || mTextColorsAreForBackground != backgroundColor) {
@@ -5217,7 +5194,7 @@
                         R.id.progress, ColorStateList.valueOf(mContext.getColor(
                                 R.color.notification_progress_background_color)));
                 if (getRawColor(p) != COLOR_DEFAULT) {
-                    int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p);
+                    int color = getAccentColor(p);
                     ColorStateList colorStateList = ColorStateList.valueOf(color);
                     contentView.setProgressTintList(R.id.progress, colorStateList);
                     contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
@@ -5326,11 +5303,18 @@
         }
 
         private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) {
-            int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p);
-            contentView.setDrawableTint(R.id.expand_button, false, color,
-                    PorterDuff.Mode.SRC_ATOP);
-            contentView.setInt(R.id.expand_button, "setOriginalNotificationColor",
-                    color);
+            // set default colors
+            int textColor = getPrimaryTextColor(p);
+            int pillColor = getProtectionColor(p);
+            contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
+            contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
+            // Use different highlighted colors except when low-priority mode prevents that
+            if (!p.forceDefaultColor) {
+                textColor = getBackgroundColor(p);
+                pillColor = getAccentColor(p);
+            }
+            contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
+            contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
         }
 
         private void bindHeaderChronometerAndTime(RemoteViews contentView,
@@ -5461,11 +5445,7 @@
             }
             contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
-            if (isColorized(p)) {
-                setTextViewColorPrimary(contentView, R.id.app_name_text, p);
-            } else {
-                contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
-            }
+            contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
             return true;
         }
 
@@ -5555,6 +5535,10 @@
 
             resetStandardTemplateWithActions(big);
             bindSnoozeAction(big, p);
+            // color the snooze and bubble actions with the theme color
+            ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p));
+            big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor);
+            big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor);
 
             boolean validRemoteInput = false;
 
@@ -5604,8 +5588,7 @@
                         showSpinner ? View.VISIBLE : View.GONE);
                 big.setProgressIndeterminateTintList(
                         R.id.notification_material_reply_progress,
-                        ColorStateList.valueOf(
-                                isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p)));
+                        ColorStateList.valueOf(getAccentColor(p)));
 
                 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText())
                         && p.maxRemoteInputHistory > 1) {
@@ -6021,14 +6004,14 @@
                 // change the background bgColor
                 CharSequence title = action.title;
                 ColorStateList[] outResultColor = new ColorStateList[1];
-                int background = resolveBackgroundColor(p);
+                int background = getBackgroundColor(p);
                 if (isLegacy()) {
                     title = ContrastColorUtil.clearColorSpans(title);
                 } else {
                     title = ensureColorSpanContrast(title, background, outResultColor);
                 }
                 button.setTextViewText(R.id.action0, processTextSpans(title));
-                int textColor = getPrimaryTextColor(p);
+                final int textColor;
                 boolean hasColorOverride = outResultColor[0] != null;
                 if (hasColorOverride) {
                     // There's a span spanning the full text, let's take it and use it as the
@@ -6036,9 +6019,11 @@
                     background = outResultColor[0].getDefaultColor();
                     textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
                             background, mInNightMode);
-                } else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p)
-                        && mTintActionButtons && !mInNightMode) {
-                    textColor = resolveContrastColor(p);
+                } else if (mTintActionButtons && !mInNightMode
+                        && getRawColor(p) != COLOR_DEFAULT && !isColorized(p)) {
+                    textColor = getAccentColor(p);
+                } else {
+                    textColor = getPrimaryTextColor(p);
                 }
                 button.setTextColor(R.id.action0, textColor);
                 // We only want about 20% alpha for the ripple
@@ -6056,11 +6041,7 @@
             } else {
                 button.setTextViewText(R.id.action0, processTextSpans(
                         processLegacyText(action.title)));
-                if (isColorized(p)) {
-                    setTextViewColorPrimary(button, R.id.action0, p);
-                } else if (getRawColor(p) != COLOR_DEFAULT && mTintActionButtons) {
-                    button.setTextColor(R.id.action0, resolveContrastColor(p));
-                }
+                button.setTextColor(R.id.action0, getStandardActionColor(p));
             }
             // CallStyle notifications add action buttons which don't actually exist in mActions,
             //  so we have to omit the index in that case.
@@ -6170,9 +6151,9 @@
         private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
                 StandardTemplateParams p) {
             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
-            int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p);
+            int color = getSmallIconColor(p);
             contentView.setInt(R.id.icon, "setBackgroundColor",
-                    resolveBackgroundColor(p));
+                    getBackgroundColor(p));
             contentView.setInt(R.id.icon, "setOriginalIconColor",
                     colorable ? color : COLOR_INVALID);
         }
@@ -6187,7 +6168,7 @@
             if (largeIcon != null && isLegacy()
                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
                 // resolve color will fall back to the default when legacy
-                int color = resolveContrastColor(p);
+                int color = getContrastColor(p);
                 contentView.setInt(R.id.icon, "setOriginalIconColor", color);
             }
         }
@@ -6198,14 +6179,94 @@
             }
         }
 
-        int resolveContrastColor(StandardTemplateParams p) {
+        /**
+         * Gets the standard action button color
+         */
+        private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) {
+            return mTintActionButtons || isColorized(p) ? getAccentColor(p) : getNeutralColor(p);
+        }
+
+        /**
+         * Gets a neutral color that can be used for icons or similar that should not stand out.
+         */
+        private @ColorInt int getHeaderIconColor(StandardTemplateParams p) {
+            return isColorized(p) ? getSecondaryTextColor(p) : getNeutralColor(p);
+        }
+
+        /**
+         * Gets the foreground color of the small icon.  If the notification is colorized, this
+         * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
+         */
+        private @ColorInt int getSmallIconColor(StandardTemplateParams p) {
+            return isColorized(p) ? getPrimaryTextColor(p) : getContrastColor(p);
+        }
+
+        /**
+         * Gets the accent color for colored UI elements.  If we're tinting with the theme
+         * accent, this is the theme accent color, otherwise this would be identical to
+         * {@link #getSmallIconColor(StandardTemplateParams)}.
+         */
+        private @ColorInt int getAccentColor(StandardTemplateParams p) {
+            if (isColorized(p)) {
+                return getPrimaryTextColor(p);
+            }
+            if (mTintWithThemeAccent) {
+                int color = obtainThemeColor(R.attr.colorAccent, COLOR_INVALID);
+                if (color != COLOR_INVALID) {
+                    return color;
+                }
+            }
+            return getContrastColor(p);
+        }
+
+        /**
+         * Gets the "surface protection" color from the theme, or a variant of the normal background
+         * color when colorized, or when not using theme color tints.
+         */
+        private @ColorInt int getProtectionColor(StandardTemplateParams p) {
+            if (mTintWithThemeAccent && !isColorized(p)) {
+                int color = obtainThemeColor(R.attr.colorBackgroundFloating, COLOR_INVALID);
+                if (color != COLOR_INVALID) {
+                    return color;
+                }
+            }
+            // TODO(b/181048615): What color should we use for the expander pill when colorized
+            return ColorUtils.blendARGB(getPrimaryTextColor(p), getBackgroundColor(p), 0.8f);
+        }
+
+        /**
+         * Gets the theme's error color, or the primary text color for colorized notifications.
+         */
+        private @ColorInt int getErrorColor(StandardTemplateParams p) {
+            if (!isColorized(p)) {
+                int color = obtainThemeColor(R.attr.colorError, COLOR_INVALID);
+                if (color != COLOR_INVALID) {
+                    return color;
+                }
+            }
+            return getPrimaryTextColor(p);
+        }
+
+        /**
+         * Gets the theme's background color
+         */
+        private @ColorInt int getDefaultBackgroundColor() {
+            return obtainThemeColor(R.attr.colorBackground,
+                    mInNightMode ? Color.BLACK : Color.WHITE);
+        }
+
+        /**
+         * Gets the contrast-adjusted version of the color provided by the app.
+         */
+        private @ColorInt int getContrastColor(StandardTemplateParams p) {
             int rawColor = getRawColor(p);
             if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
                 return mCachedContrastColor;
             }
 
             int color;
-            int background = obtainBackgroundColor();
+            // TODO: Maybe use getBackgroundColor(p) instead -- but doing so could break the cache
+            int background = getDefaultBackgroundColor();
             if (rawColor == COLOR_DEFAULT) {
                 ensureColors(p);
                 color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode);
@@ -6224,28 +6285,29 @@
         /**
          * Return the raw color of this Notification, which doesn't necessarily satisfy contrast.
          *
-         * @see #resolveContrastColor(StandardTemplateParams) for the contrasted color
+         * @see #getContrastColor(StandardTemplateParams) for the contrasted color
          * @param p the template params to inflate this with
          */
-        private int getRawColor(StandardTemplateParams p) {
+        private @ColorInt int getRawColor(StandardTemplateParams p) {
             if (p.forceDefaultColor) {
                 return COLOR_DEFAULT;
             }
             return mN.color;
         }
 
-        int resolveNeutralColor() {
-            if (mNeutralColor != COLOR_INVALID) {
-                return mNeutralColor;
-            }
-            int background = obtainBackgroundColor();
-            mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background,
+        /**
+         * Gets a neutral palette color; this is a contrast-satisfied version of the default color.
+         * @param p the template params to inflate this with
+         */
+        private @ColorInt int getNeutralColor(StandardTemplateParams p) {
+            int background = getBackgroundColor(p);
+            int neutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background,
                     mInNightMode);
-            if (Color.alpha(mNeutralColor) < 255) {
+            if (Color.alpha(neutralColor) < 255) {
                 // alpha doesn't go well for color filters, so let's blend it manually
-                mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background);
+                neutralColor = ContrastColorUtil.compositeColors(neutralColor, background);
             }
-            return mNeutralColor;
+            return neutralColor;
         }
 
         /**
@@ -6389,8 +6451,11 @@
             return mN;
         }
 
-        private @ColorInt int obtainBackgroundColor() {
-            int defaultColor = mInNightMode ? Color.BLACK : Color.WHITE;
+        /**
+         * Returns the color for the given Theme.DeviceDefault.DayNight attribute, or
+         * defValue if that could not be completed
+         */
+        private @ColorInt int obtainThemeColor(@AttrRes int attrRes, @ColorInt int defaultColor) {
             Resources.Theme theme = mContext.getTheme();
             if (theme == null) {
                 // Running unit tests with mocked context
@@ -6398,7 +6463,7 @@
             }
             theme = new ContextThemeWrapper(mContext, R.style.Theme_DeviceDefault_DayNight)
                     .getTheme();
-            TypedArray ta = theme.obtainStyledAttributes(new int[]{R.attr.colorBackground});
+            TypedArray ta = theme.obtainStyledAttributes(new int[]{attrRes});
             if (ta == null) {
                 return defaultColor;
             }
@@ -6517,7 +6582,11 @@
             return R.layout.notification_material_action_tombstone;
         }
 
-        private int getBackgroundColor(StandardTemplateParams p) {
+        /**
+         * Gets the background color, with {@link #COLOR_DEFAULT} being a valid return value,
+         * which must be resolved by the caller before being used.
+         */
+        private @ColorInt int getUnresolvedBackgroundColor(StandardTemplateParams p) {
             if (isColorized(p)) {
                 return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p);
             } else {
@@ -6526,33 +6595,17 @@
         }
 
         /**
-         * Gets a neutral color that can be used for icons or similar that should not stand out.
-         * @param p the template params to inflate this with
+         * Same as {@link #getUnresolvedBackgroundColor(StandardTemplateParams)} except that it
+         * also resolves the default color to the background.
          */
-        private int getNeutralColor(StandardTemplateParams p) {
-            if (isColorized(p)) {
-                return getSecondaryTextColor(p);
-            } else {
-                return resolveNeutralColor();
-            }
-        }
-
-        /**
-         * Same as getBackgroundColor but also resolved the default color to the background.
-         * @param p the template params to inflate this with
-         */
-        private int resolveBackgroundColor(StandardTemplateParams p) {
-            int backgroundColor = getBackgroundColor(p);
+        private @ColorInt int getBackgroundColor(StandardTemplateParams p) {
+            int backgroundColor = getUnresolvedBackgroundColor(p);
             if (backgroundColor == COLOR_DEFAULT) {
-                backgroundColor = obtainBackgroundColor();
+                backgroundColor = getDefaultBackgroundColor();
             }
             return backgroundColor;
         }
 
-        private boolean shouldTintActionButtons() {
-            return mTintActionButtons;
-        }
-
         private boolean textColorsNeedInversion() {
             if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) {
                 return false;
@@ -6570,7 +6623,7 @@
          *
          * @hide
          */
-        public void setColorPalette(int backgroundColor, int foregroundColor) {
+        public void setColorPalette(@ColorInt int backgroundColor, @ColorInt int foregroundColor) {
             mBackgroundColor = backgroundColor;
             mForegroundColor = foregroundColor;
             mTextColorsAreForBackground = COLOR_INVALID;
@@ -8200,16 +8253,14 @@
                         TypedValue.COMPLEX_UNIT_DIP);
             }
             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
-                    mBuilder.isColorized(p)
-                            ? mBuilder.getPrimaryTextColor(p)
-                            : mBuilder.resolveContrastColor(p));
+                    mBuilder.getSmallIconColor(p));
             contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor",
                     mBuilder.getPrimaryTextColor(p));
             contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor",
                     mBuilder.getSecondaryTextColor(p));
             contentView.setInt(R.id.status_bar_latest_event_content,
                     "setNotificationBackgroundColor",
-                    mBuilder.resolveBackgroundColor(p));
+                    mBuilder.getBackgroundColor(p));
             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
                     isCollapsed);
             contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement",
@@ -8964,14 +9015,7 @@
 
             // If the action buttons should not be tinted, then just use the default
             // notification color. Otherwise, just use the passed-in color.
-            Resources resources = mBuilder.mContext.getResources();
-            Configuration currentConfig = resources.getConfiguration();
-            boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
-                    == Configuration.UI_MODE_NIGHT_YES;
-            int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized(p)
-                    ? getActionColor(p)
-                    : ContrastColorUtil.resolveColor(mBuilder.mContext,
-                            Notification.COLOR_DEFAULT, inNightMode);
+            int tintColor = mBuilder.getStandardActionColor(p);
 
             container.setDrawableTint(buttonId, false, tintColor,
                     PorterDuff.Mode.SRC_ATOP);
@@ -9027,11 +9071,6 @@
             return view;
         }
 
-        private int getActionColor(StandardTemplateParams p) {
-            return mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p)
-                    : mBuilder.resolveContrastColor(p);
-        }
-
         private RemoteViews makeMediaBigContentView() {
             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
             // Dont add an expanded view if there is no more content to be revealed
@@ -9373,7 +9412,6 @@
                     .hideLargeIcon(true)
                     .text(text)
                     .summaryText(mBuilder.processLegacyText(mVerificationText));
-            // TODO(b/179178086): hide the snooze button
             RemoteViews contentView = mBuilder.applyStandardTemplate(
                     mBuilder.getCallLayoutResource(), p, null /* result */);
 
@@ -9390,11 +9428,9 @@
 
             // Bind some custom CallLayout properties
             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
-                    mBuilder.isColorized(p)
-                            ? mBuilder.getPrimaryTextColor(p)
-                            : mBuilder.resolveContrastColor(p));
+                    mBuilder.getSmallIconColor(p));
             contentView.setInt(R.id.status_bar_latest_event_content,
-                    "setNotificationBackgroundColor", mBuilder.resolveBackgroundColor(p));
+                    "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p));
             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
                     mBuilder.mN.mLargeIcon);
             contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 1ff64db..e0e9b62 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -73,6 +73,7 @@
 per-file Fragment.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file *Task* = file:/services/core/java/com/android/server/wm/OWNERS
 per-file Window* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS
 
 # TODO(b/174932174): determine the ownership of KeyguardManager.java
 
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index ea7eab2..358ce6a 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -202,7 +202,7 @@
         }
         if (in.readInt() != 0) {
             mUserActions = new ArrayList<>();
-            in.readParcelableList(mUserActions, RemoteAction.class.getClassLoader());
+            in.readTypedList(mUserActions, RemoteAction.CREATOR);
         }
         if (in.readInt() != 0) {
             mSourceRectHint = Rect.CREATOR.createFromParcel(in);
@@ -386,7 +386,7 @@
         }
         if (mUserActions != null) {
             out.writeInt(1);
-            out.writeParcelableList(mUserActions, 0);
+            out.writeTypedList(mUserActions, 0);
         } else {
             out.writeInt(0);
         }
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 1d5dc1d..098d8b6 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -16,6 +16,8 @@
 
 package android.app.usage;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -28,8 +30,11 @@
 import android.net.ConnectivityManager;
 import android.net.DataUsageRequest;
 import android.net.INetworkStatsService;
+import android.net.Network;
 import android.net.NetworkStack;
+import android.net.NetworkStateSnapshot;
 import android.net.NetworkTemplate;
+import android.net.UnderlyingNetworkInfo;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.net.netstats.provider.NetworkStatsProvider;
 import android.os.Binder;
@@ -48,6 +53,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.NetworkIdentityUtils;
 
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -633,6 +639,50 @@
         return template;
     }
 
+    /**
+     *  Notify {@code NetworkStatsService} about network status changed.
+     *
+     *  Notifies NetworkStatsService of network state changes for data usage accounting purposes.
+     *
+     *  To avoid races that attribute data usage to wrong network, such as new network with
+     *  the same interface after SIM hot-swap, this function will not return until
+     *  {@code NetworkStatsService} finishes its work of retrieving traffic statistics from
+     *  all data sources.
+     *
+     * @param defaultNetworks the list of all networks that could be used by network traffic that
+     *                        does not explicitly select a network.
+     * @param networkStateSnapshots a list of {@link NetworkStateSnapshot}s, one for
+     *                              each network that is currently connected.
+     * @param activeIface the active (i.e., connected) default network interface for the calling
+     *                    uid. Used to determine on which network future calls to
+     *                    {@link android.net.TrafficStats#incrementOperationCount} applies to.
+     * @param underlyingNetworkInfos the list of underlying network information for all
+     *                               currently-connected VPNs.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK})
+    public void notifyNetworkStatus(
+            @NonNull List<Network> defaultNetworks,
+            @NonNull List<NetworkStateSnapshot> networkStateSnapshots,
+            @Nullable String activeIface,
+            @NonNull List<UnderlyingNetworkInfo> underlyingNetworkInfos) {
+        try {
+            Objects.requireNonNull(defaultNetworks);
+            Objects.requireNonNull(networkStateSnapshots);
+            Objects.requireNonNull(underlyingNetworkInfos);
+            // TODO: Change internal namings after the name is decided.
+            mService.forceUpdateIfaces(defaultNetworks.toArray(new Network[0]),
+                    networkStateSnapshots.toArray(new NetworkStateSnapshot[0]), activeIface,
+                    underlyingNetworkInfos.toArray(new UnderlyingNetworkInfo[0]));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private static class CallbackHandler extends Handler {
         private final int mNetworkType;
         private final String mSubscriberId;
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index e7661db..ec94faa 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -374,6 +374,35 @@
     public static final String ACTION_SDP_RECORD =
             "android.bluetooth.device.action.SDP_RECORD";
 
+    /** @hide */
+    @IntDef(prefix = "METADATA_", value = {
+            METADATA_MANUFACTURER_NAME,
+            METADATA_MODEL_NAME,
+            METADATA_SOFTWARE_VERSION,
+            METADATA_HARDWARE_VERSION,
+            METADATA_COMPANION_APP,
+            METADATA_MAIN_ICON,
+            METADATA_IS_UNTETHERED_HEADSET,
+            METADATA_UNTETHERED_LEFT_ICON,
+            METADATA_UNTETHERED_RIGHT_ICON,
+            METADATA_UNTETHERED_CASE_ICON,
+            METADATA_UNTETHERED_LEFT_BATTERY,
+            METADATA_UNTETHERED_RIGHT_BATTERY,
+            METADATA_UNTETHERED_CASE_BATTERY,
+            METADATA_UNTETHERED_LEFT_CHARGING,
+            METADATA_UNTETHERED_RIGHT_CHARGING,
+            METADATA_UNTETHERED_CASE_CHARGING,
+            METADATA_ENHANCED_SETTINGS_UI_URI,
+            METADATA_DEVICE_TYPE,
+            METADATA_MAIN_BATTERY,
+            METADATA_MAIN_CHARGING,
+            METADATA_MAIN_LOW_BATTERY_THRESHOLD,
+            METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD,
+            METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD,
+            METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MetadataKey{}
+
     /**
      * Maximum length of a metadata entry, this is to avoid exploding Bluetooth
      * disk usage
@@ -523,6 +552,89 @@
     public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16;
 
     /**
+     * Type of the Bluetooth device, must be within the list of
+     * BluetoothDevice.DEVICE_TYPE_*
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_DEVICE_TYPE = 17;
+
+    /**
+     * Battery level of the Bluetooth device, use when the Bluetooth device
+     * does not support HFP battery indicator.
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MAIN_BATTERY = 18;
+
+    /**
+     * Whether the device is charging.
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MAIN_CHARGING = 19;
+
+    /**
+     * The battery threshold of the Bluetooth device to show low battery icon.
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MAIN_LOW_BATTERY_THRESHOLD = 20;
+
+    /**
+     * The battery threshold of the left headset to show low battery icon.
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD = 21;
+
+    /**
+     * The battery threshold of the right headset to show low battery icon.
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD = 22;
+
+    /**
+     * The battery threshold of the case to show low battery icon.
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD = 23;
+
+    /**
+     * Device type which is used in METADATA_DEVICE_TYPE
+     * Indicates this Bluetooth device is a standard Bluetooth accessory or
+     * not listed in METADATA_DEVICE_TYPE_*.
+     * @hide
+     */
+    @SystemApi
+    public static final String DEVICE_TYPE_DEFAULT = "Default";
+
+    /**
+     * Device type which is used in METADATA_DEVICE_TYPE
+     * Indicates this Bluetooth device is a watch.
+     * @hide
+     */
+    @SystemApi
+    public static final String DEVICE_TYPE_WATCH = "Watch";
+
+    /**
+     * Device type which is used in METADATA_DEVICE_TYPE
+     * Indicates this Bluetooth device is an untethered headset.
+     * @hide
+     */
+    @SystemApi
+    public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset";
+
+    /**
      * Broadcast Action: This intent is used to broadcast the {@link UUID}
      * wrapped as a {@link android.os.ParcelUuid} of the remote device after it
      * has been fetched. This intent is sent only when the UUIDs of the remote
@@ -2316,7 +2428,7 @@
     */
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
-    public boolean setMetadata(int key, @NonNull byte[] value) {
+    public boolean setMetadata(@MetadataKey int key, @NonNull byte[] value) {
         final IBluetooth service = sService;
         if (service == null) {
             Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata");
@@ -2344,7 +2456,7 @@
     @SystemApi
     @Nullable
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
-    public byte[] getMetadata(int key) {
+    public byte[] getMetadata(@MetadataKey int key) {
         final IBluetooth service = sService;
         if (service == null) {
             Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata");
@@ -2357,4 +2469,14 @@
             return null;
         }
     }
+
+    /**
+     * Get the maxinum metadata key ID.
+     *
+     * @return the last supported metadata key
+     * @hide
+     */
+    public static @MetadataKey int getMaxMetadataKey() {
+        return METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD;
+    }
 }
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
index e3a130c..4e64dbe 100644
--- a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
@@ -22,7 +22,7 @@
 /**
  * The {@link PeriodicAdvertisingParameters} provide a way to adjust periodic
  * advertising preferences for each Bluetooth LE advertising set. Use {@link
- * AdvertisingSetParameters.Builder} to create an instance of this class.
+ * PeriodicAdvertisingParameters.Builder} to create an instance of this class.
  */
 public final class PeriodicAdvertisingParameters implements Parcelable {
 
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 7ecb112..7696cbe 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -42,6 +42,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 
 import libcore.io.IoUtils;
@@ -161,18 +162,20 @@
         intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         intentFilter.addDataScheme("package");
-        mContext.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, intentFilter, null, null);
+        Handler handler = BackgroundThread.getHandler();
+        mContext.registerReceiverAsUser(
+                mPackageReceiver, UserHandle.ALL, intentFilter, null, handler);
 
         // Register for events related to sdcard installation.
         IntentFilter sdFilter = new IntentFilter();
         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
-        mContext.registerReceiver(mExternalReceiver, sdFilter);
+        mContext.registerReceiver(mExternalReceiver, sdFilter, null, handler);
 
         // Register for user-related events
         IntentFilter userFilter = new IntentFilter();
         sdFilter.addAction(Intent.ACTION_USER_REMOVED);
-        mContext.registerReceiver(mUserRemovedReceiver, userFilter);
+        mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler);
     }
 
     private void handlePackageEvent(Intent intent, int userId) {
@@ -265,7 +268,7 @@
 
     public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) {
         if (handler == null) {
-            handler = new Handler(mContext.getMainLooper());
+            handler = BackgroundThread.getHandler();
         }
         synchronized (this) {
             mHandler = handler;
diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS
index d302b0a..cf7e689 100644
--- a/core/java/android/content/pm/permission/OWNERS
+++ b/core/java/android/content/pm/permission/OWNERS
@@ -1,10 +1,8 @@
 # Bug component: 137825
 
+include platform/frameworks/base:/core/java/android/permission/OWNERS
+
 toddke@android.com
 toddke@google.com
 patb@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
-zhanghai@google.com
-evanseverson@google.com
-ntmyren@google.com
+
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 06b5b67..a5c9a7f 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -34,10 +34,7 @@
 import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
 import android.os.ParcelFileDescriptor;
 import android.os.SharedMemory;
-import android.system.ErrnoException;
 
-import java.io.FileDescriptor;
-import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.UUID;
@@ -111,13 +108,9 @@
         aidlModel.type = apiModel.getType();
         aidlModel.uuid = api2aidlUuid(apiModel.getUuid());
         aidlModel.vendorUuid = api2aidlUuid(apiModel.getVendorUuid());
-        try {
-            aidlModel.data = ParcelFileDescriptor.dup(
-                    byteArrayToSharedMemory(apiModel.getData(), "SoundTrigger SoundModel"));
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        aidlModel.dataSize = apiModel.getData().length;
+        byte[] data = apiModel.getData();
+        aidlModel.data = byteArrayToSharedMemory(data, "SoundTrigger SoundModel");
+        aidlModel.dataSize = data.length;
         return aidlModel;
     }
 
@@ -379,7 +372,7 @@
         return result;
     }
 
-    private static @Nullable FileDescriptor byteArrayToSharedMemory(byte[] data, String name) {
+    private static @Nullable ParcelFileDescriptor byteArrayToSharedMemory(byte[] data, String name) {
         if (data.length == 0) {
             return null;
         }
@@ -389,8 +382,10 @@
             ByteBuffer buffer = shmem.mapReadWrite();
             buffer.put(data);
             shmem.unmap(buffer);
-            return shmem.getFileDescriptor();
-        } catch (ErrnoException e) {
+            ParcelFileDescriptor fd = shmem.getFdDup();
+            shmem.close();
+            return fd;
+        } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 183f500..cc1312b 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -24,10 +24,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.content.pm.PackageManager;
-import android.os.Process;
 import android.security.Credentials;
-import android.security.KeyStore;
-import android.security.keystore.AndroidKeyStoreProvider;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.net.VpnProfile;
@@ -35,7 +32,9 @@
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
+import java.security.Key;
 import java.security.KeyFactory;
+import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.cert.CertificateEncodingException;
@@ -66,6 +65,7 @@
     /** Prefix for when a Private Key is stored directly in the profile @hide */
     public static final String PREFIX_INLINE = "INLINE:";
 
+    private static final String ANDROID_KEYSTORE_PROVIDER = "AndroidKeyStore";
     private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s";
     private static final String EMPTY_CERT = "";
 
@@ -430,32 +430,31 @@
         return profile;
     }
 
-    /**
-     * Constructs a Ikev2VpnProfile from an internal-use VpnProfile instance.
-     *
-     * <p>Redundant authentication information (not related to profile type) will be discarded.
-     *
-     * @hide
-     */
-    @NonNull
-    public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile)
-            throws IOException, GeneralSecurityException {
-        return fromVpnProfile(profile, null);
+    private static PrivateKey getPrivateKeyFromAndroidKeystore(String alias) {
+        try {
+            final KeyStore keystore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER);
+            keystore.load(null);
+            final Key key = keystore.getKey(alias, null);
+            if (!(key instanceof PrivateKey)) {
+                throw new IllegalStateException(
+                        "Unexpected key type returned from android keystore.");
+            }
+            return (PrivateKey) key;
+        } catch (Exception e) {
+            throw new IllegalStateException("Failed to load key from android keystore.", e);
+        }
     }
 
     /**
      * Builds the Ikev2VpnProfile from the given profile.
      *
      * @param profile the source VpnProfile to build from
-     * @param keyStore the Android Keystore instance to use to retrieve the private key, or null if
-     *     the private key is PEM-encoded into the profile.
      * @return The IKEv2/IPsec VPN profile
      * @hide
      */
     @NonNull
-    public static Ikev2VpnProfile fromVpnProfile(
-            @NonNull VpnProfile profile, @Nullable KeyStore keyStore)
-            throws IOException, GeneralSecurityException {
+    public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile)
+            throws GeneralSecurityException {
         final Builder builder = new Builder(profile.server, profile.ipsecIdentifier);
         builder.setProxy(profile.proxy);
         builder.setAllowedAlgorithms(profile.getAllowedAlgorithms());
@@ -479,12 +478,9 @@
             case TYPE_IKEV2_IPSEC_RSA:
                 final PrivateKey key;
                 if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) {
-                    Objects.requireNonNull(keyStore, "Missing Keystore for aliased PrivateKey");
-
                     final String alias =
                             profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length());
-                    key = AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
-                            keyStore, alias, Process.myUid());
+                    key = getPrivateKeyFromAndroidKeystore(alias);
                 } else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) {
                     key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length()));
                 } else {
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 8f1e2de..268002f 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -232,11 +232,10 @@
         ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA512, SDK_VERSION_ZERO);
         ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_AES_GCM, SDK_VERSION_ZERO);
 
-        // STOPSHIP: b/170424293 Use Build.VERSION_CODES.S when it is defined
-        ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.R + 1);
-        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.R + 1);
-        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.R + 1);
-        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.R + 1);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.S);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.S);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.S);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.S);
     }
 
     private static final Set<String> ENABLED_ALGOS =
diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java
index b403455..48bd297 100644
--- a/core/java/android/net/OemNetworkPreferences.java
+++ b/core/java/android/net/OemNetworkPreferences.java
@@ -29,7 +29,15 @@
 import java.util.Map;
 import java.util.Objects;
 
-/** @hide */
+/**
+ * Network preferences to set the default active network on a per-application basis as per a given
+ * {@link OemNetworkPreference}. An example of this would be to set an application's network
+ * preference to {@link #OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK} which would have the default
+ * network for that application set to an unmetered network first if available and if not, it then
+ * set that application's default network to an OEM managed network if available.
+ *
+ * @hide
+ */
 @SystemApi
 public final class OemNetworkPreferences implements Parcelable {
     /**
@@ -64,6 +72,10 @@
     @NonNull
     private final Bundle mNetworkMappings;
 
+    /**
+     * Return the currently built application package name to {@link OemNetworkPreference} mappings.
+     * @return the current network preferences map.
+     */
     @NonNull
     public Map<String, Integer> getNetworkPreferences() {
         return convertToUnmodifiableMap(mNetworkMappings);
@@ -105,6 +117,11 @@
             mNetworkMappings = new Bundle();
         }
 
+        /**
+         * Constructor to populate the builder's values with an already built
+         * {@link OemNetworkPreferences}.
+         * @param preferences the {@link OemNetworkPreferences} to populate with.
+         */
         public Builder(@NonNull final OemNetworkPreferences preferences) {
             Objects.requireNonNull(preferences);
             mNetworkMappings = (Bundle) preferences.mNetworkMappings.clone();
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index c8e682c..e068772 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -76,7 +76,9 @@
      * A sequential vibration effect should be performed by multiple vibrators in order.
      *
      * @see CombinedVibrationEffect.SequentialCombination
+     * @hide
      */
+    @TestApi
     @NonNull
     public static SequentialCombination startSequential() {
         return new SequentialCombination();
@@ -162,7 +164,9 @@
      * A combination of haptic effects that should be played in multiple vibrators in sequence.
      *
      * @see CombinedVibrationEffect#startSequential()
+     * @hide
      */
+    @TestApi
     public static final class SequentialCombination {
 
         private final ArrayList<CombinedVibrationEffect> mEffects = new ArrayList<>();
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 592e98a..87dced8 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -155,22 +155,21 @@
     }
 
     /**
-     * Set up an app's code path. The expected outcome of this method is:
+     * Link an app's files from the stage dir to the final installation location.
+     * The expected outcome of this method is:
      * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory
      * of {@code afterCodeFile}.
      * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}.
      *
      * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it.
-     *                       Should no longer have any APKs after this method is called.
      *                       Example: /data/app/vmdl*tmp
      * @param afterCodeFile Path that should will have APKs after this method is called. Its parent
      *                      directory should be bind-mounted to a directory under /data/incremental.
      *                      Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB]
      * @throws IllegalArgumentException
      * @throws IOException
-     * TODO(b/147371381): add unit tests
      */
-    public void renameCodePath(File beforeCodeFile, File afterCodeFile)
+    public void linkCodePath(File beforeCodeFile, File afterCodeFile)
             throws IllegalArgumentException, IOException {
         final File beforeCodeAbsolute = beforeCodeFile.getAbsoluteFile();
         final IncrementalStorage apkStorage = openStorage(beforeCodeAbsolute.toString());
@@ -188,7 +187,6 @@
         try {
             final String afterCodePathName = afterCodeFile.getName();
             linkFiles(apkStorage, beforeCodeAbsolute, "", linkedApkStorage, afterCodePathName);
-            apkStorage.unBind(beforeCodeAbsolute.toString());
         } catch (Exception e) {
             linkedApkStorage.unBind(targetStorageDir);
             throw e;
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 0041699..98b4e0b 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -28,6 +28,8 @@
 import android.os.storage.VolumeInfo;
 import android.os.storage.VolumeRecord;
 import com.android.internal.os.AppFuseMount;
+import android.app.PendingIntent;
+
 
 /**
  * WARNING! Update IMountService.h and IMountService.cpp if you change this
@@ -198,4 +200,5 @@
     void disableAppDataIsolation(in String pkgName, int pid, int userId) = 90;
     void notifyAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 91;
     void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 92;
+    PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 93;
     }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 7c8874c..c967deb 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -49,6 +49,7 @@
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -701,6 +702,33 @@
         }
     }
 
+    /**
+     * Returns a {@link PendingIntent} that can be used by Apps with
+     * {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission
+     * to launch the manageSpaceActivity for any App that implements it, irrespective of its
+     * exported status.
+     * <p>
+     * Caller has the responsibility of supplying a valid packageName which has
+     * manageSpaceActivity implemented.
+     *
+     * @param packageName package name for the App for which manageSpaceActivity is to be launched
+     * @param requestCode for launching the activity
+     * @return PendingIntent to launch the manageSpaceActivity if successful, null if the
+     * packageName doesn't have a manageSpaceActivity.
+     * @throws IllegalArgumentException an invalid packageName is supplied.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+    @Nullable
+    public PendingIntent getManageSpaceActivityIntent(
+            @NonNull String packageName, int requestCode) {
+        try {
+            return mStorageManager.getManageSpaceActivityIntent(packageName,
+                    requestCode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private ObbInfo getObbInfo(String canonicalPath) {
         try {
             final ObbInfo obbInfo = ObbScanner.getObbInfo(canonicalPath);
@@ -2738,10 +2766,11 @@
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public void notifyAppIoBlocked(@NonNull String volumeUuid, int uid, int tid,
+    public void notifyAppIoBlocked(@NonNull UUID volumeUuid, int uid, int tid,
             @AppIoBlockedReason int reason) {
+        Objects.requireNonNull(volumeUuid);
         try {
-            mStorageManager.notifyAppIoBlocked(volumeUuid, uid, tid, reason);
+            mStorageManager.notifyAppIoBlocked(convert(volumeUuid), uid, tid, reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2764,10 +2793,11 @@
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public void notifyAppIoResumed(@NonNull String volumeUuid, int uid, int tid,
+    public void notifyAppIoResumed(@NonNull UUID volumeUuid, int uid, int tid,
             @AppIoBlockedReason int reason) {
+        Objects.requireNonNull(volumeUuid);
         try {
-            mStorageManager.notifyAppIoResumed(volumeUuid, uid, tid, reason);
+            mStorageManager.notifyAppIoResumed(convert(volumeUuid), uid, tid, reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index b323468..19a3a8b 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,7 +1,13 @@
 # Bug component: 137825
 
+eugenesusla@google.com
 evanseverson@google.com
+evanxinchen@google.com
+ewol@google.com
+guojing@google.com
+jaysullivan@google.com
 ntmyren@google.com
-zhanghai@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
+theianchen@google.com
+zhanghai@google.com
diff --git a/core/java/android/permissionpresenterservice/OWNERS b/core/java/android/permissionpresenterservice/OWNERS
index b323468..fb6099c 100644
--- a/core/java/android/permissionpresenterservice/OWNERS
+++ b/core/java/android/permissionpresenterservice/OWNERS
@@ -1,7 +1,3 @@
 # Bug component: 137825
 
-evanseverson@google.com
-ntmyren@google.com
-zhanghai@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 7996f09..8a4812a 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5302,5 +5302,13 @@
          * @hide
          */
         public static final String COLUMN_RCS_CONFIG = "rcs_config";
+
+        /**
+         * TelephonyProvider column name for VoIMS provisioning. Default is 0.
+         * <P>Type: INTEGER </P>
+         *
+         * @hide
+         */
+        public static final String COLUMN_VOIMS_OPT_IN_STATUS = "voims_opt_in_status";
     }
 }
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 0499f39..09452828 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -86,19 +86,12 @@
     // accessibility from hanging
     private static final long REQUEST_PREPARER_TIMEOUT_MS = 500;
 
-    // Callbacks should have the same configuration of the flags below to allow satisfying a pending
-    // node request on prefetch
-    private static final int FLAGS_AFFECTING_REPORTED_DATA =
-            AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
-            | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
-
     private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
         new ArrayList<AccessibilityNodeInfo>();
 
     private final Object mLock = new Object();
 
-    @VisibleForTesting
-    public final PrivateHandler mHandler;
+    private final PrivateHandler mHandler;
 
     private final ViewRootImpl mViewRootImpl;
 
@@ -121,9 +114,6 @@
     private AddNodeInfosForViewId mAddNodeInfosForViewId;
 
     @GuardedBy("mLock")
-    private ArrayList<Message> mPendingFindNodeByIdMessages;
-
-    @GuardedBy("mLock")
     private int mNumActiveRequestPreparers;
     @GuardedBy("mLock")
     private List<MessageHolder> mMessagesWaitingForRequestPreparer;
@@ -138,7 +128,6 @@
         mViewRootImpl = viewRootImpl;
         mPrefetcher = new AccessibilityNodePrefetcher();
         mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class);
-        mPendingFindNodeByIdMessages = new ArrayList<>();
     }
 
     private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,
@@ -188,9 +177,6 @@
         args.arg4 = arguments;
         message.obj = args;
 
-        synchronized (mLock) {
-            mPendingFindNodeByIdMessages.add(message);
-        }
         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
     }
 
@@ -329,9 +315,6 @@
     }
 
     private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
-        synchronized (mLock) {
-            mPendingFindNodeByIdMessages.remove(message);
-        }
         final int flags = message.arg1;
 
         SomeArgs args = (SomeArgs) message.obj;
@@ -346,58 +329,22 @@
 
         args.recycle();
 
-        View rootView = null;
-        AccessibilityNodeInfo rootNode = null;
+        List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+        infos.clear();
         try {
             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
                 return;
             }
             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
-            rootView = findViewByAccessibilityId(accessibilityViewId);
-            if (rootView != null && isShown(rootView)) {
-                rootNode = populateAccessibilityNodeInfoForView(
-                        rootView, arguments, virtualDescendantId);
+            final View root = findViewByAccessibilityId(accessibilityViewId);
+            if (root != null && isShown(root)) {
+                mPrefetcher.prefetchAccessibilityNodeInfos(
+                        root, virtualDescendantId, flags, infos, arguments);
             }
         } finally {
-            updateInfoForViewportAndReturnFindNodeResult(
-                    rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode),
-                    callback, interactionId, spec, interactiveRegion);
+            updateInfosForViewportAndReturnFindNodeResult(
+                    infos, callback, interactionId, spec, interactiveRegion);
         }
-        ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
-        infos.clear();
-        mPrefetcher.prefetchAccessibilityNodeInfos(
-                rootView, rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode),
-                virtualDescendantId, flags, infos);
-        mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
-        updateInfosForViewPort(infos, spec, interactiveRegion);
-        returnPrefetchResult(interactionId, infos, callback);
-        returnPendingFindAccessibilityNodeInfosInPrefetch(rootNode, infos, flags);
-    }
-
-    private AccessibilityNodeInfo populateAccessibilityNodeInfoForView(
-            View view, Bundle arguments, int virtualViewId) {
-        AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
-        // Determine if we'll be populating extra data
-        final String extraDataRequested = (arguments == null) ? null
-                : arguments.getString(EXTRA_DATA_REQUESTED_KEY);
-        AccessibilityNodeInfo root = null;
-        if (provider == null) {
-            root = view.createAccessibilityNodeInfo();
-            if (root != null) {
-                if (extraDataRequested != null) {
-                    view.addExtraDataToAccessibilityNodeInfo(root, extraDataRequested, arguments);
-                }
-            }
-        } else {
-            root = provider.createAccessibilityNodeInfo(virtualViewId);
-            if (root != null) {
-                if (extraDataRequested != null) {
-                    provider.addExtraDataToAccessibilityNodeInfo(
-                            virtualViewId, root, extraDataRequested, arguments);
-                }
-            }
-        }
-        return root;
     }
 
     public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
@@ -436,7 +383,8 @@
         final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
         infos.clear();
         try {
-            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null
+                    || viewId == null) {
                 return;
             }
             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
@@ -455,7 +403,6 @@
                 mAddNodeInfosForViewId.reset();
             }
         } finally {
-            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfosForViewportAndReturnFindNodeResult(
                     infos, callback, interactionId, spec, interactiveRegion);
         }
@@ -538,7 +485,6 @@
                 }
             }
         } finally {
-            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfosForViewportAndReturnFindNodeResult(
                     infos, callback, interactionId, spec, interactiveRegion);
         }
@@ -630,7 +576,6 @@
                 }
             }
         } finally {
-            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfoForViewportAndReturnFindNodeResult(
                     focused, callback, interactionId, spec, interactiveRegion);
         }
@@ -685,7 +630,6 @@
                 }
             }
         } finally {
-            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfoForViewportAndReturnFindNodeResult(
                     next, callback, interactionId, spec, interactiveRegion);
         }
@@ -842,6 +786,33 @@
         }
     }
 
+    private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
+            MagnificationSpec spec) {
+        if (infos == null) {
+            return;
+        }
+        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
+        if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
+            final int infoCount = infos.size();
+            for (int i = 0; i < infoCount; i++) {
+                AccessibilityNodeInfo info = infos.get(i);
+                applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+            }
+        }
+    }
+
+    private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos,
+            Region interactiveRegion) {
+        if (interactiveRegion == null || infos == null) {
+            return;
+        }
+        final int infoCount = infos.size();
+        for (int i = 0; i < infoCount; i++) {
+            AccessibilityNodeInfo info = infos.get(i);
+            adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
+        }
+    }
+
     private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
             Region interactiveRegion) {
         if (interactiveRegion == null || info == null) {
@@ -862,6 +833,17 @@
         return false;
     }
 
+    private void adjustBoundsInScreenIfNeeded(List<AccessibilityNodeInfo> infos) {
+        if (infos == null || shouldBypassAdjustBoundsInScreen()) {
+            return;
+        }
+        final int infoCount = infos.size();
+        for (int i = 0; i < infoCount; i++) {
+            final AccessibilityNodeInfo info = infos.get(i);
+            adjustBoundsInScreenIfNeeded(info);
+        }
+    }
+
     private void adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info) {
         if (info == null || shouldBypassAdjustBoundsInScreen()) {
             return;
@@ -909,6 +891,17 @@
         return screenMatrix == null || screenMatrix.isIdentity();
     }
 
+    private void associateLeashedParentIfNeeded(List<AccessibilityNodeInfo> infos) {
+        if (infos == null || shouldBypassAssociateLeashedParent()) {
+            return;
+        }
+        final int infoCount = infos.size();
+        for (int i = 0; i < infoCount; i++) {
+            final AccessibilityNodeInfo info = infos.get(i);
+            associateLeashedParentIfNeeded(info);
+        }
+    }
+
     private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) {
         if (info == null || shouldBypassAssociateLeashedParent()) {
             return;
@@ -982,46 +975,18 @@
         return (appScale != 1.0f || (spec != null && !spec.isNop()));
     }
 
-    private void updateInfosForViewPort(List<AccessibilityNodeInfo> infos, MagnificationSpec spec,
-                                        Region interactiveRegion) {
-        for (int i = 0; i < infos.size(); i++) {
-            updateInfoForViewPort(infos.get(i), spec, interactiveRegion);
-        }
-    }
-
-    private void updateInfoForViewPort(AccessibilityNodeInfo info, MagnificationSpec spec,
-                                       Region interactiveRegion) {
-        associateLeashedParentIfNeeded(info);
-        applyScreenMatrixIfNeeded(info);
-        adjustBoundsInScreenIfNeeded(info);
-        // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
-        // then impact the visibility result, we need to adjust visibility before apply scale.
-        adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
-        applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
-    }
-
     private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
             IAccessibilityInteractionConnectionCallback callback, int interactionId,
             MagnificationSpec spec, Region interactiveRegion) {
-        if (infos != null) {
-            updateInfosForViewPort(infos, spec, interactiveRegion);
-        }
-        returnFindNodesResult(infos, callback, interactionId);
-    }
-
-    private void returnFindNodeResult(AccessibilityNodeInfo info,
-                                      IAccessibilityInteractionConnectionCallback callback,
-                                      int interactionId) {
         try {
-            callback.setFindAccessibilityNodeInfoResult(info, interactionId);
-        } catch (RemoteException re) {
-            /* ignore - the other side will time out */
-        }
-    }
-
-    private void returnFindNodesResult(List<AccessibilityNodeInfo> infos,
-            IAccessibilityInteractionConnectionCallback callback, int interactionId) {
-        try {
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+            associateLeashedParentIfNeeded(infos);
+            applyScreenMatrixIfNeeded(infos);
+            adjustBoundsInScreenIfNeeded(infos);
+            // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
+            // then impact the visibility result, we need to adjust visibility before apply scale.
+            adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
+            applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
             callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
             if (infos != null) {
                 infos.clear();
@@ -1031,80 +996,22 @@
         }
     }
 
-    private void returnPendingFindAccessibilityNodeInfosInPrefetch(AccessibilityNodeInfo rootNode,
-            List<AccessibilityNodeInfo> infos, int flags) {
-
-        AccessibilityNodeInfo satisfiedPendingRequestPrefetchedNode = null;
-        IAccessibilityInteractionConnectionCallback satisfiedPendingRequestCallback = null;
-        int satisfiedPendingRequestInteractionId = AccessibilityInteractionClient.NO_ID;
-
-        synchronized (mLock) {
-            for (int i = 0; i < mPendingFindNodeByIdMessages.size(); i++) {
-                final Message pendingMessage = mPendingFindNodeByIdMessages.get(i);
-                final int pendingFlags = pendingMessage.arg1;
-                if ((pendingFlags & FLAGS_AFFECTING_REPORTED_DATA)
-                        != (flags & FLAGS_AFFECTING_REPORTED_DATA)) {
-                    continue;
-                }
-                SomeArgs args = (SomeArgs) pendingMessage.obj;
-                final int accessibilityViewId = args.argi1;
-                final int virtualDescendantId = args.argi2;
-
-                satisfiedPendingRequestPrefetchedNode = nodeWithIdFromList(rootNode,
-                        infos, AccessibilityNodeInfo.makeNodeId(
-                                accessibilityViewId, virtualDescendantId));
-
-                if (satisfiedPendingRequestPrefetchedNode != null) {
-                    satisfiedPendingRequestCallback =
-                            (IAccessibilityInteractionConnectionCallback) args.arg1;
-                    satisfiedPendingRequestInteractionId = args.argi3;
-                    mHandler.removeMessages(
-                            PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID,
-                            pendingMessage.obj);
-                    args.recycle();
-                    break;
-                }
-            }
-            mPendingFindNodeByIdMessages.clear();
-        }
-
-        if (satisfiedPendingRequestPrefetchedNode != null) {
-            returnFindNodeResult(
-                    AccessibilityNodeInfo.obtain(satisfiedPendingRequestPrefetchedNode),
-                    satisfiedPendingRequestCallback, satisfiedPendingRequestInteractionId);
-        }
-    }
-
-    private AccessibilityNodeInfo nodeWithIdFromList(AccessibilityNodeInfo rootNode,
-            List<AccessibilityNodeInfo> infos, long nodeId) {
-        if (rootNode != null && rootNode.getSourceNodeId() == nodeId) {
-            return rootNode;
-        }
-        for (int j = 0; j < infos.size(); j++) {
-            AccessibilityNodeInfo info = infos.get(j);
-            if (info.getSourceNodeId() == nodeId) {
-                return info;
-            }
-        }
-        return null;
-    }
-
-    private void returnPrefetchResult(int interactionId, List<AccessibilityNodeInfo> infos,
-                                      IAccessibilityInteractionConnectionCallback callback) {
-        if (infos.size() > 0) {
-            try {
-                callback.setPrefetchAccessibilityNodeInfoResult(infos, interactionId);
-            } catch (RemoteException re) {
-                /* ignore - other side isn't too bothered if this doesn't arrive */
-            }
-        }
-    }
-
     private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
             IAccessibilityInteractionConnectionCallback callback, int interactionId,
             MagnificationSpec spec, Region interactiveRegion) {
-        updateInfoForViewPort(info, spec, interactiveRegion);
-        returnFindNodeResult(info, callback, interactionId);
+        try {
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+            associateLeashedParentIfNeeded(info);
+            applyScreenMatrixIfNeeded(info);
+            adjustBoundsInScreenIfNeeded(info);
+            // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
+            // then impact the visibility result, we need to adjust visibility before apply scale.
+            adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
+            applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+            callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+        } catch (RemoteException re) {
+                /* ignore - the other side will time out */
+        }
     }
 
     private boolean handleClickableSpanActionUiThread(
@@ -1147,45 +1054,56 @@
 
         private final ArrayList<View> mTempViewList = new ArrayList<View>();
 
-        public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root,
-                int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) {
-            if (root == null) {
-                return;
-            }
+        public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
+                List<AccessibilityNodeInfo> outInfos, Bundle arguments) {
             AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+            // Determine if we'll be populating extra data
+            final String extraDataRequested = (arguments == null) ? null
+                    : arguments.getString(EXTRA_DATA_REQUESTED_KEY);
             if (provider == null) {
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
-                    prefetchPredecessorsOfRealNode(view, outInfos);
-                }
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
-                    prefetchSiblingsOfRealNode(view, outInfos);
-                }
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
-                    prefetchDescendantsOfRealNode(view, outInfos);
+                AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
+                if (root != null) {
+                    if (extraDataRequested != null) {
+                        view.addExtraDataToAccessibilityNodeInfo(
+                                root, extraDataRequested, arguments);
+                    }
+                    outInfos.add(root);
+                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+                        prefetchPredecessorsOfRealNode(view, outInfos);
+                    }
+                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+                        prefetchSiblingsOfRealNode(view, outInfos);
+                    }
+                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+                        prefetchDescendantsOfRealNode(view, outInfos);
+                    }
                 }
             } else {
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
-                    prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
-                }
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
-                    prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
-                }
-                if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
-                    prefetchDescendantsOfVirtualNode(root, provider, outInfos);
+                final AccessibilityNodeInfo root =
+                        provider.createAccessibilityNodeInfo(virtualViewId);
+                if (root != null) {
+                    if (extraDataRequested != null) {
+                        provider.addExtraDataToAccessibilityNodeInfo(
+                                virtualViewId, root, extraDataRequested, arguments);
+                    }
+                    outInfos.add(root);
+                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+                        prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
+                    }
+                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+                        prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
+                    }
+                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+                        prefetchDescendantsOfVirtualNode(root, provider, outInfos);
+                    }
                 }
             }
             if (ENFORCE_NODE_TREE_CONSISTENT) {
-                enforceNodeTreeConsistent(root, outInfos);
+                enforceNodeTreeConsistent(outInfos);
             }
         }
 
-        private boolean shouldStopPrefetching(List prefetchededInfos) {
-            return mHandler.hasUserInteractiveMessagesWaiting()
-                    || prefetchededInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE;
-        }
-
-        private void enforceNodeTreeConsistent(
-                AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes) {
+        private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
             LongSparseArray<AccessibilityNodeInfo> nodeMap =
                     new LongSparseArray<AccessibilityNodeInfo>();
             final int nodeCount = nodes.size();
@@ -1196,6 +1114,7 @@
 
             // If the nodes are a tree it does not matter from
             // which node we start to search for the root.
+            AccessibilityNodeInfo root = nodeMap.valueAt(0);
             AccessibilityNodeInfo parent = root;
             while (parent != null) {
                 root = parent;
@@ -1262,11 +1181,9 @@
 
         private void prefetchPredecessorsOfRealNode(View view,
                 List<AccessibilityNodeInfo> outInfos) {
-            if (shouldStopPrefetching(outInfos)) {
-                return;
-            }
             ViewParent parent = view.getParentForAccessibility();
-            while (parent instanceof View && !shouldStopPrefetching(outInfos)) {
+            while (parent instanceof View
+                    && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
                 View parentView = (View) parent;
                 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
                 if (info != null) {
@@ -1278,9 +1195,6 @@
 
         private void prefetchSiblingsOfRealNode(View current,
                 List<AccessibilityNodeInfo> outInfos) {
-            if (shouldStopPrefetching(outInfos)) {
-                return;
-            }
             ViewParent parent = current.getParentForAccessibility();
             if (parent instanceof ViewGroup) {
                 ViewGroup parentGroup = (ViewGroup) parent;
@@ -1290,7 +1204,7 @@
                     parentGroup.addChildrenForAccessibility(children);
                     final int childCount = children.size();
                     for (int i = 0; i < childCount; i++) {
-                        if (shouldStopPrefetching(outInfos)) {
+                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
                             return;
                         }
                         View child = children.get(i);
@@ -1318,7 +1232,7 @@
 
         private void prefetchDescendantsOfRealNode(View root,
                 List<AccessibilityNodeInfo> outInfos) {
-            if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) {
+            if (!(root instanceof ViewGroup)) {
                 return;
             }
             HashMap<View, AccessibilityNodeInfo> addedChildren =
@@ -1329,7 +1243,7 @@
                 root.addChildrenForAccessibility(children);
                 final int childCount = children.size();
                 for (int i = 0; i < childCount; i++) {
-                    if (shouldStopPrefetching(outInfos)) {
+                    if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
                         return;
                     }
                     View child = children.get(i);
@@ -1354,7 +1268,7 @@
             } finally {
                 children.clear();
             }
-            if (!shouldStopPrefetching(outInfos)) {
+            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
                 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
                     View addedChild = entry.getKey();
                     AccessibilityNodeInfo virtualRoot = entry.getValue();
@@ -1376,7 +1290,7 @@
             long parentNodeId = root.getParentNodeId();
             int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
             while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
-                if (shouldStopPrefetching(outInfos)) {
+                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
                     return;
                 }
                 final int virtualDescendantId =
@@ -1421,7 +1335,7 @@
                 if (parent != null) {
                     final int childCount = parent.getChildCount();
                     for (int i = 0; i < childCount; i++) {
-                        if (shouldStopPrefetching(outInfos)) {
+                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
                             return;
                         }
                         final long childNodeId = parent.getChildId(i);
@@ -1446,7 +1360,7 @@
             final int initialOutInfosSize = outInfos.size();
             final int childCount = root.getChildCount();
             for (int i = 0; i < childCount; i++) {
-                if (shouldStopPrefetching(outInfos)) {
+                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
                     return;
                 }
                 final long childNodeId = root.getChildId(i);
@@ -1456,7 +1370,7 @@
                     outInfos.add(child);
                 }
             }
-            if (!shouldStopPrefetching(outInfos)) {
+            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
                 final int addedChildCount = outInfos.size() - initialOutInfosSize;
                 for (int i = 0; i < addedChildCount; i++) {
                     AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
@@ -1565,10 +1479,6 @@
         boolean hasAccessibilityCallback(Message message) {
             return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false;
         }
-
-        boolean hasUserInteractiveMessagesWaiting() {
-            return hasMessagesOrCallbacks();
-        }
     }
 
     private final class AddNodeInfosForViewId implements Predicate<View> {
diff --git a/core/java/android/view/AppTransitionAnimationSpec.java b/core/java/android/view/AppTransitionAnimationSpec.java
index 877bb56..3215f2b 100644
--- a/core/java/android/view/AppTransitionAnimationSpec.java
+++ b/core/java/android/view/AppTransitionAnimationSpec.java
@@ -28,8 +28,8 @@
 
     public AppTransitionAnimationSpec(Parcel in) {
         taskId = in.readInt();
-        rect = in.readParcelable(null);
-        buffer = in.readParcelable(null);
+        rect = in.readTypedObject(Rect.CREATOR);
+        buffer = in.readTypedObject(HardwareBuffer.CREATOR);
     }
 
     @Override
@@ -40,8 +40,8 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(taskId);
-        dest.writeParcelable(rect, 0 /* flags */);
-        dest.writeParcelable(buffer, 0);
+        dest.writeTypedObject(rect, 0 /* flags */);
+        dest.writeTypedObject(buffer, 0 /* flags */);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<AppTransitionAnimationSpec> CREATOR
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 2a00b5a..655f423 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -810,6 +810,9 @@
         if ((flags & Display.FLAG_TRUSTED) != 0) {
             result.append(", FLAG_TRUSTED");
         }
+        if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
+            result.append(", FLAG_OWN_DISPLAY_GROUP");
+        }
         return result.toString();
     }
 }
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 4a5c95f..d23a1e5 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -116,8 +116,9 @@
         if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
             return;
         }
+        View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
         if (DEBUG) {
-            Log.v(TAG, "onWindowFocus: " + focusedView
+            Log.v(TAG, "onWindowFocus: " + viewForWindowFocus
                     + " softInputMode=" + InputMethodDebug.softInputModeToString(
                     windowAttribute.softInputMode));
         }
@@ -128,8 +129,8 @@
             if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true");
             forceFocus = true;
         }
+
         // Update mNextServedView when focusedView changed.
-        final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
         onViewFocusChanged(viewForWindowFocus, true);
 
         // Starting new input when the next focused view is same as served view but the currently
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 5ce4c50..6c8753b 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -27,7 +27,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
 import android.widget.RemoteViews;
 
 import com.android.internal.R;
@@ -42,7 +42,7 @@
  * @hide
  */
 @RemoteViews.RemoteView
-public class NotificationHeaderView extends FrameLayout {
+public class NotificationHeaderView extends RelativeLayout {
     private final int mHeadingEndMargin;
     private final int mTouchableHeight;
     private OnClickListener mExpandClickListener;
@@ -159,7 +159,7 @@
      * @param extraMarginEnd extra margin in px
      */
     public void setTopLineExtraMarginEnd(int extraMarginEnd) {
-        mTopLineView.setHeaderTextMarginEnd(extraMarginEnd + mHeadingEndMargin);
+        mTopLineView.setHeaderTextMarginEnd(extraMarginEnd);
     }
 
     /**
@@ -181,12 +181,15 @@
      * @return extra margin
      */
     public int getTopLineExtraMarginEnd() {
-        return mTopLineView.getHeaderTextMarginEnd() - mHeadingEndMargin;
+        return mTopLineView.getHeaderTextMarginEnd();
     }
 
     /**
      * Get the base margin at the end of the top line view.
      * Add this to {@link #getTopLineExtraMarginEnd()} to get the total margin of the top line.
+     * <p>
+     * NOTE: This method's result is only valid if the expander does not have a number. Currently
+     * only groups headers and conversations have numbers, so this is safe to use by MediaStyle.
      *
      * @return base margin
      */
diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java
index a5ff19e..ea97995 100644
--- a/core/java/android/view/RemoteAnimationDefinition.java
+++ b/core/java/android/view/RemoteAnimationDefinition.java
@@ -184,13 +184,13 @@
         }
 
         private RemoteAnimationAdapterEntry(Parcel in) {
-            adapter = in.readParcelable(RemoteAnimationAdapter.class.getClassLoader());
+            adapter = in.readTypedObject(RemoteAnimationAdapter.CREATOR);
             activityTypeFilter = in.readInt();
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeParcelable(adapter, flags);
+            dest.writeTypedObject(adapter, flags);
             dest.writeInt(activityTypeFilter);
         }
 
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 258a72c..b1b670f 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -222,20 +222,20 @@
     public RemoteAnimationTarget(Parcel in) {
         taskId = in.readInt();
         mode = in.readInt();
-        leash = in.readParcelable(null);
+        leash = in.readTypedObject(SurfaceControl.CREATOR);
         isTranslucent = in.readBoolean();
-        clipRect = in.readParcelable(null);
-        contentInsets = in.readParcelable(null);
+        clipRect = in.readTypedObject(Rect.CREATOR);
+        contentInsets = in.readTypedObject(Rect.CREATOR);
         prefixOrderIndex = in.readInt();
-        position = in.readParcelable(null);
-        localBounds = in.readParcelable(null);
-        sourceContainerBounds = in.readParcelable(null);
-        screenSpaceBounds = in.readParcelable(null);
-        windowConfiguration = in.readParcelable(null);
+        position = in.readTypedObject(Point.CREATOR);
+        localBounds = in.readTypedObject(Rect.CREATOR);
+        sourceContainerBounds = in.readTypedObject(Rect.CREATOR);
+        screenSpaceBounds = in.readTypedObject(Rect.CREATOR);
+        windowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR);
         isNotInRecents = in.readBoolean();
-        startLeash = in.readParcelable(null);
-        startBounds = in.readParcelable(null);
-        pictureInPictureParams = in.readParcelable(null);
+        startLeash = in.readTypedObject(SurfaceControl.CREATOR);
+        startBounds = in.readTypedObject(Rect.CREATOR);
+        pictureInPictureParams = in.readTypedObject(PictureInPictureParams.CREATOR);
     }
 
     @Override
@@ -247,20 +247,20 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(taskId);
         dest.writeInt(mode);
-        dest.writeParcelable(leash, 0 /* flags */);
+        dest.writeTypedObject(leash, 0 /* flags */);
         dest.writeBoolean(isTranslucent);
-        dest.writeParcelable(clipRect, 0 /* flags */);
-        dest.writeParcelable(contentInsets, 0 /* flags */);
+        dest.writeTypedObject(clipRect, 0 /* flags */);
+        dest.writeTypedObject(contentInsets, 0 /* flags */);
         dest.writeInt(prefixOrderIndex);
-        dest.writeParcelable(position, 0 /* flags */);
-        dest.writeParcelable(localBounds, 0 /* flags */);
-        dest.writeParcelable(sourceContainerBounds, 0 /* flags */);
-        dest.writeParcelable(screenSpaceBounds, 0 /* flags */);
-        dest.writeParcelable(windowConfiguration, 0 /* flags */);
+        dest.writeTypedObject(position, 0 /* flags */);
+        dest.writeTypedObject(localBounds, 0 /* flags */);
+        dest.writeTypedObject(sourceContainerBounds, 0 /* flags */);
+        dest.writeTypedObject(screenSpaceBounds, 0 /* flags */);
+        dest.writeTypedObject(windowConfiguration, 0 /* flags */);
         dest.writeBoolean(isNotInRecents);
-        dest.writeParcelable(startLeash, 0 /* flags */);
-        dest.writeParcelable(startBounds, 0 /* flags */);
-        dest.writeParcelable(pictureInPictureParams, 0 /* flags */);
+        dest.writeTypedObject(startLeash, 0 /* flags */);
+        dest.writeTypedObject(startBounds, 0 /* flags */);
+        dest.writeTypedObject(pictureInPictureParams, 0 /* flags */);
     }
 
     public void dump(PrintWriter pw, String prefix) {
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 9556c25..f63749b 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -23,9 +23,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
@@ -115,8 +113,6 @@
 
     private final Object mInstanceLock = new Object();
 
-    private Handler mMainHandler;
-
     private volatile int mInteractionId = -1;
 
     private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
@@ -127,11 +123,6 @@
 
     private Message mSameThreadMessage;
 
-    private int mInteractionIdWaitingForPrefetchResult;
-    private int mConnectionIdWaitingForPrefetchResult;
-    private String[] mPackageNamesForNextPrefetchResult;
-    private Runnable mPrefetchResultRunnable;
-
     /**
      * @return The client for the current thread.
      */
@@ -206,9 +197,6 @@
 
     private AccessibilityInteractionClient() {
         /* reducing constructor visibility */
-        if (Looper.getMainLooper() != null) {
-            mMainHandler = new Handler(Looper.getMainLooper());
-        }
     }
 
     /**
@@ -463,16 +451,16 @@
                     Binder.restoreCallingIdentity(identityToken);
                 }
                 if (packageNames != null) {
-                    AccessibilityNodeInfo info =
-                            getFindAccessibilityNodeInfoResultAndClear(interactionId);
-                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
-                            && info != null) {
-                        setInteractionWaitingForPrefetchResult(interactionId, connectionId,
-                                packageNames);
-                    }
-                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
+                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
+                            interactionId);
+                    finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
                             bypassCache, packageNames);
-                    return info;
+                    if (infos != null && !infos.isEmpty()) {
+                        for (int i = 1; i < infos.size(); i++) {
+                            infos.get(i).recycle();
+                        }
+                        return infos.get(0);
+                    }
                 }
             } else {
                 if (DEBUG) {
@@ -486,15 +474,6 @@
         return null;
     }
 
-    private void setInteractionWaitingForPrefetchResult(int interactionId, int connectionId,
-            String[] packageNames) {
-        synchronized (mInstanceLock) {
-            mInteractionIdWaitingForPrefetchResult = interactionId;
-            mConnectionIdWaitingForPrefetchResult = connectionId;
-            mPackageNamesForNextPrefetchResult = packageNames;
-        }
-    }
-
     private static String idToString(int accessibilityWindowId, long accessibilityNodeId) {
         return accessibilityWindowId + "/"
                 + AccessibilityNodeInfo.idToString(accessibilityNodeId);
@@ -850,60 +829,6 @@
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos,
-                                                       int interactionId) {
-        List<AccessibilityNodeInfo> infosCopy = null;
-        int mConnectionIdWaitingForPrefetchResultCopy = -1;
-        String[] mPackageNamesForNextPrefetchResultCopy = null;
-
-        synchronized (mInstanceLock) {
-            if (!infos.isEmpty() && mInteractionIdWaitingForPrefetchResult == interactionId) {
-                if (mMainHandler != null) {
-                    if (mPrefetchResultRunnable != null) {
-                        mMainHandler.removeCallbacks(mPrefetchResultRunnable);
-                        mPrefetchResultRunnable = null;
-                    }
-                    /**
-                     * TODO(b/180957109): AccessibilityCache is prone to deadlocks
-                     * We post caching the prefetched nodes in the main thread. Using the binder
-                     * thread results in "Long monitor contention with owner main" logs where
-                     * service response times may exceed 5 seconds. This is due to the cache calling
-                     * out to the system when refreshing nodes with the lock held.
-                     */
-                    mPrefetchResultRunnable = () -> finalizeAndCacheAccessibilityNodeInfos(
-                            infos, mConnectionIdWaitingForPrefetchResult, false,
-                            mPackageNamesForNextPrefetchResult);
-                    mMainHandler.post(mPrefetchResultRunnable);
-
-                } else {
-                    for (AccessibilityNodeInfo info : infos) {
-                        infosCopy.add(new AccessibilityNodeInfo(info));
-                    }
-                    mConnectionIdWaitingForPrefetchResultCopy =
-                            mConnectionIdWaitingForPrefetchResult;
-                    mPackageNamesForNextPrefetchResultCopy =
-                            new String[mPackageNamesForNextPrefetchResult.length];
-                    for (int i = 0; i < mPackageNamesForNextPrefetchResult.length; i++) {
-                        mPackageNamesForNextPrefetchResultCopy[i] =
-                                mPackageNamesForNextPrefetchResult[i];
-
-                    }
-                }
-            }
-
-        }
-
-        if (infosCopy != null) {
-            finalizeAndCacheAccessibilityNodeInfos(
-                    infosCopy, mConnectionIdWaitingForPrefetchResultCopy, false,
-                    mPackageNamesForNextPrefetchResultCopy);
-        }
-    }
-
-    /**
      * Gets the result of a request to perform an accessibility action.
      *
      * @param interactionId The interaction id to match the result with the request.
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 97ce92c..ab46170 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -1781,8 +1781,12 @@
      * @param viewId The fully qualified resource name of the view id to find.
      * @return A list of node info.
      */
-    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId) {
+    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(@NonNull String viewId) {
         enforceSealed();
+        if (viewId == null) {
+            Log.e(TAG, "returns empty list due to null viewId.");
+            return Collections.emptyList();
+        }
         if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) {
             return Collections.emptyList();
         }
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index 231e75a..049bb31 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -47,15 +47,6 @@
         int interactionId);
 
     /**
-     * Sets the result of a prefetch request that returns {@link AccessibilityNodeInfo}s.
-     *
-     * @param root The {@link AccessibilityNodeInfo} for which the prefetching is based off of.
-     * @param infos The result {@link AccessibilityNodeInfo}s.
-     */
-    void setPrefetchAccessibilityNodeInfoResult(
-        in List<AccessibilityNodeInfo> infos, int interactionId);
-
-    /**
      * Sets the result of a request to perform an accessibility action.
      *
      * @param Whether the action was performed.
diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl
index e175453..872e15e 100644
--- a/core/java/android/view/translation/ITranslationManager.aidl
+++ b/core/java/android/view/translation/ITranslationManager.aidl
@@ -36,6 +36,10 @@
          int sessionId, in IResultReceiver receiver, int userId);
 
     void updateUiTranslationState(int state, in TranslationSpec sourceSpec,
-         in TranslationSpec destSpec, in List<AutofillId> viewIds, in int taskId,
+         in TranslationSpec destSpec, in List<AutofillId> viewIds, IBinder token, int taskId,
+         int userId);
+    // deprecated
+    void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec,
+         in TranslationSpec destSpec, in List<AutofillId> viewIds, int taskId,
          int userId);
 }
diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java
index eeb463a..a3a6a2e 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.app.assist.ActivityId;
 import android.content.Context;
 import android.os.RemoteException;
 import android.view.View;
@@ -95,26 +96,61 @@
     /**
      * Request ui translation for a given Views.
      *
+     * NOTE: Please use {@code startTranslation(TranslationSpec, TranslationSpec, List<AutofillId>,
+     * ActivityId)} instead.
+     *
      * @param sourceSpec {@link TranslationSpec} for the data to be translated.
      * @param destSpec {@link TranslationSpec} for the translated data.
      * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated
      * @param taskId the Activity Task id which needs ui translation
      */
+    // TODO, hide the APIs
     @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
     public void startTranslation(@NonNull TranslationSpec sourceSpec,
             @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds,
             int taskId) {
-        // TODO(b/177789967): Return result code or find a way to notify the status.
-        // TODO(b/177394471): The is a temparary API, the expected is requestUiTranslation(
-        //  TranslationSpec, TranslationSpec,List<AutofillId>, Binder). We may need more time to
-        //  implement it, use task id as initial version for demo.
         Objects.requireNonNull(sourceSpec);
         Objects.requireNonNull(destSpec);
         Objects.requireNonNull(viewIds);
+        if (viewIds.size() == 0) {
+            throw new IllegalArgumentException("Invalid empty views: " + viewIds);
+        }
+        try {
+            mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_STARTED, sourceSpec,
+                    destSpec, viewIds, taskId, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
+    /**
+     * Request ui translation for a given Views.
+     *
+     * @param sourceSpec {@link TranslationSpec} for the data to be translated.
+     * @param destSpec {@link TranslationSpec} for the translated data.
+     * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated
+     * @param activityId the identifier for the Activity which needs ui translation
+     * @throws IllegalArgumentException if the no {@link View}'s {@link AutofillId} in the list
+     * @throws NullPointerException the sourceSpec, destSpec, viewIds, activityId or
+     *         {@link android.app.assist.ActivityId#getToken()} is {@code null}
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+    public void startTranslation(@NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds,
+            @NonNull ActivityId activityId) {
+        // TODO(b/177789967): Return result code or find a way to notify the status.
+        Objects.requireNonNull(sourceSpec);
+        Objects.requireNonNull(destSpec);
+        Objects.requireNonNull(viewIds);
+        Objects.requireNonNull(activityId);
+        Objects.requireNonNull(activityId.getToken());
+        if (viewIds.size() == 0) {
+            throw new IllegalArgumentException("Invalid empty views: " + viewIds);
+        }
         try {
             mService.updateUiTranslationState(STATE_UI_TRANSLATION_STARTED, sourceSpec,
-                    destSpec, viewIds, taskId, mContext.getUserId());
+                    destSpec, viewIds, activityId.getToken(), activityId.getTaskId(),
+                    mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -124,14 +160,56 @@
      * Request to disable the ui translation. It will destroy all the {@link Translator}s and no
      * longer to show to show the translated text.
      *
+     * NOTE: Please use {@code finishTranslation(ActivityId)} instead.
+     *
      * @param taskId the Activity Task id which needs ui translation
      */
+    // TODO, hide the APIs
     @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
     public void finishTranslation(int taskId) {
         try {
-            // TODO(b/177394471): The is a temparary API, the expected is finishUiTranslation(
-            //  Binder). We may need more time to implement it, use task id as initial version.
+            mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_FINISHED,
+                    null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Request to disable the ui translation. It will destroy all the {@link Translator}s and no
+     * longer to show to show the translated text.
+     *
+     * @param activityId the identifier for the Activity which needs ui translation
+     * @throws NullPointerException the activityId or
+     *         {@link android.app.assist.ActivityId#getToken()} is {@code null}
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+    public void finishTranslation(@NonNull ActivityId activityId) {
+        try {
+            Objects.requireNonNull(activityId);
+            Objects.requireNonNull(activityId.getToken());
             mService.updateUiTranslationState(STATE_UI_TRANSLATION_FINISHED,
+                    null /* sourceSpec */, null /* destSpec*/, null /* viewIds */,
+                    activityId.getToken(), activityId.getTaskId(), mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Request to pause the current ui translation's {@link Translator} which will switch back to
+     * the original language.
+     *
+     * NOTE: Please use {@code pauseTranslation(ActivityId)} instead.
+     *
+     * @param taskId the Activity Task id which needs ui translation
+     */
+    // TODO, hide the APIs
+    @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+    public void pauseTranslation(int taskId) {
+        try {
+            mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_PAUSED,
                     null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId,
                     mContext.getUserId());
         } catch (RemoteException e) {
@@ -143,16 +221,18 @@
      * Request to pause the current ui translation's {@link Translator} which will switch back to
      * the original language.
      *
-     * @param taskId the Activity Task id which needs ui translation
+     * @param activityId the identifier for the Activity which needs ui translation
+     * @throws NullPointerException the activityId or
+     *         {@link android.app.assist.ActivityId#getToken()} is {@code null}
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
-    public void pauseTranslation(int taskId) {
+    public void pauseTranslation(@NonNull ActivityId activityId) {
         try {
-            // TODO(b/177394471): The is a temparary API, the expected is pauseUiTranslation(Binder)
-            // We may need more time to implement it, use task id as initial version for demo
+            Objects.requireNonNull(activityId);
+            Objects.requireNonNull(activityId.getToken());
             mService.updateUiTranslationState(STATE_UI_TRANSLATION_PAUSED,
-                    null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId,
-                    mContext.getUserId());
+                    null /* sourceSpec */, null /* destSpec*/, null /* viewIds */,
+                    activityId.getToken(), activityId.getTaskId(), mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -162,18 +242,40 @@
      * Request to resume the paused ui translation's {@link Translator} which will switch to the
      * translated language if the text had been translated.
      *
+     * NOTE: Please use {@code resumeTranslation(ActivityId)} instead.
+     *
      * @param taskId the Activity Task id which needs ui translation
      */
+    // TODO, hide the APIs
     @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
     public void resumeTranslation(int taskId) {
         try {
-            // TODO(b/177394471): The is a temparary API, the expected is resumeUiTranslation(
-            //  Binder). We may need more time to implement it, use task id as initial version.
-            mService.updateUiTranslationState(STATE_UI_TRANSLATION_RESUMED,
+            mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_RESUMED,
                     null /* sourceSpec */, null /* destSpec*/, null /* viewIds */,
                     taskId, mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Request to resume the paused ui translation's {@link Translator} which will switch to the
+     * translated language if the text had been translated.
+     *
+     * @param activityId the identifier for the Activity which needs ui translation
+     * @throws NullPointerException the activityId or
+     *         {@link android.app.assist.ActivityId#getToken()} is {@code null}
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+    public void resumeTranslation(@NonNull ActivityId activityId) {
+        try {
+            Objects.requireNonNull(activityId);
+            Objects.requireNonNull(activityId.getToken());
+            mService.updateUiTranslationState(STATE_UI_TRANSLATION_RESUMED,
+                    null /* sourceSpec */, null /* destSpec*/, null /* viewIds */,
+                    activityId.getToken(), activityId.getTaskId(), mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java
index dc07e44..f1e5fb9 100644
--- a/core/java/android/window/TaskSnapshot.java
+++ b/core/java/android/window/TaskSnapshot.java
@@ -46,7 +46,7 @@
     private final int mOrientation;
     /** See {@link android.view.Surface.Rotation} */
     @Surface.Rotation
-    private int mRotation;
+    private final int mRotation;
     /** The size of the snapshot before scaling */
     private final Point mTaskSize;
     private final Rect mContentInsets;
@@ -90,15 +90,15 @@
     private TaskSnapshot(Parcel source) {
         mId = source.readLong();
         mTopActivityComponent = ComponentName.readFromParcel(source);
-        mSnapshot = source.readParcelable(null /* classLoader */);
+        mSnapshot = source.readTypedObject(HardwareBuffer.CREATOR);
         int colorSpaceId = source.readInt();
         mColorSpace = colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length
                 ? ColorSpace.get(ColorSpace.Named.values()[colorSpaceId])
                 : ColorSpace.get(ColorSpace.Named.SRGB);
         mOrientation = source.readInt();
         mRotation = source.readInt();
-        mTaskSize = source.readParcelable(null /* classLoader */);
-        mContentInsets = source.readParcelable(null /* classLoader */);
+        mTaskSize = source.readTypedObject(Point.CREATOR);
+        mContentInsets = source.readTypedObject(Rect.CREATOR);
         mIsLowResolution = source.readBoolean();
         mIsRealSnapshot = source.readBoolean();
         mWindowingMode = source.readInt();
@@ -235,13 +235,12 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeLong(mId);
         ComponentName.writeToParcel(mTopActivityComponent, dest);
-        dest.writeParcelable(mSnapshot != null && !mSnapshot.isClosed() ? mSnapshot : null,
-                0);
+        dest.writeTypedObject(mSnapshot != null && !mSnapshot.isClosed() ? mSnapshot : null, 0);
         dest.writeInt(mColorSpace.getId());
         dest.writeInt(mOrientation);
         dest.writeInt(mRotation);
-        dest.writeParcelable(mTaskSize, 0);
-        dest.writeParcelable(mContentInsets, 0);
+        dest.writeTypedObject(mTaskSize, 0);
+        dest.writeTypedObject(mContentInsets, 0);
         dest.writeBoolean(mIsLowResolution);
         dest.writeBoolean(mIsRealSnapshot);
         dest.writeInt(mWindowingMode);
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 3a9f3b9..eecd0cf 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -74,11 +74,11 @@
     @UnsupportedAppUsage
     List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
     void getHistoricalOps(int uid, String packageName, String attributionTag, in List<String> ops,
-            int filter, long beginTimeMillis, long endTimeMillis, int flags,
+            int historyFlags, int filter, long beginTimeMillis, long endTimeMillis, int flags,
             in RemoteCallback callback);
     void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
-            in List<String> ops, int filter, long beginTimeMillis, long endTimeMillis, int flags,
-            in RemoteCallback callback);
+            in List<String> ops, int historyFlags, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, in RemoteCallback callback);
     void offsetHistory(long duration);
     void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep);
     void addHistoricalOps(in AppOpsManager.HistoricalOps ops);
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index c353de8..93374ba 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -228,6 +228,8 @@
                 return "HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR";
             case SoftInputShowHideReason.HIDE_REMOVE_CLIENT:
                 return "HIDE_REMOVE_CLIENT";
+            case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY:
+                return "SHOW_RESTORE_IME_VISIBILITY";
             default:
                 return "Unknown=" + reason;
         }
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index 1553e2e..f1cdf2b 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -49,7 +49,8 @@
         SoftInputShowHideReason.HIDE_RECENTS_ANIMATION,
         SoftInputShowHideReason.HIDE_BUBBLES,
         SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR,
-        SoftInputShowHideReason.HIDE_REMOVE_CLIENT})
+        SoftInputShowHideReason.HIDE_REMOVE_CLIENT,
+        SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY})
 public @interface SoftInputShowHideReason {
     /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
     int SHOW_SOFT_INPUT = 0;
@@ -167,4 +168,10 @@
      * Hide soft input when a {@link com.android.internal.view.IInputMethodClient} is removed.
      */
     int HIDE_REMOVE_CLIENT = 21;
+
+    /**
+     * Show soft input when the system invoking
+     * {@link com.android.server.wm.WindowManagerInternal#shouldRestoreImeVisibility}.
+     */
+    int SHOW_RESTORE_IME_VISIBILITY = 22;
 }
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 342456a..33ee8f0 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -203,6 +203,9 @@
         Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
         mCancelled = true;
         removeObservers();
+        if (mListener != null) {
+            mListener.onNotifyCujEvents(mSession, InteractionJankMonitor.ACTION_SESSION_CANCEL);
+        }
     }
 
     @Override
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 0294ec3..fbc92c1 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -99,7 +99,7 @@
     private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
 
     public static final String ACTION_SESSION_BEGIN = ACTION_PREFIX + ".ACTION_SESSION_BEGIN";
-    public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END";
+    public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL";
     public static final String ACTION_METRICS_LOGGED = ACTION_PREFIX + ".ACTION_METRICS_LOGGED";
     public static final String BUNDLE_KEY_CUJ_NAME = ACTION_PREFIX + ".CUJ_NAME";
     public static final String BUNDLE_KEY_TIMESTAMP = ACTION_PREFIX + ".TIMESTAMP";
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 1b1e0bf..8ecc809 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -32,7 +32,6 @@
 import android.app.RemoteInputHistoryItem;
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.graphics.drawable.GradientDrawable;
@@ -62,11 +61,9 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
-import com.android.internal.graphics.ColorUtils;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Locale;
 import java.util.Objects;
 import java.util.function.Consumer;
 
@@ -118,7 +115,6 @@
     private ViewGroup mExpandButtonAndContentContainer;
     private NotificationExpandButton mExpandButton;
     private MessagingLinearLayout mImageMessageContainer;
-    private int mExpandButtonExpandedTopMargin;
     private int mBadgedSideMargins;
     private int mConversationAvatarSize;
     private int mConversationAvatarSizeExpanded;
@@ -147,7 +143,6 @@
     private int mFacePileProtectionWidth;
     private int mFacePileProtectionWidthExpanded;
     private boolean mImportantConversation;
-    private TextView mUnreadBadge;
     private View mFeedbackIcon;
     private float mMinTouchSize;
     private Icon mConversationIcon;
@@ -245,8 +240,6 @@
         mContentContainer = findViewById(R.id.notification_action_list_margin_target);
         mExpandButtonAndContentContainer = findViewById(R.id.expand_button_and_content_container);
         mExpandButton = findViewById(R.id.expand_button);
-        mExpandButtonExpandedTopMargin = getResources().getDimensionPixelSize(
-                R.dimen.conversation_expand_button_top_margin_expanded);
         mNotificationHeaderExpandedPadding = getResources().getDimensionPixelSize(
                 R.dimen.conversation_header_expanded_padding_end);
         mContentMarginEnd = getResources().getDimensionPixelSize(
@@ -286,7 +279,6 @@
         mAppName.setOnVisibilityChangedListener((visibility) -> {
             onAppNameVisibilityChanged();
         });
-        mUnreadBadge = findViewById(R.id.conversation_unread_count);
         mConversationContentStart = getResources().getDimensionPixelSize(
                 R.dimen.conversation_content_start);
         mInternalButtonPadding
@@ -426,17 +418,7 @@
 
     /** @hide */
     public void setUnreadCount(int unreadCount) {
-        boolean visible = mIsCollapsed && unreadCount > 1;
-        mUnreadBadge.setVisibility(visible ? VISIBLE : GONE);
-        if (visible) {
-            CharSequence text = unreadCount >= 100
-                    ? getResources().getString(R.string.unread_convo_overflow, 99)
-                    : String.format(Locale.getDefault(), "%d", unreadCount);
-            mUnreadBadge.setText(text);
-            mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor));
-            boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f;
-            mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE);
-        }
+        mExpandButton.setNumber(unreadCount);
     }
 
     private void addRemoteInputHistoryToMessages(
@@ -1132,15 +1114,16 @@
     }
 
     private void updateExpandButton() {
-        int gravity;
-        int topMargin = 0;
+        int buttonGravity;
+        int containerHeight;
         ViewGroup newContainer;
         if (mIsCollapsed) {
-            gravity = Gravity.CENTER;
+            buttonGravity = Gravity.CENTER;
+            containerHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
             newContainer = mExpandButtonAndContentContainer;
         } else {
-            gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
-            topMargin = mExpandButtonExpandedTopMargin;
+            buttonGravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
+            containerHeight = ViewGroup.LayoutParams.MATCH_PARENT;
             newContainer = this;
         }
         mExpandButton.setExpanded(!mIsCollapsed);
@@ -1149,14 +1132,14 @@
         // content when collapsed, but allows the content to flow under it when expanded.
         if (newContainer != mExpandButtonContainer.getParent()) {
             ((ViewGroup) mExpandButtonContainer.getParent()).removeView(mExpandButtonContainer);
+            mExpandButtonContainer.getLayoutParams().height = containerHeight;
             newContainer.addView(mExpandButtonContainer);
         }
 
         // update if the expand button is centered
         LinearLayout.LayoutParams layoutParams =
                 (LinearLayout.LayoutParams) mExpandButton.getLayoutParams();
-        layoutParams.gravity = gravity;
-        layoutParams.topMargin = topMargin;
+        layoutParams.gravity = buttonGravity;
         mExpandButton.setLayoutParams(layoutParams);
     }
 
@@ -1210,6 +1193,7 @@
             mExpandButtonContainer.setVisibility(GONE);
             mConversationIconContainer.setOnClickListener(null);
         }
+        mExpandButton.setVisibility(VISIBLE);
         updateContentEndPaddings();
     }
 
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index 8add34f..fc4cc57 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -16,30 +16,42 @@
 
 package com.android.internal.widget;
 
-import static com.android.internal.widget.ColoredIconHelper.applyGrayTint;
-
+import android.annotation.ColorInt;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.RemotableViewMethod;
+import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Button;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.RemoteViews;
+import android.widget.TextView;
 
 import com.android.internal.R;
 
+import java.util.Locale;
+
 /**
  * An expand button in a notification
  */
 @RemoteViews.RemoteView
-public class NotificationExpandButton extends ImageView {
+public class NotificationExpandButton extends FrameLayout {
 
-    private final int mMinTouchTargetSize;
+    private View mPillView;
+    private TextView mNumberView;
+    private ImageView mIconView;
     private boolean mExpanded;
-    private int mOriginalNotificationColor;
+    private int mNumber;
+    private int mDefaultPillColor;
+    private int mDefaultTextColor;
+    private int mHighlightPillColor;
+    private int mHighlightTextColor;
+    private boolean mDisallowColor;
 
     public NotificationExpandButton(Context context) {
         this(context, null, 0, 0);
@@ -57,7 +69,14 @@
     public NotificationExpandButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mMinTouchTargetSize = (int) (getResources().getDisplayMetrics().density * 48 + 0.5);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mPillView = findViewById(R.id.expand_button_pill);
+        mNumberView = findViewById(R.id.expand_button_number);
+        mIconView = findViewById(R.id.expand_button_icon);
     }
 
     /**
@@ -72,7 +91,6 @@
         } else {
             super.getBoundsOnScreen(outRect, clipToParent);
         }
-        extendRectToMinTouchSize(outRect);
     }
 
     /**
@@ -89,32 +107,12 @@
         return super.pointInView(localX, localY, slop);
     }
 
-    @RemotableViewMethod
-    public void setOriginalNotificationColor(int color) {
-        mOriginalNotificationColor = color;
-    }
-
-    public int getOriginalNotificationColor() {
-        return mOriginalNotificationColor;
-    }
-
     /**
-     * Set the button's color filter: to gray if true, otherwise colored.
-     * If this button has no original color, this has no effect.
+     * Disable the use of the accent colors for this view, if true.
      */
     public void setGrayedOut(boolean shouldApply) {
-        applyGrayTint(mContext, getDrawable(), shouldApply, mOriginalNotificationColor);
-    }
-
-    private void extendRectToMinTouchSize(Rect rect) {
-        if (rect.width() < mMinTouchTargetSize) {
-            rect.left = rect.centerX() - mMinTouchTargetSize / 2;
-            rect.right = rect.left + mMinTouchTargetSize;
-        }
-        if (rect.height() < mMinTouchTargetSize) {
-            rect.top = rect.centerY() - mMinTouchTargetSize / 2;
-            rect.bottom = rect.top + mMinTouchTargetSize;
-        }
+        mDisallowColor = shouldApply;
+        updateColors();
     }
 
     @Override
@@ -129,10 +127,10 @@
     @RemotableViewMethod
     public void setExpanded(boolean expanded) {
         mExpanded = expanded;
-        updateExpandButton();
+        updateExpandedState();
     }
 
-    private void updateExpandButton() {
+    private void updateExpandedState() {
         int drawableId;
         int contentDescriptionId;
         if (mExpanded) {
@@ -142,8 +140,89 @@
             drawableId = R.drawable.ic_expand_notification;
             contentDescriptionId = R.string.expand_button_content_description_collapsed;
         }
-        setImageDrawable(getContext().getDrawable(drawableId));
-        setColorFilter(mOriginalNotificationColor);
         setContentDescription(mContext.getText(contentDescriptionId));
+        mIconView.setImageDrawable(getContext().getDrawable(drawableId));
+
+        // changing the expanded state can affect the number display
+        updateNumber();
+    }
+
+    private void updateNumber() {
+        if (shouldShowNumber()) {
+            CharSequence text = mNumber >= 100
+                    ? getResources().getString(R.string.unread_convo_overflow, 99)
+                    : String.format(Locale.getDefault(), "%d", mNumber);
+            mNumberView.setText(text);
+            mNumberView.setVisibility(VISIBLE);
+        } else {
+            mNumberView.setVisibility(GONE);
+        }
+
+        // changing number can affect the color
+        updateColors();
+    }
+
+    private void updateColors() {
+        if (shouldShowNumber() && !mDisallowColor) {
+            mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor));
+            mIconView.setColorFilter(mHighlightTextColor);
+            mNumberView.setTextColor(mHighlightTextColor);
+        } else {
+            mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor));
+            mIconView.setColorFilter(mDefaultTextColor);
+            mNumberView.setTextColor(mDefaultTextColor);
+        }
+    }
+
+    private boolean shouldShowNumber() {
+        return !mExpanded && mNumber > 1;
+    }
+
+    /**
+     * Set the color used for the expand chevron and the text
+     */
+    @RemotableViewMethod
+    public void setDefaultTextColor(int color) {
+        mDefaultTextColor = color;
+        updateColors();
+    }
+
+    /**
+     * Sets the color used to for the expander when there is no number shown
+     */
+    @RemotableViewMethod
+    public void setDefaultPillColor(@ColorInt int color) {
+        mDefaultPillColor = color;
+        updateColors();
+    }
+
+    /**
+     * Set the color used for the expand chevron and the text
+     */
+    @RemotableViewMethod
+    public void setHighlightTextColor(int color) {
+        mHighlightTextColor = color;
+        updateColors();
+    }
+
+    /**
+     * Sets the color used to highlight the expander when there is a number shown
+     */
+    @RemotableViewMethod
+    public void setHighlightPillColor(@ColorInt int color) {
+        mHighlightPillColor = color;
+        updateColors();
+    }
+
+    /**
+     * Sets the number shown inside the expand button.
+     * This only appears when the expand button is collapsed, and when greater than 1.
+     */
+    @RemotableViewMethod
+    public void setNumber(int number) {
+        if (mNumber != number) {
+            mNumber = number;
+            updateNumber();
+        }
     }
 }
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c9062d8..bcfb06b 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -304,15 +304,14 @@
 static FileDescriptorTable* gOpenFdTable = nullptr;
 
 // Must match values in com.android.internal.os.Zygote.
-// Note that there are gaps in the constants:
-// This is to further keep the values consistent with IVold.aidl
+// The values should be consistent with IVold.aidl
 enum MountExternalKind {
     MOUNT_EXTERNAL_NONE = 0,
     MOUNT_EXTERNAL_DEFAULT = 1,
-    MOUNT_EXTERNAL_INSTALLER = 5,
-    MOUNT_EXTERNAL_PASS_THROUGH = 7,
-    MOUNT_EXTERNAL_ANDROID_WRITABLE = 8,
-    MOUNT_EXTERNAL_COUNT = 9
+    MOUNT_EXTERNAL_INSTALLER = 2,
+    MOUNT_EXTERNAL_PASS_THROUGH = 3,
+    MOUNT_EXTERNAL_ANDROID_WRITABLE = 4,
+    MOUNT_EXTERNAL_COUNT = 5
 };
 
 // Must match values in com.android.internal.os.Zygote.
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index ec502c3..f26bf7c 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -337,7 +337,7 @@
     optional bool starting_displayed = 20;
     optional bool starting_moved = 201;
     optional bool visible_set_from_transferred_starting_window = 22;
-    repeated .android.graphics.RectProto frozen_bounds = 23;
+    repeated .android.graphics.RectProto frozen_bounds = 23 [deprecated=true];
     optional bool visible = 24;
     reserved 25; // configuration_container
     optional IdentifierProto identifier = 26 [deprecated=true];
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d5f5d28..50d1e6b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1886,6 +1886,11 @@
     <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows system APK to manage country code.
+             <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MANAGE_WIFI_COUNTRY_CODE"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi @hide Allows an application to manage an automotive device's application network
          preference as it relates to OEM_PAID and OEM_PRIVATE capable networks.
          <p>Not for use by third-party or privileged applications. -->
@@ -2371,6 +2376,15 @@
     <permission android:name="android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by a {@link android.telecom.CallDiagnosticService},
+         to ensure that only the system can bind to it.
+         <p>Protection level: signature
+         @SystemApi
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by a {@link android.telecom.CallRedirectionService},
          to ensure that only the system can bind to it.
          <p>Protection level: signature|privileged
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_primary_device_default_dark.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_primary_device_default_dark.xml
index 380dd85..90d6b07 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_primary_device_default_dark.xml
@@ -14,8 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.biometrics.UdfpsAnimationView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/udfps_animation_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"/>
+<!-- Please see primary_text_material_dark.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?attr/disabledAlpha"
+          android:color="@color/system_primary_50"/>
+    <item android:color="@color/system_primary_50"/>
+</selector>
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_primary_device_default_light.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_primary_device_default_light.xml
index 380dd85..bdc4fa9 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_primary_device_default_light.xml
@@ -14,8 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.biometrics.UdfpsAnimationView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/udfps_animation_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"/>
+<!-- Please see primary_text_material_light.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?attr/disabledAlpha"
+          android:color="@color/system_primary_900"/>
+    <item android:color="@color/system_primary_900"/>
+</selector>
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_secondary_device_default_dark.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_secondary_device_default_dark.xml
index 380dd85..799636ad 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_secondary_device_default_dark.xml
@@ -14,8 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.biometrics.UdfpsAnimationView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/udfps_animation_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"/>
+<!-- Please see secondary_text_material_dark.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?attr/disabledAlpha"
+          android:color="@color/system_primary_200"/>
+    <item android:color="@color/system_primary_200"/>
+</selector>
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_secondary_device_default_light.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_secondary_device_default_light.xml
index 380dd85..4793bb8 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_secondary_device_default_light.xml
@@ -14,8 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.biometrics.UdfpsAnimationView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/udfps_animation_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"/>
+<!-- Please see secondary_text_material_light.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?attr/disabledAlpha"
+          android:color="@color/system_primary_700"/>
+    <item android:color="@color/system_primary_700"/>
+</selector>
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_tertiary_device_default_dark.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_tertiary_device_default_dark.xml
index 380dd85..c828631 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_tertiary_device_default_dark.xml
@@ -14,8 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.biometrics.UdfpsAnimationView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/udfps_animation_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"/>
+<!-- Please see tertiary_text_material_dark.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?attr/disabledAlpha"
+          android:color="@color/system_primary_400"/>
+    <item android:color="@color/system_primary_400"/>
+</selector>
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/core/res/res/color/text_color_tertiary_device_default_light.xml
similarity index 66%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to core/res/res/color/text_color_tertiary_device_default_light.xml
index 380dd85..82c420a 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/core/res/res/color/text_color_tertiary_device_default_light.xml
@@ -14,8 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.biometrics.UdfpsAnimationView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/udfps_animation_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"/>
+<!-- Please see tertiary_text_material_light.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?attr/disabledAlpha"
+          android:color="@color/system_primary_500"/>
+    <item android:color="@color/system_primary_500"/>
+</selector>
diff --git a/core/res/res/drawable/conversation_unread_bg.xml b/core/res/res/drawable/expand_button_pill_bg.xml
similarity index 90%
rename from core/res/res/drawable/conversation_unread_bg.xml
rename to core/res/res/drawable/expand_button_pill_bg.xml
index d3e00cf..f95044a 100644
--- a/core/res/res/drawable/conversation_unread_bg.xml
+++ b/core/res/res/drawable/expand_button_pill_bg.xml
@@ -14,6 +14,6 @@
   ~ limitations under the License.
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <corners android:radius="20sp" />
+    <corners android:radius="@dimen/notification_expand_button_pill_height" />
     <solid android:color="@android:color/white" />
 </shape>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_collapse_notification.xml b/core/res/res/drawable/ic_collapse_notification.xml
index ca4f0ed..a06ec9f 100644
--- a/core/res/res/drawable/ic_collapse_notification.xml
+++ b/core/res/res/drawable/ic_collapse_notification.xml
@@ -21,8 +21,5 @@
     android:viewportHeight="24.0">
     <path
         android:fillColor="#FF000000"
-        android:pathData="M18.59,16.41L20.0,15.0l-8.0,-8.0 -8.0,8.0 1.41,1.41L12.0,9.83"/>
-    <path
-        android:pathData="M0 0h24v24H0V0z"
-        android:fillColor="#00000000"/>
+        android:pathData="M18.59,15.41L20.0,14.0l-8.0,-8.0 -8.0,8.0 1.41,1.41L12.0,8.83"/>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_expand_notification.xml b/core/res/res/drawable/ic_expand_notification.xml
index a080ce4..160a9c2 100644
--- a/core/res/res/drawable/ic_expand_notification.xml
+++ b/core/res/res/drawable/ic_expand_notification.xml
@@ -21,8 +21,5 @@
     android:viewportHeight="24.0">
     <path
         android:fillColor="#FF000000"
-        android:pathData="M5.41,7.59L4.0,9.0l8.0,8.0 8.0,-8.0 -1.41,-1.41L12.0,14.17"/>
-    <path
-        android:pathData="M24 24H0V0h24v24z"
-        android:fillColor="#00000000"/>
+        android:pathData="M5.41,8.59L4.0,10.0l8.0,8.0 8.0,-8.0 -1.41,-1.41L12.0,15.17"/>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
new file mode 100644
index 0000000..f92e6d6
--- /dev/null
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -0,0 +1,56 @@
+<?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
+  -->
+
+<com.android.internal.widget.NotificationExpandButton
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/expand_button"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="top|end"
+    android:contentDescription="@string/expand_button_content_description_collapsed"
+    android:padding="16dp"
+    android:visibility="gone"
+    >
+
+    <LinearLayout
+        android:id="@+id/expand_button_pill"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/notification_expand_button_pill_height"
+        android:orientation="horizontal"
+        android:background="@drawable/expand_button_pill_bg"
+        >
+
+        <TextView
+            android:id="@+id/expand_button_number"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/notification_expand_button_pill_height"
+            android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+            android:gravity="center_vertical"
+            android:paddingLeft="8dp"
+            />
+
+        <ImageView
+            android:id="@+id/expand_button_icon"
+            android:layout_width="@dimen/notification_expand_button_pill_height"
+            android:layout_height="@dimen/notification_expand_button_pill_height"
+            android:padding="2dp"
+            android:scaleType="fitCenter"
+            android:importantForAccessibility="no"
+            />
+
+    </LinearLayout>
+
+</com.android.internal.widget.NotificationExpandButton>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 1de1d04..81a79c5 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -30,7 +30,8 @@
         android:id="@+id/left_icon"
         android:layout_width="@dimen/notification_left_icon_size"
         android:layout_height="@dimen/notification_left_icon_size"
-        android:layout_gravity="center_vertical|start"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
         android:layout_marginStart="@dimen/notification_left_icon_start"
         android:background="@drawable/notification_large_icon_outline"
         android:clipToOutline="true"
@@ -43,7 +44,8 @@
         android:id="@+id/icon"
         android:layout_width="@dimen/notification_icon_circle_size"
         android:layout_height="@dimen/notification_icon_circle_size"
-        android:layout_gravity="center_vertical|start"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
         android:layout_marginStart="@dimen/notification_icon_circle_start"
         android:background="@drawable/notification_icon_circle"
         android:padding="@dimen/notification_icon_circle_padding"
@@ -55,10 +57,12 @@
         android:id="@+id/notification_top_line"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:layout_gravity="center_vertical"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
+        android:layout_toStartOf="@id/expand_button"
+        android:layout_alignWithParentIfMissing="true"
         android:clipChildren="false"
         android:gravity="center_vertical"
-        android:paddingEnd="@dimen/notification_heading_margin_end"
         android:paddingStart="@dimen/notification_content_margin_start"
         android:theme="@style/Theme.DeviceDefault.Notification"
         >
@@ -71,19 +75,15 @@
         android:id="@+id/alternate_expand_target"
         android:layout_width="@dimen/notification_content_margin_start"
         android:layout_height="match_parent"
-        android:layout_gravity="start"
+        android:layout_alignParentStart="true"
         android:importantForAccessibility="no"
         />
 
-    <com.android.internal.widget.NotificationExpandButton
-        android:id="@+id/expand_button"
-        android:layout_width="@dimen/notification_header_expand_icon_size"
-        android:layout_height="@dimen/notification_header_expand_icon_size"
-        android:layout_gravity="center_vertical|end"
-        android:contentDescription="@string/expand_button_content_description_collapsed"
-        android:paddingTop="@dimen/notification_expand_button_padding_top"
-        android:scaleType="center"
-        android:visibility="gone"
+    <include layout="@layout/notification_expand_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
         />
 
 </NotificationHeaderView>
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index b83611bc..bad9a6b 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -74,14 +74,10 @@
         android:layout_height="match_parent"
         android:layout_gravity="end">
 
-        <com.android.internal.widget.NotificationExpandButton
-            android:id="@+id/expand_button"
-            android:layout_width="@dimen/notification_header_expand_icon_size"
-            android:layout_height="@dimen/notification_header_expand_icon_size"
+        <include layout="@layout/notification_expand_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:layout_gravity="center_vertical|end"
-            android:contentDescription="@string/expand_button_content_description_collapsed"
-            android:paddingTop="@dimen/notification_expand_button_padding_top"
-            android:scaleType="center"
             />
 
     </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
index 471d874..7b52ec3 100644
--- a/core/res/res/layout/notification_template_material_call.xml
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -72,15 +72,10 @@
             </LinearLayout>
 
             <!-- TODO(b/179178086): remove padding from main column when this is visible -->
-            <com.android.internal.widget.NotificationExpandButton
-                android:id="@+id/expand_button"
-                android:layout_width="@dimen/notification_header_expand_icon_size"
-                android:layout_height="@dimen/notification_header_expand_icon_size"
+            <include layout="@layout/notification_expand_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
                 android:layout_gravity="top|end"
-                android:contentDescription="@string/expand_button_content_description_collapsed"
-                android:paddingTop="@dimen/notification_expand_button_padding_top"
-                android:scaleType="center"
-                android:visibility="gone"
                 />
 
         </LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index f3aa540..42fb4a2 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -104,11 +104,10 @@
         <LinearLayout
             android:id="@+id/expand_button_touch_container"
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/conversation_expand_button_size"
-            android:paddingStart="@dimen/conversation_expand_button_side_margin"
+            android:layout_height="@dimen/conversation_expand_button_height"
             android:orientation="horizontal"
             android:layout_gravity="end|top"
-            android:paddingEnd="@dimen/conversation_expand_button_side_margin"
+            android:paddingEnd="0dp"
             android:clipToPadding="false"
             android:clipChildren="false"
             >
@@ -118,34 +117,16 @@
                 android:forceHasOverlappingRendering="false"
                 android:layout_width="40dp"
                 android:layout_height="40dp"
-                android:layout_marginEnd="11dp"
+                android:layout_marginStart="@dimen/conversation_image_start_margin"
                 android:spacing="0dp"
                 android:layout_gravity="center"
                 android:clipToPadding="false"
                 android:clipChildren="false"
                 />
-            <!-- Unread Count -->
-            <TextView
-                android:id="@+id/conversation_unread_count"
-                android:layout_width="33sp"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="11dp"
-                android:layout_gravity="center"
-                android:gravity="center"
-                android:padding="2dp"
-                android:visibility="gone"
-                android:textAppearance="@style/TextAppearance.DeviceDefault.Notification"
-                android:textColor="#FFFFFF"
-                android:textSize="12sp"
-                android:background="@drawable/conversation_unread_bg"
-                />
-            <com.android.internal.widget.NotificationExpandButton
-                android:id="@+id/expand_button"
+            <include layout="@layout/notification_expand_button"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="center"
-                android:drawable="@drawable/ic_expand_notification"
-                android:contentDescription="@string/expand_button_content_description_collapsed"
                 />
         </LinearLayout>
     </FrameLayout>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 1020c14..9b56321 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -42,12 +42,7 @@
     <color name="background_floating_device_default_dark">@color/system_primary_900</color>
     <color name="background_floating_device_default_light">@color/system_primary_100</color>
 
-    <color name="text_color_primary_device_default_light">@color/system_primary_900</color>
-    <color name="text_color_primary_device_default_dark">@color/system_primary_50</color>
-    <color name="text_color_secondary_device_default_light">@color/system_primary_700</color>
-    <color name="text_color_secondary_device_default_dark">@color/system_primary_200</color>
-    <color name="text_color_tertiary_device_default_light">@color/system_primary_500</color>
-    <color name="text_color_tertiary_device_default_dark">@color/system_primary_400</color>
+    <!-- Please refer to text_color_[primary]_device_default_[light].xml for text colors-->
     <color name="foreground_device_default_light">@color/text_color_primary_device_default_light</color>
     <color name="foreground_device_default_dark">@color/text_color_primary_device_default_dark</color>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 359bb69..a0cb3df 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3948,6 +3948,10 @@
          color supplied by the Notification.Builder if present. -->
     <bool name="config_tintNotificationActionButtons">true</bool>
 
+    <!-- Flag indicating that tinted items (actions, expander, etc) are to be tinted using the
+         theme color, rather than the notification color. -->
+    <bool name="config_tintNotificationsWithTheme">true</bool>
+
     <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
     <bool name="config_showAreaUpdateInfoSettings">false</bool>
 
@@ -4662,15 +4666,6 @@
     <!-- WindowsManager JetPack display features -->
     <string name="config_display_features" translatable="false" />
 
-    <!-- Physical Display IDs of the display-devices that are swapped when a folding device folds.
-         This list is expected to contain two elements: the first is the display to use
-         when the device is folded, the second is the display to use when unfolded. If the array
-         is empty or the display IDs are not recognized, this feature is turned off and the value
-         ignored.
-         TODO: b/170470621 - remove once we can have multiple Internal displays in DMS as
-               well as a notification from DisplayStateManager. -->
-    <string-array name="config_internalFoldedPhysicalDisplayIds" translatable="false" />
-
     <!-- Aspect ratio of task level letterboxing. Values <= 1.0 will be ignored.
          Note: Activity min/max aspect ratio restrictions will still be respected by the
          activity-level letterboxing (size-compat mode). Therefore this override can control the
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index c2b6b99..10aa7b3 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -224,7 +224,7 @@
     <dimen name="notification_content_margin_end">16dp</dimen>
 
     <!-- The margin on the end of the top-line content views (accommodates the expander) -->
-    <dimen name="notification_heading_margin_end">48dp</dimen>
+    <dimen name="notification_heading_margin_end">56dp</dimen>
 
     <!-- The margin for text at the end of the image view for media notifications -->
     <dimen name="notification_media_image_margin_end">72dp</dimen>
@@ -248,7 +248,7 @@
     <dimen name="call_notification_collapsible_indent">64dp</dimen>
 
     <!-- The size of icons for visual actions in the notification_material_action_list -->
-    <dimen name="notification_actions_icon_size">48dp</dimen>
+    <dimen name="notification_actions_icon_size">56dp</dimen>
 
     <!-- The size of icons for visual actions in the notification_material_action_list -->
     <dimen name="notification_actions_icon_drawable_size">20dp</dimen>
@@ -314,10 +314,10 @@
     <dimen name="notification_conversation_header_separating_margin">4dp</dimen>
 
     <!-- The absolute size of the notification expand icon. -->
-    <dimen name="notification_header_expand_icon_size">48dp</dimen>
+    <dimen name="notification_header_expand_icon_size">56dp</dimen>
 
-    <!-- The top padding for the notification expand button. -->
-    <dimen name="notification_expand_button_padding_top">1dp</dimen>
+    <!-- the height of the expand button pill -->
+    <dimen name="notification_expand_button_pill_height">24dp</dimen>
 
     <!-- Vertical margin for the headerless notification content, when content has 1 line -->
     <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) -->
@@ -690,6 +690,13 @@
     <!-- The default minimal size of a PiP task, in both dimensions. -->
     <dimen name="default_minimal_size_pip_resizable_task">108dp</dimen>
 
+    <!--
+      The overridable minimal size of a PiP task, in both dimensions.
+      Different from default_minimal_size_pip_resizable_task, this is to limit the dimension
+      when the pinned stack size is overridden by app via minWidth/minHeight.
+    -->
+    <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen>
+
     <!-- Height of a task when in minimized mode from the top when launcher is resizable. -->
     <dimen name="task_height_of_minimized_mode">80dp</dimen>
 
@@ -739,7 +746,7 @@
     <dimen name="notification_right_icon_headerless_margin">20dp</dimen>
     <!-- The top margin of the right icon in the "big" notification states -->
     <!--  TODO(b/181048615): Move the large icon below the expander in big states  -->
-    <dimen name="notification_right_icon_big_margin_top">16dp</dimen>
+    <dimen name="notification_right_icon_big_margin_top">20dp</dimen>
     <!-- The size of the left icon -->
     <dimen name="notification_left_icon_size">@dimen/notification_icon_circle_size</dimen>
     <!-- The left padding of the left icon -->
@@ -770,13 +777,10 @@
     <dimen name="conversation_icon_circle_start">28dp</dimen>
     <!-- Start of the content in the conversation template -->
     <dimen name="conversation_content_start">80dp</dimen>
-    <!-- Size of the expand button in the conversation layout -->
-    <dimen name="conversation_expand_button_size">80dp</dimen>
-    <!-- Top margin of the expand button for conversations when expanded -->
-    <dimen name="conversation_expand_button_top_margin_expanded">18dp</dimen>
-    <!-- Side margin of the expand button for conversations.
-         width of expand asset (22) + 2 * this (13) == notification_header_expand_icon_size (48) -->
-    <dimen name="conversation_expand_button_side_margin">13dp</dimen>
+    <!-- Height of the expand button in the conversation layout -->
+    <dimen name="conversation_expand_button_height">80dp</dimen>
+    <!-- this is the margin between the Conversation image and the content -->
+    <dimen name="conversation_image_start_margin">12dp</dimen>
     <!-- Side margins of the conversation badge in relation to the conversation icon -->
     <dimen name="conversation_badge_side_margin">36dp</dimen>
     <!-- size of the notification badge when applied to the conversation icon -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5d56eb7..e380f40 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1893,6 +1893,7 @@
   <java-symbol type="bool" name="config_notificationHeaderClickableForExpand" />
   <java-symbol type="bool" name="config_enableNightMode" />
   <java-symbol type="bool" name="config_tintNotificationActionButtons" />
+  <java-symbol type="bool" name="config_tintNotificationsWithTheme" />
   <java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" />
   <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
@@ -1948,6 +1949,7 @@
   <java-symbol type="fraction" name="config_dimBehindFadeDuration" />
   <java-symbol type="dimen" name="default_minimal_size_resizable_task" />
   <java-symbol type="dimen" name="default_minimal_size_pip_resizable_task" />
+  <java-symbol type="dimen" name="overridable_minimal_size_pip_resizable_task" />
   <java-symbol type="dimen" name="task_height_of_minimized_mode" />
   <java-symbol type="fraction" name="config_screenAutoBrightnessDozeScaleFactor" />
   <java-symbol type="bool" name="config_allowPriorityVibrationsInLowPowerMode" />
@@ -2892,6 +2894,9 @@
   <java-symbol type="id" name="header_text" />
   <java-symbol type="id" name="header_text_secondary" />
   <java-symbol type="id" name="expand_button" />
+  <java-symbol type="id" name="expand_button_pill" />
+  <java-symbol type="id" name="expand_button_number" />
+  <java-symbol type="id" name="expand_button_icon" />
   <java-symbol type="id" name="alternate_expand_target" />
   <java-symbol type="id" name="notification_header" />
   <java-symbol type="id" name="notification_top_line" />
@@ -2912,7 +2917,6 @@
   <java-symbol type="dimen" name="notification_header_background_height" />
   <java-symbol type="dimen" name="notification_header_touchable_height" />
   <java-symbol type="dimen" name="notification_header_expand_icon_size" />
-  <java-symbol type="dimen" name="notification_expand_button_padding_top" />
   <java-symbol type="dimen" name="notification_header_icon_size" />
   <java-symbol type="dimen" name="notification_header_app_name_margin_start" />
   <java-symbol type="dimen" name="notification_header_separating_margin" />
@@ -4032,7 +4036,6 @@
   <java-symbol type="id" name="message_icon_container" />
   <java-symbol type="id" name="conversation_image_message_container" />
   <java-symbol type="id" name="conversation_icon_container" />
-  <java-symbol type="dimen" name="conversation_expand_button_top_margin_expanded" />
   <java-symbol type="dimen" name="messaging_group_singleline_sender_padding_end" />
   <java-symbol type="dimen" name="conversation_badge_side_margin" />
   <java-symbol type="dimen" name="conversation_avatar_size" />
@@ -4053,7 +4056,6 @@
   <java-symbol type="dimen" name="button_padding_horizontal_material" />
   <java-symbol type="dimen" name="button_inset_horizontal_material" />
   <java-symbol type="layout" name="conversation_face_pile_layout" />
-  <java-symbol type="id" name="conversation_unread_count" />
   <java-symbol type="string" name="unread_convo_overflow" />
   <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Conversation.AppName" />
   <java-symbol type="drawable" name="conversation_badge_background" />
@@ -4160,7 +4162,6 @@
   <java-symbol type="dimen" name="default_background_blur_radius" />
   <java-symbol type="array" name="config_keep_warming_services" />
   <java-symbol type="string" name="config_display_features" />
-  <java-symbol type="array" name="config_internalFoldedPhysicalDisplayIds" />
 
   <java-symbol type="dimen" name="controls_thumbnail_image_max_height" />
   <java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
diff --git a/core/tests/coretests/src/android/app/usage/OWNERS b/core/tests/coretests/src/android/app/usage/OWNERS
new file mode 100644
index 0000000..1271fa79
--- /dev/null
+++ b/core/tests/coretests/src/android/app/usage/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 532296
+include /services/usage/OWNERS
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index 7e1e7f4..ab24f89 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -33,6 +33,9 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Tests for AccessibilityInteractionClient
  */
@@ -62,7 +65,7 @@
         final long accessibilityNodeId = 0x4321L;
         AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain();
         nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId);
-        mMockConnection.mInfoToReturn = nodeFromConnection;
+        mMockConnection.mInfosToReturn = Arrays.asList(nodeFromConnection);
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
         AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId(
                 MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null);
@@ -72,7 +75,7 @@
     }
 
     private static class MockConnection extends AccessibilityServiceConnectionImpl {
-        AccessibilityNodeInfo mInfoToReturn;
+        List<AccessibilityNodeInfo> mInfosToReturn;
 
         @Override
         public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
@@ -80,7 +83,7 @@
                 IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
                 Bundle arguments) {
             try {
-                callback.setFindAccessibilityNodeInfoResult(mInfoToReturn, interactionId);
+                callback.setFindAccessibilityNodeInfosResult(mInfosToReturn, interactionId);
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 3d964fb..f2a33de 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -70,5 +70,6 @@
         <permission name="android.permission.READ_WIFI_CREDENTIAL" />
         <permission name="android.permission.USE_BACKGROUND_BLUR" />
         <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
+        <permission name="android.permission.FORCE_STOP_PACKAGES" />
     </privapp-permissions>
 </permissions>
diff --git a/keystore/java/android/security/LegacyVpnProfileStore.java b/keystore/java/android/security/LegacyVpnProfileStore.java
new file mode 100644
index 0000000..41cfb27
--- /dev/null
+++ b/keystore/java/android/security/LegacyVpnProfileStore.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import android.annotation.NonNull;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.security.keystore.AndroidKeyStoreProvider;
+import android.security.vpnprofilestore.IVpnProfileStore;
+import android.util.Log;
+
+/**
+ * @hide This class allows legacy VPN access to its profiles that were stored in Keystore.
+ * The storage of unstructured blobs in Android Keystore is going away, because there is no
+ * architectural or security benefit of storing profiles in keystore over storing them
+ * in the file system. This class allows access to the blobs that still exist in keystore.
+ * And it stores new blob in a database that is still owned by Android Keystore.
+ */
+public class LegacyVpnProfileStore {
+    private static final String TAG = "LegacyVpnProfileStore";
+
+    public static final int SYSTEM_ERROR = IVpnProfileStore.ERROR_SYSTEM_ERROR;
+    public static final int PROFILE_NOT_FOUND = IVpnProfileStore.ERROR_PROFILE_NOT_FOUND;
+
+    private static final String VPN_PROFILE_STORE_SERVICE_NAME = "android.security.vpnprofilestore";
+
+    private static IVpnProfileStore getService() {
+        return IVpnProfileStore.Stub.asInterface(
+                    ServiceManager.checkService(VPN_PROFILE_STORE_SERVICE_NAME));
+    }
+
+    /**
+     * Stores the profile under the alias in the profile database. Existing profiles by the
+     * same name will be replaced.
+     * @param alias The name of the profile
+     * @param profile The profile.
+     * @return true if the profile was successfully added. False otherwise.
+     * @hide
+     */
+    public static boolean put(@NonNull String alias, @NonNull byte[] profile) {
+        try {
+            if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
+                getService().put(alias, profile);
+                return true;
+            } else {
+                return KeyStore.getInstance().put(
+                        alias, profile, KeyStore.UID_SELF, 0);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to put vpn profile.", e);
+            return false;
+        }
+    }
+
+    /**
+     * Retrieves a profile by the name alias from the profile database.
+     * @param alias Name of the profile to retrieve.
+     * @return The unstructured blob, that is the profile that was stored using
+     *         LegacyVpnProfileStore#put or with
+     *         android.security.Keystore.put(Credentials.VPN + alias).
+     *         Returns null if no profile was found.
+     * @hide
+     */
+    public static byte[] get(@NonNull String alias) {
+        try {
+            if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
+                return getService().get(alias);
+            } else {
+                return KeyStore.getInstance().get(alias, true /* suppressKeyNotFoundWarning */);
+            }
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode != PROFILE_NOT_FOUND) {
+                Log.e(TAG, "Failed to get vpn profile.", e);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to get vpn profile.", e);
+        }
+        return null;
+    }
+
+    /**
+     * Removes a profile by the name alias from the profile database.
+     * @param alias Name of the profile to be removed.
+     * @return True if a profile was removed. False if no such profile was found.
+     * @hide
+     */
+    public static boolean remove(@NonNull String alias) {
+        try {
+            if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
+                getService().remove(alias);
+                return true;
+            } else {
+                return KeyStore.getInstance().delete(alias);
+            }
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode != PROFILE_NOT_FOUND) {
+                Log.e(TAG, "Failed to remove vpn profile.", e);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to remove vpn profile.", e);
+        }
+        return false;
+    }
+
+    /**
+     * Lists the vpn profiles stored in the database.
+     * @return An array of strings representing the aliases stored in the profile database.
+     *         The return value may be empty but never null.
+     * @hide
+     */
+    public static @NonNull String[] list(@NonNull String prefix) {
+        try {
+            if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
+                final String[] aliases = getService().list(prefix);
+                for (int i = 0; i < aliases.length; ++i) {
+                    aliases[i] = aliases[i].substring(prefix.length());
+                }
+                return aliases;
+            } else {
+                final String[] result = KeyStore.getInstance().list(prefix);
+                return result != null ? result : new String[0];
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to list vpn profiles.", e);
+        }
+        return new String[0];
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index cc353dc..85bd24c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -24,6 +24,7 @@
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.startingsurface.StartingSurface;
 import com.android.wm.shell.transition.Transitions;
@@ -42,6 +43,7 @@
     private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
     private final Optional<SplitScreenController> mSplitScreenOptional;
     private final Optional<AppPairsController> mAppPairsOptional;
+    private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
     private final FullscreenTaskListener mFullscreenTaskListener;
     private final ShellExecutor mMainExecutor;
     private final Transitions mTransitions;
@@ -56,6 +58,7 @@
             Optional<SplitScreenController> splitScreenOptional,
             Optional<AppPairsController> appPairsOptional,
             Optional<StartingSurface> startingSurfaceOptional,
+            Optional<PipTouchHandler> pipTouchHandlerOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
             ShellExecutor mainExecutor) {
@@ -66,6 +69,7 @@
                 splitScreenOptional,
                 appPairsOptional,
                 startingSurfaceOptional,
+                pipTouchHandlerOptional,
                 fullscreenTaskListener,
                 transitions,
                 mainExecutor).mImpl;
@@ -78,6 +82,7 @@
             Optional<SplitScreenController> splitScreenOptional,
             Optional<AppPairsController> appPairsOptional,
             Optional<StartingSurface> startingSurfaceOptional,
+            Optional<PipTouchHandler> pipTouchHandlerOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
             ShellExecutor mainExecutor) {
@@ -88,6 +93,7 @@
         mSplitScreenOptional = splitScreenOptional;
         mAppPairsOptional = appPairsOptional;
         mFullscreenTaskListener = fullscreenTaskListener;
+        mPipTouchHandlerOptional = pipTouchHandlerOptional;
         mTransitions = transitions;
         mMainExecutor = mainExecutor;
         mStartingSurfaceOptional = startingSurfaceOptional;
@@ -112,6 +118,11 @@
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             mTransitions.register(mShellTaskOrganizer);
         }
+
+        // TODO(b/181599115): This should really be the pip controller, but until we can provide the
+        // controller instead of the feature interface, can just initialize the touch handler if
+        // needed
+        mPipTouchHandlerOptional.ifPresent((handler) -> handler.init());
     }
 
     @ExternalThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index ac5d14c..702385e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -54,6 +54,7 @@
     private float mMaxAspectRatio;
     private int mDefaultStackGravity;
     private int mDefaultMinSize;
+    private int mOverridableMinSize;
     private Point mScreenEdgeInsets;
 
     public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState) {
@@ -78,6 +79,8 @@
                 com.android.internal.R.integer.config_defaultPictureInPictureGravity);
         mDefaultMinSize = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
+        mOverridableMinSize = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.overridable_minimal_size_pip_resizable_task);
         final String screenEdgeInsetsDpString = res.getString(
                 com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets);
         final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
@@ -155,7 +158,10 @@
         // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout>
         // without minWidth/minHeight
         if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
-            return new Size(windowLayout.minWidth, windowLayout.minHeight);
+            // If either dimension is smaller than the allowed minimum, adjust them
+            // according to mOverridableMinSize
+            return new Size(Math.max(windowLayout.minWidth, mOverridableMinSize),
+                    Math.max(windowLayout.minHeight, mOverridableMinSize));
         }
         return null;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index d9a7bdb..9ee6a22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -87,7 +87,7 @@
                     SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
 
     // Allow dragging the PIP to a location to close it
-    private final boolean mEnableDismissDragToEdge;
+    private boolean mEnableDismissDragToEdge;
 
     private int mDismissAreaHeight;
 
@@ -104,67 +104,66 @@
         mMotionHelper = motionHelper;
         mMainExecutor = mainExecutor;
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+    }
 
-        Resources res = context.getResources();
+    public void init() {
+        Resources res = mContext.getResources();
         mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
         mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
 
-        mMainExecutor.execute(() -> {
-            mTargetView = new DismissCircleView(context);
-            mTargetViewContainer = new FrameLayout(context);
-            mTargetViewContainer.setBackgroundDrawable(
-                    context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
-            mTargetViewContainer.setClipChildren(false);
-            mTargetViewContainer.addView(mTargetView);
+        mTargetView = new DismissCircleView(mContext);
+        mTargetViewContainer = new FrameLayout(mContext);
+        mTargetViewContainer.setBackgroundDrawable(
+                mContext.getDrawable(R.drawable.floating_dismiss_gradient_transition));
+        mTargetViewContainer.setClipChildren(false);
+        mTargetViewContainer.addView(mTargetView);
 
-            mMagnetizedPip = mMotionHelper.getMagnetizedPip();
-            mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
-            updateMagneticTargetSize();
+        mMagnetizedPip = mMotionHelper.getMagnetizedPip();
+        mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
+        updateMagneticTargetSize();
 
-            mMagnetizedPip.setAnimateStuckToTarget(
-                    (target, velX, velY, flung, after) -> {
-                        if (mEnableDismissDragToEdge) {
-                            mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung,
-                                    after);
-                        }
-                        return Unit.INSTANCE;
-                    });
-            mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
-                @Override
-                public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                    // Show the dismiss target, in case the initial touch event occurred within
-                    // the magnetic field radius.
+        mMagnetizedPip.setAnimateStuckToTarget(
+                (target, velX, velY, flung, after) -> {
                     if (mEnableDismissDragToEdge) {
-                        showDismissTargetMaybe();
+                        mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
                     }
+                    return Unit.INSTANCE;
+                });
+        mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
+            @Override
+            public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+                // Show the dismiss target, in case the initial touch event occurred within
+                // the magnetic field radius.
+                if (mEnableDismissDragToEdge) {
+                    showDismissTargetMaybe();
                 }
+            }
 
-                @Override
-                public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
-                        float velX, float velY, boolean wasFlungOut) {
-                    if (wasFlungOut) {
-                        mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
-                        hideDismissTargetMaybe();
-                    } else {
-                        mMotionHelper.setSpringingToTouch(true);
-                    }
+            @Override
+            public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+                    float velX, float velY, boolean wasFlungOut) {
+                if (wasFlungOut) {
+                    mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
+                    hideDismissTargetMaybe();
+                } else {
+                    mMotionHelper.setSpringingToTouch(true);
                 }
+            }
 
-                @Override
-                public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                    mMainExecutor.executeDelayed(() -> {
-                        mMotionHelper.notifyDismissalPending();
-                        mMotionHelper.animateDismiss();
-                        hideDismissTargetMaybe();
+            @Override
+            public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+                mMainExecutor.executeDelayed(() -> {
+                    mMotionHelper.notifyDismissalPending();
+                    mMotionHelper.animateDismiss();
+                    hideDismissTargetMaybe();
 
-                        mPipUiEventLogger.log(
-                                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
-                    }, 0);
-                }
-            });
-
-            mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
+                    mPipUiEventLogger.log(
+                            PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
+                }, 0);
+            }
         });
+
+        mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index eae8945..d742aa6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -102,7 +102,7 @@
      * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
      * using physics animations.
      */
-    private final PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator;
+    private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator;
 
     private MagnetizedObject<Rect> mMagnetizedPip;
 
@@ -171,7 +171,7 @@
     public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
             PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
             PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
-            FloatingContentCoordinator floatingContentCoordinator, ShellExecutor mainExecutor) {
+            FloatingContentCoordinator floatingContentCoordinator) {
         mContext = context;
         mPipTaskOrganizer = pipTaskOrganizer;
         mPipBoundsState = pipBoundsState;
@@ -179,15 +179,6 @@
         mSnapAlgorithm = snapAlgorithm;
         mFloatingContentCoordinator = floatingContentCoordinator;
         pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
-        mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
-                mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
-
-        // Need to get the shell main thread sf vsync animation handler
-        mainExecutor.execute(() -> {
-            mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
-                    mSfAnimationHandlerThreadLocal.get());
-        });
-
         mResizePipUpdateListener = (target, values) -> {
             if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
                 mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
@@ -196,6 +187,14 @@
         };
     }
 
+    public void init() {
+        // Note: Needs to get the shell main thread sf vsync animation handler
+        mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
+                mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+        mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
+                mSfAnimationHandlerThreadLocal.get());
+    }
+
     @NonNull
     @Override
     public Rect getFloatingBoundsOnScreen() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 78ee186..31057f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -132,8 +132,10 @@
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mPhonePipMenuController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
+    }
 
-        context.getDisplay().getRealSize(mMaxSize);
+    public void init() {
+        mContext.getDisplay().getRealSize(mMaxSize);
         reloadResources();
 
         mEnablePinchResize = DeviceConfig.getBoolean(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 5e23281..543ecfc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -71,12 +71,13 @@
     private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
 
     // Allow PIP to resize to a slightly bigger state upon touch
-    private final boolean mEnableResize;
+    private boolean mEnableResize;
     private final Context mContext;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     private final @NonNull PipBoundsState mPipBoundsState;
     private final PipUiEventLogger mPipUiEventLogger;
     private final PipDismissTargetHandler mPipDismissTargetHandler;
+    private final ShellExecutor mMainExecutor;
 
     private PipResizeGestureHandler mPipResizeGestureHandler;
     private WeakReference<Consumer<Rect>> mPipExclusionBoundsChangeListener;
@@ -166,16 +167,18 @@
             ShellExecutor mainExecutor) {
         // Initialize the Pip input consumer
         mContext = context;
+        mMainExecutor = mainExecutor;
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipBoundsState = pipBoundsState;
         mMenuController = menuController;
         mPipUiEventLogger = pipUiEventLogger;
+        mFloatingContentCoordinator = floatingContentCoordinator;
         mMenuController.addListener(new PipMenuListener());
         mGesture = new DefaultPipTouchGesture();
         mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer,
                 mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(), pipTransitionController,
-                floatingContentCoordinator, mainExecutor);
+                floatingContentCoordinator);
         mPipResizeGestureHandler =
                 new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
                         mMotionHelper, pipTaskOrganizer, this::getMovementBounds,
@@ -199,22 +202,26 @@
                 },
                 menuController::hideMenu,
                 mainExecutor);
-
-        Resources res = context.getResources();
-        mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
-        reloadResources();
-
-        mFloatingContentCoordinator = floatingContentCoordinator;
         mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
                 mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
                 this::onAccessibilityShowMenu, this::updateMovementBounds, mainExecutor);
+    }
+
+    public void init() {
+        Resources res = mContext.getResources();
+        mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
+        reloadResources();
+
+        mMotionHelper.init();
+        mPipResizeGestureHandler.init();
+        mPipDismissTargetHandler.init();
 
         mEnableStash = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 PIP_STASHING,
                 /* defaultValue = */ true);
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                mainExecutor,
+                mMainExecutor,
                 properties -> {
                     if (properties.getKeyset().contains(PIP_STASHING)) {
                         mEnableStash = properties.getBoolean(
@@ -226,7 +233,7 @@
                 PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
                 DEFAULT_STASH_VELOCITY_THRESHOLD);
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                mainExecutor,
+                mMainExecutor,
                 properties -> {
                     if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) {
                         mStashVelocityThreshold = properties.getFloat(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index d10c036..79ec624 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -43,7 +43,6 @@
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 
@@ -125,7 +124,7 @@
 
     @Test
     public void startSwipePipToHome_updatesOverrideMinSize() {
-        final Size minSize = new Size(100, 80);
+        final Size minSize = new Size(400, 320);
 
         mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, createActivityInfo(minSize),
                 createPipParams(null));
@@ -153,7 +152,7 @@
 
     @Test
     public void onTaskAppeared_updatesOverrideMinSize() {
-        final Size minSize = new Size(100, 80);
+        final Size minSize = new Size(400, 320);
 
         mSpiedPipTaskOrganizer.onTaskAppeared(
                 createTaskInfo(mComponent1, createPipParams(null), minSize),
@@ -191,7 +190,7 @@
         mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
                 createPipParams(null)), null /* leash */);
 
-        final Size minSize = new Size(100, 80);
+        final Size minSize = new Size(400, 320);
         mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent2,
                 createPipParams(null), minSize));
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 19930485..75ea4ac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -106,6 +106,7 @@
                 mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
                 mMockPipTransitionController, mFloatingContentCoordinator, mPipUiEventLogger,
                 mMainExecutor);
+        mPipTouchHandler.init();
         mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
         mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
         mPipTouchHandler.setPipMotionHelper(mMotionHelper);
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index af7271e..61f9960 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -176,9 +176,6 @@
     if (sApiLevel <= 27 && paint.getBlendMode() == SkBlendMode::kClear) {
         paint.setBlendMode(SkBlendMode::kDstOut);
     }
-
-    // disabling AA on bitmap draws matches legacy HWUI behavior
-    paint.setAntiAlias(false);
 }
 
 static SkFilterMode Paint_to_filter(const SkPaint& paint) {
diff --git a/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl
index cee3635..8186fb7 100644
--- a/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl
@@ -32,8 +32,8 @@
      * Unique vendor ID. Identifies the engine the sound model
      * was build for */
     String vendorUuid;
-    /** Opaque data transparent to Android framework */
-    ParcelFileDescriptor data;
+    /** Opaque data transparent to Android framework. May be null if dataSize is 0. */
+    @nullable ParcelFileDescriptor data;
     /** Size of the above data, in bytes. */
     int dataSize;
 }
diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java
index ff9fd41..ca175b4 100644
--- a/media/java/android/media/AudioMetadata.java
+++ b/media/java/android/media/AudioMetadata.java
@@ -195,6 +195,61 @@
         @NonNull public static final Key<Integer> KEY_AUDIO_ENCODING =
                 createKey("audio-encoding", Integer.class);
 
+
+        /**
+         * A key representing the audio presentation id being decoded by a next generation
+         * audio decoder.
+         *
+         * An Integer value representing presentation id.
+         *
+         * @see AudioPresentation#getPresentationId()
+         */
+        @NonNull public static final Key<Integer> KEY_PRESENTATION_ID =
+                createKey("presentation-id", Integer.class);
+
+         /**
+         * A key representing the audio program id being decoded by a next generation
+         * audio decoder.
+         *
+         * An Integer value representing program id.
+         *
+         * @see AudioPresentation#getProgramId()
+         */
+        @NonNull public static final Key<Integer> KEY_PROGRAM_ID =
+                createKey("program-id", Integer.class);
+
+
+         /**
+         * A key representing the audio presentation content classifier being rendered
+         * by a next generation audio decoder.
+         *
+         * An Integer value representing presentation content classifier.
+         *
+         * @see AudioPresentation.ContentClassifier
+         * One of {@link AudioPresentation#CONTENT_UNKNOWN},
+         *     {@link AudioPresentation#CONTENT_MAIN},
+         *     {@link AudioPresentation#CONTENT_MUSIC_AND_EFFECTS},
+         *     {@link AudioPresentation#CONTENT_VISUALLY_IMPAIRED},
+         *     {@link AudioPresentation#CONTENT_HEARING_IMPAIRED},
+         *     {@link AudioPresentation#CONTENT_DIALOG},
+         *     {@link AudioPresentation#CONTENT_COMMENTARY},
+         *     {@link AudioPresentation#CONTENT_EMERGENCY},
+         *     {@link AudioPresentation#CONTENT_VOICEOVER}.
+         */
+        @NonNull public static final Key<Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER =
+                createKey("presentation-content-classifier", Integer.class);
+
+        /**
+         * A key representing the audio presentation language being rendered by a next
+         * generation audio decoder.
+         *
+         * A String value representing ISO 639-2 (three letter code).
+         *
+         * @see AudioPresentation#getLocale()
+         */
+        @NonNull public static final Key<String> KEY_PRESENTATION_LANGUAGE =
+                createKey("presentation-language", String.class);
+
         private Format() {} // delete constructor
     }
 
diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
index 894fbba..47358be 100644
--- a/media/java/android/media/AudioPresentation.java
+++ b/media/java/android/media/AudioPresentation.java
@@ -57,6 +57,64 @@
     /** @hide */
     @IntDef(
         value = {
+        CONTENT_UNKNOWN,
+        CONTENT_MAIN,
+        CONTENT_MUSIC_AND_EFFECTS,
+        CONTENT_VISUALLY_IMPAIRED,
+        CONTENT_HEARING_IMPAIRED,
+        CONTENT_DIALOG,
+        CONTENT_COMMENTARY,
+        CONTENT_EMERGENCY,
+        CONTENT_VOICEOVER,
+    })
+
+    /**
+     * The ContentClassifier int definitions represent the AudioPresentation content
+     * classifier (as per TS 103 190-1 v1.2.1 4.3.3.8.1)
+    */
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ContentClassifier {}
+
+    /**
+     * Audio presentation classifier: Unknown.
+     */
+    public static final int CONTENT_UNKNOWN                 = -1;
+    /**
+     * Audio presentation classifier: Complete main.
+     */
+    public static final int CONTENT_MAIN                    = 0;
+    /**
+     * Audio presentation content classifier: Music and effects.
+     */
+    public static final int CONTENT_MUSIC_AND_EFFECTS       = 1;
+    /**
+     * Audio presentation content classifier: Visually impaired.
+     */
+    public static final int CONTENT_VISUALLY_IMPAIRED       = 2;
+    /**
+     * Audio presentation content classifier: Hearing impaired.
+     */
+    public static final int CONTENT_HEARING_IMPAIRED        = 3;
+    /**
+     * Audio presentation content classifier: Dialog.
+     */
+    public static final int CONTENT_DIALOG                  = 4;
+    /**
+     * Audio presentation content classifier: Commentary.
+     */
+    public static final int CONTENT_COMMENTARY              = 5;
+    /**
+     * Audio presentation content classifier: Emergency.
+     */
+    public static final int CONTENT_EMERGENCY               = 6;
+    /**
+     * Audio presentation content classifier: Voice over.
+     */
+    public static final int CONTENT_VOICEOVER               = 7;
+
+    /** @hide */
+    @IntDef(
+        value = {
             MASTERING_NOT_INDICATED,
             MASTERED_FOR_STEREO,
             MASTERED_FOR_SURROUND,
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 7f19662..c1dca5d 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -394,16 +394,20 @@
     }
 
     private void executeNotifyIfInUseCommand() {
-        int status = getStatus();
-
-        if (status == STATUS_IN_USE) {
-            startForeground(NOTIFICATION_ID,
-                    buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
-        } else if (status == STATUS_READY) {
-            startForeground(NOTIFICATION_ID,
-                    buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
-        } else {
-            stopSelf();
+        switch (getStatus()) {
+            case STATUS_IN_USE:
+                startForeground(NOTIFICATION_ID,
+                        buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
+                break;
+            case STATUS_READY:
+                startForeground(NOTIFICATION_ID,
+                        buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
+                break;
+            case STATUS_IN_PROGRESS:
+                break;
+            case STATUS_NOT_STARTED:
+            default:
+                stopSelf();
         }
     }
 
diff --git a/packages/LocalTransport/OWNERS b/packages/LocalTransport/OWNERS
new file mode 100644
index 0000000..d99779e
--- /dev/null
+++ b/packages/LocalTransport/OWNERS
@@ -0,0 +1 @@
+include /services/backup/OWNERS
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java
index 228de03..35499c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java
@@ -80,7 +80,8 @@
      * Fills a list of applications which queried location recently within specified time.
      * Apps are sorted by recency. Apps with more recent location accesses are in the front.
      */
-    public List<Access> getAppList() {
+    @VisibleForTesting
+    List<Access> getAppList(boolean showSystemApps) {
         // Retrieve a location usage list from AppOps
         PackageManager pm = mContext.getPackageManager();
         AppOpsManager aoManager =
@@ -108,22 +109,26 @@
 
             // Don't show apps that do not have user sensitive location permissions
             boolean showApp = true;
-            for (int op : LOCATION_OPS) {
-                final String permission = AppOpsManager.opToPermission(op);
-                final int permissionFlags = pm.getPermissionFlags(permission, packageName, user);
-                if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
-                        PermissionChecker.PID_UNKNOWN, uid, packageName)
-                                == PermissionChecker.PERMISSION_GRANTED) {
-                    if ((permissionFlags
-                            & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) == 0) {
-                        showApp = false;
-                        break;
-                    }
-                } else {
-                    if ((permissionFlags
-                            & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) {
-                        showApp = false;
-                        break;
+            if (!showSystemApps) {
+                for (int op : LOCATION_OPS) {
+                    final String permission = AppOpsManager.opToPermission(op);
+                    final int permissionFlags = pm.getPermissionFlags(permission, packageName,
+                            user);
+                    if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
+                            PermissionChecker.PID_UNKNOWN, uid, packageName)
+                            == PermissionChecker.PERMISSION_GRANTED) {
+                        if ((permissionFlags
+                                & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)
+                                == 0) {
+                            showApp = false;
+                            break;
+                        }
+                    } else {
+                        if ((permissionFlags
+                                & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) {
+                            showApp = false;
+                            break;
+                        }
                     }
                 }
             }
@@ -137,8 +142,15 @@
         return accesses;
     }
 
-    public List<Access> getAppListSorted() {
-        List<Access> accesses = getAppList();
+
+    /**
+     * Gets a list of apps that accessed location recently, sorting by recency.
+     *
+     * @param showSystemApps whether includes system apps in the list.
+     * @return the list of apps that recently accessed location.
+     */
+    public List<Access> getAppListSorted(boolean showSystemApps) {
+        List<Access> accesses = getAppList(showSystemApps);
         // Sort the list of Access by recency. Most recent accesses first.
         Collections.sort(accesses, Collections.reverseOrder(new Comparator<Access>() {
             @Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java
index 245b7843..16d73a3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java
@@ -86,7 +86,7 @@
     @Test
     @Ignore
     public void testGetAppList_shouldFilterRecentAccesses() {
-        List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList();
+        List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(false);
         // Only two of the apps have requested location within 15 min.
         assertThat(requests).hasSize(2);
         // Make sure apps are ordered by recency
@@ -115,7 +115,7 @@
         mockTestApplicationInfos(
                 Process.SYSTEM_UID, RecentLocationAccesses.ANDROID_SYSTEM_PACKAGE_NAME);
 
-        List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList();
+        List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(true);
         // Android OS shouldn't show up in the list of apps.
         assertThat(requests).hasSize(2);
         // Make sure apps are ordered by recency
diff --git a/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml
index 6f91d77..edfc0ab 100644
--- a/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml
+++ b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml
@@ -20,6 +20,7 @@
      Make the visibility to "gone" to prevent failures.
  -->
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/add_new_sound_text"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:minHeight="?android:attr/listPreferredItemHeightSmall"
diff --git a/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml b/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml
new file mode 100644
index 0000000..9b5752d
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+<com.android.systemui.biometrics.UdfpsAnimationViewEnroll
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/udfps_animation_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- Enrollment progress bar-->
+    <com.android.systemui.biometrics.UdfpsProgressBar
+        android:id="@+id/progress_bar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:max="100"
+        android:padding="@dimen/udfps_enroll_progress_thickness"
+        android:progress="0"
+        android:layout_gravity="center"
+        android:visibility="gone"/>
+
+</com.android.systemui.biometrics.UdfpsAnimationViewEnroll>
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/packages/SystemUI/res/layout/udfps_animation_view_fpm_other.xml
similarity index 83%
copy from packages/SystemUI/res/layout/udfps_animation_view.xml
copy to packages/SystemUI/res/layout/udfps_animation_view_fpm_other.xml
index 380dd85..f32faa0 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/packages/SystemUI/res/layout/udfps_animation_view_fpm_other.xml
@@ -14,8 +14,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.biometrics.UdfpsAnimationView
+<com.android.systemui.biometrics.UdfpsAnimationViewFpmOther
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/udfps_animation_view"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"/>
+    android:layout_height="match_parent">
+</com.android.systemui.biometrics.UdfpsAnimationViewFpmOther>
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml
similarity index 83%
rename from packages/SystemUI/res/layout/udfps_animation_view.xml
rename to packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml
index 380dd85..644d1ada 100644
--- a/packages/SystemUI/res/layout/udfps_animation_view.xml
+++ b/packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml
@@ -14,8 +14,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.biometrics.UdfpsAnimationView
+<com.android.systemui.biometrics.UdfpsAnimationViewKeyguard
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/udfps_animation_view"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"/>
+    android:layout_height="match_parent">
+</com.android.systemui.biometrics.UdfpsAnimationViewKeyguard>
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index 6ae306e..e24c9e9 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -22,15 +22,10 @@
     android:layout_height="match_parent"
     systemui:sensorTouchAreaCoefficient="0.5">
 
-    <!-- Enrollment progress bar-->
-    <com.android.systemui.biometrics.UdfpsProgressBar
-        android:id="@+id/progress_bar"
+    <com.android.systemui.biometrics.UdfpsSurfaceView
+        android:id="@+id/hbm_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:max="100"
-        android:padding="@dimen/udfps_enroll_progress_thickness"
-        android:progress="0"
-        android:layout_gravity="center"
-        android:visibility="gone"/>
+        android:visibility="invisible"/>
 
 </com.android.systemui.biometrics.UdfpsView>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 761512c..b75a0bc 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -91,9 +91,6 @@
     <!-- The number of columns in the QuickSettings -->
     <integer name="quick_settings_num_columns">3</integer>
 
-    <!-- The number of columns in the Quick Settings customizer -->
-    <integer name="quick_settings_edit_num_columns">@integer/quick_settings_num_columns</integer>
-
     <!-- The number of rows in the QuickSettings -->
     <integer name="quick_settings_max_rows">3</integer>
 
@@ -415,6 +412,9 @@
     <!-- Whether or not child notifications that are part of a group will have shadows. -->
     <bool name="config_enableShadowOnChildNotifications">true</bool>
 
+    <!-- If true, group numbers are shown in the expander instead of via "+N" overflow number -->
+    <bool name="config_showNotificationGroupCountInExpander">true</bool>
+
     <!-- Whether or not a view containing child notifications will have a custom background when
          it has been expanded to reveal its children. -->
     <bool name="config_showGroupNotificationBgWhenExpanded">false</bool>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
index 3bf75d1..a029003 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
@@ -31,15 +31,18 @@
  * sensor area.
  */
 public abstract class UdfpsAnimation extends Drawable {
-    abstract void updateColor();
+    protected abstract void updateColor();
+    protected abstract void onDestroy();
 
     @NonNull protected final Context mContext;
     @NonNull protected final Drawable mFingerprintDrawable;
     @Nullable private View mView;
+    private boolean mIlluminationShowing;
 
     public UdfpsAnimation(@NonNull Context context) {
         mContext = context;
         mFingerprintDrawable = context.getResources().getDrawable(R.drawable.ic_fingerprint, null);
+        mFingerprintDrawable.mutate();
     }
 
     public void onSensorRectUpdated(@NonNull RectF sensorRect) {
@@ -60,6 +63,14 @@
         mView = view;
     }
 
+    boolean isIlluminationShowing() {
+        return mIlluminationShowing;
+    }
+
+    void setIlluminationShowing(boolean showing) {
+        mIlluminationShowing = showing;
+    }
+
     /**
      * @return The amount of padding that's needed on each side of the sensor, in pixels.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
index 5290986..28b5719 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
@@ -58,11 +58,16 @@
     }
 
     @Override
-    void updateColor() {
+    protected void updateColor() {
         mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon));
     }
 
     @Override
+    protected void onDestroy() {
+
+    }
+
+    @Override
     public void onSensorRectUpdated(@NonNull RectF sensorRect) {
         super.onSensorRectUpdated(sensorRect);
         mSensorRect = sensorRect;
@@ -70,6 +75,10 @@
 
     @Override
     public void draw(@NonNull Canvas canvas) {
+        if (isIlluminationShowing()) {
+            return;
+        }
+
         final boolean isNightMode = (mContext.getResources().getConfiguration().uiMode
                 & Configuration.UI_MODE_NIGHT_YES) != 0;
         if (!isNightMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java
index efc864a..ef7a340 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java
@@ -34,12 +34,21 @@
     }
 
     @Override
-    void updateColor() {
+    protected void updateColor() {
+
+    }
+
+    @Override
+    protected void onDestroy() {
 
     }
 
     @Override
     public void draw(@NonNull Canvas canvas) {
+        if (isIlluminationShowing()) {
+            return;
+        }
+
         mFingerprintDrawable.draw(canvas);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
index 8664e44..5f268cf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
@@ -42,6 +42,7 @@
     private static final String TAG = "UdfpsAnimationKeyguard";
 
     @NonNull private final Context mContext;
+    @NonNull private final StatusBarStateController mStatusBarStateController;
     private final int mMaxBurnInOffsetX;
     private final int mMaxBurnInOffsetY;
 
@@ -54,6 +55,7 @@
             @NonNull StatusBarStateController statusBarStateController) {
         super(context);
         mContext = context;
+        mStatusBarStateController = statusBarStateController;
 
         mMaxBurnInOffsetX = context.getResources()
                 .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
@@ -89,6 +91,10 @@
 
     @Override
     public void draw(@NonNull Canvas canvas) {
+        if (isIlluminationShowing()) {
+            return;
+        }
+
         canvas.save();
         canvas.translate(mBurnInOffsetX, mBurnInOffsetY);
         mFingerprintDrawable.draw(canvas);
@@ -106,11 +112,16 @@
     }
 
     @Override
-    public void updateColor() {
+    protected void updateColor() {
         final int lockScreenIconColor = Utils.getColorAttrDefaultColor(mContext,
                 R.attr.wallpaperTextColor);
         final int ambientDisplayIconColor = Color.WHITE;
         mFingerprintDrawable.setTint(ColorUtils.blendARGB(lockScreenIconColor,
                 ambientDisplayIconColor, mInterpolatedDarkAmount));
     }
+
+    @Override
+    protected void onDestroy() {
+        mStatusBarStateController.removeCallback(this);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index 44122cb..f4dd181 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -22,41 +22,49 @@
 import android.graphics.Canvas;
 import android.graphics.RectF;
 import android.util.AttributeSet;
-import android.view.View;
+import android.widget.FrameLayout;
 
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 /**
- * Class that coordinates non-HBM animations (such as enroll, keyguard, BiometricPrompt,
- * FingerprintManager).
+ * Base class for views containing UDFPS animations. Note that this is a FrameLayout so that we
+ * can support multiple child views drawing on the same region around the sensor location.
  */
-public class UdfpsAnimationView extends View implements DozeReceiver,
+public abstract class UdfpsAnimationView extends FrameLayout implements DozeReceiver,
         StatusBar.ExpansionChangedListener {
 
     private static final String TAG = "UdfpsAnimationView";
 
+    @Nullable protected abstract UdfpsAnimation getUdfpsAnimation();
+
     @NonNull private UdfpsView mParent;
-    @Nullable private UdfpsAnimation mUdfpsAnimation;
     @NonNull private RectF mSensorRect;
     private int mAlpha;
 
     public UdfpsAnimationView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         mSensorRect = new RectF();
+        setWillNotDraw(false);
     }
 
     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
 
-        if (mUdfpsAnimation != null) {
+        if (getUdfpsAnimation() != null) {
             final int alpha = mParent.shouldPauseAuth() ? mAlpha : 255;
-            mUdfpsAnimation.setAlpha(alpha);
-            mUdfpsAnimation.draw(canvas);
+            getUdfpsAnimation().setAlpha(alpha);
+            getUdfpsAnimation().draw(canvas);
         }
     }
 
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        getUdfpsAnimation().onDestroy();
+    }
+
     private int expansionToAlpha(float expansion) {
         // Fade to 0 opacity when reaching this expansion amount
         final float maxExpansion = 0.4f;
@@ -69,38 +77,38 @@
         return (int) ((1 - percent) * 255);
     }
 
+    void onIlluminationStarting() {
+        getUdfpsAnimation().setIlluminationShowing(true);
+        postInvalidate();
+    }
+
+    void onIlluminationStopped() {
+        getUdfpsAnimation().setIlluminationShowing(false);
+        postInvalidate();
+    }
+
     void setParent(@NonNull UdfpsView parent) {
         mParent = parent;
     }
 
-    void setAnimation(@Nullable UdfpsAnimation animation) {
-        if (mUdfpsAnimation != null) {
-            mUdfpsAnimation.setAnimationView(null);
-        }
-
-        mUdfpsAnimation = animation;
-        if (mUdfpsAnimation != null) {
-            mUdfpsAnimation.setAnimationView(this);
-        }
-    }
-
     void onSensorRectUpdated(@NonNull RectF sensorRect) {
         mSensorRect = sensorRect;
-        if (mUdfpsAnimation != null) {
-            mUdfpsAnimation.onSensorRectUpdated(mSensorRect);
+        if (getUdfpsAnimation() != null) {
+            getUdfpsAnimation().onSensorRectUpdated(mSensorRect);
         }
     }
 
     void updateColor() {
-        if (mUdfpsAnimation != null) {
-            mUdfpsAnimation.updateColor();
+        if (getUdfpsAnimation() != null) {
+            getUdfpsAnimation().updateColor();
         }
+        postInvalidate();
     }
 
     @Override
     public void dozeTimeTick() {
-        if (mUdfpsAnimation instanceof DozeReceiver) {
-            ((DozeReceiver) mUdfpsAnimation).dozeTimeTick();
+        if (getUdfpsAnimation() instanceof DozeReceiver) {
+            ((DozeReceiver) getUdfpsAnimation()).dozeTimeTick();
         }
     }
 
@@ -111,16 +119,16 @@
     }
 
     public int getPaddingX() {
-        if (mUdfpsAnimation == null) {
+        if (getUdfpsAnimation() == null) {
             return 0;
         }
-        return mUdfpsAnimation.getPaddingX();
+        return getUdfpsAnimation().getPaddingX();
     }
 
     public int getPaddingY() {
-        if (mUdfpsAnimation == null) {
+        if (getUdfpsAnimation() == null) {
             return 0;
         }
-        return mUdfpsAnimation.getPaddingY();
+        return getUdfpsAnimation().getPaddingY();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java
new file mode 100644
index 0000000..19e77493
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java
@@ -0,0 +1,84 @@
+/*
+ * 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.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * Class that coordinates non-HBM animations during enrollment.
+ */
+public class UdfpsAnimationViewEnroll extends UdfpsAnimationView
+        implements UdfpsEnrollHelper.Listener {
+
+    private static final String TAG = "UdfpsAnimationViewEnroll";
+
+    @NonNull private UdfpsAnimation mUdfpsAnimation;
+    @NonNull private UdfpsProgressBar mProgressBar;
+    @Nullable private UdfpsEnrollHelper mEnrollHelper;
+
+    @NonNull
+    @Override
+    protected UdfpsAnimation getUdfpsAnimation() {
+        return mUdfpsAnimation;
+    }
+
+    public UdfpsAnimationViewEnroll(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mUdfpsAnimation = new UdfpsAnimationEnroll(context);
+    }
+
+    public void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
+        mEnrollHelper = helper;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mProgressBar = findViewById(R.id.progress_bar);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if (mEnrollHelper == null) {
+            Log.e(TAG, "Enroll helper is null");
+            return;
+        }
+
+        if (mEnrollHelper.shouldShowProgressBar()) {
+            mProgressBar.setVisibility(View.VISIBLE);
+
+            // Only need enrollment updates if the progress bar is showing :)
+            mEnrollHelper.setListener(this);
+        }
+    }
+
+    @Override
+    public void onEnrollmentProgress(int remaining, int totalSteps) {
+        final int interpolatedProgress = mProgressBar.getMax()
+                * Math.max(0, totalSteps + 1 - remaining) / (totalSteps + 1);
+
+        mProgressBar.setProgress(interpolatedProgress, true);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java
new file mode 100644
index 0000000..3d2f5a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java
@@ -0,0 +1,42 @@
+/*
+ * 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.biometrics;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Class that coordinates non-HBM animations during other usage of FingerprintManager (not
+ * including Keyguard).
+ */
+public class UdfpsAnimationViewFpmOther extends UdfpsAnimationView {
+
+    private final UdfpsAnimationFpmOther mAnimation;
+
+    public UdfpsAnimationViewFpmOther(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mAnimation = new UdfpsAnimationFpmOther(context);
+    }
+
+    @Nullable
+    @Override
+    protected UdfpsAnimation getUdfpsAnimation() {
+        return mAnimation;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java
new file mode 100644
index 0000000..7d0b3e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java
@@ -0,0 +1,49 @@
+/*
+ * 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.biometrics;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+
+/**
+ * Class that coordinates non-HBM animations during keyguard authentication.
+ */
+public class UdfpsAnimationViewKeyguard extends UdfpsAnimationView {
+    @Nullable private UdfpsAnimationKeyguard mAnimation;
+
+    public UdfpsAnimationViewKeyguard(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    void setStatusBarStateController(@NonNull StatusBarStateController statusBarStateController) {
+        if (mAnimation == null) {
+            mAnimation = new UdfpsAnimationKeyguard(getContext(), statusBarStateController);
+            mAnimation.setAnimationView(this);
+        }
+    }
+
+    @Nullable
+    @Override
+    protected UdfpsAnimation getUdfpsAnimation() {
+        return mAnimation;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 71fba33..e1d7eb3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -71,7 +71,8 @@
     @NonNull private final LayoutInflater mInflater;
     private final WindowManager mWindowManager;
     private final DelayableExecutor mFgExecutor;
-    private final StatusBarStateController mStatusBarStateController;
+    @NonNull private final StatusBar mStatusBar;
+    @NonNull private final StatusBarStateController mStatusBarStateController;
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
     @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
@@ -110,18 +111,20 @@
 
         @Override
         public void onEnrollmentProgress(int sensorId, int remaining) {
-            if (mView == null) {
+            if (mEnrollHelper == null) {
+                Log.e(TAG, "onEnrollProgress received but helper is null");
                 return;
             }
-            mView.onEnrollmentProgress(remaining);
+            mEnrollHelper.onEnrollmentProgress(remaining);
         }
 
         @Override
         public void onEnrollmentHelp(int sensorId) {
-            if (mView == null) {
+            if (mEnrollHelper == null) {
+                Log.e(TAG, "onEnrollmentHelp received but helper is null");
                 return;
             }
-            mView.onEnrollmentHelp();
+            mEnrollHelper.onEnrollmentHelp();
         }
 
         @Override
@@ -135,20 +138,14 @@
 
     @VisibleForTesting
     final StatusBar.ExpansionChangedListener mStatusBarExpansionListener =
-            (expansion, expanded) -> {
-                if (mView != null) {
-                    mView.onExpansionChanged(expansion, expanded);
-                }
-    };
+            (expansion, expanded) -> mView.onExpansionChanged(expansion, expanded);
 
     @VisibleForTesting
     final StatusBarStateController.StateListener mStatusBarStateListener =
             new StatusBarStateController.StateListener() {
                 @Override
                 public void onStateChanged(int newState) {
-                    if (mView != null) {
                         mView.onStateChanged(newState);
-                    }
                 }
     };
 
@@ -189,7 +186,7 @@
             WindowManager windowManager,
             @NonNull StatusBarStateController statusBarStateController,
             @Main DelayableExecutor fgExecutor,
-            @Nullable StatusBar statusBar) {
+            @NonNull StatusBar statusBar) {
         mContext = context;
         mInflater = inflater;
         // The fingerprint manager is queried for UDFPS before this class is constructed, so the
@@ -197,6 +194,7 @@
         mFingerprintManager = checkNotNull(fingerprintManager);
         mWindowManager = windowManager;
         mFgExecutor = fgExecutor;
+        mStatusBar = statusBar;
         mStatusBarStateController = statusBarStateController;
 
         mSensorProps = findFirstUdfps();
@@ -217,9 +215,6 @@
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 
-        statusBar.addExpansionChangedListener(mStatusBarExpansionListener);
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
-
         mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController());
     }
 
@@ -278,7 +273,7 @@
         }
     }
 
-    private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimation animation) {
+    private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimationView animation) {
         final int paddingX = animation != null ? animation.getPaddingX() : 0;
         final int paddingY = animation != null ? animation.getPaddingY() : 0;
 
@@ -330,13 +325,19 @@
             if (mView == null) {
                 try {
                     Log.v(TAG, "showUdfpsOverlay | adding window");
-
+                    // TODO: Eventually we should refactor the code to inflate an
+                    //  operation-specific view here, instead of inflating a generic udfps_view
+                    //  and adding operation-specific animations to it.
                     mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false);
                     mView.setSensorProperties(mSensorProps);
                     mView.setHbmCallback(this);
 
-                    final UdfpsAnimation animation = getUdfpsAnimationForReason(reason);
-                    mView.setExtras(animation, mEnrollHelper);
+                    final UdfpsAnimationView animation = getUdfpsAnimationViewForReason(reason);
+                    mView.setAnimationView(animation);
+
+                    mStatusBar.addExpansionChangedListener(mStatusBarExpansionListener);
+                    mStatusBarStateController.addCallback(mStatusBarStateListener);
+
                     mWindowManager.addView(mView, computeLayoutParams(animation));
                     mView.setOnTouchListener(mOnTouchListener);
                 } catch (RuntimeException e) {
@@ -348,17 +349,34 @@
         });
     }
 
-    @Nullable
-    private UdfpsAnimation getUdfpsAnimationForReason(int reason) {
+    @NonNull
+    private UdfpsAnimationView getUdfpsAnimationViewForReason(int reason) {
         Log.d(TAG, "getUdfpsAnimationForReason: " + reason);
+
+        final LayoutInflater inflater = LayoutInflater.from(mContext);
+
         switch (reason) {
             case IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR:
-            case IUdfpsOverlayController.REASON_ENROLL_ENROLLING:
-                return new UdfpsAnimationEnroll(mContext);
-            case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD:
-                return new UdfpsAnimationKeyguard(mContext, mStatusBarStateController);
-            case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER:
-                return new UdfpsAnimationFpmOther(mContext);
+            case IUdfpsOverlayController.REASON_ENROLL_ENROLLING: {
+                final UdfpsAnimationViewEnroll animation = (UdfpsAnimationViewEnroll)
+                        inflater.inflate(R.layout.udfps_animation_view_enroll, null, false);
+                animation.setEnrollHelper(mEnrollHelper);
+                return animation;
+            }
+
+            case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD: {
+                final UdfpsAnimationViewKeyguard animation = (UdfpsAnimationViewKeyguard)
+                        inflater.inflate(R.layout.udfps_animation_view_keyguard, null, false);
+                animation.setStatusBarStateController(mStatusBarStateController);
+                return animation;
+            }
+
+            case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER: {
+                final UdfpsAnimationViewFpmOther animation = (UdfpsAnimationViewFpmOther)
+                        inflater.inflate(R.layout.udfps_animation_view_fpm_other, null, false);
+                return animation;
+            }
+
             default:
                 Log.d(TAG, "Animation for reason " + reason + " not supported yet");
                 return null;
@@ -371,6 +389,10 @@
                 Log.v(TAG, "hideUdfpsOverlay | removing window");
                 // Reset the controller back to its starting state.
                 onFingerUp();
+
+                mStatusBar.removeExpansionChangedListener(mStatusBarExpansionListener);
+                mStatusBarStateController.removeCallback(mStatusBarStateListener);
+
                 mWindowManager.removeView(mView);
                 mView = null;
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index 2442633..942fa7a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -16,22 +16,28 @@
 
 package com.android.systemui.biometrics;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 
-import androidx.annotation.NonNull;
-
 /**
  * Helps keep track of enrollment state and animates the progress bar accordingly.
  */
 public class UdfpsEnrollHelper {
     private static final String TAG = "UdfpsEnrollHelper";
 
+    interface Listener {
+        void onEnrollmentProgress(int remaining, int totalSteps);
+    }
+
     // IUdfpsOverlayController reason
     private final int mEnrollReason;
 
     private int mTotalSteps = -1;
     private int mCurrentProgress = 0;
 
+    @Nullable Listener mListener;
+
     public UdfpsEnrollHelper(int reason) {
         mEnrollReason = reason;
     }
@@ -40,21 +46,29 @@
         return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
     }
 
-    void onEnrollmentProgress(int remaining, @NonNull UdfpsProgressBar progressBar) {
+    void onEnrollmentProgress(int remaining) {
         if (mTotalSteps == -1) {
             mTotalSteps = remaining;
         }
 
-        mCurrentProgress = progressBar.getMax() * Math.max(0, mTotalSteps + 1 - remaining)
-                / (mTotalSteps + 1);
-        progressBar.setProgress(mCurrentProgress, true /* animate */);
-    }
-
-    void updateProgress(@NonNull UdfpsProgressBar progressBar) {
-        progressBar.setProgress(mCurrentProgress);
+        if (mListener != null) {
+            mListener.onEnrollmentProgress(remaining, mTotalSteps);
+        }
     }
 
     void onEnrollmentHelp() {
 
     }
+
+    void setListener(@NonNull Listener listener) {
+        mListener = listener;
+
+        // Only notify during setListener if enrollment is already in progress, so the progress
+        // bar can be updated. If enrollment has not started yet, the progress bar will be empty
+        // anyway.
+        if (mTotalSteps != -1) {
+            final int remainingSteps = mTotalSteps - mCurrentProgress;
+            mListener.onEnrollmentProgress(remainingSteps, mTotalSteps);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
index 97c215e..61ec127 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
@@ -52,6 +52,12 @@
     public UdfpsSurfaceView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
+        // Make this SurfaceView draw on top of everything else in this window. This allows us to
+        // 1) Always show the HBM circle on top of everything else, and
+        // 2) Properly composite this view with any other animations in the same window no matter
+        //    what contents are added in which order to this view hierarchy.
+        setZOrderOnTop(true);
+
         mHolder = getHolder();
         mHolder.setFormat(PixelFormat.RGBA_8888);
 
@@ -61,7 +67,9 @@
         mSensorPaint.setARGB(255, 255, 255, 255);
         mSensorPaint.setStyle(Paint.Style.FILL);
 
-        mIlluminationDotDrawable = canvas -> canvas.drawOval(mSensorRect, mSensorPaint);
+        mIlluminationDotDrawable = canvas -> {
+            canvas.drawOval(mSensorRect, mSensorPaint);
+        };
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 3997943..cd849e6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -32,7 +32,6 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
 
@@ -51,12 +50,11 @@
 
     private static final int DEBUG_TEXT_SIZE_PX = 32;
 
-    @NonNull private final UdfpsSurfaceView mHbmSurfaceView;
-    @NonNull private final UdfpsAnimationView mAnimationView;
     @NonNull private final RectF mSensorRect;
     @NonNull private final Paint mDebugTextPaint;
 
-    @Nullable private UdfpsProgressBar mProgressBar;
+    @NonNull private UdfpsSurfaceView mHbmSurfaceView;
+    @Nullable private UdfpsAnimationView mAnimationView;
 
     // Used to obtain the sensor location.
     @NonNull private FingerprintSensorPropertiesInternal mSensorProps;
@@ -66,7 +64,6 @@
     private boolean mIlluminationRequested;
     private int mStatusBarState;
     private boolean mNotificationShadeExpanded;
-    @Nullable private UdfpsEnrollHelper mEnrollHelper;
 
     public UdfpsView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -84,19 +81,6 @@
             a.recycle();
         }
 
-        // Inflate UdfpsSurfaceView
-        final LayoutInflater inflater = LayoutInflater.from(context);
-        mHbmSurfaceView = (UdfpsSurfaceView) inflater.inflate(R.layout.udfps_surface_view,
-                null, false);
-        addView(mHbmSurfaceView);
-        mHbmSurfaceView.setVisibility(View.INVISIBLE);
-
-        // Inflate UdfpsAnimationView
-        mAnimationView = (UdfpsAnimationView) inflater.inflate(R.layout.udfps_animation_view,
-                null, false);
-        mAnimationView.setParent(this);
-        addView(mAnimationView);
-
         mSensorRect = new RectF();
 
         mDebugTextPaint = new Paint();
@@ -107,22 +91,22 @@
         mIlluminationRequested = false;
     }
 
+    @Override
+    protected void onFinishInflate() {
+        mHbmSurfaceView = findViewById(R.id.hbm_view);
+    }
+
     void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
         mSensorProps = properties;
     }
 
-    void setExtras(@Nullable UdfpsAnimation animation, @Nullable UdfpsEnrollHelper enrollHelper) {
-        mAnimationView.setAnimation(animation);
+    void setAnimationView(@NonNull UdfpsAnimationView animation) {
+        mAnimationView = animation;
+        animation.setParent(this);
 
-        mEnrollHelper = enrollHelper;
-
-        if (enrollHelper != null) {
-            mEnrollHelper.updateProgress(mProgressBar);
-            mProgressBar.setVisibility(enrollHelper.shouldShowProgressBar()
-                    ? View.VISIBLE : View.GONE);
-        } else {
-            mProgressBar.setVisibility(View.GONE);
-        }
+        // TODO: Consider using a ViewStub placeholder to maintain positioning and inflating it
+        //  after the animation type has been decided.
+        addView(animation, 0);
     }
 
     @Override
@@ -132,6 +116,9 @@
 
     @Override
     public void dozeTimeTick() {
+        if (mAnimationView == null) {
+            return;
+        }
         mAnimationView.dozeTimeTick();
     }
 
@@ -143,12 +130,10 @@
     @Override
     public void onExpansionChanged(float expansion, boolean expanded) {
         mNotificationShadeExpanded = expanded;
-        mAnimationView.onExpansionChanged(expansion, expanded);
-    }
 
-    @Override
-    protected void onFinishInflate() {
-        mProgressBar = findViewById(R.id.progress_bar);
+        if (mAnimationView != null) {
+            mAnimationView.onExpansionChanged(expansion, expanded);
+        }
     }
 
     @Override
@@ -229,7 +214,7 @@
     @Override
     public void startIllumination(@Nullable Runnable onIlluminatedRunnable) {
         mIlluminationRequested = true;
-        mAnimationView.setVisibility(View.INVISIBLE);
+        mAnimationView.onIlluminationStarting();
         mHbmSurfaceView.setVisibility(View.VISIBLE);
         mHbmSurfaceView.startIllumination(onIlluminatedRunnable);
     }
@@ -237,16 +222,8 @@
     @Override
     public void stopIllumination() {
         mIlluminationRequested = false;
-        mAnimationView.setVisibility(View.VISIBLE);
+        mAnimationView.onIlluminationStopped();
         mHbmSurfaceView.setVisibility(View.INVISIBLE);
         mHbmSurfaceView.stopIllumination();
     }
-
-    void onEnrollmentProgress(int remaining) {
-        mEnrollHelper.onEnrollmentProgress(remaining, mProgressBar);
-    }
-
-    void onEnrollmentHelp() {
-
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index 680a617..7679d48 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -64,6 +64,7 @@
         super.onCreate(savedInstanceState)
         window?.apply {
             attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
+            attributes.receiveInsetsIgnoringZOrder = true
             setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT)
             setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
index c3cc3af..52f111e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.util.AttributeSet
-import com.android.systemui.R
 
 open class SideLabelTileLayout(
     context: Context,
@@ -28,9 +27,6 @@
     override fun updateResources(): Boolean {
         return super.updateResources().also {
             mMaxAllowedRows = 4
-            mCellMarginHorizontal = (mCellMarginHorizontal * 1.2).toInt()
-            mCellMarginVertical = mCellMarginHorizontal
-            mMaxCellHeight = context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java
index 47cb45b..ce8f6c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java
@@ -16,19 +16,19 @@
 
 import android.content.Context;
 import android.view.View;
-import android.widget.TextView;
 
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.tileimpl.QSTileView;
 
-public class CustomizeTileView extends QSTileView {
+public class CustomizeTileView extends QSTileView implements TileAdapter.CustomizeView {
     private boolean mShowAppLabel;
 
     public CustomizeTileView(Context context, QSIconView icon) {
         super(context, icon);
     }
 
+    @Override
     public void setShowAppLabel(boolean showAppLabel) {
         mShowAppLabel = showAppLabel;
         mSecondLine.setVisibility(showAppLabel ? View.VISIBLE : View.GONE);
@@ -41,10 +41,6 @@
         mSecondLine.setVisibility(mShowAppLabel ? View.VISIBLE : View.GONE);
     }
 
-    public TextView getAppLabel() {
-        return mSecondLine;
-    }
-
     @Override
     protected boolean animationsEnabled() {
         return false;
@@ -54,4 +50,9 @@
     public boolean isLongClickable() {
         return false;
     }
+
+    @Override
+    public void changeState(QSTile.State state) {
+        handleStateChanged(state);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt
new file mode 100644
index 0000000..4ffcd8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt
@@ -0,0 +1,50 @@
+package com.android.systemui.qs.customize
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.view.View
+import com.android.systemui.plugins.qs.QSIconView
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.tileimpl.QSTileViewHorizontal
+
+/**
+ * Class for displaying tiles in [QSCustomizer] with the new design (labels on the side).
+ *
+ * This is a class parallel to [CustomizeTileView], but inheriting from [QSTileViewHorizontal].
+ */
+class CustomizeTileViewHorizontal(
+    context: Context,
+    icon: QSIconView
+) : QSTileViewHorizontal(context, icon),
+    TileAdapter.CustomizeView {
+
+    private var showAppLabel = false
+
+    override fun setShowAppLabel(showAppLabel: Boolean) {
+        this.showAppLabel = showAppLabel
+        mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE
+        mLabel.isSingleLine = showAppLabel
+    }
+
+    override fun handleStateChanged(state: QSTile.State) {
+        super.handleStateChanged(state)
+        mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE
+    }
+
+    override fun animationsEnabled(): Boolean {
+        return false
+    }
+
+    override fun isLongClickable(): Boolean {
+        return false
+    }
+
+    override fun changeState(state: QSTile.State) {
+        handleStateChanged(state)
+    }
+
+    override fun newTileBackground(): Drawable? {
+        super.newTileBackground()
+        return paintDrawable
+    }
+}
\ No newline at end of file
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 7a91421..0adc844 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui.qs.customize;
 
+import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
@@ -30,6 +32,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.core.view.ViewCompat;
 import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
@@ -41,6 +44,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSEditEvent;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.customize.TileAdapter.Holder;
@@ -49,11 +53,13 @@
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.tileimpl.QSIconViewImpl;
+import com.android.systemui.qs.tileimpl.QSTileView;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /** */
 @QSScope
@@ -75,7 +81,7 @@
     private static final int ACTION_ADD = 1;
     private static final int ACTION_MOVE = 2;
 
-    private static final int NUM_COLUMNS_ID = R.integer.quick_settings_edit_num_columns;
+    private static final int NUM_COLUMNS_ID = R.integer.quick_settings_num_columns;
 
     private final Context mContext;
 
@@ -102,9 +108,11 @@
     private final AccessibilityDelegateCompat mAccessibilityDelegate;
     private RecyclerView mRecyclerView;
     private int mNumColumns;
+    private final boolean mUseHorizontalTiles;
 
     @Inject
-    public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) {
+    public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger,
+            @Named(QS_LABELS_FLAG) boolean useHorizontalTiles) {
         mContext = context;
         mHost = qsHost;
         mUiEventLogger = uiEventLogger;
@@ -114,6 +122,7 @@
         mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles);
         mNumColumns = context.getResources().getInteger(NUM_COLUMNS_ID);
         mAccessibilityDelegate = new TileAdapterDelegate();
+        mUseHorizontalTiles = useHorizontalTiles;
     }
 
     @Override
@@ -271,7 +280,10 @@
         }
         FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
                 false);
-        frame.addView(new CustomizeTileView(context, new QSIconViewImpl(context)));
+        View view = mUseHorizontalTiles
+                ? new CustomizeTileViewHorizontal(context, new QSIconViewImpl(context))
+                : new CustomizeTileView(context, new QSIconViewImpl(context));
+        frame.addView(view);
         return new Holder(frame);
     }
 
@@ -354,8 +366,9 @@
         }
         info.state.expandedAccessibilityClassName = "";
 
-        holder.mTileView.handleStateChanged(info.state);
-        holder.mTileView.setShowAppLabel(position > mEditIndex && !info.isSystem);
+        // The holder has a tileView, therefore this call is not null
+        holder.getTileAsCustomizeView().changeState(info.state);
+        holder.getTileAsCustomizeView().setShowAppLabel(position > mEditIndex && !info.isSystem);
         holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
         holder.mTileView.setClickable(true);
         holder.mTileView.setOnClickListener(null);
@@ -534,25 +547,34 @@
     }
 
     public class Holder extends ViewHolder {
-        private CustomizeTileView mTileView;
+        private QSTileView mTileView;
 
         public Holder(View itemView) {
             super(itemView);
             if (itemView instanceof FrameLayout) {
-                mTileView = (CustomizeTileView) ((FrameLayout) itemView).getChildAt(0);
-                mTileView.setBackground(null);
+                mTileView = (QSTileView) ((FrameLayout) itemView).getChildAt(0);
+                if (mTileView instanceof CustomizeTileView) {
+                    mTileView.setBackground(null);
+                }
                 mTileView.getIcon().disableAnimation();
                 mTileView.setTag(this);
                 ViewCompat.setAccessibilityDelegate(mTileView, mAccessibilityDelegate);
             }
         }
 
+        @Nullable
+        public CustomizeView getTileAsCustomizeView() {
+            return (CustomizeView) mTileView;
+        }
+
         public void clearDrag() {
             itemView.clearAnimation();
-            mTileView.findViewById(R.id.tile_label).clearAnimation();
-            mTileView.findViewById(R.id.tile_label).setAlpha(1);
-            mTileView.getAppLabel().clearAnimation();
-            mTileView.getAppLabel().setAlpha(.6f);
+            if (mTileView instanceof CustomizeTileView) {
+                mTileView.findViewById(R.id.tile_label).clearAnimation();
+                mTileView.findViewById(R.id.tile_label).setAlpha(1);
+                mTileView.getAppLabel().clearAnimation();
+                mTileView.getAppLabel().setAlpha(.6f);
+            }
         }
 
         public void startDrag() {
@@ -560,12 +582,14 @@
                     .setDuration(DRAG_LENGTH)
                     .scaleX(DRAG_SCALE)
                     .scaleY(DRAG_SCALE);
-            mTileView.findViewById(R.id.tile_label).animate()
-                    .setDuration(DRAG_LENGTH)
-                    .alpha(0);
-            mTileView.getAppLabel().animate()
-                    .setDuration(DRAG_LENGTH)
-                    .alpha(0);
+            if (mTileView instanceof CustomizeTileView) {
+                mTileView.findViewById(R.id.tile_label).animate()
+                        .setDuration(DRAG_LENGTH)
+                        .alpha(0);
+                mTileView.getAppLabel().animate()
+                        .setDuration(DRAG_LENGTH)
+                        .alpha(0);
+            }
         }
 
         public void stopDrag() {
@@ -573,12 +597,14 @@
                     .setDuration(DRAG_LENGTH)
                     .scaleX(1)
                     .scaleY(1);
-            mTileView.findViewById(R.id.tile_label).animate()
-                    .setDuration(DRAG_LENGTH)
-                    .alpha(1);
-            mTileView.getAppLabel().animate()
-                    .setDuration(DRAG_LENGTH)
-                    .alpha(.6f);
+            if (mTileView instanceof CustomizeTileView) {
+                mTileView.findViewById(R.id.tile_label).animate()
+                        .setDuration(DRAG_LENGTH)
+                        .alpha(1);
+                mTileView.getAppLabel().animate()
+                        .setDuration(DRAG_LENGTH)
+                        .alpha(.6f);
+            }
         }
 
         boolean canRemove() {
@@ -722,7 +748,7 @@
                 int position = mCurrentDrag.getAdapterPosition();
                 if (position == RecyclerView.NO_POSITION) return;
                 TileInfo info = mTiles.get(position);
-                mCurrentDrag.mTileView.setShowAppLabel(
+                ((CustomizeView) mCurrentDrag.mTileView).setShowAppLabel(
                         position > mEditIndex && !info.isSystem);
                 mCurrentDrag.stopDrag();
                 mCurrentDrag = null;
@@ -782,4 +808,9 @@
         public void onSwiped(ViewHolder viewHolder, int direction) {
         }
     };
+
+    interface CustomizeView {
+        void setShowAppLabel(boolean showAppLabel);
+        void changeState(@NonNull QSTile.State state);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index 207b25d0..b59326a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -174,4 +174,8 @@
         mLabelContainer.setClickable(false);
         mLabelContainer.setLongClickable(false);
     }
+
+    public TextView getAppLabel() {
+        return mSecondLine;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
index 07d48f3..231037f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
@@ -35,13 +35,13 @@
 // Placeholder
 private const val CORNER_RADIUS = 40f
 
-class QSTileViewHorizontal(
+open class QSTileViewHorizontal(
     context: Context,
     icon: QSIconView
 ) : QSTileView(context, icon, false) {
 
-    private var paintDrawable: PaintDrawable? = null
-    private var paintColor = Color.TRANSPARENT
+    protected var paintDrawable: PaintDrawable? = null
+    private var paintColor = Color.WHITE
     private var paintAnimator: ValueAnimator? = null
 
     init {
@@ -103,7 +103,7 @@
         mSecondLine.setTextColor(mLabel.textColors)
         mLabelContainer.background = null
 
-        val allowAnimations = animationsEnabled() && paintColor != Color.TRANSPARENT
+        val allowAnimations = animationsEnabled() && paintColor != Color.WHITE
         val newColor = getCircleColor(state.state)
         if (allowAnimations) {
             animateToNewState(newColor)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 805ac7c..3d6dea3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -519,7 +519,7 @@
         setWindowFocusable(true);
 
         if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) {
+                SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, true)) {
             View decorView = mWindow.getDecorView();
 
             // Wait until this window is attached to request because it is
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index bf65132..9da6b8f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -38,13 +38,15 @@
     private static final float MAX_PAGES_DEFAULT = 3f;
 
     private static final String SETTING_KEY_MAX_PAGES = "screenshot.scroll_max_pages";
+    // Portion of the tiles to be acquired above the starting position in infinite scroll
+    // situations. 1.0 means maximize the area above, 0 means just go down.
+    private static final float IDEAL_PORTION_ABOVE = 0.4f;
 
-    private static final int UP = -1;
-    private static final int DOWN = 1;
+    private boolean mScrollingUp = true;
+    // If true, stop acquiring images when no more bitmap data is available in the current direction
+    // or if the desired bitmap size is reached.
+    private boolean mFinishOnBoundary;
 
-    private int mDirection = DOWN;
-    private boolean mAtBottomEdge;
-    private boolean mAtTopEdge;
     private Session mSession;
 
     public static final int MAX_HEIGHT = 12000;
@@ -86,7 +88,8 @@
     }
 
     private void onCaptureResult(CaptureResult result) {
-        Log.d(TAG, "onCaptureResult: " + result);
+        Log.d(TAG, "onCaptureResult: " + result + " scrolling up: " + mScrollingUp
+                + " finish on boundary: " + mFinishOnBoundary);
         boolean emptyResult = result.captured.height() == 0;
         boolean partialResult = !emptyResult
                 && result.captured.height() < result.requested.height();
@@ -94,34 +97,28 @@
 
         if (partialResult || emptyResult) {
             // Potentially reached a vertical boundary. Extend in the other direction.
-            switch (mDirection) {
-                case DOWN:
-                    Log.d(TAG, "Reached bottom edge.");
-                    mAtBottomEdge = true;
-                    mDirection = UP;
-                    break;
-                case UP:
-                    Log.d(TAG, "Reached top edge.");
-                    mAtTopEdge = true;
-                    mDirection = DOWN;
-                    break;
-            }
-
-            if (mAtTopEdge && mAtBottomEdge) {
-                Log.d(TAG, "Reached both top and bottom edge, ending.");
+            if (mFinishOnBoundary) {
                 finish = true;
             } else {
-                // only reverse if the edge was relatively close to the starting point
-                if (mImageTileSet.getHeight() < mSession.getPageHeight() * 3) {
-                    Log.d(TAG, "Restarting in reverse direction.");
-
-                    // Because of temporary limitations, we cannot just jump to the opposite edge
-                    // and continue there. Instead, clear the results and start over capturing from
-                    // here in the other direction.
-                    mImageTileSet.clear();
-                } else {
-                    Log.d(TAG, "Capture is tall enough, stopping here.");
-                    finish = true;
+                // We hit a boundary, clear the tiles, capture everything in the opposite direction,
+                // then finish.
+                mImageTileSet.clear();
+                mFinishOnBoundary = true;
+                mScrollingUp = !mScrollingUp;
+            }
+        } else {
+            // Got the full requested result, but may have got enough bitmap data now
+            int expectedTiles = mImageTileSet.size() + 1;
+            boolean hitMaxTiles = expectedTiles >= mSession.getMaxTiles();
+            if (hitMaxTiles && mFinishOnBoundary) {
+                finish = true;
+            } else {
+                if (mScrollingUp) {
+                    if (expectedTiles >= mSession.getMaxTiles() * IDEAL_PORTION_ABOVE) {
+                        // We got enough above the start point, now see how far down it can go.
+                        mImageTileSet.clear();
+                        mScrollingUp = false;
+                    }
                 }
             }
         }
@@ -136,9 +133,8 @@
 
 
         // Stop when "too tall"
-        if (mImageTileSet.size() >= mSession.getMaxTiles()
-                || mImageTileSet.getHeight() > MAX_HEIGHT) {
-            Log.d(TAG, "Max height and/or tile count reached.");
+        if (mImageTileSet.getHeight() > MAX_HEIGHT) {
+            Log.d(TAG, "Max height reached.");
             finish = true;
         }
 
@@ -150,8 +146,8 @@
             return;
         }
 
-        int nextTop = (mDirection == DOWN) ? result.captured.bottom
-                : result.captured.top - mSession.getTileHeight();
+        int nextTop = (mScrollingUp)
+                ? result.captured.top - mSession.getTileHeight() : result.captured.bottom;
         Log.d(TAG, "requestTile: " + nextTop);
         mSession.requestTile(nextTop, /* consumer */ this::onCaptureResult);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java b/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java
index 9ed9659..f2adaf0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java
@@ -207,7 +207,7 @@
             sb.append(g.toJson());
             count++;
         }
-        mLastSaveLen += count;
+        mLastSaveLen = count;
         sb.append("]");
         return sb.toString();
     }
@@ -249,9 +249,7 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         save();
         if (mLastSaveLen >= 0) {
-            pw.println(String.valueOf(mLastSaveLen)
-                    + " gestures since last dump written to " + mLogfile);
-            mLastSaveLen = 0;
+            pw.println(String.valueOf(mLastSaveLen) + " gestures written to " + mLogfile);
         } else {
             pw.println("error writing gestures");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 01d3103..e5a960e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -83,6 +83,9 @@
     public static final int STATE_DOT = 1;
     public static final int STATE_HIDDEN = 2;
 
+    /** Maximum allowed width or height for an icon drawable */
+    private static final int MAX_IMAGE_SIZE = 500;
+
     private static final String TAG = "StatusBarIconView";
     private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
             = new FloatProperty<StatusBarIconView>("iconAppearAmount") {
@@ -378,6 +381,13 @@
             Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon);
             return false;
         }
+
+        if (drawable.getIntrinsicWidth() > MAX_IMAGE_SIZE
+                || drawable.getIntrinsicHeight() > MAX_IMAGE_SIZE) {
+            Log.w(TAG, "Drawable is too large " + mIcon);
+            return false;
+        }
+
         if (withClear) {
             setImageDrawable(null);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 7691761..b0b91bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -22,6 +22,8 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -30,6 +32,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Pair;
+import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.NotificationHeaderView;
@@ -1234,6 +1237,7 @@
         mCachedHeadsUpRemoteInput = null;
     }
 
+
     private RemoteInputView applyRemoteInput(View view, NotificationEntry entry,
             boolean hasRemoteInput, PendingIntent existingPendingIntent,
             RemoteInputView cachedView, NotificationViewWrapper wrapper) {
@@ -1271,6 +1275,15 @@
                 if (color == Notification.COLOR_DEFAULT) {
                     color = mContext.getColor(R.color.default_remote_input_background);
                 }
+                if (mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_tintNotificationsWithTheme)) {
+                    Resources.Theme theme = new ContextThemeWrapper(mContext,
+                            com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme();
+                    TypedArray ta = theme.obtainStyledAttributes(
+                            new int[]{com.android.internal.R.attr.colorAccent});
+                    color = ta.getColor(0, color);
+                    ta.recycle();
+                }
                 existing.setBackgroundColor(ContrastColorUtil.ensureTextBackgroundColor(color,
                         mContext.getColor(R.color.remote_input_text_enabled),
                         mContext.getColor(R.color.remote_input_hint)));
@@ -1342,12 +1355,10 @@
                 && isPersonWithShortcut
                 && entry.getBubbleMetadata() != null;
         if (showButton) {
-            Drawable d = mContext.getResources().getDrawable(entry.isBubble()
+            // explicitly resolve drawable resource using SystemUI's theme
+            Drawable d = mContext.getDrawable(entry.isBubble()
                     ? R.drawable.bubble_ic_stop_bubble
                     : R.drawable.bubble_ic_create_bubble);
-            mContainingNotification.updateNotificationColor();
-            final int tint = mContainingNotification.getNotificationColor();
-            d.setTint(tint);
 
             String contentDescription = mContext.getResources().getString(entry.isBubble()
                     ? R.string.notification_conversation_unbubble
@@ -1381,9 +1392,8 @@
             return;
         }
 
+        // explicitly resolve drawable resource using SystemUI's theme
         Drawable snoozeDrawable = mContext.getDrawable(R.drawable.ic_snooze);
-        mContainingNotification.updateNotificationColor();
-        snoozeDrawable.setTint(mContainingNotification.getNotificationColor());
         snoozeButton.setImageDrawable(snoozeDrawable);
 
         final NotificationSnooze snoozeGuts = (NotificationSnooze) LayoutInflater.from(mContext)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 3833637..3739424 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -20,10 +20,12 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.Pair;
+import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.NotificationHeaderView;
 import android.view.View;
@@ -33,6 +35,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.CachingIconView;
+import com.android.internal.widget.NotificationExpandButton;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationGroupingUtil;
@@ -103,6 +106,8 @@
     private ViewGroup mCurrentHeader;
     private boolean mIsConversation;
 
+    private boolean mTintWithThemeAccent;
+    private boolean mShowGroupCountInExpander;
     private boolean mShowDividersWhenExpanded;
     private boolean mHideDividersDuringExpand;
     private int mTranslationForHeader;
@@ -145,6 +150,10 @@
                 com.android.internal.R.dimen.notification_content_margin);
         mEnableShadowOnChildNotifications =
                 res.getBoolean(R.bool.config_enableShadowOnChildNotifications);
+        mTintWithThemeAccent =
+                res.getBoolean(com.android.internal.R.bool.config_tintNotificationsWithTheme);
+        mShowGroupCountInExpander =
+                res.getBoolean(R.bool.config_showNotificationGroupCountInExpander);
         mShowDividersWhenExpanded =
                 res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded);
         mHideDividersDuringExpand =
@@ -229,7 +238,6 @@
             mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec);
         }
         if (mNotificationHeaderLowPriority != null) {
-            headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
             mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec);
         }
 
@@ -397,7 +405,20 @@
         mGroupingUtil.updateChildrenAppearance();
     }
 
+    private void setExpandButtonNumber(NotificationViewWrapper wrapper) {
+        View expandButton = wrapper == null
+                ? null : wrapper.getExpandButton();
+        if (expandButton instanceof NotificationExpandButton) {
+            ((NotificationExpandButton) expandButton).setNumber(mUntruncatedChildCount);
+        }
+    }
+
     public void updateGroupOverflow() {
+        if (mShowGroupCountInExpander) {
+            setExpandButtonNumber(mNotificationHeaderWrapper);
+            setExpandButtonNumber(mNotificationHeaderWrapperLowPriority);
+            return;
+        }
         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
         if (mUntruncatedChildCount > maxAllowedVisibleChildren) {
             int number = mUntruncatedChildCount - maxAllowedVisibleChildren;
@@ -1201,8 +1222,21 @@
     }
 
     public void onNotificationUpdated() {
-        mHybridGroupManager.setOverflowNumberColor(mOverflowNumber,
-                mContainingNotification.getNotificationColor());
+        if (mShowGroupCountInExpander) {
+            // The overflow number is not used, so its color is irrelevant; skip this
+            return;
+        }
+        int color = mContainingNotification.getNotificationColor();
+        if (mTintWithThemeAccent) {
+            // We're using the theme accent, color with the accent color instead of the notif color
+            Resources.Theme theme = new ContextThemeWrapper(mContext,
+                    com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme();
+            TypedArray ta = theme.obtainStyledAttributes(
+                    new int[]{com.android.internal.R.attr.colorAccent});
+            color = ta.getColor(0, color);
+            ta.recycle();
+        }
+        mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, color);
     }
 
     public int getPositionInLinearLayout(View childInGroup) {
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 b25fced..bf36435 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -78,7 +78,6 @@
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -277,8 +276,7 @@
     public static final boolean DEBUG = false;
     public static final boolean SPEW = false;
     public static final boolean DUMPTRUCK = true; // extra dumpsys info
-    public static final boolean DEBUG_GESTURES = Build.IS_DEBUGGABLE; // TODO(b/178277858)
-    public static final boolean DEBUG_GESTURES_VERBOSE = true;
+    public static final boolean DEBUG_GESTURES = false;
     public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false;
     public static final boolean DEBUG_CAMERA_LIFT = false;
 
@@ -458,7 +456,9 @@
     private final DisplayMetrics mDisplayMetrics;
 
     // XXX: gesture research
-    private GestureRecorder mGestureRec = null;
+    private final GestureRecorder mGestureRec = DEBUG_GESTURES
+        ? new GestureRecorder("/sdcard/statusbar_gestures.dat")
+        : null;
 
     private final ScreenPinningRequest mScreenPinningRequest;
 
@@ -856,10 +856,6 @@
 
         mActivityIntentHelper = new ActivityIntentHelper(mContext);
         DateTimeView.setReceiverHandler(timeTickHandler);
-
-        if (DEBUG_GESTURES) {
-            mGestureRec = new GestureRecorder(mContext.getCacheDir() + "/statusbar_gestures.dat");
-        }
     }
 
     @Override
@@ -2271,7 +2267,7 @@
 
     public boolean interceptTouchEvent(MotionEvent event) {
         if (DEBUG_GESTURES) {
-            if (DEBUG_GESTURES_VERBOSE || event.getActionMasked() != MotionEvent.ACTION_MOVE) {
+            if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
                 EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH,
                         event.getActionMasked(), (int) event.getX(), (int) event.getY(),
                         mDisabled1, mDisabled2);
@@ -2696,6 +2692,10 @@
         return mDisplay.getRotation();
     }
 
+    int getDisplayId() {
+        return mDisplayId;
+    }
+
     public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
             boolean dismissShade, int flags) {
         startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade,
@@ -2721,7 +2721,7 @@
                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
             intent.addFlags(flags);
             int result = ActivityManager.START_CANCELED;
-            ActivityOptions options = new ActivityOptions(getActivityOptions(
+            ActivityOptions options = new ActivityOptions(getActivityOptions(mDisplayId,
                     null /* remoteAnimation */));
             options.setDisallowEnterPictureInPictureWhileLaunching(
                     disallowEnterPictureInPictureWhileLaunching);
@@ -4366,6 +4366,7 @@
         executeActionDismissingKeyguard(() -> {
             try {
                 intent.send(null, 0, null, null, null, null, getActivityOptions(
+                        mDisplayId,
                         mActivityLaunchAnimator.getLaunchAnimation(associatedView, isOccluded())));
             } catch (PendingIntent.CanceledException e) {
                 // the stack trace isn't very helpful here.
@@ -4387,15 +4388,38 @@
         mMainThreadHandler.post(runnable);
     }
 
-    public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) {
+    /**
+     * Returns an ActivityOptions bundle created using the given parameters.
+     *
+     * @param displayId The ID of the display to launch the activity in. Typically this would be the
+     *                  display the status bar is on.
+     * @param animationAdapter The animation adapter used to start this activity, or {@code null}
+     *                         for the default animation.
+     */
+    public static Bundle getActivityOptions(int displayId,
+            @Nullable RemoteAnimationAdapter animationAdapter) {
         return getDefaultActivityOptions(animationAdapter).toBundle();
     }
 
-    public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter,
-            boolean isKeyguardShowing, long eventTime) {
+    /**
+     * Returns an ActivityOptions bundle created using the given parameters.
+     *
+     * @param displayId The ID of the display to launch the activity in. Typically this would be the
+     *                  display the status bar is on.
+     * @param animationAdapter The animation adapter used to start this activity, or {@code null}
+     *                         for the default animation.
+     * @param isKeyguardShowing Whether keyguard is currently showing.
+     * @param eventTime The event time in milliseconds since boot, not including sleep. See
+     *                  {@link ActivityOptions#setSourceInfo}.
+     */
+    public static Bundle getActivityOptions(int displayId,
+            @Nullable RemoteAnimationAdapter animationAdapter, boolean isKeyguardShowing,
+            long eventTime) {
         ActivityOptions options = getDefaultActivityOptions(animationAdapter);
         options.setSourceInfo(isKeyguardShowing ? ActivityOptions.SourceInfo.TYPE_LOCKSCREEN
                 : ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime);
+        options.setLaunchDisplayId(displayId);
+        options.setCallerDisplayId(displayId);
         return options.toBundle();
     }
 
@@ -4535,4 +4559,8 @@
     public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
         mExpansionChangedListeners.add(listener);
     }
+
+    public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
+        mExpansionChangedListeners.remove(listener);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 598addc..34673f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -427,8 +427,13 @@
                                 intent.getCreatorPackage(), adapter);
             }
             long eventTime = row.getAndResetLastActionUpTime();
-            Bundle options = eventTime > 0 ? getActivityOptions(adapter,
-                    mKeyguardStateController.isShowing(), eventTime) : getActivityOptions(adapter);
+            Bundle options = eventTime > 0
+                    ? getActivityOptions(
+                            mStatusBar.getDisplayId(),
+                            adapter,
+                            mKeyguardStateController.isShowing(),
+                            eventTime)
+                    : getActivityOptions(mStatusBar.getDisplayId(), adapter);
             int launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
                     null, null, options);
             mMainThreadHandler.post(() -> {
@@ -450,6 +455,7 @@
                 int launchResult = TaskStackBuilder.create(mContext)
                         .addNextIntentWithParentStack(intent)
                         .startActivities(getActivityOptions(
+                                mStatusBar.getDisplayId(),
                                 mActivityLaunchAnimator.getLaunchAnimation(
                                         row, mStatusBar.isOccluded())),
                                 new UserHandle(UserHandle.getUserId(appUid)));
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 5d02845..f192287 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -43,7 +43,6 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
 import com.android.systemui.SystemUI;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -87,12 +86,6 @@
     protected static final int SECONDARY = 1;
     protected static final int NEUTRAL = 2;
 
-    // If lock screen wallpaper colors should also be considered when selecting the theme.
-    // Doing this has performance impact, given that overlays would need to be swapped when
-    // the device unlocks.
-    @VisibleForTesting
-    static final boolean USE_LOCK_SCREEN_WALLPAPER = false;
-
     private final ThemeOverlayApplier mThemeManager;
     private final UserManager mUserManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -103,7 +96,6 @@
     private final WallpaperManager mWallpaperManager;
     private final KeyguardStateController mKeyguardStateController;
     private final boolean mIsMonetEnabled;
-    private WallpaperColors mLockColors;
     private WallpaperColors mSystemColors;
     // If fabricated overlays were already created for the current theme.
     private boolean mNeedsOverlayCreation;
@@ -117,6 +109,8 @@
     private FabricatedOverlay mSecondaryOverlay;
     // Neutral system colors overlay
     private FabricatedOverlay mNeutralOverlay;
+    // If wallpaper color event will be accepted and change the UI colors.
+    private boolean mAcceptColorEvents = true;
 
     @Inject
     public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
@@ -146,13 +140,20 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+        filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
         mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
-                reevaluateSystemTheme(true /* forceReload */);
+                if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())
+                        || Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction())) {
+                    if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
+                    reevaluateSystemTheme(true /* forceReload */);
+                } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) {
+                    mAcceptColorEvents = true;
+                    Log.i(TAG, "Allowing color events again");
+                }
             }
-        }, filter, mBgExecutor, UserHandle.ALL);
+        }, filter, mMainExecutor, UserHandle.ALL);
         mSecureSettings.registerContentObserverForUser(
                 Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
                 false,
@@ -170,38 +171,22 @@
 
         // Upon boot, make sure we have the most up to date colors
         mBgExecutor.execute(() -> {
-            WallpaperColors lockColors = mWallpaperManager.getWallpaperColors(
-                    WallpaperManager.FLAG_LOCK);
             WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
                     WallpaperManager.FLAG_SYSTEM);
             mMainExecutor.execute(() -> {
-                if (USE_LOCK_SCREEN_WALLPAPER) {
-                    mLockColors = lockColors;
-                }
                 mSystemColors = systemColor;
                 reevaluateSystemTheme(false /* forceReload */);
             });
         });
-        if (USE_LOCK_SCREEN_WALLPAPER) {
-            mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
-                @Override
-                public void onKeyguardShowingChanged() {
-                    if (mLockColors == null) {
-                        return;
-                    }
-                    // It's possible that the user has a lock screen wallpaper. On this case we'll
-                    // end up with different colors after unlocking.
-                    reevaluateSystemTheme(false /* forceReload */);
-                }
-            });
-        }
         mWallpaperManager.addOnColorsChangedListener((wallpaperColors, which) -> {
-            if (USE_LOCK_SCREEN_WALLPAPER && (which & WallpaperManager.FLAG_LOCK) != 0) {
-                mLockColors = wallpaperColors;
-                if (DEBUG) {
-                    Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which);
-                }
+            if (!mAcceptColorEvents) {
+                Log.i(TAG, "Wallpaper color event rejected: " + wallpaperColors);
+                return;
             }
+            if (wallpaperColors != null && mAcceptColorEvents) {
+                mAcceptColorEvents = false;
+            }
+
             if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
                 mSystemColors = wallpaperColors;
                 if (DEBUG) {
@@ -213,10 +198,7 @@
     }
 
     private void reevaluateSystemTheme(boolean forceReload) {
-        WallpaperColors currentColors =
-                mKeyguardStateController.isShowing() && mLockColors != null
-                        ? mLockColors : mSystemColors;
-
+        final WallpaperColors currentColors = mSystemColors;
         final int mainColor;
         final int accentCandidate;
         if (currentColors == null) {
@@ -378,8 +360,6 @@
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("USE_LOCK_SCREEN_WALLPAPER=" + USE_LOCK_SCREEN_WALLPAPER);
-        pw.println("mLockColors=" + mLockColors);
         pw.println("mSystemColors=" + mSystemColors);
         pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
         pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
@@ -388,5 +368,6 @@
         pw.println("mNeutralOverlay=" + mNeutralOverlay);
         pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
         pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
+        pw.println("mAcceptColorEvents=" + mAcceptColorEvents);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 25345d5..5dc7006 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -778,6 +778,12 @@
 
     /** Animates away the ringer drawer. */
     private void hideRingerDrawer() {
+
+        // If the ringer drawer isn't present, don't try to hide it.
+        if (mRingerDrawerContainer == null) {
+            return;
+        }
+
         // Hide the drawer icon for the selected ringer - it's visible in the ringer button and we
         // don't want to be able to see it while it animates away.
         getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 8d3a040..78cd3a82 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -375,6 +375,9 @@
         return new PipUiEventLogger(uiEventLogger, packageManager);
     }
 
+    @BindsOptionalOf
+    abstract PipTouchHandler optionalPipTouchHandler();
+
     //
     // Shell transitions
     //
@@ -498,6 +501,7 @@
             Optional<SplitScreenController> splitScreenOptional,
             Optional<AppPairsController> appPairsOptional,
             Optional<StartingSurface> startingSurface,
+            Optional<PipTouchHandler> pipTouchHandlerOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
             @ShellMainThread ShellExecutor mainExecutor) {
@@ -508,6 +512,7 @@
                 splitScreenOptional,
                 appPairsOptional,
                 startingSurface,
+                pipTouchHandlerOptional,
                 fullscreenTaskListener,
                 transitions,
                 mainExecutor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 700f101..fb778e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -242,9 +242,18 @@
     }
 
     @Test
-    public void registersViewForCallbacks() throws RemoteException {
+    public void registersAndUnregistersViewForCallbacks() throws RemoteException {
+        mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD);
+        mFgExecutor.runAllReady();
         verify(mStatusBarStateController).addCallback(mUdfpsController.mStatusBarStateListener);
         verify(mStatusBar).addExpansionChangedListener(
                 mUdfpsController.mStatusBarExpansionListener);
+
+        mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+        mFgExecutor.runAllReady();
+        verify(mStatusBarStateController).removeCallback(mUdfpsController.mStatusBarStateListener);
+        verify(mStatusBar).removeExpansionChangedListener(
+                mUdfpsController.mStatusBarExpansionListener);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
index 3d53062..62cc9b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
@@ -49,7 +49,7 @@
         MockitoAnnotations.initMocks(this);
 
         TestableLooper.get(this).runWithLooper(() -> mTileAdapter =
-                new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake()));
+                new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake(), /* qsFlag */false));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 71f146b..f31639c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -35,6 +35,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
@@ -121,4 +122,13 @@
         assertEquals("Transparent backgrounds should fallback to drawable color",
                 color, mIconView.getStaticDrawableColor());
     }
+
+    @Test
+    public void testGiantImageNotAllowed() {
+        Bitmap largeBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
+        Icon icon = Icon.createWithBitmap(largeBitmap);
+        StatusBarIcon largeIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
+                icon, 0, 0, "");
+        assertFalse(mIconView.set(largeIcon));
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index d80c40f..8a0ac11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -19,13 +19,13 @@
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
-import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -33,6 +33,8 @@
 
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
 import android.content.om.FabricatedOverlay;
 import android.content.om.OverlayIdentifier;
 import android.graphics.Color;
@@ -91,7 +93,7 @@
     @Mock
     private FeatureFlags mFeatureFlags;
     @Captor
-    private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback;
+    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiver;
     @Captor
     private ArgumentCaptor<WallpaperManager.OnColorsChangedListener> mColorsListener;
 
@@ -114,12 +116,10 @@
         };
 
         mThemeOverlayController.start();
-        if (USE_LOCK_SCREEN_WALLPAPER) {
-            verify(mKeyguardStateController).addCallback(
-                    mKeyguardStateControllerCallback.capture());
-        }
         verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
                 eq(UserHandle.USER_ALL));
+        verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiver.capture(), any(),
+                eq(mMainExecutor), any());
         verify(mDumpManager).registerDumpable(any(), any());
     }
 
@@ -129,7 +129,6 @@
         verify(mBgExecutor).execute(registrationRunnable.capture());
 
         registrationRunnable.getValue().run();
-        verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_LOCK));
         verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM));
     }
 
@@ -156,6 +155,18 @@
         // Should not ask again if changed to same value
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
         verifyNoMoreInteractions(mThemeOverlayApplier);
+
+        // Should not ask again even for new colors until we change wallpapers
+        mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
+                null, null), WallpaperManager.FLAG_SYSTEM);
+        verifyNoMoreInteractions(mThemeOverlayApplier);
+
+        // But should change theme after changing wallpapers
+        clearInvocations(mThemeOverlayApplier);
+        mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
+        mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
+                null, null), WallpaperManager.FLAG_SYSTEM);
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
     }
 
     @Test
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 065e2bb..88e6b66 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -102,6 +102,10 @@
         FingerprintGestureDispatcher.FingerprintGestureClient {
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = "AbstractAccessibilityServiceConnection";
+    private static final String TRACE_A11Y_SERVICE_CONNECTION =
+            LOG_TAG + ".IAccessibilityServiceConnection";
+    private static final String TRACE_A11Y_SERVICE_CLIENT =
+            LOG_TAG + ".IAccessibilityServiceClient";
     private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000;
 
     protected static final String TAKE_SCREENSHOT = "takeScreenshot";
@@ -127,6 +131,7 @@
     protected final Object mLock;
 
     protected final AccessibilitySecurityPolicy mSecurityPolicy;
+    protected final AccessibilityTrace mTrace;
 
     // The service that's bound to this instance. Whenever this value is non-null, this
     // object is registered as a death recipient
@@ -247,7 +252,7 @@
     public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
             AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
             Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport,
-            WindowManagerInternal windowManagerInternal,
+            AccessibilityTrace trace, WindowManagerInternal windowManagerInternal,
             SystemActionPerformer systemActionPerfomer,
             AccessibilityWindowManager a11yWindowManager) {
         mContext = context;
@@ -259,6 +264,7 @@
         mSecurityPolicy = securityPolicy;
         mSystemActionPerformer = systemActionPerfomer;
         mSystemSupport = systemSupport;
+        mTrace = trace;
         mMainHandler = mainHandler;
         mInvocationHandler = new InvocationHandler(mainHandler.getLooper());
         mA11yWindowManager = a11yWindowManager;
@@ -291,6 +297,10 @@
             return false;
         }
         try {
+            if (mTrace.isA11yTracingEnabled()) {
+                mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onKeyEvent",
+                        keyEvent + ", " + sequenceNumber);
+            }
             mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
         } catch (RemoteException e) {
             return false;
@@ -354,11 +364,18 @@
 
     @Override
     public void setOnKeyEventResult(boolean handled, int sequence) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setOnKeyEventResult",
+                    "handled=" + handled + ";sequence=" + sequence);
+        }
         mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
     }
 
     @Override
     public AccessibilityServiceInfo getServiceInfo() {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getServiceInfo");
+        }
         synchronized (mLock) {
             return mAccessibilityServiceInfo;
         }
@@ -375,6 +392,9 @@
 
     @Override
     public void setServiceInfo(AccessibilityServiceInfo info) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setServiceInfo", "info=" + info);
+        }
         final long identity = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -400,6 +420,9 @@
     @Nullable
     @Override
     public AccessibilityWindowInfo.WindowListSparseArray getWindows() {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindows");
+        }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return null;
@@ -434,6 +457,9 @@
 
     @Override
     public AccessibilityWindowInfo getWindow(int windowId) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindow", "windowId=" + windowId);
+        }
         synchronized (mLock) {
             int displayId = Display.INVALID_DISPLAY;
             if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
@@ -469,6 +495,13 @@
             long accessibilityNodeId, String viewIdResName, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
             throws RemoteException {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByViewId",
+                    "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
+                    + accessibilityNodeId + ";viewIdResName=" + viewIdResName + ";interactionId="
+                    + interactionId + ";callback=" + callback + ";interrogatingTid="
+                    + interrogatingTid);
+        }
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
         Region partialInteractiveRegion = Region.obtain();
@@ -530,6 +563,12 @@
             long accessibilityNodeId, String text, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
             throws RemoteException {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByText",
+                    "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
+                    + accessibilityNodeId + ";text=" + text + ";interactionId=" + interactionId
+                    + ";callback=" + callback + ";interrogatingTid=" + interrogatingTid);
+        }
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
         Region partialInteractiveRegion = Region.obtain();
@@ -591,6 +630,14 @@
             int accessibilityWindowId, long accessibilityNodeId, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, int flags,
             long interrogatingTid, Bundle arguments) throws RemoteException {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(
+                    TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfoByAccessibilityId",
+                    "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
+                            + accessibilityNodeId + ";interactionId=" + interactionId + ";callback="
+                            + callback + ";flags=" + flags + ";interrogatingTid=" + interrogatingTid
+                            + ";arguments=" + arguments);
+        }
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
         Region partialInteractiveRegion = Region.obtain();
@@ -652,6 +699,13 @@
             int focusType, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
             throws RemoteException {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findFocus",
+                    "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
+                            + accessibilityNodeId + ";focusType=" + focusType + ";interactionId="
+                            + interactionId + ";callback=" + callback + ";interrogatingTid="
+                            + interrogatingTid);
+        }
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
         Region partialInteractiveRegion = Region.obtain();
@@ -713,6 +767,13 @@
             int direction, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
             throws RemoteException {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".focusSearch",
+                    "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
+                            + accessibilityNodeId + ";direction=" + direction + ";interactionId="
+                            + interactionId + ";callback=" + callback + ";interrogatingTid="
+                            + interrogatingTid);
+        }
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
         Region partialInteractiveRegion = Region.obtain();
@@ -770,10 +831,18 @@
 
     @Override
     public void sendGesture(int sequence, ParceledListSlice gestureSteps) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".sendGesture",
+                    "sequence=" + sequence + ";gestureSteps=" + gestureSteps);
+        }
     }
 
     @Override
     public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".dispatchGesture", "sequence="
+                    + sequence + ";gestureSteps=" + gestureSteps + ";displayId=" + displayId);
+        }
     }
 
     @Override
@@ -781,6 +850,13 @@
             long accessibilityNodeId, int action, Bundle arguments, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
             throws RemoteException {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performAccessibilityAction",
+                    "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
+                            + accessibilityNodeId + ";action=" + action + ";arguments=" + arguments
+                            + ";interactionId=" + interactionId + ";callback=" + callback
+                            + ";interrogatingTid=" + interrogatingTid);
+        }
         final int resolvedWindowId;
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -802,6 +878,10 @@
 
     @Override
     public boolean performGlobalAction(int action) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performGlobalAction",
+                    "action=" + action);
+        }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return false;
@@ -812,6 +892,9 @@
 
     @Override
     public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSystemActions");
+        }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return Collections.emptyList();
@@ -822,6 +905,10 @@
 
     @Override
     public boolean isFingerprintGestureDetectionAvailable() {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(
+                    TRACE_A11Y_SERVICE_CONNECTION + ".isFingerprintGestureDetectionAvailable");
+        }
         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
             return false;
         }
@@ -835,6 +922,10 @@
 
     @Override
     public float getMagnificationScale(int displayId) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationScale",
+                    "displayId=" + displayId);
+        }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return 1.0f;
@@ -850,6 +941,10 @@
 
     @Override
     public Region getMagnificationRegion(int displayId) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationRegion",
+                    "displayId=" + displayId);
+        }
         synchronized (mLock) {
             final Region region = Region.obtain();
             if (!hasRightsToCurrentUserLocked()) {
@@ -874,6 +969,10 @@
 
     @Override
     public float getMagnificationCenterX(int displayId) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterX",
+                    "displayId=" + displayId);
+        }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return 0.0f;
@@ -896,6 +995,10 @@
 
     @Override
     public float getMagnificationCenterY(int displayId) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterY",
+                    "displayId=" + displayId);
+        }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return 0.0f;
@@ -928,6 +1031,10 @@
 
     @Override
     public boolean resetMagnification(int displayId, boolean animate) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".resetMagnification",
+                    "displayId=" + displayId + ";animate=" + animate);
+        }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return false;
@@ -950,6 +1057,11 @@
     @Override
     public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
             float centerY, boolean animate) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationScaleAndCenter",
+                    "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX
+                            + ";centerY=" + centerY + ";animate=" + animate);
+        }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return false;
@@ -974,6 +1086,10 @@
 
     @Override
     public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationCallbackEnabled",
+                    "displayId=" + displayId + ";enabled=" + enabled);
+        }
         mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled);
     }
 
@@ -983,11 +1099,19 @@
 
     @Override
     public void setSoftKeyboardCallbackEnabled(boolean enabled) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardCallbackEnabled",
+                    "enabled=" + enabled);
+        }
         mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
     }
 
     @Override
     public void takeScreenshot(int displayId, RemoteCallback callback) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".takeScreenshot",
+                    "displayId=" + displayId + ";callback=" + callback);
+        }
         final long currentTimestamp = SystemClock.uptimeMillis();
         if (mRequestTakeScreenshotTimestampMs != 0
                 && (currentTimestamp - mRequestTakeScreenshotTimestampMs)
@@ -1157,6 +1281,10 @@
      */
     @Override
     public IBinder getOverlayWindowToken(int displayId) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getOverlayWindowToken",
+                    "displayId=" + displayId);
+        }
         synchronized (mLock) {
             return mOverlayWindowTokens.get(displayId);
         }
@@ -1170,6 +1298,10 @@
      */
     @Override
     public int getWindowIdForLeashToken(@NonNull IBinder token) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindowIdForLeashToken",
+                    "token=" + token);
+        }
         synchronized (mLock) {
             return mA11yWindowManager.getWindowIdLocked(token);
         }
@@ -1181,6 +1313,9 @@
             // Clear the proxy in the other process so this
             // IAccessibilityServiceConnection can be garbage collected.
             if (mServiceInterface != null) {
+                if (mTrace.isA11yTracingEnabled()) {
+                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", "null, " + mId + ", null");
+                }
                 mServiceInterface.init(null, mId, null);
             }
         } catch (RemoteException re) {
@@ -1329,6 +1464,10 @@
         }
 
         try {
+            if (mTrace.isA11yTracingEnabled()) {
+                mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityEvent",
+                        event + ";" + serviceWantsEvent);
+            }
             listener.onAccessibilityEvent(event, serviceWantsEvent);
             if (DEBUG) {
                 Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
@@ -1382,6 +1521,10 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
+                if (mTrace.isA11yTracingEnabled()) {
+                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onMagnificationChanged", displayId
+                            + ", " + region + ", " + scale + ", " + centerX + ", " + centerY);
+                }
                 listener.onMagnificationChanged(displayId, region, scale, centerX, centerY);
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
@@ -1397,6 +1540,10 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
+                if (mTrace.isA11yTracingEnabled()) {
+                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSoftKeyboardShowModeChanged",
+                            String.valueOf(showState));
+                }
                 listener.onSoftKeyboardShowModeChanged(showState);
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService,
@@ -1409,6 +1556,10 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
+                if (mTrace.isA11yTracingEnabled()) {
+                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonClicked",
+                            String.valueOf(displayId));
+                }
                 listener.onAccessibilityButtonClicked(displayId);
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re);
@@ -1427,6 +1578,11 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
+                if (mTrace.isA11yTracingEnabled()) {
+                    mTrace.logTrace(
+                            TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonAvailabilityChanged",
+                            String.valueOf(available));
+                }
                 listener.onAccessibilityButtonAvailabilityChanged(available);
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG,
@@ -1440,6 +1596,10 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
+                if (mTrace.isA11yTracingEnabled()) {
+                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onGesture",
+                            gestureInfo.toString());
+                }
                 listener.onGesture(gestureInfo);
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error during sending gesture " + gestureInfo
@@ -1452,6 +1612,9 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
+                if (mTrace.isA11yTracingEnabled()) {
+                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSystemActionsChanged");
+                }
                 listener.onSystemActionsChanged();
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error sending system actions change to " + mService,
@@ -1464,6 +1627,9 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
+                if (mTrace.isA11yTracingEnabled()) {
+                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".clearAccessibilityCache");
+                }
                 listener.clearAccessibilityCache();
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error during requesting accessibility info cache"
@@ -1790,14 +1956,27 @@
 
     @Override
     public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setGestureDetectionPassthroughRegion",
+                    "displayId=" + displayId + ";region=" + region);
+        }
         mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region);
     }
 
     @Override
     public void setTouchExplorationPassthroughRegion(int displayId, Region region) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setTouchExplorationPassthroughRegion",
+                    "displayId=" + displayId + ";region=" + region);
+        }
         mSystemSupport.setTouchExplorationPassthroughRegion(displayId, region);
     }
 
     @Override
-    public void setFocusAppearance(int strokeWidth, int color) { }
+    public void setFocusAppearance(int strokeWidth, int color) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setFocusAppearance",
+                    "strokeWidth=" + strokeWidth + ";color=" + color);
+        }
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index c63c2e1..b3be044 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -149,6 +149,7 @@
  */
 public class AccessibilityManagerService extends IAccessibilityManager.Stub
         implements AbstractAccessibilityServiceConnection.SystemSupport,
+        AccessibilityTrace,
         AccessibilityUserState.ServiceInfoChangeListener,
         AccessibilityWindowManager.AccessibilityEventSender,
         AccessibilitySecurityPolicy.AccessibilityUserManager,
@@ -243,6 +244,7 @@
     final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>();
 
     private final UiAutomationManager mUiAutomationManager = new UiAutomationManager(mLock);
+    private final WindowManagerInternal.AccessibilityControllerInternal mA11yController;
 
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
 
@@ -288,6 +290,7 @@
         mContext = context;
         mPowerManager =  (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
+        mA11yController = mWindowManagerService.getAccessibilityController();
         mMainHandler = new MainHandler(mContext.getMainLooper());
         mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
         mPackageManager = packageManager;
@@ -308,6 +311,7 @@
         mContext = context;
         mPowerManager =  (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
+        mA11yController = mWindowManagerService.getAccessibilityController();
         mMainHandler = new MainHandler(mContext.getMainLooper());
         mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
         mPackageManager = mContext.getPackageManager();
@@ -328,16 +332,25 @@
 
     @Override
     public int getCurrentUserIdLocked() {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getCurrentUserIdLocked");
+        }
         return mCurrentUserId;
     }
 
     @Override
     public boolean isAccessibilityButtonShown() {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".isAccessibilityButtonShown");
+        }
         return mIsAccessibilityButtonShown;
     }
 
     @Override
     public void onServiceInfoChangedLocked(AccessibilityUserState userState) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".onServiceInfoChangedLocked", "userState=" + userState);
+        }
         scheduleNotifyClientsOfServicesStateChangeLocked(userState);
     }
 
@@ -395,6 +408,10 @@
         PackageMonitor monitor = new PackageMonitor() {
             @Override
             public void onSomePackagesChanged() {
+                if (isA11yTracingEnabled()) {
+                    logTrace(LOG_TAG + ".PM.onSomePackagesChanged");
+                }
+
                 synchronized (mLock) {
                     // Only the profile parent can install accessibility services.
                     // Therefore we ignore packages from linked profiles.
@@ -419,6 +436,10 @@
                 // mBindingServices in binderDied() during updating. Remove services from  this
                 // package from mBindingServices, and then update the user state to re-bind new
                 // versions of them.
+                if (isA11yTracingEnabled()) {
+                    logTrace(LOG_TAG + ".PM.onPackageUpdateFinished",
+                            "packageName=" + packageName + ";uid=" + uid);
+                }
                 synchronized (mLock) {
                     final int userId = getChangingUserId();
                     if (userId != mCurrentUserId) {
@@ -448,6 +469,11 @@
 
             @Override
             public void onPackageRemoved(String packageName, int uid) {
+                if (isA11yTracingEnabled()) {
+                    logTrace(LOG_TAG + ".PM.onPackageRemoved",
+                            "packageName=" + packageName + ";uid=" + uid);
+                }
+
                 synchronized (mLock) {
                     final int userId = getChangingUserId();
                     // Only the profile parent can install accessibility services.
@@ -487,6 +513,10 @@
             @Override
             public boolean onHandleForceStop(Intent intent, String[] packages,
                     int uid, boolean doit) {
+                if (isA11yTracingEnabled()) {
+                    logTrace(LOG_TAG + ".PM.onHandleForceStop", "intent=" + intent + ";packages="
+                            + packages + ";uid=" + uid + ";doit=" + doit);
+                }
                 synchronized (mLock) {
                     final int userId = getChangingUserId();
                     // Only the profile parent can install accessibility services.
@@ -533,6 +563,10 @@
         mContext.registerReceiverAsUser(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
+                if (isA11yTracingEnabled()) {
+                    logTrace(LOG_TAG + ".BR.onReceive", "context=" + context + ";intent=" + intent);
+                }
+
                 String action = intent.getAction();
                 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                     switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
@@ -616,6 +650,10 @@
 
     @Override
     public long addClient(IAccessibilityManagerClient callback, int userId) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".addClient", "callback=" + callback + ";userId=" + userId);
+        }
+
         synchronized (mLock) {
             // We treat calls from a profile as if made by its parent as profiles
             // share the accessibility state of the parent. The call below
@@ -654,6 +692,9 @@
 
     @Override
     public void sendAccessibilityEvent(AccessibilityEvent event, int userId) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".sendAccessibilityEvent", "event=" + event + ";userId=" + userId);
+        }
         boolean dispatchEvent = false;
 
         synchronized (mLock) {
@@ -746,6 +787,10 @@
      */
     @Override
     public void registerSystemAction(RemoteAction action, int actionId) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".registerSystemAction", "action=" + action + ";actionId="
+                    + actionId);
+        }
         mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
         getSystemActionPerformer().registerSystemAction(actionId, action);
     }
@@ -757,6 +802,9 @@
      */
     @Override
     public void unregisterSystemAction(int actionId) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".unregisterSystemAction", "actionId=" + actionId);
+        }
         mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
         getSystemActionPerformer().unregisterSystemAction(actionId);
     }
@@ -771,6 +819,10 @@
 
     @Override
     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getInstalledAccessibilityServiceList", "userId=" + userId);
+        }
+
         synchronized (mLock) {
             // We treat calls from a profile as if made by its parent as profiles
             // share the accessibility state of the parent. The call below
@@ -788,6 +840,11 @@
     @Override
     public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType,
             int userId) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getEnabledAccessibilityServiceList",
+                    "feedbackType=" + feedbackType + ";userId=" + userId);
+        }
+
         synchronized (mLock) {
             // We treat calls from a profile as if made by its parent as profiles
             // share the accessibility state of the parent. The call below
@@ -816,6 +873,10 @@
 
     @Override
     public void interrupt(int userId) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".interrupt", "userId=" + userId);
+        }
+
         List<IAccessibilityServiceClient> interfacesToInterrupt;
         synchronized (mLock) {
             // We treat calls from a profile as if made by its parent as profiles
@@ -842,6 +903,9 @@
         }
         for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
             try {
+                if (isA11yTracingEnabled()) {
+                    logTrace(LOG_TAG + ".IAccessibilityServiceClient.onInterrupt");
+                }
                 interfacesToInterrupt.get(i).onInterrupt();
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error sending interrupt request to "
@@ -854,18 +918,31 @@
     public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken,
             IAccessibilityInteractionConnection connection, String packageName,
             int userId) throws RemoteException {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".addAccessibilityInteractionConnection",
+                    "windowToken=" + windowToken + "leashToken=" + leashToken + ";connection="
+                            + connection + "; packageName=" + packageName + ";userId=" + userId);
+        }
+
         return mA11yWindowManager.addAccessibilityInteractionConnection(
                 windowToken, leashToken, connection, packageName, userId);
     }
 
     @Override
     public void removeAccessibilityInteractionConnection(IWindow window) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".removeAccessibilityInteractionConnection", "window=" + window);
+        }
         mA11yWindowManager.removeAccessibilityInteractionConnection(window);
     }
 
     @Override
     public void setPictureInPictureActionReplacingConnection(
             IAccessibilityInteractionConnection connection) throws RemoteException {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".setPictureInPictureActionReplacingConnection",
+                    "connection=" + connection);
+        }
         mSecurityPolicy.enforceCallingPermission(Manifest.permission.MODIFY_ACCESSIBILITY_DATA,
                 SET_PIP_ACTION_REPLACEMENT);
         mA11yWindowManager.setPictureInPictureActionReplacingConnection(connection);
@@ -876,13 +953,19 @@
             IAccessibilityServiceClient serviceClient,
             AccessibilityServiceInfo accessibilityServiceInfo,
             int flags) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".registerUiTestAutomationService", "owner=" + owner
+                    + ";serviceClient=" + serviceClient + ";accessibilityServiceInfo="
+                    + accessibilityServiceInfo + ";flags=" + flags);
+        }
+
         mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
                 FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
 
         synchronized (mLock) {
             mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient,
                     mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler,
-                    mSecurityPolicy, this, mWindowManagerService, getSystemActionPerformer(),
+                    mSecurityPolicy, this, this, mWindowManagerService, getSystemActionPerformer(),
                     mA11yWindowManager, flags);
             onUserStateChangedLocked(getCurrentUserStateLocked());
         }
@@ -890,6 +973,10 @@
 
     @Override
     public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".unregisterUiTestAutomationService",
+                    "serviceClient=" + serviceClient);
+        }
         synchronized (mLock) {
             mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient);
         }
@@ -898,6 +985,11 @@
     @Override
     public void temporaryEnableAccessibilityStateUntilKeyguardRemoved(
             ComponentName service, boolean touchExplorationEnabled) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".temporaryEnableAccessibilityStateUntilKeyguardRemoved",
+                    "service=" + service + ";touchExplorationEnabled=" + touchExplorationEnabled);
+        }
+
         mSecurityPolicy.enforceCallingPermission(
                 Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY,
                 TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED);
@@ -926,6 +1018,10 @@
 
     @Override
     public IBinder getWindowToken(int windowId, int userId) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getWindowToken", "windowId=" + windowId + ";userId=" + userId);
+        }
+
         mSecurityPolicy.enforceCallingPermission(
                 Manifest.permission.RETRIEVE_WINDOW_TOKEN,
                 GET_WINDOW_TOKEN);
@@ -965,6 +1061,11 @@
      */
     @Override
     public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked",
+                    "displayId=" + displayId + ";targetName=" + targetName);
+        }
+
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Caller does not hold permission "
@@ -990,6 +1091,10 @@
      */
     @Override
     public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged", "shown=" + shown);
+        }
+
         mSecurityPolicy.enforceCallingOrSelfPermission(
                 android.Manifest.permission.STATUS_BAR_SERVICE);
         synchronized (mLock) {
@@ -1018,6 +1123,10 @@
      */
     @Override
     public void onSystemActionsChanged() {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".onSystemActionsChanged");
+        }
+
         synchronized (mLock) {
             AccessibilityUserState state = getCurrentUserStateLocked();
             notifySystemActionsChangedLocked(state);
@@ -1080,6 +1189,10 @@
 
     @Override
     public @Nullable MotionEventInjector getMotionEventInjectorForDisplayLocked(int displayId) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getMotionEventInjectorForDisplayLocked", "displayId=" + displayId);
+        }
+
         final long endMillis = SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS;
         MotionEventInjector motionEventInjector = null;
         while ((mMotionEventInjectors == null) && (SystemClock.uptimeMillis() < endMillis)) {
@@ -1646,6 +1759,11 @@
     @Override
     public void persistComponentNamesToSettingLocked(String settingName,
             Set<ComponentName> componentNames, int userId) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".persistComponentNamesToSettingLocked", "settingName=" + settingName
+                    + ";componentNames=" + componentNames + ";userId=" + userId);
+        }
+
         persistColonDelimitedSetToSettingLocked(settingName, userId, componentNames,
                 componentName -> componentName.flattenToShortString());
     }
@@ -1730,7 +1848,7 @@
                 if (service == null) {
                     service = new AccessibilityServiceConnection(userState, mContext, componentName,
                             installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy,
-                            this, mWindowManagerService, getSystemActionPerformer(),
+                            this, this, mWindowManagerService, getSystemActionPerformer(),
                             mA11yWindowManager, mActivityTaskManagerService);
                 } else if (userState.mBoundServices.contains(service)) {
                     continue;
@@ -2607,6 +2725,10 @@
     @GuardedBy("mLock")
     @Override
     public MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getCompatibleMagnificationSpecLocked", "windowId=" + windowId);
+        }
+
         IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
                 mCurrentUserId, windowId);
         if (windowToken != null) {
@@ -2618,6 +2740,10 @@
 
     @Override
     public KeyEventDispatcher getKeyEventDispatcher() {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getKeyEventDispatcher");
+        }
+
         if (mKeyEventDispatcher == null) {
             mKeyEventDispatcher = new KeyEventDispatcher(
                     mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock,
@@ -2630,6 +2756,12 @@
     @SuppressWarnings("AndroidFrameworkPendingIntentMutability")
     public PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent,
             int flags) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getPendingIntentActivity", "context=" + context + ";requestCode="
+                    + requestCode + ";intent=" + intent + ";flags=" + flags);
+        }
+
+
         return PendingIntent.getActivity(context, requestCode, intent, flags);
     }
 
@@ -2644,6 +2776,10 @@
      */
     @Override
     public void performAccessibilityShortcut(String targetName) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".performAccessibilityShortcut", "targetName=" + targetName);
+        }
+
         if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
                 && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
                 != PackageManager.PERMISSION_GRANTED)) {
@@ -2828,6 +2964,10 @@
 
     @Override
     public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getAccessibilityShortcutTargets", "shortcutType=" + shortcutType);
+        }
+
         if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException(
@@ -2897,6 +3037,10 @@
 
     @Override
     public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".sendAccessibilityEventForCurrentUserLocked", "event=" + event);
+        }
+
         sendAccessibilityEventLocked(event, mCurrentUserId);
     }
 
@@ -2918,6 +3062,10 @@
      */
     @Override
     public boolean sendFingerprintGesture(int gestureKeyCode) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".sendFingerprintGesture", "gestureKeyCode=" + gestureKeyCode);
+        }
+
         synchronized(mLock) {
             if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
                 throw new SecurityException("Only SYSTEM can call sendFingerprintGesture");
@@ -2939,6 +3087,10 @@
      */
     @Override
     public int getAccessibilityWindowId(@Nullable IBinder windowToken) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getAccessibilityWindowId", "windowToken=" + windowToken);
+        }
+
         synchronized (mLock) {
             if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
                 throw new SecurityException("Only SYSTEM can call getAccessibilityWindowId");
@@ -2956,6 +3108,10 @@
      */
     @Override
     public long getRecommendedTimeoutMillis() {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getRecommendedTimeoutMillis");
+        }
+
         synchronized(mLock) {
             final AccessibilityUserState userState = getCurrentUserStateLocked();
             return getRecommendedTimeoutMillisLocked(userState);
@@ -2970,6 +3126,10 @@
     @Override
     public void setWindowMagnificationConnection(
             IWindowMagnificationConnection connection) throws RemoteException {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".setWindowMagnificationConnection", "connection=" + connection);
+        }
+
         mSecurityPolicy.enforceCallingOrSelfPermission(
                 android.Manifest.permission.STATUS_BAR_SERVICE);
 
@@ -3000,6 +3160,11 @@
 
     @Override
     public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".associateEmbeddedHierarchy",
+                    "host=" + host + ";embedded=" + embedded);
+        }
+
         synchronized (mLock) {
             mA11yWindowManager.associateEmbeddedHierarchyLocked(host, embedded);
         }
@@ -3007,6 +3172,10 @@
 
     @Override
     public void disassociateEmbeddedHierarchy(@NonNull IBinder token) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy", "token=" + token);
+        }
+
         synchronized (mLock) {
             mA11yWindowManager.disassociateEmbeddedHierarchyLocked(token);
         }
@@ -3084,6 +3253,9 @@
 
     @Override
     public FullScreenMagnificationController getFullScreenMagnificationController() {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".getFullScreenMagnificationController");
+        }
         synchronized (mLock) {
             return mMagnificationController.getFullScreenMagnificationController();
         }
@@ -3091,6 +3263,10 @@
 
     @Override
     public void onClientChangeLocked(boolean serviceInfoChanged) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".onClientChangeLocked", "serviceInfoChanged=" + serviceInfoChanged);
+        }
+
         AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
         onUserStateChangedLocked(userState);
         if (serviceInfoChanged) {
@@ -3126,8 +3302,9 @@
             AccessibilityServiceConnection service = new AccessibilityServiceConnection(
                     userState, mContext,
                     COMPONENT_NAME, info, sIdCounter++, mMainHandler, mLock, mSecurityPolicy,
-                    AccessibilityManagerService.this, mWindowManagerService,
-                    getSystemActionPerformer(), mA11yWindowManager, mActivityTaskManagerService) {
+                    AccessibilityManagerService.this, AccessibilityManagerService.this,
+                    mWindowManagerService, getSystemActionPerformer(), mA11yWindowManager,
+                    mActivityTaskManagerService) {
                 @Override
                 public boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
                     return true;
@@ -3614,6 +3791,11 @@
 
     @Override
     public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".setGestureDetectionPassthroughRegion",
+                    "displayId=" + displayId + ";region=" + region);
+        }
+
         mMainHandler.sendMessage(
                 obtainMessage(
                         AccessibilityManagerService::setGestureDetectionPassthroughRegionInternal,
@@ -3624,6 +3806,11 @@
 
     @Override
     public void setTouchExplorationPassthroughRegion(int displayId, Region region) {
+        if (isA11yTracingEnabled()) {
+            logTrace(LOG_TAG + ".setTouchExplorationPassthroughRegion",
+                    "displayId=" + displayId + ";region=" + region);
+        }
+
         mMainHandler.sendMessage(
                 obtainMessage(
                         AccessibilityManagerService::setTouchExplorationPassthroughRegionInternal,
@@ -3661,4 +3848,20 @@
         });
 
     }
+
+    @Override
+    public boolean isA11yTracingEnabled() {
+        return mA11yController.isAccessibilityTracingEnabled();
+    }
+
+    @Override
+    public void logTrace(String where) {
+        logTrace(where, "");
+    }
+
+    @Override
+    public void logTrace(String where, String callingParams) {
+        mA11yController.logTrace(where, callingParams, "".getBytes(),
+                Binder.getCallingUid(), Thread.currentThread().getStackTrace());
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 6756268..7d75b73 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -53,6 +53,10 @@
  */
 class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
     private static final String LOG_TAG = "AccessibilityServiceConnection";
+    private static final String TRACE_A11Y_SERVICE_CONNECTION =
+            LOG_TAG + ".IAccessibilityServiceConnection";
+    private static final String TRACE_A11Y_SERVICE_CLIENT =
+            LOG_TAG + ".IAccessibilityServiceClient";
     /*
      Holding a weak reference so there isn't a loop of references. AccessibilityUserState keeps
      lists of bound and binding services. These are freed on user changes, but just in case it
@@ -70,11 +74,12 @@
             ComponentName componentName,
             AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
             Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport,
-            WindowManagerInternal windowManagerInternal,
+            AccessibilityTrace trace, WindowManagerInternal windowManagerInternal,
             SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager awm,
             ActivityTaskManagerInternal activityTaskManagerService) {
         super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock,
-                securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer, awm);
+                securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerfomer,
+                awm);
         mUserStateWeakReference = new WeakReference<AccessibilityUserState>(userState);
         mIntent = new Intent().setComponent(mComponentName);
         mMainHandler = mainHandler;
@@ -132,6 +137,9 @@
 
     @Override
     public void disableSelf() {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".disableSelf");
+        }
         synchronized (mLock) {
             AccessibilityUserState userState = mUserStateWeakReference.get();
             if (userState == null) return;
@@ -210,6 +218,10 @@
             return;
         }
         try {
+            if (mTrace.isA11yTracingEnabled()) {
+                mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", this + ", " + mId + ", "
+                        + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+            }
             serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
         } catch (RemoteException re) {
             Slog.w(LOG_TAG, "Error while setting connection for service: "
@@ -252,6 +264,10 @@
 
     @Override
     public boolean setSoftKeyboardShowMode(int showMode) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardShowMode",
+                    "showMode=" + showMode);
+        }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return false;
@@ -264,12 +280,19 @@
 
     @Override
     public int getSoftKeyboardShowMode() {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSoftKeyboardShowMode");
+        }
         final AccessibilityUserState userState = mUserStateWeakReference.get();
         return (userState != null) ? userState.getSoftKeyboardShowModeLocked() : 0;
     }
 
     @Override
     public boolean switchToInputMethod(String imeId) {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".switchToInputMethod",
+                    "imeId=" + imeId);
+        }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return false;
@@ -288,6 +311,9 @@
 
     @Override
     public boolean isAccessibilityButtonAvailable() {
+        if (mTrace.isA11yTracingEnabled()) {
+            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".isAccessibilityButtonAvailable");
+        }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return false;
@@ -347,6 +373,10 @@
         }
         if (serviceInterface != null) {
             try {
+                if (mTrace.isA11yTracingEnabled()) {
+                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT
+                            + ".onFingerprintCapturingGesturesChanged", String.valueOf(active));
+                }
                 mServiceInterface.onFingerprintCapturingGesturesChanged(active);
             } catch (RemoteException e) {
             }
@@ -364,6 +394,10 @@
         }
         if (serviceInterface != null) {
             try {
+                if (mTrace.isA11yTracingEnabled()) {
+                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onFingerprintGesture",
+                            String.valueOf(gesture));
+                }
                 mServiceInterface.onFingerprintGesture(gesture);
             } catch (RemoteException e) {
             }
@@ -382,6 +416,10 @@
                             gestureSteps.getList(), mServiceInterface, sequence, displayId);
                 } else {
                     try {
+                        if (mTrace.isA11yTracingEnabled()) {
+                            mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onPerformGestureResult",
+                                    sequence + ", false");
+                        }
                         mServiceInterface.onPerformGestureResult(sequence, false);
                     } catch (RemoteException re) {
                         Slog.e(LOG_TAG, "Error sending motion event injection failure to "
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java
new file mode 100644
index 0000000..0c03877
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java
@@ -0,0 +1,41 @@
+/**
+ * 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.accessibility;
+
+/**
+ * Interface to log accessibility trace.
+ */
+public interface AccessibilityTrace {
+    /**
+     * Whether the trace is enabled.
+     */
+    boolean isA11yTracingEnabled();
+
+    /**
+     * Log one trace entry.
+     * @param where A string to identify this log entry, which can be used to filter/search
+     *        through the tracing file.
+     */
+    void logTrace(String where);
+
+    /**
+     * Log one trace entry.
+     * @param where A string to identify this log entry, which can be used to filter/search
+     *        through the tracing file.
+     * @param callingParams The parameters for the method to be logged.
+     */
+    void logTrace(String where, String callingParams);
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index 6828dd9..bafb641 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -40,34 +40,29 @@
     private final IAccessibilityInteractionConnectionCallback mServiceCallback;
     private final IAccessibilityInteractionConnection mConnectionWithReplacementActions;
     private final int mInteractionId;
-    private final int mNodeWithReplacementActionsInteractionId;
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
-    private boolean mReplacementNodeIsReadyOrFailed;
-
-    @GuardedBy("mLock")
-    AccessibilityNodeInfo mNodeWithReplacementActions;
+    List<AccessibilityNodeInfo> mNodesWithReplacementActions;
 
     @GuardedBy("mLock")
     List<AccessibilityNodeInfo> mNodesFromOriginalWindow;
 
     @GuardedBy("mLock")
-    boolean mSetFindNodeFromOriginalWindowCalled = false;
-
-    @GuardedBy("mLock")
     AccessibilityNodeInfo mNodeFromOriginalWindow;
 
+    // Keep track of whether or not we've been called back for a single node
     @GuardedBy("mLock")
-    boolean mSetFindNodesFromOriginalWindowCalled = false;
+    boolean mSingleNodeCallbackHappened;
 
-
+    // Keep track of whether or not we've been called back for multiple node
     @GuardedBy("mLock")
-    List<AccessibilityNodeInfo> mPrefetchedNodesFromOriginalWindow;
+    boolean mMultiNodeCallbackHappened;
 
+    // We shouldn't get any more callbacks after we've called back the original service, but
+    // keep track to make sure we catch such strange things
     @GuardedBy("mLock")
-    boolean mSetPrefetchFromOriginalWindowCalled = false;
-
+    boolean mDone;
 
     public ActionReplacingCallback(IAccessibilityInteractionConnectionCallback serviceCallback,
             IAccessibilityInteractionConnection connectionWithReplacementActions,
@@ -75,20 +70,19 @@
         mServiceCallback = serviceCallback;
         mConnectionWithReplacementActions = connectionWithReplacementActions;
         mInteractionId = interactionId;
-        mNodeWithReplacementActionsInteractionId = interactionId + 1;
 
         // Request the root node of the replacing window
         final long identityToken = Binder.clearCallingIdentity();
         try {
             mConnectionWithReplacementActions.findAccessibilityNodeInfoByAccessibilityId(
-                    AccessibilityNodeInfo.ROOT_NODE_ID, null,
-                    mNodeWithReplacementActionsInteractionId, this, 0,
+                    AccessibilityNodeInfo.ROOT_NODE_ID, null, interactionId + 1, this, 0,
                     interrogatingPid, interrogatingTid, null, null);
         } catch (RemoteException re) {
             if (DEBUG) {
                 Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
             }
-            mReplacementNodeIsReadyOrFailed = true;
+            // Pretend we already got a (null) list of replacement nodes
+            mMultiNodeCallbackHappened = true;
         } finally {
             Binder.restoreCallingIdentity(identityToken);
         }
@@ -96,67 +90,46 @@
 
     @Override
     public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) {
-        synchronized (mLock) {
+        boolean readyForCallback;
+        synchronized(mLock) {
             if (interactionId == mInteractionId) {
                 mNodeFromOriginalWindow = info;
-                mSetFindNodeFromOriginalWindowCalled = true;
-            } else if (interactionId == mNodeWithReplacementActionsInteractionId) {
-                mNodeWithReplacementActions = info;
-                mReplacementNodeIsReadyOrFailed = true;
             } else {
                 Slog.e(LOG_TAG, "Callback with unexpected interactionId");
                 return;
             }
+
+            mSingleNodeCallbackHappened = true;
+            readyForCallback = mMultiNodeCallbackHappened;
         }
-        replaceInfoActionsAndCallServiceIfReady();
+        if (readyForCallback) {
+            replaceInfoActionsAndCallService();
+        }
     }
 
     @Override
     public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
             int interactionId) {
-        synchronized (mLock) {
+        boolean callbackForSingleNode;
+        boolean callbackForMultipleNodes;
+        synchronized(mLock) {
             if (interactionId == mInteractionId) {
                 mNodesFromOriginalWindow = infos;
-                mSetFindNodesFromOriginalWindowCalled = true;
-            } else if (interactionId == mNodeWithReplacementActionsInteractionId) {
-                setNodeWithReplacementActionsFromList(infos);
-                mReplacementNodeIsReadyOrFailed = true;
+            } else if (interactionId == mInteractionId + 1) {
+                mNodesWithReplacementActions = infos;
             } else {
                 Slog.e(LOG_TAG, "Callback with unexpected interactionId");
                 return;
             }
+            callbackForSingleNode = mSingleNodeCallbackHappened;
+            callbackForMultipleNodes = mMultiNodeCallbackHappened;
+            mMultiNodeCallbackHappened = true;
         }
-        replaceInfoActionsAndCallServiceIfReady();
-    }
-
-    @Override
-    public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos,
-                                                       int interactionId)
-            throws RemoteException {
-        synchronized (mLock) {
-            if (interactionId == mInteractionId) {
-                mPrefetchedNodesFromOriginalWindow = infos;
-                mSetPrefetchFromOriginalWindowCalled = true;
-            }  else {
-                Slog.e(LOG_TAG, "Callback with unexpected interactionId");
-                return;
-            }
+        if (callbackForSingleNode) {
+            replaceInfoActionsAndCallService();
         }
-        replaceInfoActionsAndCallServiceIfReady();
-    }
-
-    private void replaceInfoActionsAndCallServiceIfReady() {
-        replaceInfoActionsAndCallService();
-        replaceInfosActionsAndCallService();
-        replacePrefetchInfosActionsAndCallService();
-    }
-
-    private void setNodeWithReplacementActionsFromList(List<AccessibilityNodeInfo> infos) {
-        for (int i = 0; i < infos.size(); i++) {
-            AccessibilityNodeInfo info = infos.get(i);
-            if (info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) {
-                mNodeWithReplacementActions = info;
-            }
+        if (callbackForMultipleNodes) {
+            replaceInfosActionsAndCallService();
         }
     }
 
@@ -169,81 +142,55 @@
 
     private void replaceInfoActionsAndCallService() {
         final AccessibilityNodeInfo nodeToReturn;
-        boolean doCallback = false;
         synchronized (mLock) {
-            doCallback = mReplacementNodeIsReadyOrFailed
-                    && mSetFindNodeFromOriginalWindowCalled;
-            if (doCallback && mNodeFromOriginalWindow != null) {
-                replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
-                mSetFindNodeFromOriginalWindowCalled = false;
-            }
-            nodeToReturn = mNodeFromOriginalWindow;
-        }
-        if (doCallback) {
-            try {
-                mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId);
-            } catch (RemoteException re) {
+            if (mDone) {
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult");
+                    Slog.e(LOG_TAG, "Extra callback");
                 }
+                return;
+            }
+            if (mNodeFromOriginalWindow != null) {
+                replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
+            }
+            recycleReplaceActionNodesLocked();
+            nodeToReturn = mNodeFromOriginalWindow;
+            mDone = true;
+        }
+        try {
+            mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId);
+        } catch (RemoteException re) {
+            if (DEBUG) {
+                Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult");
             }
         }
     }
 
     private void replaceInfosActionsAndCallService() {
-        List<AccessibilityNodeInfo> nodesToReturn = null;
-        boolean doCallback = false;
+        final List<AccessibilityNodeInfo> nodesToReturn;
         synchronized (mLock) {
-            doCallback = mReplacementNodeIsReadyOrFailed
-                    && mSetFindNodesFromOriginalWindowCalled;
-            if (doCallback) {
-                nodesToReturn = replaceActionsLocked(mNodesFromOriginalWindow);
-                mSetFindNodesFromOriginalWindowCalled = false;
-            }
-        }
-        if (doCallback) {
-            try {
-                mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId);
-            } catch (RemoteException re) {
+            if (mDone) {
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult");
+                    Slog.e(LOG_TAG, "Extra callback");
+                }
+                return;
+            }
+            if (mNodesFromOriginalWindow != null) {
+                for (int i = 0; i < mNodesFromOriginalWindow.size(); i++) {
+                    replaceActionsOnInfoLocked(mNodesFromOriginalWindow.get(i));
                 }
             }
+            recycleReplaceActionNodesLocked();
+            nodesToReturn = (mNodesFromOriginalWindow == null)
+                    ? null : new ArrayList<>(mNodesFromOriginalWindow);
+            mDone = true;
         }
-    }
-
-    private void replacePrefetchInfosActionsAndCallService() {
-        List<AccessibilityNodeInfo> nodesToReturn = null;
-        boolean doCallback = false;
-        synchronized (mLock) {
-            doCallback = mReplacementNodeIsReadyOrFailed
-                    && mSetPrefetchFromOriginalWindowCalled;
-            if (doCallback) {
-                nodesToReturn = replaceActionsLocked(mPrefetchedNodesFromOriginalWindow);
-                mSetPrefetchFromOriginalWindowCalled = false;
+        try {
+            mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId);
+        } catch (RemoteException re) {
+            if (DEBUG) {
+                Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult");
             }
         }
-        if (doCallback) {
-            try {
-                mServiceCallback.setPrefetchAccessibilityNodeInfoResult(
-                        nodesToReturn, mInteractionId);
-            } catch (RemoteException re) {
-                if (DEBUG) {
-                    Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult");
-                }
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private List<AccessibilityNodeInfo> replaceActionsLocked(List<AccessibilityNodeInfo> infos) {
-        if (infos != null) {
-            for (int i = 0; i < infos.size(); i++) {
-                replaceActionsOnInfoLocked(infos.get(i));
-            }
-        }
-        return (infos == null)
-                ? null : new ArrayList<>(infos);
     }
 
     @GuardedBy("mLock")
@@ -257,22 +204,40 @@
         info.setDismissable(false);
         // We currently only replace actions for the root node
         if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID)
-                && mNodeWithReplacementActions != null) {
-            List<AccessibilityAction> actions = mNodeWithReplacementActions.getActionList();
-            if (actions != null) {
-                for (int j = 0; j < actions.size(); j++) {
-                    info.addAction(actions.get(j));
+                && mNodesWithReplacementActions != null) {
+            // This list should always contain a single node with the root ID
+            for (int i = 0; i < mNodesWithReplacementActions.size(); i++) {
+                AccessibilityNodeInfo nodeWithReplacementActions =
+                        mNodesWithReplacementActions.get(i);
+                if (nodeWithReplacementActions.getSourceNodeId()
+                        == AccessibilityNodeInfo.ROOT_NODE_ID) {
+                    List<AccessibilityAction> actions = nodeWithReplacementActions.getActionList();
+                    if (actions != null) {
+                        for (int j = 0; j < actions.size(); j++) {
+                            info.addAction(actions.get(j));
+                        }
+                        // The PIP needs to be able to take accessibility focus
+                        info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
+                        info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+                    }
+                    info.setClickable(nodeWithReplacementActions.isClickable());
+                    info.setFocusable(nodeWithReplacementActions.isFocusable());
+                    info.setContextClickable(nodeWithReplacementActions.isContextClickable());
+                    info.setScrollable(nodeWithReplacementActions.isScrollable());
+                    info.setLongClickable(nodeWithReplacementActions.isLongClickable());
+                    info.setDismissable(nodeWithReplacementActions.isDismissable());
                 }
-                // The PIP needs to be able to take accessibility focus
-                info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
-                info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
             }
-            info.setClickable(mNodeWithReplacementActions.isClickable());
-            info.setFocusable(mNodeWithReplacementActions.isFocusable());
-            info.setContextClickable(mNodeWithReplacementActions.isContextClickable());
-            info.setScrollable(mNodeWithReplacementActions.isScrollable());
-            info.setLongClickable(mNodeWithReplacementActions.isLongClickable());
-            info.setDismissable(mNodeWithReplacementActions.isDismissable());
         }
     }
+
+    @GuardedBy("mLock")
+    private void recycleReplaceActionNodesLocked() {
+        if (mNodesWithReplacementActions == null) return;
+        for (int i = mNodesWithReplacementActions.size() - 1; i >= 0; i--) {
+            AccessibilityNodeInfo nodeWithReplacementAction = mNodesWithReplacementActions.get(i);
+            nodeWithReplacementAction.recycle();
+        }
+        mNodesWithReplacementActions = null;
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 4473754..9547280 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -53,6 +53,8 @@
 
     private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport;
 
+    private AccessibilityTrace mTrace;
+
     private int mUiAutomationFlags;
 
     UiAutomationManager(Object lock) {
@@ -89,6 +91,7 @@
             int id, Handler mainHandler,
             AccessibilitySecurityPolicy securityPolicy,
             AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
+            AccessibilityTrace trace,
             WindowManagerInternal windowManagerInternal,
             SystemActionPerformer systemActionPerformer,
             AccessibilityWindowManager awm, int flags) {
@@ -111,13 +114,14 @@
 
             mUiAutomationFlags = flags;
             mSystemSupport = systemSupport;
+            mTrace = trace;
             // Ignore registering UiAutomation if it is not allowed to use the accessibility
             // subsystem.
             if (!useAccessibility()) {
                 return;
             }
             mUiAutomationService = new UiAutomationService(context, accessibilityServiceInfo, id,
-                    mainHandler, mLock, securityPolicy, systemSupport, windowManagerInternal,
+                    mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal,
                     systemActionPerformer, awm);
             mUiAutomationServiceOwner = owner;
             mUiAutomationServiceInfo = accessibilityServiceInfo;
@@ -239,11 +243,12 @@
         UiAutomationService(Context context, AccessibilityServiceInfo accessibilityServiceInfo,
                 int id, Handler mainHandler, Object lock,
                 AccessibilitySecurityPolicy securityPolicy,
-                SystemSupport systemSupport, WindowManagerInternal windowManagerInternal,
+                SystemSupport systemSupport, AccessibilityTrace trace,
+                WindowManagerInternal windowManagerInternal,
                 SystemActionPerformer systemActionPerformer, AccessibilityWindowManager awm) {
             super(context, COMPONENT_NAME, accessibilityServiceInfo, id, mainHandler, lock,
-                    securityPolicy, systemSupport, windowManagerInternal, systemActionPerformer,
-                    awm);
+                    securityPolicy, systemSupport, trace, windowManagerInternal,
+                    systemActionPerformer, awm);
             mMainHandler = mainHandler;
         }
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b67bdc2..99ce2db 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -97,6 +97,7 @@
         ":platform-compat-config",
         ":platform-compat-overrides",
         ":display-device-config",
+        ":display-layout-config",
         ":cec-config",
         ":device-state-config",
         "java/com/android/server/EventLogTags.logtags",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ff3d016..986e2acf 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -142,10 +142,13 @@
 import android.net.Uri;
 import android.net.VpnManager;
 import android.net.VpnTransportInfo;
-import android.net.metrics.INetdEventListener;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.netlink.InetDiagMessage;
+import android.net.resolv.aidl.DnsHealthEventParcel;
+import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
+import android.net.resolv.aidl.Nat64PrefixEventParcel;
+import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
@@ -1400,7 +1403,7 @@
         return null;
     }
 
-    private NetworkState getUnfilteredActiveNetworkState(int uid) {
+    private NetworkAgentInfo getNetworkAgentInfoForUid(int uid) {
         NetworkAgentInfo nai = getDefaultNetworkForUid(uid);
 
         final Network[] networks = getVpnUnderlyingNetworks(uid);
@@ -1416,12 +1419,7 @@
                 nai = null;
             }
         }
-
-        if (nai != null) {
-            return nai.getNetworkState();
-        } else {
-            return NetworkState.EMPTY;
-        }
+        return nai;
     }
 
     /**
@@ -1474,24 +1472,28 @@
                 "%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId()));
     }
 
-    private void filterNetworkInfo(@NonNull NetworkInfo networkInfo,
-            @NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) {
-        if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) {
-            networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
-        }
-        networkInfo.setDetailedState(
-                getLegacyLockdownState(networkInfo.getDetailedState()),
-                "" /* reason */, null /* extraInfo */);
-    }
-
     /**
-     * Apply any relevant filters to {@link NetworkState} for the given UID. For
+     * Apply any relevant filters to the specified {@link NetworkInfo} for the given UID. For
      * example, this may mark the network as {@link DetailedState#BLOCKED} based
      * on {@link #isNetworkWithCapabilitiesBlocked}.
      */
-    private void filterNetworkStateForUid(NetworkState state, int uid, boolean ignoreBlocked) {
-        if (state == null || state.networkInfo == null || state.linkProperties == null) return;
-        filterNetworkInfo(state.networkInfo, state.networkCapabilities, uid, ignoreBlocked);
+    @NonNull
+    private NetworkInfo filterNetworkInfo(@NonNull NetworkInfo networkInfo, int type,
+            @NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) {
+        NetworkInfo filtered = new NetworkInfo(networkInfo);
+        filtered.setType(type);
+        final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)
+                ? DetailedState.BLOCKED
+                : filtered.getDetailedState();
+        filtered.setDetailedState(getLegacyLockdownState(state),
+                "" /* reason */, null /* extraInfo */);
+        return filtered;
+    }
+
+    private NetworkInfo getFilteredNetworkInfo(NetworkAgentInfo nai, int uid,
+            boolean ignoreBlocked) {
+        return filterNetworkInfo(nai.networkInfo, nai.networkInfo.getType(),
+                nai.networkCapabilities, uid, ignoreBlocked);
     }
 
     /**
@@ -1505,10 +1507,11 @@
     public NetworkInfo getActiveNetworkInfo() {
         enforceAccessPermission();
         final int uid = mDeps.getCallingUid();
-        final NetworkState state = getUnfilteredActiveNetworkState(uid);
-        filterNetworkStateForUid(state, uid, false);
-        maybeLogBlockedNetworkInfo(state.networkInfo, uid);
-        return state.networkInfo;
+        final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
+        if (nai == null) return null;
+        final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false);
+        maybeLogBlockedNetworkInfo(networkInfo, uid);
+        return networkInfo;
     }
 
     @Override
@@ -1543,30 +1546,37 @@
     @Override
     public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) {
         PermissionUtils.enforceNetworkStackPermission(mContext);
-        final NetworkState state = getUnfilteredActiveNetworkState(uid);
-        filterNetworkStateForUid(state, uid, ignoreBlocked);
-        return state.networkInfo;
+        final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
+        if (nai == null) return null;
+        return getFilteredNetworkInfo(nai, uid, ignoreBlocked);
     }
 
-    private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) {
+    /** Returns a NetworkInfo object for a network that doesn't exist. */
+    private NetworkInfo makeFakeNetworkInfo(int networkType, int uid) {
+        final NetworkInfo info = new NetworkInfo(networkType, 0 /* subtype */,
+                getNetworkTypeName(networkType), "" /* subtypeName */);
+        info.setIsAvailable(true);
+        // For compatibility with legacy code, return BLOCKED instead of DISCONNECTED when
+        // background data is restricted.
+        final NetworkCapabilities nc = new NetworkCapabilities();  // Metered.
+        final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, false)
+                ? DetailedState.BLOCKED
+                : DetailedState.DISCONNECTED;
+        info.setDetailedState(getLegacyLockdownState(state),
+                "" /* reason */, null /* extraInfo */);
+        return info;
+    }
+
+    private NetworkInfo getFilteredNetworkInfoForType(int networkType, int uid) {
         if (!mLegacyTypeTracker.isTypeSupported(networkType)) {
             return null;
         }
         final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
-        final NetworkInfo info;
-        final NetworkCapabilities nc;
-        if (nai != null) {
-            info = new NetworkInfo(nai.networkInfo);
-            info.setType(networkType);
-            nc = nai.networkCapabilities;
-        } else {
-            info = new NetworkInfo(networkType, 0, getNetworkTypeName(networkType), "");
-            info.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
-            info.setIsAvailable(true);
-            nc = new NetworkCapabilities();
+        if (nai == null) {
+            return makeFakeNetworkInfo(networkType, uid);
         }
-        filterNetworkInfo(info, nc, uid, false);
-        return info;
+        return filterNetworkInfo(nai.networkInfo, networkType, nai.networkCapabilities, uid,
+                false);
     }
 
     @Override
@@ -1576,27 +1586,23 @@
         if (getVpnUnderlyingNetworks(uid) != null) {
             // A VPN is active, so we may need to return one of its underlying networks. This
             // information is not available in LegacyTypeTracker, so we have to get it from
-            // getUnfilteredActiveNetworkState.
-            final NetworkState state = getUnfilteredActiveNetworkState(uid);
-            if (state.networkInfo != null && state.networkInfo.getType() == networkType) {
-                filterNetworkStateForUid(state, uid, false);
-                return state.networkInfo;
+            // getNetworkAgentInfoForUid.
+            final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
+            if (nai == null) return null;
+            final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false);
+            if (networkInfo.getType() == networkType) {
+                return networkInfo;
             }
         }
-        return getFilteredNetworkInfo(networkType, uid);
+        return getFilteredNetworkInfoForType(networkType, uid);
     }
 
     @Override
     public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) {
         enforceAccessPermission();
         final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
-        if (nai != null) {
-            final NetworkState state = nai.getNetworkState();
-            filterNetworkStateForUid(state, uid, ignoreBlocked);
-            return state.networkInfo;
-        } else {
-            return null;
-        }
+        if (nai == null) return null;
+        return getFilteredNetworkInfo(nai, uid, ignoreBlocked);
     }
 
     @Override
@@ -1624,10 +1630,10 @@
             return null;
         }
         final int uid = mDeps.getCallingUid();
-        if (!isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, false)) {
-            return nai.network;
+        if (isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, false)) {
+            return null;
         }
-        return null;
+        return nai.network;
     }
 
     @Override
@@ -1716,9 +1722,9 @@
     public LinkProperties getActiveLinkProperties() {
         enforceAccessPermission();
         final int uid = mDeps.getCallingUid();
-        NetworkState state = getUnfilteredActiveNetworkState(uid);
-        if (state.linkProperties == null) return null;
-        return linkPropertiesRestrictedForCallerPermissions(state.linkProperties,
+        NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
+        if (nai == null) return null;
+        return linkPropertiesRestrictedForCallerPermissions(nai.linkProperties,
                 Binder.getCallingPid(), uid);
     }
 
@@ -2037,25 +2043,24 @@
         return true;
     }
 
-    private class NetdEventCallback extends INetdEventListener.Stub {
+    class DnsResolverUnsolicitedEventCallback extends
+            IDnsResolverUnsolicitedEventListener.Stub {
         @Override
-        public void onPrivateDnsValidationEvent(int netId, String ipAddress,
-                String hostname, boolean validated) {
+        public void onPrivateDnsValidationEvent(final PrivateDnsValidationEventParcel event) {
             try {
                 mHandler.sendMessage(mHandler.obtainMessage(
                         EVENT_PRIVATE_DNS_VALIDATION_UPDATE,
-                        new PrivateDnsValidationUpdate(netId,
-                                InetAddresses.parseNumericAddress(ipAddress),
-                                hostname, validated)));
+                        new PrivateDnsValidationUpdate(event.netId,
+                                InetAddresses.parseNumericAddress(event.ipAddress),
+                                event.hostname, event.validation)));
             } catch (IllegalArgumentException e) {
                 loge("Error parsing ip address in validation event");
             }
         }
 
         @Override
-        public void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
-                String hostname,  String[] ipAddresses, int ipAddressesCount, int uid) {
-            NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
+        public void onDnsHealthEvent(final DnsHealthEventParcel event) {
+            NetworkAgentInfo nai = getNetworkAgentInfoForNetId(event.netId);
             // Netd event only allow registrants from system. Each NetworkMonitor thread is under
             // the caller thread of registerNetworkAgent. Thus, it's not allowed to register netd
             // event callback for certain nai. e.g. cellular. Register here to pass to
@@ -2064,34 +2069,18 @@
             // callback from each caller type. Need to re-factor NetdEventListenerService to allow
             // multiple NetworkMonitor registrants.
             if (nai != null && nai.satisfies(mDefaultRequest.mRequests.get(0))) {
-                nai.networkMonitor().notifyDnsResponse(returnCode);
+                nai.networkMonitor().notifyDnsResponse(event.healthResult);
             }
         }
 
         @Override
-        public void onNat64PrefixEvent(int netId, boolean added,
-                                       String prefixString, int prefixLength) {
-            mHandler.post(() -> handleNat64PrefixEvent(netId, added, prefixString, prefixLength));
+        public void onNat64PrefixEvent(final Nat64PrefixEventParcel event) {
+            mHandler.post(() -> handleNat64PrefixEvent(event.netId, event.prefixOperation,
+                    event.prefixAddress, event.prefixLength));
         }
 
         @Override
-        public void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port,
-                int uid) {
-        }
-
-        @Override
-        public void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader,
-                byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort,
-                long timestampNs) {
-        }
-
-        @Override
-        public void onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets, int[] lostPackets,
-                int[] rttsUs, int[] sentAckDiffsMs) {
-        }
-
-        @Override
-        public int getInterfaceVersion() throws RemoteException {
+        public int getInterfaceVersion() {
             return this.VERSION;
         }
 
@@ -2099,16 +2088,17 @@
         public String getInterfaceHash() {
             return this.HASH;
         }
-    };
+    }
 
     @VisibleForTesting
-    protected final INetdEventListener mNetdEventCallback = new NetdEventCallback();
+    protected final DnsResolverUnsolicitedEventCallback mResolverUnsolEventCallback =
+            new DnsResolverUnsolicitedEventCallback();
 
-    private void registerNetdEventCallback() {
+    private void registerDnsResolverUnsolicitedEventListener() {
         try {
-            mDnsResolver.registerEventListener(mNetdEventCallback);
+            mDnsResolver.registerUnsolicitedEventListener(mResolverUnsolEventCallback);
         } catch (Exception e) {
-            loge("Error registering DnsResolver callback: " + e);
+            loge("Error registering DnsResolver unsolicited event callback: " + e);
         }
     }
 
@@ -2439,7 +2429,7 @@
         // to ensure the tracking will be initialized correctly.
         mPermissionMonitor.startMonitoring();
         mProxyTracker.loadGlobalProxy();
-        registerNetdEventCallback();
+        registerDnsResolverUnsolicitedEventListener();
 
         synchronized (this) {
             mSystemReady = true;
@@ -3080,9 +3070,6 @@
                 log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg);
             }
             if (valid != nai.lastValidated) {
-                if (wasDefault) {
-                    mMetricsLog.logDefaultNetworkValidity(valid);
-                }
                 final int oldScore = nai.getCurrentScore();
                 nai.lastValidated = valid;
                 nai.everValidated |= valid;
@@ -3370,21 +3357,21 @@
         handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
     }
 
-    private void handleNat64PrefixEvent(int netId, boolean added, String prefixString,
+    private void handleNat64PrefixEvent(int netId, int operation, String prefixAddress,
             int prefixLength) {
         NetworkAgentInfo nai = mNetworkForNetId.get(netId);
         if (nai == null) return;
 
-        log(String.format("NAT64 prefix %s on netId %d: %s/%d",
-                          (added ? "added" : "removed"), netId, prefixString, prefixLength));
+        log(String.format("NAT64 prefix changed on netId %d: operation=%d, %s/%d",
+                netId, operation, prefixAddress, prefixLength));
 
         IpPrefix prefix = null;
-        if (added) {
+        if (operation == IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED) {
             try {
-                prefix = new IpPrefix(InetAddresses.parseNumericAddress(prefixString),
+                prefix = new IpPrefix(InetAddresses.parseNumericAddress(prefixAddress),
                         prefixLength);
             } catch (IllegalArgumentException e) {
-                loge("Invalid NAT64 prefix " + prefixString + "/" + prefixLength);
+                loge("Invalid NAT64 prefix " + prefixAddress + "/" + prefixLength);
                 return;
             }
         }
@@ -3503,14 +3490,6 @@
         final boolean wasDefault = isDefaultNetwork(nai);
         if (wasDefault) {
             mDefaultInetConditionPublished = 0;
-            // Log default network disconnection before required book-keeping.
-            // Let rematchAllNetworksAndRequests() below record a new default network event
-            // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence
-            // whose timestamps tell how long it takes to recover a default network.
-            long now = SystemClock.elapsedRealtime();
-            mMetricsLog.logDefaultNetworkEvent(null, 0, false,
-                    null /* lp */, null /* nc */, nai.network, nai.getCurrentScore(),
-                    nai.linkProperties, nai.networkCapabilities);
         }
         notifyIfacesChangedForNetworkStats();
         // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
@@ -7146,27 +7125,6 @@
         updateTcpBufferSizes(null != newDefaultNetwork
                 ? newDefaultNetwork.linkProperties.getTcpBufferSizes() : null);
         notifyIfacesChangedForNetworkStats();
-
-        // Log 0 -> X and Y -> X default network transitions, where X is the new default.
-        final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null;
-        final int score = (newDefaultNetwork != null) ? newDefaultNetwork.getCurrentScore() : 0;
-        final boolean validated = newDefaultNetwork != null && newDefaultNetwork.lastValidated;
-        final LinkProperties lp = (newDefaultNetwork != null)
-                ? newDefaultNetwork.linkProperties : null;
-        final NetworkCapabilities nc = (newDefaultNetwork != null)
-                ? newDefaultNetwork.networkCapabilities : null;
-
-        final Network prevNetwork = (oldDefaultNetwork != null)
-                ? oldDefaultNetwork.network : null;
-        final int prevScore = (oldDefaultNetwork != null)
-                ? oldDefaultNetwork.getCurrentScore() : 0;
-        final LinkProperties prevLp = (oldDefaultNetwork != null)
-                ? oldDefaultNetwork.linkProperties : null;
-        final NetworkCapabilities prevNc = (oldDefaultNetwork != null)
-                ? oldDefaultNetwork.networkCapabilities : null;
-
-        mMetricsLog.logDefaultNetworkEvent(network, score, validated, lp, nc,
-                prevNetwork, prevScore, prevLp, prevNc);
     }
 
     private void makeDefaultForApps(@NonNull final NetworkRequestInfo nri,
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 233a50d..c5233f4 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -24,6 +24,10 @@
 import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES;
 import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE;
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_ONE_SHOT;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
@@ -55,6 +59,7 @@
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.KeyguardManager;
+import android.app.PendingIntent;
 import android.app.admin.SecurityLog;
 import android.app.usage.StorageStatsManager;
 import android.content.BroadcastReceiver;
@@ -3371,6 +3376,54 @@
         }
     }
 
+    /**
+     * Returns PendingIntent which can be used by Apps with MANAGE_EXTERNAL_STORAGE permission
+     * to launch the manageSpaceActivity of the App specified by packageName.
+     */
+    @Override
+    @Nullable
+    public PendingIntent getManageSpaceActivityIntent(
+            @NonNull String packageName, int requestCode) {
+        // Only Apps with MANAGE_EXTERNAL_STORAGE permission should be able to call this API.
+        enforcePermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+
+        // We want to call the manageSpaceActivity as a SystemService and clear identity
+        // of the calling App
+        int originalUid = Binder.getCallingUidOrThrow();
+        long token = Binder.clearCallingIdentity();
+
+        try {
+            ApplicationInfo appInfo = mIPackageManager.getApplicationInfo(packageName, 0,
+                    UserHandle.getUserId(originalUid));
+            if (appInfo == null) {
+                throw new IllegalArgumentException(
+                        "Invalid packageName");
+            }
+            if (appInfo.manageSpaceActivityName == null) {
+                Log.i(TAG, packageName + " doesn't have a manageSpaceActivity");
+                return null;
+            }
+            Context targetAppContext = mContext.createPackageContext(packageName, 0);
+
+            Intent intent = new Intent(Intent.ACTION_DEFAULT);
+            intent.setClassName(packageName,
+                    appInfo.manageSpaceActivityName);
+            intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+
+            PendingIntent activity = PendingIntent.getActivity(targetAppContext, requestCode,
+                    intent,
+                    FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE);
+            return activity;
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalArgumentException(
+                    "packageName not found");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     /** Not thread safe */
     class AppFuseMountScope extends AppFuseBridge.MountScope {
         private boolean mMounted = false;
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 5d89bf1..56aabc20 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -47,7 +47,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.security.Credentials;
-import android.security.KeyStore;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -60,6 +59,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.connectivity.Vpn;
+import com.android.server.connectivity.VpnProfileStore;
 import com.android.server.net.LockdownVpnTracker;
 
 import java.io.FileDescriptor;
@@ -83,7 +83,7 @@
     private final Dependencies mDeps;
 
     private final ConnectivityManager mCm;
-    private final KeyStore mKeyStore;
+    private final VpnProfileStore mVpnProfileStore;
     private final INetworkManagementService mNMS;
     private final INetd mNetd;
     private final UserManager mUserManager;
@@ -114,9 +114,9 @@
             return new HandlerThread("VpnManagerService");
         }
 
-        /** Returns the KeyStore instance to be used by this class. */
-        public KeyStore getKeyStore() {
-            return KeyStore.getInstance();
+        /** Return the VpnProfileStore to be used by this class */
+        public VpnProfileStore getVpnProfileStore() {
+            return new VpnProfileStore();
         }
 
         public INetd getNetd() {
@@ -135,7 +135,7 @@
         mHandlerThread = mDeps.makeHandlerThread();
         mHandlerThread.start();
         mHandler = mHandlerThread.getThreadHandler();
-        mKeyStore = mDeps.getKeyStore();
+        mVpnProfileStore = mDeps.getVpnProfileStore();
         mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
         mCm = mContext.getSystemService(ConnectivityManager.class);
         mNMS = mDeps.getINetworkManagementService();
@@ -289,7 +289,7 @@
     public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
         final int user = UserHandle.getUserId(mDeps.getCallingUid());
         synchronized (mVpns) {
-            return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore);
+            return mVpns.get(user).provisionVpnProfile(packageName, profile);
         }
     }
 
@@ -307,7 +307,7 @@
     public void deleteVpnProfile(@NonNull String packageName) {
         final int user = UserHandle.getUserId(mDeps.getCallingUid());
         synchronized (mVpns) {
-            mVpns.get(user).deleteVpnProfile(packageName, mKeyStore);
+            mVpns.get(user).deleteVpnProfile(packageName);
         }
     }
 
@@ -325,7 +325,7 @@
         final int user = UserHandle.getUserId(mDeps.getCallingUid());
         synchronized (mVpns) {
             throwIfLockdownEnabled();
-            mVpns.get(user).startVpnProfile(packageName, mKeyStore);
+            mVpns.get(user).startVpnProfile(packageName);
         }
     }
 
@@ -358,7 +358,7 @@
         }
         synchronized (mVpns) {
             throwIfLockdownEnabled();
-            mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
+            mVpns.get(user).startLegacyVpn(profile, null /* underlying */, egress);
         }
     }
 
@@ -396,7 +396,7 @@
     }
 
     private boolean isLockdownVpnEnabled() {
-        return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
+        return mVpnProfileStore.get(Credentials.LOCKDOWN_VPN) != null;
     }
 
     @Override
@@ -417,14 +417,14 @@
                 return true;
             }
 
-            byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
+            byte[] profileTag = mVpnProfileStore.get(Credentials.LOCKDOWN_VPN);
             if (profileTag == null) {
                 loge("Lockdown VPN configured but cannot be read from keystore");
                 return false;
             }
             String profileName = new String(profileTag);
             final VpnProfile profile = VpnProfile.decode(
-                    profileName, mKeyStore.get(Credentials.VPN + profileName));
+                    profileName, mVpnProfileStore.get(Credentials.VPN + profileName));
             if (profile == null) {
                 loge("Lockdown VPN configured invalid profile " + profileName);
                 setLockdownTracker(null);
@@ -437,7 +437,7 @@
                 return false;
             }
             setLockdownTracker(
-                    new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn,  profile));
+                    new LockdownVpnTracker(mContext, mHandler, vpn,  profile));
         }
 
         return true;
@@ -495,7 +495,7 @@
                 return false;
             }
 
-            return vpn.startAlwaysOnVpn(mKeyStore);
+            return vpn.startAlwaysOnVpn();
         }
     }
 
@@ -510,7 +510,7 @@
                 logw("User " + userId + " has no Vpn configuration");
                 return false;
             }
-            return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
+            return vpn.isAlwaysOnPackageSupported(packageName);
         }
     }
 
@@ -531,11 +531,11 @@
                 logw("User " + userId + " has no Vpn configuration");
                 return false;
             }
-            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist, mKeyStore)) {
+            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist)) {
                 return false;
             }
             if (!startAlwaysOnVpn(userId)) {
-                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
+                vpn.setAlwaysOnPackage(null, false, null);
                 return false;
             }
         }
@@ -705,7 +705,8 @@
                 loge("Starting user already has a VPN");
                 return;
             }
-            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore);
+            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId,
+                    new VpnProfileStore());
             mVpns.put(userId, userVpn);
             if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
                 updateLockdownVpn();
@@ -777,7 +778,7 @@
             if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
                 log("Restarting always-on VPN package " + packageName + " for user "
                         + userId);
-                vpn.startAlwaysOnVpn(mKeyStore);
+                vpn.startAlwaysOnVpn();
             }
         }
     }
@@ -798,7 +799,7 @@
             if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
                 log("Removing always-on VPN package " + packageName + " for user "
                         + userId);
-                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
+                vpn.setAlwaysOnPackage(null, false, null);
             }
         }
     }
@@ -843,7 +844,7 @@
             if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    mKeyStore.delete(Credentials.LOCKDOWN_VPN);
+                    mVpnProfileStore.remove(Credentials.LOCKDOWN_VPN);
                     mLockdownEnabled = false;
                     setLockdownTracker(null);
                 } finally {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 9930eac..7375523 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -279,7 +279,7 @@
         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
         mHandler = new MessageHandler(injector.getMessageHandlerLooper());
         mAuthenticatorCache = mInjector.getAccountAuthenticatorCache();
-        mAuthenticatorCache.setListener(this, null /* Handler */);
+        mAuthenticatorCache.setListener(this, mHandler);
 
         sThis.set(this);
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index c971bd2..5ad77a3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -168,6 +168,7 @@
     private int mTaskId;
     private boolean mIsTaskOverlay;
     private boolean mIsLockTask;
+    private boolean mAsync;
     private BroadcastOptions mBroadcastOptions;
 
     final boolean mDumping;
@@ -342,6 +343,7 @@
         mTaskId = INVALID_TASK_ID;
         mIsTaskOverlay = false;
         mIsLockTask = false;
+        mAsync = false;
         mBroadcastOptions = null;
 
         return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {
@@ -406,6 +408,8 @@
                         mBroadcastOptions = BroadcastOptions.makeBasic();
                     }
                     mBroadcastOptions.setBackgroundActivityStartsAllowed(true);
+                } else if (opt.equals("--async")) {
+                    mAsync = true;
                 } else {
                     return false;
                 }
@@ -756,7 +760,9 @@
         mInterface.broadcastIntentWithFeature(null, null, intent, null, receiver, 0, null, null,
                 requiredPermissions, android.app.AppOpsManager.OP_NONE, bundle, true, false,
                 mUserId);
-        receiver.waitForFinish();
+        if (!mAsync) {
+            receiver.waitForFinish();
+        }
         return 0;
     }
 
@@ -3180,13 +3186,17 @@
             pw.println("      Stop a Service.  Options are:");
             pw.println("      --user <USER_ID> | current: Specify which user to run as; if not");
             pw.println("          specified then run as the current user.");
-            pw.println("  broadcast [--user <USER_ID> | all | current] <INTENT>");
+            pw.println("  broadcast [--user <USER_ID> | all | current]");
+            pw.println("          [--receiver-permission <PERMISSION>]");
+            pw.println("          [--allow-background-activity-starts]");
+            pw.println("          [--async] <INTENT>");
             pw.println("      Send a broadcast Intent.  Options are:");
             pw.println("      --user <USER_ID> | all | current: Specify which user to send to; if not");
             pw.println("          specified then send to all users.");
             pw.println("      --receiver-permission <PERMISSION>: Require receiver to hold permission.");
             pw.println("      --allow-background-activity-starts: The receiver may start activities");
             pw.println("          even if in the background.");
+            pw.println("      --async: Send without waiting for the completion of the receiver.");
             pw.println("  instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]");
             pw.println("          [--user <USER_ID> | current]");
             pw.println("          [--no-hidden-api-checks [--no-test-api-access]]");
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 44dcc20..11125dd 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -843,11 +843,15 @@
         public void accessed(int proxyUid, @Nullable String proxyPackageName,
                 @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
                 @OpFlags int flags) {
-            accessed(System.currentTimeMillis(), -1, proxyUid, proxyPackageName,
+            long accessTime = System.currentTimeMillis();
+            accessed(accessTime, -1, proxyUid, proxyPackageName,
                     proxyAttributionTag, uidState, flags);
 
             mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
                     tag, uidState, flags);
+
+            mHistoricalRegistry.mDiscreteRegistry.recordDiscreteAccess(parent.uid,
+                    parent.packageName, parent.op, tag, flags, uidState, accessTime, -1);
         }
 
         /**
@@ -1004,8 +1008,10 @@
                 OpEventProxyInfo proxyCopy = event.getProxy() != null
                         ? new OpEventProxyInfo(event.getProxy()) : null;
 
+                long accessDurationMillis =
+                        SystemClock.elapsedRealtime() - event.getStartElapsedTime();
                 NoteOpEvent finishedEvent = new NoteOpEvent(event.getStartTime(),
-                        SystemClock.elapsedRealtime() - event.getStartElapsedTime(), proxyCopy);
+                        accessDurationMillis, proxyCopy);
                 mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
                         finishedEvent);
 
@@ -1013,6 +1019,10 @@
                         parent.packageName, tag, event.getUidState(),
                         event.getFlags(), finishedEvent.getDuration());
 
+                mHistoricalRegistry.mDiscreteRegistry.recordDiscreteAccess(parent.uid,
+                        parent.packageName, parent.op, tag, event.getFlags(), event.getUidState(),
+                        event.getStartTime(), accessDurationMillis);
+
                 mInProgressStartOpEventPool.release(event);
 
                 if (mInProgressEvents.isEmpty()) {
@@ -2087,8 +2097,8 @@
 
     @Override
     public void getHistoricalOps(int uid, String packageName, String attributionTag,
-            List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
-            int flags, RemoteCallback callback) {
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback) {
         PackageManager pm = mContext.getPackageManager();
 
         ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
@@ -2120,14 +2130,14 @@
 
         // Must not hold the appops lock
         mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
-                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, filter,
-                beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
+                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+                filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
     }
 
     @Override
     public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
-            List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
-            int flags, RemoteCallback callback) {
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback) {
         ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
                 beginTimeMillis, endTimeMillis, flags);
         Objects.requireNonNull(callback, "callback cannot be null");
@@ -2140,7 +2150,7 @@
 
         // Must not hold the appops lock
         mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
-                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray,
+                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
                 filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
     }
 
@@ -4759,6 +4769,7 @@
                 mFile.failWrite(stream);
             }
         }
+        mHistoricalRegistry.mDiscreteRegistry.writeAndClearAccessHistory();
     }
 
     static class Shell extends ShellCommand {
@@ -6115,6 +6126,7 @@
                 "clearHistory");
         // Must not hold the appops lock
         mHistoricalRegistry.clearHistory();
+        mHistoricalRegistry.mDiscreteRegistry.clearHistory();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
new file mode 100644
index 0000000..7699045
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -0,0 +1,616 @@
+/*
+ * 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.appop;
+
+import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_COARSE_LOCATION;
+import static android.app.AppOpsManager.OP_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_FLAG_SELF;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+
+import static java.lang.Math.max;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class manages information about recent accesses to ops for
+ * permission usage timeline.
+ *
+ * The timeline history is kept for limited time (initial default is 24 hours) and
+ * discarded after that.
+ *
+ * Every time state is saved (default is 30 minutes), memory state is dumped to a
+ * new file and memory state is cleared. Files older than time limit are deleted
+ * during the process.
+ *
+ * When request comes in, files are read and requested information is collected
+ * and delivered.
+ */
+
+final class DiscreteRegistry {
+    static final String TIMELINE_FILE_SUFFIX = "tl";
+    private static final String TAG = DiscreteRegistry.class.getSimpleName();
+
+    private static final long TIMELINE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis();
+    private static final String TAG_HISTORY = "h";
+    private static final String ATTR_VERSION = "v";
+    private static final int CURRENT_VERSION = 1;
+
+    private static final String TAG_UID = "u";
+    private static final String ATTR_UID = "ui";
+
+    private static final String TAG_PACKAGE = "p";
+    private static final String ATTR_PACKAGE_NAME = "pn";
+
+    private static final String TAG_OP = "o";
+    private static final String ATTR_OP_ID = "op";
+
+    private static final String TAG_TAG = "a";
+    private static final String ATTR_TAG = "at";
+
+    private static final String TAG_ENTRY = "e";
+    private static final String ATTR_NOTE_TIME = "nt";
+    private static final String ATTR_NOTE_DURATION = "nd";
+    private static final String ATTR_UID_STATE = "us";
+    private static final String ATTR_FLAGS = "f";
+
+    // Lock for read/write access to on disk state
+    private final Object mOnDiskLock = new Object();
+
+    //Lock for read/write access to in memory state
+    private final @NonNull Object mInMemoryLock;
+
+    @GuardedBy("mOnDiskLock")
+    private final File mDiscreteAccessDir;
+
+    @GuardedBy("mInMemoryLock")
+    private DiscreteOps mDiscreteOps;
+
+    DiscreteRegistry(Object inMemoryLock) {
+        mInMemoryLock = inMemoryLock;
+        mDiscreteAccessDir = new File(new File(Environment.getDataSystemDirectory(), "appops"),
+                "discrete");
+        createDiscreteAccessDir();
+        mDiscreteOps = new DiscreteOps();
+    }
+
+    private void createDiscreteAccessDir() {
+        if (!mDiscreteAccessDir.exists()) {
+            if (!mDiscreteAccessDir.mkdirs()) {
+                Slog.e(TAG, "Failed to create DiscreteRegistry directory");
+            }
+            FileUtils.setPermissions(mDiscreteAccessDir.getPath(),
+                    FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1);
+        }
+    }
+
+    void recordDiscreteAccess(int uid, String packageName, int op, @Nullable String attributionTag,
+            @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime,
+            long accessDuration) {
+        if (!isDiscreteOp(op, uid, flags)) {
+            return;
+        }
+        synchronized (mInMemoryLock) {
+            mDiscreteOps.addDiscreteAccess(op, uid, packageName, attributionTag, flags, uidState,
+                    accessTime, accessDuration);
+        }
+    }
+
+    void writeAndClearAccessHistory() {
+        synchronized (mOnDiskLock) {
+            final File[] files = mDiscreteAccessDir.listFiles();
+            if (files != null && files.length > 0) {
+                for (File f : files) {
+                    final String fileName = f.getName();
+                    if (!fileName.endsWith(TIMELINE_FILE_SUFFIX)) {
+                        continue;
+                    }
+                    try {
+                        long timestamp = Long.valueOf(fileName.substring(0,
+                                fileName.length() - TIMELINE_FILE_SUFFIX.length()));
+                        if (Instant.now().minus(TIMELINE_HISTORY_CUTOFF,
+                                ChronoUnit.MILLIS).toEpochMilli() > timestamp) {
+                            f.delete();
+                            Slog.e(TAG, "Deleting file " + fileName);
+
+                        }
+                    } catch (Throwable t) {
+                        Slog.e(TAG, "Error while cleaning timeline files: " + t.getMessage() + " "
+                                + t.getStackTrace());
+                    }
+                }
+            }
+        }
+        DiscreteOps discreteOps;
+        synchronized (mInMemoryLock) {
+            discreteOps = mDiscreteOps;
+            mDiscreteOps = new DiscreteOps();
+        }
+        if (discreteOps.isEmpty()) {
+            return;
+        }
+        long currentTimeStamp = Instant.now().toEpochMilli();
+        try {
+            final File file = new File(mDiscreteAccessDir, currentTimeStamp + TIMELINE_FILE_SUFFIX);
+            discreteOps.writeToFile(file);
+        } catch (Throwable t) {
+            Slog.e(TAG,
+                    "Error writing timeline state: " + t.getMessage() + " "
+                            + Arrays.toString(t.getStackTrace()));
+        }
+    }
+
+    void getHistoricalDiscreteOps(AppOpsManager.HistoricalOps result, long beginTimeMillis,
+            long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
+            @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
+            @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
+        writeAndClearAccessHistory();
+        DiscreteOps discreteOps = new DiscreteOps();
+        readDiscreteOpsFromDisk(discreteOps, beginTimeMillis, endTimeMillis, filter, uidFilter,
+                packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter);
+        discreteOps.applyToHistoricalOps(result);
+        return;
+    }
+
+    private void readDiscreteOpsFromDisk(DiscreteOps discreteOps, long beginTimeMillis,
+            long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
+            @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
+            @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
+        synchronized (mOnDiskLock) {
+            long historyBeginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF,
+                    ChronoUnit.MILLIS).toEpochMilli();
+            if (historyBeginTimeMillis > endTimeMillis) {
+                return;
+            }
+            beginTimeMillis = max(beginTimeMillis, historyBeginTimeMillis);
+
+            final File[] files = mDiscreteAccessDir.listFiles();
+            if (files != null && files.length > 0) {
+                for (File f : files) {
+                    final String fileName = f.getName();
+                    if (!fileName.endsWith(TIMELINE_FILE_SUFFIX)) {
+                        continue;
+                    }
+                    long timestamp = Long.valueOf(fileName.substring(0,
+                            fileName.length() - TIMELINE_FILE_SUFFIX.length()));
+                    if (timestamp < beginTimeMillis) {
+                        continue;
+                    }
+                    discreteOps.readFromFile(f, beginTimeMillis, endTimeMillis, filter, uidFilter,
+                            packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter);
+                }
+            }
+        }
+    }
+
+    void clearHistory() {
+        synchronized (mOnDiskLock) {
+            synchronized (mInMemoryLock) {
+                mDiscreteOps = new DiscreteOps();
+            }
+            FileUtils.deleteContentsAndDir(mDiscreteAccessDir);
+            createDiscreteAccessDir();
+        }
+    }
+
+    public static boolean isDiscreteOp(int op, int uid, @AppOpsManager.OpFlags int flags) {
+        if (!isDiscreteOp(op)) {
+            return false;
+        }
+        if (!isDiscreteUid(uid)) {
+            return false;
+        }
+        if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) == 0) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean isDiscreteOp(int op) {
+        if (op != OP_CAMERA && op != OP_RECORD_AUDIO && op != OP_FINE_LOCATION
+                && op != OP_COARSE_LOCATION) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean isDiscreteUid(int uid) {
+        if (uid < Process.FIRST_APPLICATION_UID) {
+            return false;
+        }
+        return true;
+    }
+
+    private final class DiscreteOps {
+        ArrayMap<Integer, DiscreteUidOps> mUids;
+
+        DiscreteOps() {
+            mUids = new ArrayMap<>();
+        }
+
+        void addDiscreteAccess(int op, int uid, @NonNull String packageName,
+                @Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
+                @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) {
+            getOrCreateDiscreteUidOps(uid).addDiscreteAccess(op, packageName, attributionTag, flags,
+                    uidState, accessTime, accessDuration);
+        }
+
+        private void applyToHistoricalOps(AppOpsManager.HistoricalOps result) {
+            int nUids = mUids.size();
+            for (int i = 0; i < nUids; i++) {
+                mUids.valueAt(i).applyToHistory(result, mUids.keyAt(i));
+            }
+        }
+
+        private void writeToFile(File f) throws Exception {
+            FileOutputStream stream = new FileOutputStream(f);
+            TypedXmlSerializer out = Xml.resolveSerializer(stream);
+
+            out.startDocument(null, true);
+            out.startTag(null, TAG_HISTORY);
+            out.attributeInt(null, ATTR_VERSION, CURRENT_VERSION);
+
+            int nUids = mUids.size();
+            for (int i = 0; i < nUids; i++) {
+                out.startTag(null, TAG_UID);
+                out.attributeInt(null, ATTR_UID, mUids.keyAt(i));
+                mUids.valueAt(i).serialize(out);
+                out.endTag(null, TAG_UID);
+            }
+            out.endTag(null, TAG_HISTORY);
+            out.endDocument();
+            stream.close();
+        }
+
+        private DiscreteUidOps getOrCreateDiscreteUidOps(int uid) {
+            DiscreteUidOps result = mUids.get(uid);
+            if (result == null) {
+                result = new DiscreteUidOps();
+                mUids.put(uid, result);
+            }
+            return result;
+        }
+
+        boolean isEmpty() {
+            return mUids.isEmpty();
+        }
+
+        private void readFromFile(File f, long beginTimeMillis, long endTimeMillis,
+                @AppOpsManager.HistoricalOpsRequestFilter int filter,
+                int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
+                @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
+            try {
+                FileInputStream stream = new FileInputStream(f);
+                TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+                XmlUtils.beginDocument(parser, TAG_HISTORY);
+
+                // We haven't released version 1 and have more detailed
+                // accounting - just nuke the current state
+                final int version = parser.getAttributeInt(null, ATTR_VERSION);
+                if (version != CURRENT_VERSION) {
+                    throw new IllegalStateException("Dropping unsupported discrete history " + f);
+                }
+
+                int depth = parser.getDepth();
+                while (XmlUtils.nextElementWithin(parser, depth)) {
+                    if (TAG_UID.equals(parser.getName())) {
+                        int uid = parser.getAttributeInt(null, ATTR_UID, -1);
+                        if ((filter & FILTER_BY_UID) != 0 && uid != uidFilter) {
+                            continue;
+                        }
+                        getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis,
+                                endTimeMillis, filter, packageNameFilter, opNamesFilter,
+                                attributionTagFilter, flagsFilter);
+                    }
+                }
+            } catch (Throwable t) {
+                Slog.e(TAG, "Failed to read file " + f.getName() + " " + t.getMessage() + " "
+                        + Arrays.toString(t.getStackTrace()));
+            }
+
+        }
+    }
+
+    private final class DiscreteUidOps {
+        ArrayMap<String, DiscretePackageOps> mPackages;
+
+        DiscreteUidOps() {
+            mPackages = new ArrayMap<>();
+        }
+
+        void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag,
+                @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
+                long accessTime, long accessDuration) {
+            getOrCreateDiscretePackageOps(packageName).addDiscreteAccess(op, attributionTag, flags,
+                    uidState, accessTime, accessDuration);
+        }
+
+        private DiscretePackageOps getOrCreateDiscretePackageOps(String packageName) {
+            DiscretePackageOps result = mPackages.get(packageName);
+            if (result == null) {
+                result = new DiscretePackageOps();
+                mPackages.put(packageName, result);
+            }
+            return result;
+        }
+
+        private void applyToHistory(AppOpsManager.HistoricalOps result, int uid) {
+            int nPackages = mPackages.size();
+            for (int i = 0; i < nPackages; i++) {
+                mPackages.valueAt(i).applyToHistory(result, uid, mPackages.keyAt(i));
+            }
+        }
+
+        void serialize(TypedXmlSerializer out) throws Exception {
+            int nPackages = mPackages.size();
+            for (int i = 0; i < nPackages; i++) {
+                out.startTag(null, TAG_PACKAGE);
+                out.attribute(null, ATTR_PACKAGE_NAME, mPackages.keyAt(i));
+                mPackages.valueAt(i).serialize(out);
+                out.endTag(null, TAG_PACKAGE);
+            }
+        }
+
+        void deserialize(TypedXmlPullParser parser, long beginTimeMillis,
+                long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter,
+                @Nullable String packageNameFilter,
+                @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter,
+                @AppOpsManager.OpFlags int flagsFilter) throws Exception {
+            int depth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, depth)) {
+                if (TAG_PACKAGE.equals(parser.getName())) {
+                    String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+                    if ((filter & FILTER_BY_PACKAGE_NAME) != 0
+                            && !packageName.equals(packageNameFilter)) {
+                        continue;
+                    }
+                    getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis,
+                            endTimeMillis, filter, opNamesFilter, attributionTagFilter,
+                            flagsFilter);
+                }
+            }
+        }
+    }
+
+    private final class DiscretePackageOps {
+        ArrayMap<Integer, DiscreteOp> mPackageOps;
+
+        DiscretePackageOps() {
+            mPackageOps = new ArrayMap<>();
+        }
+
+        void addDiscreteAccess(int op, @Nullable String attributionTag,
+                @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
+                long accessTime, long accessDuration) {
+            getOrCreateDiscreteOp(op).addDiscreteAccess(attributionTag, flags, uidState, accessTime,
+                    accessDuration);
+        }
+
+        private DiscreteOp getOrCreateDiscreteOp(int op) {
+            DiscreteOp result = mPackageOps.get(op);
+            if (result == null) {
+                result = new DiscreteOp();
+                mPackageOps.put(op, result);
+            }
+            return result;
+        }
+
+        private void applyToHistory(AppOpsManager.HistoricalOps result, int uid,
+                @NonNull String packageName) {
+            int nPackageOps = mPackageOps.size();
+            for (int i = 0; i < nPackageOps; i++) {
+                mPackageOps.valueAt(i).applyToHistory(result, uid, packageName,
+                        mPackageOps.keyAt(i));
+            }
+        }
+
+        void serialize(TypedXmlSerializer out) throws Exception {
+            int nOps = mPackageOps.size();
+            for (int i = 0; i < nOps; i++) {
+                out.startTag(null, TAG_OP);
+                out.attributeInt(null, ATTR_OP_ID, mPackageOps.keyAt(i));
+                mPackageOps.valueAt(i).serialize(out);
+                out.endTag(null, TAG_OP);
+            }
+        }
+
+        void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis,
+                @AppOpsManager.HistoricalOpsRequestFilter int filter,
+                @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter,
+                @AppOpsManager.OpFlags int flagsFilter) throws Exception {
+            int depth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, depth)) {
+                if (TAG_OP.equals(parser.getName())) {
+                    int op = parser.getAttributeInt(null, ATTR_OP_ID);
+                    if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter,
+                            AppOpsManager.opToPublicName(op))) {
+                        continue;
+                    }
+                    getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis, endTimeMillis,
+                            filter, attributionTagFilter, flagsFilter);
+                }
+            }
+        }
+    }
+
+    private final class DiscreteOp {
+        ArrayMap<String, List<DiscreteOpEvent>> mAttributedOps;
+
+        DiscreteOp() {
+            mAttributedOps = new ArrayMap<>();
+        }
+
+        void addDiscreteAccess(@Nullable String attributionTag,
+                @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
+                long accessTime, long accessDuration) {
+            List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList(
+                    attributionTag);
+            accessTime = Instant.ofEpochMilli(accessTime).truncatedTo(
+                    ChronoUnit.MINUTES).toEpochMilli();
+
+            int nAttributedOps = attributedOps.size();
+            for (int i = nAttributedOps - 1; i >= 0; i--) {
+                DiscreteOpEvent previousOp = attributedOps.get(i);
+                if (previousOp.mNoteTime < accessTime) {
+                    break;
+                }
+                if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) {
+                    return;
+                }
+            }
+            attributedOps.add(new DiscreteOpEvent(accessTime, accessDuration, uidState, flags));
+        }
+
+        private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) {
+            List<DiscreteOpEvent> result = mAttributedOps.get(attributionTag);
+            if (result == null) {
+                result = new ArrayList<>();
+                mAttributedOps.put(attributionTag, result);
+            }
+            return result;
+        }
+
+        private void applyToHistory(AppOpsManager.HistoricalOps result, int uid,
+                @NonNull String packageName, int op) {
+            int nOps = mAttributedOps.size();
+            for (int i = 0; i < nOps; i++) {
+                String tag = mAttributedOps.keyAt(i);
+                List<DiscreteOpEvent> events = mAttributedOps.valueAt(i);
+                int nEvents = events.size();
+                for (int j = 0; j < nEvents; j++) {
+                    DiscreteOpEvent event = events.get(j);
+                    result.addDiscreteAccess(op, uid, packageName, tag, event.mUidState,
+                            event.mOpFlag, event.mNoteTime, event.mNoteDuration);
+                }
+            }
+        }
+
+        void serialize(TypedXmlSerializer out) throws Exception {
+            int nAttributions = mAttributedOps.size();
+            for (int i = 0; i < nAttributions; i++) {
+                out.startTag(null, TAG_TAG);
+                String tag = mAttributedOps.keyAt(i);
+                if (tag != null) {
+                    out.attribute(null, ATTR_TAG, mAttributedOps.keyAt(i));
+                }
+                List<DiscreteOpEvent> ops = mAttributedOps.valueAt(i);
+                int nOps = ops.size();
+                for (int j = 0; j < nOps; j++) {
+                    out.startTag(null, TAG_ENTRY);
+                    ops.get(j).serialize(out);
+                    out.endTag(null, TAG_ENTRY);
+                }
+                out.endTag(null, TAG_TAG);
+            }
+        }
+
+        void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis,
+                @AppOpsManager.HistoricalOpsRequestFilter int filter,
+                @Nullable String attributionTagFilter,
+                @AppOpsManager.OpFlags int flagsFilter) throws Exception {
+            int outerDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                if (TAG_TAG.equals(parser.getName())) {
+                    String attributionTag = parser.getAttributeValue(null, ATTR_TAG);
+                    if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !attributionTag.equals(
+                            attributionTagFilter)) {
+                        continue;
+                    }
+                    List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList(
+                            attributionTag);
+                    int innerDepth = parser.getDepth();
+                    while (XmlUtils.nextElementWithin(parser, innerDepth)) {
+                        if (TAG_ENTRY.equals(parser.getName())) {
+                            long noteTime = parser.getAttributeLong(null, ATTR_NOTE_TIME);
+                            long noteDuration = parser.getAttributeLong(null, ATTR_NOTE_DURATION,
+                                    -1);
+                            int uidState = parser.getAttributeInt(null, ATTR_UID_STATE);
+                            int opFlags = parser.getAttributeInt(null, ATTR_FLAGS);
+                            if ((flagsFilter & opFlags) == 0) {
+                                continue;
+                            }
+                            if ((noteTime + noteDuration < beginTimeMillis
+                                    && noteTime > endTimeMillis)) {
+                                continue;
+                            }
+                            DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration,
+                                    uidState, opFlags);
+                            events.add(event);
+                        }
+                    }
+                    Collections.sort(events, (a, b) -> a.mNoteTime < b.mNoteTime ? -1
+                            : (a.mNoteTime == b.mNoteTime ? 0 : 1));
+                }
+            }
+        }
+    }
+
+    private final class DiscreteOpEvent {
+        final long mNoteTime;
+        final long mNoteDuration;
+        final @AppOpsManager.UidState int mUidState;
+        final @AppOpsManager.OpFlags int mOpFlag;
+
+        DiscreteOpEvent(long noteTime, long noteDuration, @AppOpsManager.UidState int uidState,
+                @AppOpsManager.OpFlags int opFlag) {
+            mNoteTime = noteTime;
+            mNoteDuration = noteDuration;
+            mUidState = uidState;
+            mOpFlag = opFlag;
+        }
+
+        private void serialize(TypedXmlSerializer out) throws Exception {
+            out.attributeLong(null, ATTR_NOTE_TIME, mNoteTime);
+            if (mNoteDuration != -1) {
+                out.attributeLong(null, ATTR_NOTE_DURATION, mNoteDuration);
+            }
+            out.attributeInt(null, ATTR_UID_STATE, mUidState);
+            out.attributeInt(null, ATTR_FLAGS, mOpFlag);
+        }
+    }
+}
+
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 17fd32c..1c43fed 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -19,6 +19,8 @@
 import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
 import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
 import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.HISTORY_FLAG_AGGREGATE;
+import static android.app.AppOpsManager.HISTORY_FLAG_DISCRETE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,6 +32,7 @@
 import android.app.AppOpsManager.HistoricalPackageOps;
 import android.app.AppOpsManager.HistoricalUidOps;
 import android.app.AppOpsManager.OpFlags;
+import android.app.AppOpsManager.OpHistoryFlags;
 import android.app.AppOpsManager.UidState;
 import android.content.ContentResolver;
 import android.database.ContentObserver;
@@ -61,9 +64,7 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
 
-import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -71,7 +72,6 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -85,7 +85,7 @@
 import java.util.concurrent.TimeUnit;
 
 /**
- * This class managers historical app op state. This includes reading, persistence,
+ * This class manages historical app op state. This includes reading, persistence,
  * accounting, querying.
  * <p>
  * The history is kept forever in multiple files. Each file time contains the
@@ -138,6 +138,8 @@
     private static final String PARAMETER_ASSIGNMENT = "=";
     private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
 
+    volatile @NonNull DiscreteRegistry mDiscreteRegistry;
+
     @GuardedBy("mLock")
     private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
 
@@ -199,6 +201,7 @@
 
     HistoricalRegistry(@NonNull Object lock) {
         mInMemoryLock = lock;
+        mDiscreteRegistry = new DiscreteRegistry(lock);
     }
 
     HistoricalRegistry(@NonNull HistoricalRegistry other) {
@@ -352,36 +355,49 @@
         }
     }
 
-    void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
+    void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable String[] opNames,
-            @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis,
-            @OpFlags int flags, @NonNull RemoteCallback callback) {
+            @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter,
+            long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
+            @NonNull RemoteCallback callback) {
         if (!isApiEnabled()) {
             callback.sendResult(new Bundle());
             return;
         }
 
-        synchronized (mOnDiskLock) {
-            synchronized (mInMemoryLock) {
-                if (!isPersistenceInitializedMLocked()) {
-                    Slog.e(LOG_TAG, "Interaction before persistence initialized");
-                    callback.sendResult(new Bundle());
-                    return;
+        final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
+
+        if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) {
+            synchronized (mOnDiskLock) {
+                synchronized (mInMemoryLock) {
+                    if (!isPersistenceInitializedMLocked()) {
+                        Slog.e(LOG_TAG, "Interaction before persistence initialized");
+                        callback.sendResult(new Bundle());
+                        return;
+                    }
+                    mPersistence.collectHistoricalOpsDLocked(result, uid, packageName,
+                            attributionTag,
+                            opNames, filter, beginTimeMillis, endTimeMillis, flags);
+
                 }
-                final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
-                mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, attributionTag,
-                        opNames, filter, beginTimeMillis, endTimeMillis, flags);
-                final Bundle payload = new Bundle();
-                payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
-                callback.sendResult(payload);
             }
         }
+
+        if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) {
+            mDiscreteRegistry.getHistoricalDiscreteOps(result, beginTimeMillis, endTimeMillis,
+                    filter, uid, packageName, opNames, attributionTag,
+                    flags);
+        }
+
+        final Bundle payload = new Bundle();
+        payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
+        callback.sendResult(payload);
     }
 
-    void getHistoricalOps(int uid, @NonNull String packageName, @Nullable String attributionTag,
-            @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
-            long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
-            @NonNull RemoteCallback callback) {
+    void getHistoricalOps(int uid, @Nullable String packageName, @Nullable String attributionTag,
+            @Nullable String[] opNames, @OpHistoryFlags int historyFlags,
+            @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis,
+            @OpFlags int flags, @NonNull RemoteCallback callback) {
         if (!isApiEnabled()) {
             callback.sendResult(new Bundle());
             return;
@@ -392,6 +408,8 @@
             endTimeMillis = currentTimeMillis;
         }
 
+        final Bundle payload = new Bundle();
+
         // Argument times are based off epoch start while our internal store is
         // based off now, so take this into account.
         final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0);
@@ -399,55 +417,63 @@
         final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis,
                 inMemoryAdjEndTimeMillis);
 
-        synchronized (mOnDiskLock) {
-            final List<HistoricalOps> pendingWrites;
-            final HistoricalOps currentOps;
-            boolean collectOpsFromDisk;
-
-            synchronized (mInMemoryLock) {
-                if (!isPersistenceInitializedMLocked()) {
-                    Slog.e(LOG_TAG, "Interaction before persistence initialized");
-                    callback.sendResult(new Bundle());
-                    return;
-                }
-
-                currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis);
-                if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis()
-                        || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
-                    // Some of the current batch falls into the query, so extract that.
-                    final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
-                    currentOpsCopy.filter(uid, packageName, attributionTag, opNames, filter,
-                            inMemoryAdjBeginTimeMillis, inMemoryAdjEndTimeMillis);
-                    result.merge(currentOpsCopy);
-                }
-                pendingWrites = new ArrayList<>(mPendingWrites);
-                mPendingWrites.clear();
-                collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis();
-            }
-
-            // If the query was only for in-memory state - done.
-            if (collectOpsFromDisk) {
-                // If there is a write in flight we need to force it now
-                persistPendingHistory(pendingWrites);
-                // Collect persisted state.
-                final long onDiskAndInMemoryOffsetMillis = currentTimeMillis
-                        - mNextPersistDueTimeMillis + mBaseSnapshotInterval;
-                final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis
-                        - onDiskAndInMemoryOffsetMillis, 0);
-                final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
-                        - onDiskAndInMemoryOffsetMillis, 0);
-                mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, attributionTag,
-                        opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags);
-            }
-
-            // Rebase the result time to be since epoch.
-            result.setBeginAndEndTime(beginTimeMillis, endTimeMillis);
-
-            // Send back the result.
-            final Bundle payload = new Bundle();
-            payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
-            callback.sendResult(payload);
+        if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) {
+            mDiscreteRegistry.getHistoricalDiscreteOps(result, beginTimeMillis, endTimeMillis,
+                    filter, uid, packageName, opNames, attributionTag, flags);
         }
+
+        if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) {
+            synchronized (mOnDiskLock) {
+                final List<HistoricalOps> pendingWrites;
+                final HistoricalOps currentOps;
+                boolean collectOpsFromDisk;
+
+                synchronized (mInMemoryLock) {
+                    if (!isPersistenceInitializedMLocked()) {
+                        Slog.e(LOG_TAG, "Interaction before persistence initialized");
+                        callback.sendResult(new Bundle());
+                        return;
+                    }
+
+                    currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis);
+                    if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis()
+                            || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
+                        // Some of the current batch falls into the query, so extract that.
+                        final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
+                        currentOpsCopy.filter(uid, packageName, attributionTag, opNames,
+                                historyFlags, filter, inMemoryAdjBeginTimeMillis,
+                                inMemoryAdjEndTimeMillis);
+                        result.merge(currentOpsCopy);
+                    }
+                    pendingWrites = new ArrayList<>(mPendingWrites);
+                    mPendingWrites.clear();
+                    collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis();
+                }
+
+                // If the query was only for in-memory state - done.
+                if (collectOpsFromDisk) {
+                    // If there is a write in flight we need to force it now
+                    persistPendingHistory(pendingWrites);
+                    // Collect persisted state.
+                    final long onDiskAndInMemoryOffsetMillis = currentTimeMillis
+                            - mNextPersistDueTimeMillis + mBaseSnapshotInterval;
+                    final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis
+                            - onDiskAndInMemoryOffsetMillis, 0);
+                    final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
+                            - onDiskAndInMemoryOffsetMillis, 0);
+                    mPersistence.collectHistoricalOpsDLocked(result, uid, packageName,
+                            attributionTag,
+                            opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis,
+                            flags);
+                }
+            }
+        }
+        // Rebase the result time to be since epoch.
+        result.setBeginAndEndTime(beginTimeMillis, endTimeMillis);
+
+        // Send back the result.
+        payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
+        callback.sendResult(payload);
     }
 
     void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
@@ -692,6 +718,7 @@
             }
             persistPendingHistory(pendingWrites);
         }
+        mDiscreteRegistry.writeAndClearAccessHistory();
     }
 
     private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 42fcd44..088249e 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -52,6 +52,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -59,6 +60,7 @@
 import android.view.autofill.AutofillManagerInternal;
 import android.widget.Toast;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
@@ -163,6 +165,10 @@
     private static final boolean IS_EMULATOR =
         SystemProperties.getBoolean("ro.kernel.qemu", false);
 
+    // DeviceConfig properties
+    private static final String PROPERTY_SHOW_ACCESS_NOTIFICATIONS = "show_access_notifications";
+    private static final boolean DEFAULT_SHOW_ACCESS_NOTIFICATIONS = true;
+
     private final ActivityManagerInternal mAmInternal;
     private final IUriGrantsManager mUgm;
     private final UriGrantsManagerInternal mUgmInternal;
@@ -176,8 +182,14 @@
     private HostClipboardMonitor mHostClipboardMonitor = null;
     private Thread mHostMonitorThread = null;
 
+    @GuardedBy("mLock")
     private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
 
+    @GuardedBy("mLock")
+    private boolean mShowAccessNotifications = DEFAULT_SHOW_ACCESS_NOTIFICATIONS;
+
+    private final Object mLock = new Object();
+
     /**
      * Instantiates the clipboard.
      */
@@ -204,7 +216,7 @@
                             new ClipData("host clipboard",
                                          new String[]{"text/plain"},
                                          new ClipData.Item(contents));
-                        synchronized(mClipboards) {
+                        synchronized (mLock) {
                             setPrimaryClipInternal(getClipboard(0), clip,
                                     android.os.Process.SYSTEM_UID, null);
                         }
@@ -213,6 +225,10 @@
             mHostMonitorThread = new Thread(mHostClipboardMonitor);
             mHostMonitorThread.start();
         }
+
+        updateConfig();
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CLIPBOARD,
+                getContext().getMainExecutor(), properties -> updateConfig());
     }
 
     @Override
@@ -222,11 +238,18 @@
 
     @Override
     public void onUserStopped(@NonNull TargetUser user) {
-        synchronized (mClipboards) {
+        synchronized (mLock) {
             mClipboards.remove(user.getUserIdentifier());
         }
     }
 
+    private void updateConfig() {
+        synchronized (mLock) {
+            mShowAccessNotifications = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
+                    PROPERTY_SHOW_ACCESS_NOTIFICATIONS, DEFAULT_SHOW_ACCESS_NOTIFICATIONS);
+        }
+    }
+
     private class ListenerInfo {
         final int mUid;
         final String mPackageName;
@@ -472,7 +495,7 @@
     };
 
     private PerUserClipboard getClipboard(@UserIdInt int userId) {
-        synchronized (mClipboards) {
+        synchronized (mLock) {
             PerUserClipboard puc = mClipboards.get(userId);
             if (puc == null) {
                 puc = new PerUserClipboard(userId);
@@ -849,9 +872,10 @@
         if (clipboard.primaryClip == null) {
             return;
         }
-        if (Settings.Global.getInt(getContext().getContentResolver(),
-                "clipboard_access_toast_enabled", 1) == 0) {
-            return;
+        synchronized (mLock) {
+            if (!mShowAccessNotifications) {
+                return;
+            }
         }
         // Don't notify if the app accessing the clipboard is the same as the current owner.
         if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) {
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index 43d9ade..4f6b530 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -19,6 +19,8 @@
 import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
+import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
 import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
 import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
 import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
@@ -147,17 +149,18 @@
     }
 
     public static class PrivateDnsValidationUpdate {
-        final public int netId;
-        final public InetAddress ipAddress;
-        final public String hostname;
-        final public boolean validated;
+        public final int netId;
+        public final InetAddress ipAddress;
+        public final String hostname;
+        // Refer to IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_*.
+        public final int validationResult;
 
         public PrivateDnsValidationUpdate(int netId, InetAddress ipAddress,
-                String hostname, boolean validated) {
+                String hostname, int validationResult) {
             this.netId = netId;
             this.ipAddress = ipAddress;
             this.hostname = hostname;
-            this.validated = validated;
+            this.validationResult = validationResult;
         }
     }
 
@@ -216,10 +219,13 @@
             if (!mValidationMap.containsKey(p)) {
                 return;
             }
-            if (update.validated) {
+            if (update.validationResult == VALIDATION_RESULT_SUCCESS) {
                 mValidationMap.put(p, ValidationStatus.SUCCEEDED);
-            } else {
+            } else if (update.validationResult == VALIDATION_RESULT_FAILURE) {
                 mValidationMap.put(p, ValidationStatus.FAILED);
+            } else {
+                Log.e(TAG, "Unknown private dns validation operation="
+                        + update.validationResult);
             }
         }
 
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 67f495a..2e61ae1 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -101,7 +101,12 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.security.Credentials;
-import android.security.KeyStore;
+import android.security.KeyStore2;
+import android.security.keystore.AndroidKeyStoreProvider;
+import android.security.keystore.KeyProperties;
+import android.system.keystore2.Domain;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyPermission;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
@@ -132,6 +137,12 @@
 import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -157,6 +168,7 @@
     private static final String TAG = "Vpn";
     private static final String VPN_PROVIDER_NAME_BASE = "VpnNetworkProvider:";
     private static final boolean LOGD = true;
+    private static final String ANDROID_KEYSTORE_PROVIDER = "AndroidKeyStore";
 
     // Length of time (in milliseconds) that an app hosting an always-on VPN is placed on
     // the device idle allowlist during service launch and VPN bootstrap.
@@ -216,6 +228,13 @@
     private final Ikev2SessionCreator mIkev2SessionCreator;
     private final UserManager mUserManager;
 
+    private final VpnProfileStore mVpnProfileStore;
+
+    @VisibleForTesting
+    VpnProfileStore getVpnProfileStore() {
+        return mVpnProfileStore;
+    }
+
     /**
      * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
      * only applies to {@link VpnService} connections.
@@ -393,24 +412,25 @@
     }
 
     public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd,
-            @UserIdInt int userId, @NonNull KeyStore keyStore) {
-        this(looper, context, new Dependencies(), netService, netd, userId, keyStore,
+            @UserIdInt int userId, VpnProfileStore vpnProfileStore) {
+        this(looper, context, new Dependencies(), netService, netd, userId, vpnProfileStore,
                 new SystemServices(context), new Ikev2SessionCreator());
     }
 
     @VisibleForTesting
     public Vpn(Looper looper, Context context, Dependencies deps,
             INetworkManagementService netService, INetd netd, @UserIdInt int userId,
-            @NonNull KeyStore keyStore) {
-        this(looper, context, deps, netService, netd, userId, keyStore,
+            VpnProfileStore vpnProfileStore) {
+        this(looper, context, deps, netService, netd, userId, vpnProfileStore,
                 new SystemServices(context), new Ikev2SessionCreator());
     }
 
     @VisibleForTesting
     protected Vpn(Looper looper, Context context, Dependencies deps,
             INetworkManagementService netService, INetd netd,
-            int userId, @NonNull KeyStore keyStore, SystemServices systemServices,
+            int userId, VpnProfileStore vpnProfileStore, SystemServices systemServices,
             Ikev2SessionCreator ikev2SessionCreator) {
+        mVpnProfileStore = vpnProfileStore;
         mContext = context;
         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
         mUserIdContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
@@ -446,7 +466,7 @@
         mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
         mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
 
-        loadAlwaysOnPackage(keyStore);
+        loadAlwaysOnPackage();
     }
 
     /**
@@ -567,11 +587,9 @@
      * </ul>
      *
      * @param packageName the canonical package name of the VPN app
-     * @param keyStore the keystore instance to use for checking if the app has a Platform VPN
-     *     profile installed.
      * @return {@code true} if and only if the VPN app exists and supports always-on mode
      */
-    public boolean isAlwaysOnPackageSupported(String packageName, @NonNull KeyStore keyStore) {
+    public boolean isAlwaysOnPackageSupported(String packageName) {
         enforceSettingsPermission();
 
         if (packageName == null) {
@@ -580,7 +598,7 @@
 
         final long oldId = Binder.clearCallingIdentity();
         try {
-            if (getVpnProfilePrivileged(packageName, keyStore) != null) {
+            if (getVpnProfilePrivileged(packageName) != null) {
                 return true;
             }
         } finally {
@@ -632,17 +650,15 @@
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
      * @param lockdownAllowlist packages to be allowed from lockdown.
-     * @param keyStore the Keystore instance to use for checking of PlatformVpnProfile(s)
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     public synchronized boolean setAlwaysOnPackage(
             @Nullable String packageName,
             boolean lockdown,
-            @Nullable List<String> lockdownAllowlist,
-            @NonNull KeyStore keyStore) {
+            @Nullable List<String> lockdownAllowlist) {
         enforceControlPermissionOrInternalCaller();
 
-        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist, keyStore)) {
+        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist)) {
             saveAlwaysOnPackage();
             return true;
         }
@@ -659,13 +675,12 @@
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
      * @param lockdownAllowlist packages to be allowed to bypass lockdown. This is only used if
      *     {@code lockdown} is {@code true}. Packages must not contain commas.
-     * @param keyStore the system keystore instance to check for profiles
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     @GuardedBy("this")
     private boolean setAlwaysOnPackageInternal(
             @Nullable String packageName, boolean lockdown,
-            @Nullable List<String> lockdownAllowlist, @NonNull KeyStore keyStore) {
+            @Nullable List<String> lockdownAllowlist) {
         if (VpnConfig.LEGACY_VPN.equals(packageName)) {
             Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
             return false;
@@ -684,7 +699,7 @@
             final VpnProfile profile;
             final long oldId = Binder.clearCallingIdentity();
             try {
-                profile = getVpnProfilePrivileged(packageName, keyStore);
+                profile = getVpnProfilePrivileged(packageName);
             } finally {
                 Binder.restoreCallingIdentity(oldId);
             }
@@ -759,7 +774,7 @@
 
     /** Load the always-on package and lockdown config from Settings. */
     @GuardedBy("this")
-    private void loadAlwaysOnPackage(@NonNull KeyStore keyStore) {
+    private void loadAlwaysOnPackage() {
         final long token = Binder.clearCallingIdentity();
         try {
             final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
@@ -771,7 +786,7 @@
             final List<String> allowedPackages = TextUtils.isEmpty(allowlistString)
                     ? Collections.emptyList() : Arrays.asList(allowlistString.split(","));
             setAlwaysOnPackageInternal(
-                    alwaysOnPackage, alwaysOnLockdown, allowedPackages, keyStore);
+                    alwaysOnPackage, alwaysOnLockdown, allowedPackages);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -780,11 +795,10 @@
     /**
      * Starts the currently selected always-on VPN
      *
-     * @param keyStore the keyStore instance for looking up PlatformVpnProfile(s)
      * @return {@code true} if the service was started, the service was already connected, or there
      *     was no always-on VPN to start. {@code false} otherwise.
      */
-    public boolean startAlwaysOnVpn(@NonNull KeyStore keyStore) {
+    public boolean startAlwaysOnVpn() {
         final String alwaysOnPackage;
         synchronized (this) {
             alwaysOnPackage = getAlwaysOnPackage();
@@ -793,8 +807,8 @@
                 return true;
             }
             // Remove always-on VPN if it's not supported.
-            if (!isAlwaysOnPackageSupported(alwaysOnPackage, keyStore)) {
-                setAlwaysOnPackage(null, false, null, keyStore);
+            if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
+                setAlwaysOnPackage(null, false, null);
                 return false;
             }
             // Skip if the service is already established. This isn't bulletproof: it's not bound
@@ -808,10 +822,9 @@
         final long oldId = Binder.clearCallingIdentity();
         try {
             // Prefer VPN profiles, if any exist.
-            VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore);
+            VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage);
             if (profile != null) {
-                startVpnProfilePrivileged(profile, alwaysOnPackage,
-                        null /* keyStore for private key retrieval - unneeded */);
+                startVpnProfilePrivileged(profile, alwaysOnPackage);
 
                 // If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was
                 // correctly parsed, and the VPN has started running in a different thread. The only
@@ -2013,27 +2026,83 @@
      * secondary thread to perform connection work, returning quickly.
      *
      * Should only be called to respond to Binder requests as this enforces caller permission. Use
-     * {@link #startLegacyVpnPrivileged(VpnProfile, KeyStore, Network, LinkProperties)} to skip the
+     * {@link #startLegacyVpnPrivileged(VpnProfile, Network, LinkProperties)} to skip the
      * permission check only when the caller is trusted (or the call is initiated by the system).
      */
-    public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, @Nullable Network underlying,
+    public void startLegacyVpn(VpnProfile profile, @Nullable Network underlying,
             LinkProperties egress) {
         enforceControlPermission();
         final long token = Binder.clearCallingIdentity();
         try {
-            startLegacyVpnPrivileged(profile, keyStore, underlying, egress);
+            startLegacyVpnPrivileged(profile, underlying, egress);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
+    private String makeKeystoreEngineGrantString(String alias) {
+        if (alias == null) {
+            return null;
+        }
+        // If Keystore 2.0 is not enabled the legacy private key prefix is used.
+        if (!AndroidKeyStoreProvider.isKeystore2Enabled()) {
+            return Credentials.USER_PRIVATE_KEY + alias;
+        }
+        final KeyStore2 keystore2 = KeyStore2.getInstance();
+
+        KeyDescriptor key = new KeyDescriptor();
+        key.domain = Domain.APP;
+        key.nspace = KeyProperties.NAMESPACE_APPLICATION;
+        key.alias = alias;
+        key.blob = null;
+
+        final int grantAccessVector = KeyPermission.USE | KeyPermission.GET_INFO;
+
+        try {
+            // The native vpn daemon is running as VPN_UID. This tells Keystore 2.0
+            // to allow a process running with this UID to access the key designated by
+            // the KeyDescriptor `key`. `grant` returns a new KeyDescriptor with a grant
+            // identifier. This identifier needs to be communicated to the vpn daemon.
+            key = keystore2.grant(key, android.os.Process.VPN_UID, grantAccessVector);
+        } catch (android.security.KeyStoreException e) {
+            Log.e(TAG, "Failed to get grant for keystore key.", e);
+            throw new IllegalStateException("Failed to get grant for keystore key.", e);
+        }
+
+        // Turn the grant identifier into a string as understood by the keystore boringssl engine
+        // in system/security/keystore-engine.
+        return KeyStore2.makeKeystoreEngineGrantString(key.nspace);
+    }
+
+    private String getCaCertificateFromKeystoreAsPem(@NonNull KeyStore keystore,
+            @NonNull String alias)
+            throws KeyStoreException, IOException, CertificateEncodingException {
+        if (keystore.isCertificateEntry(alias)) {
+            final Certificate cert = keystore.getCertificate(alias);
+            if (cert == null) return null;
+            return new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8);
+        } else {
+            final Certificate[] certs = keystore.getCertificateChain(alias);
+            // If there is none or one entry it means there is no CA entry associated with this
+            // alias.
+            if (certs == null || certs.length <= 1) {
+                return null;
+            }
+            // If this is not a (pure) certificate entry, then there is a user certificate which
+            // will be included at the beginning of the certificate chain. But the caller of this
+            // function does not expect this certificate to be included, so we cut it off.
+            return new String(Credentials.convertToPem(
+                    Arrays.copyOfRange(certs, 1, certs.length)), StandardCharsets.UTF_8);
+        }
+    }
+
     /**
-     * Like {@link #startLegacyVpn(VpnProfile, KeyStore, Network, LinkProperties)}, but does not
+     * Like {@link #startLegacyVpn(VpnProfile, Network, LinkProperties)}, but does not
      * check permissions under the assumption that the caller is the system.
      *
      * Callers are responsible for checking permissions if needed.
      */
-    public void startLegacyVpnPrivileged(VpnProfile profile, KeyStore keyStore,
+    public void startLegacyVpnPrivileged(VpnProfile profile,
             @Nullable Network underlying, @NonNull LinkProperties egress) {
         UserInfo user = mUserManager.getUserInfo(mUserId);
         if (user.isRestricted() || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN,
@@ -2050,18 +2119,27 @@
         String userCert = "";
         String caCert = "";
         String serverCert = "";
-        if (!profile.ipsecUserCert.isEmpty()) {
-            privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
-            byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
-            userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
-        }
-        if (!profile.ipsecCaCert.isEmpty()) {
-            byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
-            caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
-        }
-        if (!profile.ipsecServerCert.isEmpty()) {
-            byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
-            serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
+
+        try {
+            final KeyStore keystore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER);
+            keystore.load(null);
+            if (!profile.ipsecUserCert.isEmpty()) {
+                privateKey = profile.ipsecUserCert;
+                final Certificate cert = keystore.getCertificate(profile.ipsecUserCert);
+                userCert = (cert == null) ? null
+                         : new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8);
+            }
+            if (!profile.ipsecCaCert.isEmpty()) {
+                caCert = getCaCertificateFromKeystoreAsPem(keystore, profile.ipsecCaCert);
+            }
+            if (!profile.ipsecServerCert.isEmpty()) {
+                final Certificate cert = keystore.getCertificate(profile.ipsecServerCert);
+                serverCert = (cert == null) ? null
+                        : new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8);
+            }
+        } catch (CertificateException | KeyStoreException | IOException
+                | NoSuchAlgorithmException e) {
+            throw new IllegalStateException("Failed to load credentials from AndroidKeyStore", e);
         }
         if (userCert == null || caCert == null || serverCert == null) {
             throw new IllegalStateException("Cannot load credentials");
@@ -2082,7 +2160,7 @@
 
                 // Start VPN profile
                 profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS);
-                startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
+                startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN);
                 return;
             case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
                 // Ikev2VpnProfiles expect a base64-encoded preshared key.
@@ -2091,7 +2169,7 @@
 
                 // Start VPN profile
                 profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS);
-                startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
+                startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN);
                 return;
             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
                 racoon = new String[] {
@@ -2101,8 +2179,8 @@
                 break;
             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
                 racoon = new String[] {
-                    iface, profile.server, "udprsa", privateKey, userCert,
-                    caCert, serverCert, "1701",
+                    iface, profile.server, "udprsa", makeKeystoreEngineGrantString(privateKey),
+                    userCert, caCert, serverCert, "1701",
                 };
                 break;
             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
@@ -2113,8 +2191,8 @@
                 break;
             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
                 racoon = new String[] {
-                    iface, profile.server, "xauthrsa", privateKey, userCert,
-                    caCert, serverCert, profile.username, profile.password, "", gateway,
+                    iface, profile.server, "xauthrsa", makeKeystoreEngineGrantString(privateKey),
+                    userCert, caCert, serverCert, profile.username, profile.password, "", gateway,
                 };
                 break;
             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
@@ -3049,14 +3127,12 @@
      *
      * @param packageName the package name of the app provisioning this profile
      * @param profile the profile to be stored and provisioned
-     * @param keyStore the System keystore instance to save VPN profiles
      * @returns whether or not the app has already been granted user consent
      */
     public synchronized boolean provisionVpnProfile(
-            @NonNull String packageName, @NonNull VpnProfile profile, @NonNull KeyStore keyStore) {
+            @NonNull String packageName, @NonNull VpnProfile profile) {
         checkNotNull(packageName, "No package name provided");
         checkNotNull(profile, "No profile provided");
-        checkNotNull(keyStore, "KeyStore missing");
 
         verifyCallingUidAndPackage(packageName);
         enforceNotRestrictedUser();
@@ -3075,11 +3151,9 @@
         // Permissions checked during startVpnProfile()
         Binder.withCleanCallingIdentity(
                 () -> {
-                    keyStore.put(
+                    getVpnProfileStore().put(
                             getProfileNameForPackage(packageName),
-                            encodedProfile,
-                            Process.SYSTEM_UID,
-                            0 /* flags */);
+                            encodedProfile);
                 });
 
         // TODO: if package has CONTROL_VPN, grant the ACTIVATE_PLATFORM_VPN appop.
@@ -3097,12 +3171,10 @@
      * Deletes an app-provisioned VPN profile.
      *
      * @param packageName the package name of the app provisioning this profile
-     * @param keyStore the System keystore instance to save VPN profiles
      */
     public synchronized void deleteVpnProfile(
-            @NonNull String packageName, @NonNull KeyStore keyStore) {
+            @NonNull String packageName) {
         checkNotNull(packageName, "No package name provided");
-        checkNotNull(keyStore, "KeyStore missing");
 
         verifyCallingUidAndPackage(packageName);
         enforceNotRestrictedUser();
@@ -3114,13 +3186,13 @@
                     if (isCurrentIkev2VpnLocked(packageName)) {
                         if (mAlwaysOn) {
                             // Will transitively call prepareInternal(VpnConfig.LEGACY_VPN).
-                            setAlwaysOnPackage(null, false, null, keyStore);
+                            setAlwaysOnPackage(null, false, null);
                         } else {
                             prepareInternal(VpnConfig.LEGACY_VPN);
                         }
                     }
 
-                    keyStore.delete(getProfileNameForPackage(packageName), Process.SYSTEM_UID);
+                    getVpnProfileStore().remove(getProfileNameForPackage(packageName));
                 });
     }
 
@@ -3132,13 +3204,13 @@
      */
     @VisibleForTesting
     @Nullable
-    VpnProfile getVpnProfilePrivileged(@NonNull String packageName, @NonNull KeyStore keyStore) {
+    VpnProfile getVpnProfilePrivileged(@NonNull String packageName) {
         if (!mDeps.isCallerSystem()) {
             Log.wtf(TAG, "getVpnProfilePrivileged called as non-System UID ");
             return null;
         }
 
-        final byte[] encoded = keyStore.get(getProfileNameForPackage(packageName));
+        final byte[] encoded = getVpnProfileStore().get(getProfileNameForPackage(packageName));
         if (encoded == null) return null;
 
         return VpnProfile.decode("" /* Key unused */, encoded);
@@ -3152,12 +3224,10 @@
      * will not match during appop checks.
      *
      * @param packageName the package name of the app provisioning this profile
-     * @param keyStore the System keystore instance to retrieve VPN profiles
      */
     public synchronized void startVpnProfile(
-            @NonNull String packageName, @NonNull KeyStore keyStore) {
+            @NonNull String packageName) {
         checkNotNull(packageName, "No package name provided");
-        checkNotNull(keyStore, "KeyStore missing");
 
         enforceNotRestrictedUser();
 
@@ -3168,18 +3238,17 @@
 
         Binder.withCleanCallingIdentity(
                 () -> {
-                    final VpnProfile profile = getVpnProfilePrivileged(packageName, keyStore);
+                    final VpnProfile profile = getVpnProfilePrivileged(packageName);
                     if (profile == null) {
                         throw new IllegalArgumentException("No profile found for " + packageName);
                     }
 
-                    startVpnProfilePrivileged(profile, packageName,
-                            null /* keyStore for private key retrieval - unneeded */);
+                    startVpnProfilePrivileged(profile, packageName);
                 });
     }
 
     private synchronized void startVpnProfilePrivileged(
-            @NonNull VpnProfile profile, @NonNull String packageName, @Nullable KeyStore keyStore) {
+            @NonNull VpnProfile profile, @NonNull String packageName) {
         // Make sure VPN is prepared. This method can be called by user apps via startVpnProfile(),
         // by the Setting app via startLegacyVpn(), or by ConnectivityService via
         // startAlwaysOnVpn(), so this is the common place to prepare the VPN. This also has the
@@ -3210,7 +3279,7 @@
                 case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
                 case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
                     mVpnRunner =
-                            new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile, keyStore));
+                            new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
                     mVpnRunner.start();
                     break;
                 default:
@@ -3218,7 +3287,7 @@
                     Log.d(TAG, "Unknown VPN profile type: " + profile.type);
                     break;
             }
-        } catch (IOException | GeneralSecurityException e) {
+        } catch (GeneralSecurityException e) {
             // Reset mConfig
             mConfig = null;
 
diff --git a/services/core/java/com/android/server/connectivity/VpnProfileStore.java b/services/core/java/com/android/server/connectivity/VpnProfileStore.java
new file mode 100644
index 0000000..2f8aebf
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/VpnProfileStore.java
@@ -0,0 +1,77 @@
+/*
+ * 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.connectivity;
+
+import android.annotation.NonNull;
+import android.security.LegacyVpnProfileStore;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Mockable indirection to the actual profile store.
+ * @hide
+ */
+public class VpnProfileStore {
+    /**
+     * Stores the profile under the alias in the profile database. Existing profiles by the
+     * same name will be replaced.
+     * @param alias The name of the profile
+     * @param profile The profile.
+     * @return true if the profile was successfully added. False otherwise.
+     * @hide
+     */
+    @VisibleForTesting
+    public boolean put(@NonNull String alias, @NonNull byte[] profile) {
+        return LegacyVpnProfileStore.put(alias, profile);
+    }
+
+    /**
+     * Retrieves a profile by the name alias from the profile database.
+     * @param alias Name of the profile to retrieve.
+     * @return The unstructured blob, that is the profile that was stored using
+     *         LegacyVpnProfileStore#put or with
+     *         android.security.Keystore.put(Credentials.VPN + alias).
+     *         Returns null if no profile was found.
+     * @hide
+     */
+    @VisibleForTesting
+    public byte[] get(@NonNull String alias) {
+        return LegacyVpnProfileStore.get(alias);
+    }
+
+    /**
+     * Removes a profile by the name alias from the profile database.
+     * @param alias Name of the profile to be removed.
+     * @return True if a profile was removed. False if no such profile was found.
+     * @hide
+     */
+    @VisibleForTesting
+    public boolean remove(@NonNull String alias) {
+        return LegacyVpnProfileStore.remove(alias);
+    }
+
+    /**
+     * Lists the vpn profiles stored in the database.
+     * @return An array of strings representing the aliases stored in the profile database.
+     *         The return value may be empty but never null.
+     * @hide
+     */
+    @VisibleForTesting
+    public @NonNull String[] list(@NonNull String prefix) {
+        return LegacyVpnProfileStore.list(prefix);
+    }
+}
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index d4556ed..1acd5d0 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -16,17 +16,26 @@
 
 package com.android.server.display;
 
-import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager;
-import android.text.TextUtils;
+import android.os.Environment;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.DisplayAddress;
 
+import com.android.server.display.config.layout.Layouts;
+import com.android.server.display.config.layout.XmlParser;
 import com.android.server.display.layout.Layout;
 
-import java.util.Arrays;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.datatype.DatatypeConfigurationException;
 
 /**
  * Mapping from device states into {@link Layout}s. This allows us to map device
@@ -39,15 +48,14 @@
 
     public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
 
-    // TODO - b/168208162 - Remove these when we check in static definitions for layouts
-    public static final int STATE_FOLDED = 100;
-    public static final int STATE_UNFOLDED = 101;
+    private static final String CONFIG_FILE_PATH =
+            "etc/displayconfig/display_layout_configuration.xml";
 
     private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
 
-    DeviceStateToLayoutMap(Context context) {
-        mLayoutMap.append(STATE_DEFAULT, new Layout());
-        loadFoldedDisplayConfig(context);
+    DeviceStateToLayoutMap() {
+        loadLayoutsFromConfig();
+        createLayout(STATE_DEFAULT);
     }
 
     public void dumpLocked(IndentingPrintWriter ipw) {
@@ -68,7 +76,7 @@
         return layout;
     }
 
-    private Layout create(int state) {
+    private Layout createLayout(int state) {
         if (mLayoutMap.contains(state)) {
             Slog.e(TAG, "Attempted to create a second layout for state " + state);
             return null;
@@ -79,43 +87,37 @@
         return layout;
     }
 
-    private void loadFoldedDisplayConfig(Context context) {
-        final String[] strDisplayIds = context.getResources().getStringArray(
-                com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
+    /**
+     * Reads display-layout-configuration files to get the layouts to use for this device.
+     */
+    private void loadLayoutsFromConfig() {
+        final File configFile = Environment.buildPath(
+                Environment.getVendorDirectory(), CONFIG_FILE_PATH);
 
-        if (strDisplayIds.length != 2 || TextUtils.isEmpty(strDisplayIds[0])
-                || TextUtils.isEmpty(strDisplayIds[1])) {
-            Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(strDisplayIds)
-                    + "]");
+        if (!configFile.exists()) {
             return;
         }
 
-        final long[] displayIds;
-        try {
-            displayIds = new long[] {
-                Long.parseLong(strDisplayIds[0]),
-                Long.parseLong(strDisplayIds[1])
-            };
-        } catch (NumberFormatException nfe) {
-            Slog.w(TAG, "Folded display config non numerical: " + Arrays.toString(strDisplayIds));
-            return;
+        Slog.i(TAG, "Loading display layouts from " + configFile);
+        try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
+            final Layouts layouts = XmlParser.read(in);
+            if (layouts == null) {
+                Slog.i(TAG, "Display layout config not found: " + configFile);
+                return;
+            }
+            for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) {
+                final int state = l.getState().intValue();
+                final Layout layout = createLayout(state);
+                for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
+                    layout.createDisplayLocked(
+                            DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
+                            d.getIsDefault(),
+                            d.getEnabled());
+                }
+            }
+        } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
+            Slog.e(TAG, "Encountered an error while reading/parsing display layout config file: "
+                    + configFile, e);
         }
-
-        final int[] foldedDeviceStates = context.getResources().getIntArray(
-                com.android.internal.R.array.config_foldedDeviceStates);
-        // Only add folded states if folded state config is not empty
-        if (foldedDeviceStates.length == 0) {
-            return;
-        }
-
-        // Create the folded state layout
-        final Layout foldedLayout = create(STATE_FOLDED);
-        foldedLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/);
-
-        // Create the unfolded state layout
-        final Layout unfoldedLayout = create(STATE_UNFOLDED);
-        unfoldedLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/);
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java
index 663883a..2dcd5cc 100644
--- a/services/core/java/com/android/server/display/DisplayGroup.java
+++ b/services/core/java/com/android/server/display/DisplayGroup.java
@@ -30,6 +30,8 @@
     private final List<LogicalDisplay> mDisplays = new ArrayList<>();
     private final int mGroupId;
 
+    private int mChangeCount;
+
     DisplayGroup(int groupId) {
         mGroupId = groupId;
     }
@@ -45,11 +47,16 @@
      * @param display the {@link LogicalDisplay} to add to the Group
      */
     void addDisplayLocked(LogicalDisplay display) {
-        if (!mDisplays.contains(display)) {
+        if (!containsLocked(display)) {
+            mChangeCount++;
             mDisplays.add(display);
         }
     }
 
+    boolean containsLocked(LogicalDisplay display) {
+        return mDisplays.contains(display);
+    }
+
     /**
      * Removes the provided {@code display} from the Group.
      *
@@ -57,6 +64,7 @@
      * @return {@code true} if the {@code display} was removed; otherwise {@code false}
      */
     boolean removeDisplayLocked(LogicalDisplay display) {
+        mChangeCount++;
         return mDisplays.remove(display);
     }
 
@@ -65,6 +73,11 @@
         return mDisplays.isEmpty();
     }
 
+    /** Returns a count of the changes made to this display group. */
+    int getChangeCountLocked() {
+        return mChangeCount;
+    }
+
     /** Returns the number of {@link LogicalDisplay LogicalDisplays} in the Group. */
     int getSizeLocked() {
         return mDisplays.size();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 52149ee..174d4b2 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -423,7 +423,7 @@
         mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
         mUiHandler = UiThread.getHandler();
         mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
-        mLogicalDisplayMapper = new LogicalDisplayMapper(context, mDisplayDeviceRepo,
+        mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo,
                 new LogicalDisplayListener());
         mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
         mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
@@ -485,13 +485,13 @@
             synchronized (mSyncRoot) {
                 long timeout = SystemClock.uptimeMillis()
                         + mInjector.getDefaultDisplayDelayTimeout();
-                while (mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY) == null
+                while (mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY) == null
                         || mVirtualDisplayAdapter == null) {
                     long delay = timeout - SystemClock.uptimeMillis();
                     if (delay <= 0) {
                         throw new RuntimeException("Timeout waiting for default display "
                                 + "to be initialized. DefaultDisplay="
-                                + mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY)
+                                + mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)
                                 + ", mVirtualDisplayAdapter=" + mVirtualDisplayAdapter);
                     }
                     if (DEBUG) {
@@ -549,7 +549,7 @@
             mSystemReady = true;
             // Just in case the top inset changed before the system was ready. At this point, any
             // relevant configuration should be in place.
-            recordTopInsetLocked(mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY));
+            recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY));
 
             updateSettingsLocked();
         }
@@ -617,7 +617,7 @@
     @VisibleForTesting
     void setDisplayInfoOverrideFromWindowManagerInternal(int displayId, DisplayInfo info) {
         synchronized (mSyncRoot) {
-            LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (display != null) {
                 if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
                     handleLogicalDisplayChangedLocked(display);
@@ -632,7 +632,7 @@
      */
     private void getNonOverrideDisplayInfoInternal(int displayId, DisplayInfo outInfo) {
         synchronized (mSyncRoot) {
-            final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (display != null) {
                 display.getNonOverrideDisplayInfoLocked(outInfo);
             }
@@ -691,8 +691,8 @@
 
             mDisplayStates.setValueAt(index, state);
             mDisplayBrightnesses.setValueAt(index, brightnessState);
-            runnable = updateDisplayStateLocked(
-                    mLogicalDisplayMapper.getLocked(displayId).getPrimaryDisplayDeviceLocked());
+            runnable = updateDisplayStateLocked(mLogicalDisplayMapper.getDisplayLocked(displayId)
+                    .getPrimaryDisplayDeviceLocked());
         }
 
         // Setting the display power state can take hundreds of milliseconds
@@ -803,9 +803,9 @@
 
     private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
         synchronized (mSyncRoot) {
-            LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (display != null) {
-                DisplayInfo info =
+                final DisplayInfo info =
                         getDisplayInfoForFrameRateOverride(display.getFrameRateOverrides(),
                                 display.getDisplayInfoLocked(), callingUid);
                 if (info.hasAccess(callingUid)
@@ -952,7 +952,7 @@
 
     private void requestColorModeInternal(int displayId, int colorMode) {
         synchronized (mSyncRoot) {
-            LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (display != null &&
                     display.getRequestedColorModeLocked() != colorMode) {
                 display.setRequestedColorModeLocked(colorMode);
@@ -989,7 +989,7 @@
             mDisplayDeviceRepo.onDisplayDeviceEvent(device,
                     DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
 
-            LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
             if (display != null) {
                 return display.getDisplayIdLocked();
             }
@@ -1178,7 +1178,10 @@
 
     private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) {
         final int displayId = display.getDisplayIdLocked();
-        mDisplayPowerControllers.removeReturnOld(displayId).stop();
+        final DisplayPowerController dpc = mDisplayPowerControllers.removeReturnOld(displayId);
+        if (dpc != null) {
+            dpc.stop();
+        }
         mDisplayStates.delete(displayId);
         mDisplayBrightnesses.delete(displayId);
         DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
@@ -1200,10 +1203,7 @@
         // by the display power controller (if known).
         DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
         if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
-            // TODO - b/170498827 The rules regarding what display state to apply to each
-            // display will depend on the configuration/mapping of logical displays.
-            // Clean up LogicalDisplay.isEnabled() mechanism once this is fixed.
-            final LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
             final int state;
             final int displayId = display.getDisplayIdLocked();
 
@@ -1364,7 +1364,7 @@
             float requestedRefreshRate, int requestedModeId, boolean preferMinimalPostProcessing,
             boolean inTraversal) {
         synchronized (mSyncRoot) {
-            LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (display == null) {
                 return;
             }
@@ -1406,7 +1406,7 @@
 
     private void setDisplayOffsetsInternal(int displayId, int x, int y) {
         synchronized (mSyncRoot) {
-            LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (display == null) {
                 return;
             }
@@ -1424,7 +1424,7 @@
 
     private void setDisplayScalingDisabledInternal(int displayId, boolean disable) {
         synchronized (mSyncRoot) {
-            final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (display == null) {
                 return;
             }
@@ -1460,7 +1460,7 @@
     @Nullable
     private IBinder getDisplayToken(int displayId) {
         synchronized (mSyncRoot) {
-            final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (display != null) {
                 final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
                 if (device != null) {
@@ -1478,7 +1478,7 @@
             if (token == null) {
                 return null;
             }
-            final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getLocked(displayId);
+            final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (logicalDisplay == null) {
                 return null;
             }
@@ -1611,15 +1611,16 @@
 
         // Find the logical display that the display device is showing.
         // Certain displays only ever show their own content.
-        LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
+        LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
         if (!ownContent) {
             if (display != null && !display.hasContentLocked()) {
                 // If the display does not have any content of its own, then
                 // automatically mirror the requested logical display contents if possible.
-                display = mLogicalDisplayMapper.getLocked(device.getDisplayIdToMirrorLocked());
+                display = mLogicalDisplayMapper.getDisplayLocked(
+                        device.getDisplayIdToMirrorLocked());
             }
             if (display == null) {
-                display = mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY);
+                display = mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY);
             }
         }
 
@@ -1896,9 +1897,9 @@
     @VisibleForTesting
     DisplayDeviceInfo getDisplayDeviceInfoInternal(int displayId) {
         synchronized (mSyncRoot) {
-            LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (display != null) {
-                DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
+                final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
                 return displayDevice.getDisplayDeviceInfoLocked();
             }
             return null;
@@ -1908,9 +1909,9 @@
     @VisibleForTesting
     int getDisplayIdToMirrorInternal(int displayId) {
         synchronized (mSyncRoot) {
-            LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+            final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
             if (display != null) {
-                DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
+                final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
                 return displayDevice.getDisplayIdToMirrorLocked();
             }
             return Display.INVALID_DISPLAY;
@@ -1992,7 +1993,8 @@
                     ArraySet<Integer> uids;
                     synchronized (mSyncRoot) {
                         int displayId = msg.arg1;
-                        LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+                        final LogicalDisplay display =
+                                mLogicalDisplayMapper.getDisplayLocked(displayId);
                         uids = display.getPendingFrameRateOverrideUids();
                         display.clearPendingFrameRateOverrideUids();
                     }
@@ -2586,7 +2588,7 @@
         @Override // Binder call
         public boolean isMinimalPostProcessingRequested(int displayId) {
             synchronized (mSyncRoot) {
-                return mLogicalDisplayMapper.getLocked(displayId)
+                return mLogicalDisplayMapper.getDisplayLocked(displayId)
                         .getRequestedMinimalPostProcessingLocked();
             }
         }
@@ -2831,7 +2833,7 @@
         @Override
         public Point getDisplayPosition(int displayId) {
             synchronized (mSyncRoot) {
-                LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+                final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
                 if (display != null) {
                     return display.getDisplayPosition();
                 }
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 20b133c..d9570c7 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -17,11 +17,11 @@
 package com.android.server.display;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerInternal;
 import android.util.ArraySet;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayEventReceiver;
@@ -64,12 +64,14 @@
  */
 final class LogicalDisplay {
     private static final String TAG = "LogicalDisplay";
-    private final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
 
     // The layer stack we use when the display has been blanked to prevent any
     // of its content from appearing.
     private static final int BLANK_LAYER_STACK = -1;
 
+    private static final DisplayInfo EMPTY_DISPLAY_INFO = new DisplayInfo();
+
+    private final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
     private final int mDisplayId;
     private final int mLayerStack;
 
@@ -297,7 +299,7 @@
 
         // Check whether logical display has become invalid.
         if (!deviceRepo.containsLocked(mPrimaryDisplayDevice)) {
-            mPrimaryDisplayDevice = null;
+            setPrimaryDisplayDeviceLocked(null);
             return;
         }
 
@@ -684,18 +686,28 @@
      * @param targetDisplay The display with which to swap display-devices.
      * @return {@code true} if the displays were swapped, {@code false} otherwise.
      */
-    public boolean swapDisplaysLocked(@NonNull LogicalDisplay targetDisplay) {
-        final DisplayDevice targetDevice = targetDisplay.getPrimaryDisplayDeviceLocked();
-        if (mPrimaryDisplayDevice == null || targetDevice == null) {
-            Slog.e(TAG, "Missing display device during swap: " + mPrimaryDisplayDevice + " , "
-                    + targetDevice);
-            return false;
-        }
+    public void swapDisplaysLocked(@NonNull LogicalDisplay targetDisplay) {
+        final DisplayDevice oldTargetDevice =
+                targetDisplay.setPrimaryDisplayDeviceLocked(mPrimaryDisplayDevice);
+        setPrimaryDisplayDeviceLocked(oldTargetDevice);
+    }
 
-        final DisplayDevice tmpDevice = mPrimaryDisplayDevice;
-        mPrimaryDisplayDevice = targetDisplay.mPrimaryDisplayDevice;
-        targetDisplay.mPrimaryDisplayDevice = tmpDevice;
-        return true;
+    /**
+     * Sets the primary display device to the specified device.
+     *
+     * @param device The new device to set.
+     * @return The previously set display device.
+     */
+    public DisplayDevice setPrimaryDisplayDeviceLocked(@Nullable DisplayDevice device) {
+        final DisplayDevice old = mPrimaryDisplayDevice;
+        mPrimaryDisplayDevice = device;
+
+        // Reset all our display info data
+        mPrimaryDisplayDeviceInfo = null;
+        mBaseDisplayInfo.copyFrom(EMPTY_DISPLAY_INFO);
+        mInfo.set(null);
+
+        return old;
     }
 
     /**
@@ -718,8 +730,8 @@
 
     public void dumpLocked(PrintWriter pw) {
         pw.println("mDisplayId=" + mDisplayId);
-        pw.println("mLayerStack=" + mLayerStack);
         pw.println("mIsEnabled=" + mIsEnabled);
+        pw.println("mLayerStack=" + mLayerStack);
         pw.println("mHasContent=" + mHasContent);
         pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
         pw.println("mRequestedColorMode=" + mRequestedColorMode);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index a3ff534..d6826be 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -16,14 +16,16 @@
 
 package com.android.server.display;
 
-import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.view.Display;
-import android.view.DisplayEventReceiver;
+import android.view.DisplayAddress;
 import android.view.DisplayInfo;
 
 import com.android.server.display.layout.Layout;
@@ -74,49 +76,79 @@
     private final boolean mSingleDisplayDemoMode;
 
     /**
-     * List of all logical displays indexed by logical display id.
+     * Map of all logical displays indexed by logical display id.
      * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
      * TODO: multi-display - Move the aforementioned comment?
      */
     private final SparseArray<LogicalDisplay> mLogicalDisplays =
             new SparseArray<LogicalDisplay>();
 
-    /** A mapping from logical display id to display group. */
-    private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
+    /** Map of all display groups indexed by display group id. */
+    private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>();
 
     private final DisplayDeviceRepository mDisplayDeviceRepo;
     private final DeviceStateToLayoutMap mDeviceStateToLayoutMap;
     private final Listener mListener;
-    private final int[] mFoldedDeviceStates;
+
+    /**
+     * Has an entry for every logical display that the rest of the system has been notified about.
+     * Any entry in here requires us to send a {@link  LOGICAL_DISPLAY_EVENT_REMOVED} event when it
+     * is deleted or {@link  LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed.
+     */
+    private final SparseBooleanArray mUpdatedLogicalDisplays = new SparseBooleanArray();
+
+    /**
+     * Keeps track of all the display groups that we already told other people about. IOW, if a
+     * display group is in this array, then we *must* send change and remove notifications for it
+     * because other components know about them. Also, what this array stores is a change counter
+     * for each group, so we know if the group itself has changes since we last sent out a
+     * notification.  See {@link DisplayGroup#getChangeCountLocked}.
+     */
+    private final SparseIntArray mUpdatedDisplayGroups = new SparseIntArray();
+
+    /**
+     * Array used in {@link #updateLogicalDisplaysLocked} to track events that need to be sent out.
+     */
+    private final SparseIntArray mLogicalDisplaysToUpdate = new SparseIntArray();
+
+    /**
+     * Array used in {@link #updateLogicalDisplaysLocked} to track events that need to be sent out.
+     */
+    private final SparseIntArray mDisplayGroupsToUpdate = new SparseIntArray();
 
     private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
     private Layout mCurrentLayout = null;
-    private boolean mIsFolded = false;
+    private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
 
-    LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
+    LogicalDisplayMapper(DisplayDeviceRepository repo, Listener listener) {
         mDisplayDeviceRepo = repo;
         mListener = listener;
         mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
         mDisplayDeviceRepo.addListener(this);
-
-        mFoldedDeviceStates = context.getResources().getIntArray(
-                com.android.internal.R.array.config_foldedDeviceStates);
-
-        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(context);
+        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap();
     }
 
     @Override
     public void onDisplayDeviceEventLocked(DisplayDevice device, int event) {
         switch (event) {
             case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_ADDED:
+                if (DEBUG) {
+                    Slog.d(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked());
+                }
                 handleDisplayDeviceAddedLocked(device);
                 break;
 
             case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_CHANGED:
+                if (DEBUG) {
+                    Slog.d(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked());
+                }
                 updateLogicalDisplaysLocked();
                 break;
 
             case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_REMOVED:
+                if (DEBUG) {
+                    Slog.d(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked());
+                }
                 updateLogicalDisplaysLocked();
                 break;
         }
@@ -127,11 +159,11 @@
         mListener.onTraversalRequested();
     }
 
-    public LogicalDisplay getLocked(int displayId) {
+    public LogicalDisplay getDisplayLocked(int displayId) {
         return mLogicalDisplays.get(displayId);
     }
 
-    public LogicalDisplay getLocked(DisplayDevice device) {
+    public LogicalDisplay getDisplayLocked(DisplayDevice device) {
         final int count = mLogicalDisplays.size();
         for (int i = 0; i < count; i++) {
             LogicalDisplay display = mLogicalDisplays.valueAt(i);
@@ -166,16 +198,25 @@
         }
     }
 
-    public DisplayGroup getDisplayGroupLocked(int groupId) {
-        final int size = mDisplayIdToGroupMap.size();
+    public int getDisplayGroupIdFromDisplayIdLocked(int displayId) {
+        final LogicalDisplay display = getDisplayLocked(displayId);
+        if (display == null) {
+            return Display.INVALID_DISPLAY_GROUP;
+        }
+
+        final int size = mDisplayGroups.size();
         for (int i = 0; i < size; i++) {
-            final DisplayGroup displayGroup = mDisplayIdToGroupMap.valueAt(i);
-            if (displayGroup.getGroupId() == groupId) {
-                return displayGroup;
+            final DisplayGroup displayGroup = mDisplayGroups.valueAt(i);
+            if (displayGroup.containsLocked(display)) {
+                return mDisplayGroups.keyAt(i);
             }
         }
 
-        return null;
+        return Display.INVALID_DISPLAY_GROUP;
+    }
+
+    public DisplayGroup getDisplayGroupLocked(int groupId) {
+        return mDisplayGroups.get(groupId);
     }
 
     public void dumpLocked(PrintWriter pw) {
@@ -203,229 +244,334 @@
     }
 
     void setDeviceStateLocked(int state) {
-        boolean folded = false;
-        for (int i = 0; i < mFoldedDeviceStates.length; i++) {
-            if (state == mFoldedDeviceStates[i]) {
-                folded = true;
-                break;
-            }
+        if (state != mDeviceState) {
+            resetLayoutLocked();
+            mDeviceState = state;
+            applyLayoutLocked();
+            updateLogicalDisplaysLocked();
         }
-        setDeviceFoldedLocked(folded);
-    }
-
-    void setDeviceFoldedLocked(boolean isFolded) {
-        mIsFolded = isFolded;
-
-        // Until we have fully functioning state mapping, use hardcoded states based on isFolded
-        final int state = mIsFolded ? DeviceStateToLayoutMap.STATE_FOLDED
-                : DeviceStateToLayoutMap.STATE_UNFOLDED;
-
-        if (DEBUG) {
-            Slog.d(TAG, "New device state: " + state);
-        }
-
-        final Layout layout = mDeviceStateToLayoutMap.get(state);
-        if (layout == null) {
-            return;
-        }
-        final Layout.Display displayLayout = layout.getById(Display.DEFAULT_DISPLAY);
-        if (displayLayout == null) {
-            return;
-        }
-        final DisplayDevice newDefaultDevice =
-                mDisplayDeviceRepo.getByAddressLocked(displayLayout.getAddress());
-        if (newDefaultDevice == null) {
-            return;
-        }
-
-        final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
-        mCurrentLayout = layout;
-
-        // If we're already set up accurately, return early
-        if (defaultDisplay.getPrimaryDisplayDeviceLocked() == newDefaultDevice) {
-            return;
-        }
-
-        // We need to swap the default display's display-device with the one that is supposed
-        // to be the default in the new layout.
-        final LogicalDisplay displayToSwap = getLocked(newDefaultDevice);
-        if (displayToSwap == null) {
-            Slog.w(TAG, "Canceling display swap - unexpected empty second display for: "
-                    + newDefaultDevice);
-            return;
-        }
-        defaultDisplay.swapDisplaysLocked(displayToSwap);
-
-        // We ensure that the non-default Display is always forced to be off. This was likely
-        // already done in a previous iteration, but we do it with each swap in case something in
-        // the underlying LogicalDisplays changed: like LogicalDisplay recreation, for example.
-        defaultDisplay.setEnabled(true);
-        displayToSwap.setEnabled(false);
-
-        // Update the world
-        updateLogicalDisplaysLocked();
     }
 
     private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
         DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
-        boolean isDefault = (deviceInfo.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
-        if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) {
-            Slog.w(TAG, "Ignoring attempt to add a second default display: " + deviceInfo);
-            isDefault = false;
+        // Internal Displays need to have additional initialization.
+        // TODO: b/168208162 - This initializes a default dynamic display layout for INTERNAL
+        // devices, which will eventually just be a fallback in case no static layout definitions
+        // exist or cannot be loaded.
+        if (deviceInfo.type == Display.TYPE_INTERNAL) {
+            initializeInternalDisplayDeviceLocked(device);
         }
 
-        if (!isDefault && mSingleDisplayDemoMode) {
-            Slog.i(TAG, "Not creating a logical display for a secondary display "
-                    + " because single display demo mode is enabled: " + deviceInfo);
-            return;
-        }
+        // Create a logical display for the new display device
+        LogicalDisplay display = createNewLogicalDisplayLocked(
+                device, Layout.assignDisplayIdLocked(false /*isDefault*/));
 
-        final int displayId = Layout.assignDisplayIdLocked(isDefault);
-        final int layerStack = assignLayerStackLocked(displayId);
-
-        final DisplayGroup displayGroup;
-        final boolean addNewDisplayGroup =
-                isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0;
-        if (addNewDisplayGroup) {
-            final int groupId = assignDisplayGroupIdLocked(isDefault);
-            displayGroup = new DisplayGroup(groupId);
-        } else {
-            displayGroup = mDisplayIdToGroupMap.get(Display.DEFAULT_DISPLAY);
-        }
-
-        LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
-        display.updateDisplayGroupIdLocked(displayGroup.getGroupId());
-        display.updateLocked(mDisplayDeviceRepo);
-        if (!display.isValidLocked()) {
-            // This should never happen currently.
-            Slog.w(TAG, "Ignoring display device because the logical display "
-                    + "created from it was not considered valid: " + deviceInfo);
-            return;
-        }
-
-        // For foldable devices, we start the internal non-default displays as disabled.
-        // TODO - b/168208162 - this will be removed when we recalculate the layout with each
-        // display-device addition.
-        if (mFoldedDeviceStates.length > 0 && deviceInfo.type == Display.TYPE_INTERNAL
-                && !isDefault) {
-            display.setEnabled(false);
-        }
-
-        mLogicalDisplays.put(displayId, display);
-        displayGroup.addDisplayLocked(display);
-        mDisplayIdToGroupMap.append(displayId, displayGroup);
-
-        if (addNewDisplayGroup) {
-            // Group added events happen before Logical Display added events.
-            mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
-                    LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
-        }
-
-        mListener.onLogicalDisplayEventLocked(display,
-                LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED);
-
-        if (!addNewDisplayGroup) {
-            // Group changed events happen after Logical Display added events.
-            mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
-                    LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
-        }
-
-        if (DEBUG) {
-            Slog.d(TAG, "New Display added: " + display);
-        }
+        applyLayoutLocked();
+        updateLogicalDisplaysLocked();
     }
 
     /**
-     * Updates all existing logical displays given the current set of display devices.
-     * Removes invalid logical displays. Sends notifications if needed.
+     * Updates the rest of the display system once all the changes are applied for display
+     * devices and logical displays. The includes releasing invalid/empty LogicalDisplays,
+     * creating/adjusting/removing DisplayGroups, and notifying the rest of the system of the
+     * relevant changes.
      */
     private void updateLogicalDisplaysLocked() {
+        // Go through all the displays and figure out if they need to be updated.
+        // Loops in reverse so that displays can be removed during the loop without affecting the
+        // rest of the loop.
         for (int i = mLogicalDisplays.size() - 1; i >= 0; i--) {
             final int displayId = mLogicalDisplays.keyAt(i);
             LogicalDisplay display = mLogicalDisplays.valueAt(i);
 
             mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
             display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo);
-            DisplayEventReceiver.FrameRateOverride[] frameRatesOverrides =
-                    display.getFrameRateOverrides();
+
             display.updateLocked(mDisplayDeviceRepo);
-            final DisplayGroup changedDisplayGroup;
+            final DisplayInfo newDisplayInfo = display.getDisplayInfoLocked();
+            final boolean wasPreviouslyUpdated = mUpdatedLogicalDisplays.get(displayId);
+
+            // The display is no longer valid and needs to be removed.
             if (!display.isValidLocked()) {
-                mLogicalDisplays.removeAt(i);
-                final DisplayGroup displayGroup = mDisplayIdToGroupMap.removeReturnOld(displayId);
-                displayGroup.removeDisplayLocked(display);
+                mUpdatedLogicalDisplays.delete(displayId);
 
-                mListener.onLogicalDisplayEventLocked(display,
-                        LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED);
-
-                changedDisplayGroup = displayGroup;
-            } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
-                final int flags = display.getDisplayInfoLocked().flags;
-                final DisplayGroup defaultDisplayGroup = mDisplayIdToGroupMap.get(
-                        Display.DEFAULT_DISPLAY);
-                if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
-                    // The display should have its own DisplayGroup.
-                    if (defaultDisplayGroup.removeDisplayLocked(display)) {
-                        final int groupId = assignDisplayGroupIdLocked(false);
-                        final DisplayGroup displayGroup = new DisplayGroup(groupId);
-                        displayGroup.addDisplayLocked(display);
-                        display.updateDisplayGroupIdLocked(groupId);
-                        mDisplayIdToGroupMap.append(display.getDisplayIdLocked(), displayGroup);
-                        mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
-                                LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
-                        changedDisplayGroup = defaultDisplayGroup;
-                    } else {
-                        changedDisplayGroup = null;
-                    }
-                } else {
-                    // The display should be a part of the default DisplayGroup.
-                    final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId);
-                    if (displayGroup != defaultDisplayGroup) {
-                        displayGroup.removeDisplayLocked(display);
-                        defaultDisplayGroup.addDisplayLocked(display);
-                        display.updateDisplayGroupIdLocked(defaultDisplayGroup.getGroupId());
-                        mListener.onDisplayGroupEventLocked(defaultDisplayGroup.getGroupId(),
-                                LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
-                        mDisplayIdToGroupMap.put(displayId, defaultDisplayGroup);
-                        changedDisplayGroup = displayGroup;
-                    } else {
-                        changedDisplayGroup = null;
-                    }
+                // Remove from group
+                final DisplayGroup displayGroup = getDisplayGroupLocked(
+                        getDisplayGroupIdFromDisplayIdLocked(displayId));
+                if (displayGroup != null) {
+                    displayGroup.removeDisplayLocked(display);
                 }
 
-                final String oldUniqueId = mTempDisplayInfo.uniqueId;
-                final String newUniqueId = display.getDisplayInfoLocked().uniqueId;
-                final int eventMsg = TextUtils.equals(oldUniqueId, newUniqueId)
-                        ? LOGICAL_DISPLAY_EVENT_CHANGED : LOGICAL_DISPLAY_EVENT_SWAPPED;
-                mListener.onLogicalDisplayEventLocked(display, eventMsg);
+                if (wasPreviouslyUpdated) {
+                    // The display isn't actually removed from our internal data structures until
+                    // after the notification is sent; see {@link #sendUpdatesForDisplaysLocked}.
+                    Slog.i(TAG, "Removing display: " + displayId);
+                    mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED);
+                } else {
+                    // This display never left this class, safe to remove without notification
+                    mLogicalDisplays.removeAt(i);
+                }
+                continue;
+
+            // The display is new.
+            } else if (!wasPreviouslyUpdated) {
+                Slog.i(TAG, "Adding new display: " + displayId + ": " + newDisplayInfo);
+                assignDisplayGroupLocked(display);
+                mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_ADDED);
+
+            // Underlying displays device has changed to a different one.
+            } else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) {
+                // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case
+                assignDisplayGroupLocked(display);
+                mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_SWAPPED);
+
+            // Something about the display device has changed.
+            } else if (!mTempDisplayInfo.equals(newDisplayInfo)) {
+                // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case
+                assignDisplayGroupLocked(display);
+                mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
+
+            // Display frame rate overrides changed.
             } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
-                mListener.onLogicalDisplayEventLocked(display,
-                        LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
-                changedDisplayGroup = null;
+                mLogicalDisplaysToUpdate.put(
+                        displayId, LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
+
+            // Non-override display values changed.
             } else {
-                // While applications shouldn't know nor care about the non-overridden info, we
+                // While application shouldn't know nor care about the non-overridden info, we
                 // still need to let WindowManager know so it can update its own internal state for
                 // things like display cutouts.
                 display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
                 if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
-                    mListener.onLogicalDisplayEventLocked(display,
-                            LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED);
+                    mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
                 }
-                changedDisplayGroup = null;
             }
 
-            // CHANGED and REMOVED DisplayGroup events should always fire after Display events.
-            if (changedDisplayGroup != null) {
-                final int event = changedDisplayGroup.isEmptyLocked()
-                        ? LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED
-                        : LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED;
-                mListener.onDisplayGroupEventLocked(changedDisplayGroup.getGroupId(), event);
+            mUpdatedLogicalDisplays.put(displayId, true);
+        }
+
+        // Go through the groups and do the same thing. We do this after displays since group
+        // information can change in the previous loop.
+        // Loops in reverse so that groups can be removed during the loop without affecting the
+        // rest of the loop.
+        for (int i = mDisplayGroups.size() - 1; i >= 0; i--) {
+            final int groupId = mDisplayGroups.keyAt(i);
+            final DisplayGroup group = mDisplayGroups.valueAt(i);
+            final boolean wasPreviouslyUpdated = mUpdatedDisplayGroups.indexOfKey(groupId) < 0;
+            final int changeCount = group.getChangeCountLocked();
+
+            if (group.isEmptyLocked()) {
+                mUpdatedDisplayGroups.delete(groupId);
+                if (wasPreviouslyUpdated) {
+                    mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_REMOVED);
+                }
+                continue;
+            } else if (!wasPreviouslyUpdated) {
+                mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_ADDED);
+            } else if (mUpdatedDisplayGroups.get(groupId) != changeCount) {
+                mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_CHANGED);
+            }
+            mUpdatedDisplayGroups.put(groupId, changeCount);
+        }
+
+        // Send the display and display group updates in order by message type. This is important
+        // to ensure that addition and removal notifications happen in the right order.
+        sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_ADDED);
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REMOVED);
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED);
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED);
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED);
+        sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED);
+        sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_REMOVED);
+
+        mLogicalDisplaysToUpdate.clear();
+        mDisplayGroupsToUpdate.clear();
+    }
+
+    /**
+     * Send the specified message for all relevant displays in the specified display-to-message map.
+     */
+    private void sendUpdatesForDisplaysLocked(int msg) {
+        for (int i = mLogicalDisplaysToUpdate.size() - 1; i >= 0; --i) {
+            final int currMsg = mLogicalDisplaysToUpdate.valueAt(i);
+            if (currMsg != msg) {
+                continue;
+            }
+
+            final int id = mLogicalDisplaysToUpdate.keyAt(i);
+            mListener.onLogicalDisplayEventLocked(getDisplayLocked(id), msg);
+            if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
+                // We wait until we sent the EVENT_REMOVED event before actually removing the
+                // display.
+                mLogicalDisplays.delete(id);
             }
         }
     }
 
-    private int assignDisplayGroupIdLocked(boolean isDefault) {
-        return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++;
+    /**
+     * Send the specified message for all relevant display groups in the specified message map.
+     */
+    private void sendUpdatesForGroupsLocked(int msg) {
+        for (int i = mDisplayGroupsToUpdate.size() - 1; i >= 0; --i) {
+            final int currMsg = mDisplayGroupsToUpdate.valueAt(i);
+            if (currMsg != msg) {
+                continue;
+            }
+
+            final int id = mDisplayGroupsToUpdate.keyAt(i);
+            mListener.onDisplayGroupEventLocked(id, msg);
+            if (msg == DISPLAY_GROUP_EVENT_REMOVED) {
+                // We wait until we sent the EVENT_REMOVED event before actually removing the
+                // group.
+                mDisplayGroups.delete(id);
+            }
+        }
+    }
+
+    private void assignDisplayGroupLocked(LogicalDisplay display) {
+        final int displayId = display.getDisplayIdLocked();
+
+        // Get current display group data
+        int groupId = getDisplayGroupIdFromDisplayIdLocked(displayId);
+        final DisplayGroup oldGroup = getDisplayGroupLocked(groupId);
+
+        // Get the new display group if a change is needed
+        final DisplayInfo info = display.getDisplayInfoLocked();
+        final boolean needsOwnDisplayGroup = (info.flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0;
+        final boolean hasOwnDisplayGroup = groupId != Display.DEFAULT_DISPLAY_GROUP;
+        if (groupId == Display.INVALID_DISPLAY_GROUP
+                || hasOwnDisplayGroup != needsOwnDisplayGroup) {
+            groupId = assignDisplayGroupIdLocked(needsOwnDisplayGroup);
+        }
+
+        // Create a new group if needed
+        DisplayGroup newGroup = getDisplayGroupLocked(groupId);
+        if (newGroup == null) {
+            newGroup = new DisplayGroup(groupId);
+            mDisplayGroups.append(groupId, newGroup);
+        }
+        if (oldGroup != newGroup) {
+            if (oldGroup != null) {
+                oldGroup.removeDisplayLocked(display);
+            }
+            newGroup.addDisplayLocked(display);
+            display.updateDisplayGroupIdLocked(groupId);
+            Slog.i(TAG, "Setting new display group " + groupId + " for display "
+                    + displayId + ", from previous group: "
+                    + (oldGroup != null ? oldGroup.getGroupId() : "null"));
+        }
+    }
+
+    /**
+     * Resets the current layout in preparation for a new layout. Layouts can specify if some
+     * displays should be disabled (OFF). When switching from one layout to another, we go
+     * through each of the displays and make sure any displays we might have disabled are
+     * enabled again.
+     */
+    private void resetLayoutLocked() {
+        final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState);
+        for (int i = layout.size() - 1; i >= 0; i--) {
+            final Layout.Display displayLayout = layout.getAt(i);
+            final LogicalDisplay display = getDisplayLocked(displayLayout.getLogicalDisplayId());
+            if (display != null) {
+                enableDisplayLocked(display, true); // Reset all displays back to enabled
+            }
+        }
+    }
+
+
+    /**
+     * Apply (or reapply) the currently selected display layout.
+     */
+    private void applyLayoutLocked() {
+        final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState);
+        mCurrentLayout = layout;
+        Slog.i(TAG, "Applying the display layout for device state(" + mDeviceState
+                + "): " + layout);
+
+        // Go through each of the displays in the current layout set.
+        final int size = layout.size();
+        for (int i = 0; i < size; i++) {
+            final Layout.Display displayLayout = layout.getAt(i);
+
+            // If the underlying display-device we want to use for this display
+            // doesn't exist, then skip it. This can happen at startup as display-devices
+            // trickle in one at a time. When the new display finally shows up, the layout is
+            // recalculated so that the display is properly added to the current layout.
+            final DisplayAddress address = displayLayout.getAddress();
+            final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
+            if (device == null) {
+                Slog.w(TAG, "The display device (" + address + "), is not available"
+                        + " for the display state " + mDeviceState);
+                continue;
+            }
+
+            // Now that we have a display-device, we need a LogicalDisplay to map it to. Find the
+            // right one, if it doesn't exist, create a new one.
+            final int logicalDisplayId = displayLayout.getLogicalDisplayId();
+            LogicalDisplay newDisplay = getDisplayLocked(logicalDisplayId);
+            if (newDisplay == null) {
+                newDisplay = createNewLogicalDisplayLocked(
+                        null /*displayDevice*/, logicalDisplayId);
+            }
+
+            // Now swap the underlying display devices between the old display and the new display
+            final LogicalDisplay oldDisplay = getDisplayLocked(device);
+            if (newDisplay != oldDisplay) {
+                newDisplay.swapDisplaysLocked(oldDisplay);
+            }
+            enableDisplayLocked(newDisplay, displayLayout.isEnabled());
+        }
+    }
+
+
+    /**
+     * Creates a new logical display for the specified device and display Id and adds it to the list
+     * of logical displays.
+     *
+     * @param device The device to associate with the LogicalDisplay.
+     * @param displayId The display ID to give the new display. If invalid, a new ID is assigned.
+     * @param isDefault Indicates if we are creating the default display.
+     * @return The new logical display if created, null otherwise.
+     */
+    private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
+        final int layerStack = assignLayerStackLocked(displayId);
+        final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
+        display.updateLocked(mDisplayDeviceRepo);
+        mLogicalDisplays.put(displayId, display);
+        enableDisplayLocked(display, device != null);
+        return display;
+    }
+
+    private void enableDisplayLocked(LogicalDisplay display, boolean isEnabled) {
+        final int displayId = display.getDisplayIdLocked();
+        final DisplayInfo info = display.getDisplayInfoLocked();
+
+        final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode
+                && (info.type != Display.TYPE_INTERNAL);
+        if (isEnabled && disallowSecondaryDisplay) {
+            Slog.i(TAG, "Not creating a logical display for a secondary display because single"
+                    + " display demo mode is enabled: " + display.getDisplayInfoLocked());
+            isEnabled = false;
+        }
+
+        display.setEnabled(isEnabled);
+    }
+
+    private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) {
+        return isOwnDisplayGroup ? mNextNonDefaultGroupId++ : Display.DEFAULT_DISPLAY_GROUP;
+    }
+
+    private void initializeInternalDisplayDeviceLocked(DisplayDevice device) {
+        // We always want to make sure that our default display layout creates a logical
+        // display for every internal display device that is found.
+        // To that end, when we are notified of a new internal display, we add it to
+        // the default definition if it is not already there.
+        final Layout layoutSet = mDeviceStateToLayoutMap.get(DeviceStateToLayoutMap.STATE_DEFAULT);
+        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        final boolean isDefault = (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
+        layoutSet.createDisplayLocked(info.address, isDefault, true /* isEnabled */);
     }
 
     private int assignLayerStackLocked(int displayId) {
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index 18f39e6..ef33667 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -57,7 +57,7 @@
      * @return The new layout.
      */
     public Display createDisplayLocked(
-            @NonNull DisplayAddress address, boolean isDefault) {
+            @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled) {
         if (contains(address)) {
             Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
             return null;
@@ -74,7 +74,7 @@
         // different layouts, a logical display can be destroyed and later recreated with the
         // same logical display ID.
         final int logicalDisplayId = assignDisplayIdLocked(isDefault);
-        final Display layout = new Display(address, logicalDisplayId);
+        final Display layout = new Display(address, logicalDisplayId, isEnabled);
 
         mDisplays.add(layout);
         return layout;
@@ -130,17 +130,25 @@
      * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
      */
     public static class Display {
+        // Address of the display device to map to this display.
         private final DisplayAddress mAddress;
+
+        // Logical Display ID to apply to this display.
         private final int mLogicalDisplayId;
 
-        Display(@NonNull DisplayAddress address, int logicalDisplayId) {
+        // Indicates that this display is not usable and should remain off.
+        private final boolean mIsEnabled;
+
+        Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) {
             mAddress = address;
             mLogicalDisplayId = logicalDisplayId;
+            mIsEnabled = isEnabled;
         }
 
         @Override
         public String toString() {
-            return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "}";
+            return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId
+                    + "(" + (mIsEnabled ? "ON" : "OFF") + ")}";
         }
 
         public DisplayAddress getAddress() {
@@ -150,5 +158,9 @@
         public int getLogicalDisplayId() {
             return mLogicalDisplayId;
         }
+
+        public boolean isEnabled() {
+            return mIsEnabled;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d41f4c7..c0d577c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3536,6 +3536,20 @@
         boolean didStart = false;
 
         InputBindResult res = null;
+        // We shows the IME when the system allows the IME focused target window to restore the
+        // IME visibility (e.g. switching to the app task when last time the IME is visible).
+        if (isTextEditor && mWindowManagerInternal.shouldRestoreImeVisibility(windowToken)) {
+            if (attribute != null) {
+                res = startInputUncheckedLocked(cs, inputContext, missingMethods,
+                        attribute, startInputFlags, startInputReason);
+                showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+                        SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
+            } else {
+                res = InputBindResult.NULL_EDITOR_INFO;
+            }
+            return res;
+        }
+
         switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) {
             case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
                 if (!sameWindowFocused && (!isTextEditor || !doAutoShow)) {
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 3cc32be..851ea3d 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -35,7 +35,6 @@
 import android.net.NetworkInfo;
 import android.net.NetworkRequest;
 import android.os.Handler;
-import android.security.KeyStore;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -63,7 +62,6 @@
     @NonNull private final Handler mHandler;
     @NonNull private final Vpn mVpn;
     @NonNull private final VpnProfile mProfile;
-    @NonNull private final KeyStore mKeyStore;
 
     @NonNull private final Object mStateLock = new Object();
 
@@ -132,7 +130,6 @@
 
     public LockdownVpnTracker(@NonNull Context context,
             @NonNull Handler handler,
-            @NonNull KeyStore keyStore,
             @NonNull Vpn vpn,
             @NonNull VpnProfile profile) {
         mContext = Objects.requireNonNull(context);
@@ -140,7 +137,6 @@
         mHandler = Objects.requireNonNull(handler);
         mVpn = Objects.requireNonNull(vpn);
         mProfile = Objects.requireNonNull(profile);
-        mKeyStore = Objects.requireNonNull(keyStore);
         mNotificationManager = mContext.getSystemService(NotificationManager.class);
 
         final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
@@ -212,7 +208,7 @@
                 //    network is the system default. So, if the VPN  is up and underlying network
                 //    (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have
                 //    changed to match the new default network (e.g., cell).
-                mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp);
+                mVpn.startLegacyVpnPrivileged(mProfile, network, egressProp);
             } catch (IllegalStateException e) {
                 mAcceptedEgressIface = null;
                 Log.e(TAG, "Failed to start VPN", e);
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 3a5e10e..ebf1fe9 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -379,6 +379,7 @@
                 case MSG_UPDATE_IFACES: {
                     // If no cached states, ignore.
                     if (mLastNetworkStateSnapshots == null) break;
+                    // TODO (b/181642673): Protect mDefaultNetworks from concurrent accessing.
                     updateIfaces(mDefaultNetworks, mLastNetworkStateSnapshots, mActiveIface);
                     break;
                 }
@@ -1266,7 +1267,7 @@
      * they are combined under a single {@link NetworkIdentitySet}.
      */
     @GuardedBy("mStatsLock")
-    private void updateIfacesLocked(@Nullable Network[] defaultNetworks,
+    private void updateIfacesLocked(@NonNull Network[] defaultNetworks,
             @NonNull NetworkStateSnapshot[] snapshots) {
         if (!mSystemReady) return;
         if (LOGV) Slog.v(TAG, "updateIfacesLocked()");
@@ -1282,10 +1283,8 @@
         // Rebuild active interfaces based on connected networks
         mActiveIfaces.clear();
         mActiveUidIfaces.clear();
-        if (defaultNetworks != null) {
-            // Caller is ConnectivityService. Update the list of default networks.
-            mDefaultNetworks = defaultNetworks;
-        }
+        // Update the list of default networks.
+        mDefaultNetworks = defaultNetworks;
 
         mLastNetworkStateSnapshots = snapshots;
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 06b85cf..7da53b5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17993,7 +17993,9 @@
             try {
                 makeDirRecursive(afterCodeFile.getParentFile(), 0775);
                 if (onIncremental) {
-                    mIncrementalManager.renameCodePath(beforeCodeFile, afterCodeFile);
+                    // Just link files here. The stage dir will be removed when the installation
+                    // session is completed.
+                    mIncrementalManager.linkCodePath(beforeCodeFile, afterCodeFile);
                 } else {
                     Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
                 }
@@ -18002,7 +18004,6 @@
                 return false;
             }
 
-            //TODO(b/136132412): enable selinux restorecon for incremental directories
             if (!onIncremental && !SELinux.restoreconRecursive(afterCodeFile)) {
                 Slog.w(TAG, "Failed to restorecon");
                 return false;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index f84eb44..a377f1c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -362,6 +362,7 @@
     private List<Integer> mDirtyUserIds = new ArrayList<>();
 
     private final AtomicBoolean mBootCompleted = new AtomicBoolean();
+    private final AtomicBoolean mShutdown = new AtomicBoolean();
 
     /**
      * Note we use a fine-grained lock for {@link #mUnlockedUsers} due to b/64303666.
@@ -498,6 +499,12 @@
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL,
                 localeFilter, null, mHandler);
 
+        IntentFilter shutdownFilter = new IntentFilter();
+        shutdownFilter.addAction(Intent.ACTION_SHUTDOWN);
+        shutdownFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiverAsUser(mShutdownReceiver, UserHandle.SYSTEM,
+                shutdownFilter, null, mHandler);
+
         injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
                 | ActivityManager.UID_OBSERVER_GONE);
 
@@ -1162,6 +1169,9 @@
         if (DEBUG) {
             Slog.d(TAG, "saveDirtyInfo");
         }
+        if (mShutdown.get()) {
+            return;
+        }
         try {
             synchronized (mLock) {
                 for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) {
@@ -3494,6 +3504,22 @@
         }
     };
 
+    private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // Since it cleans up the shortcut directory and rewrite the ShortcutPackageItems
+            // in odrder during saveToXml(), it could lead to shortcuts missing when shutdown.
+            // We need it so that it can finish up saving before shutdown.
+            synchronized (mLock) {
+                if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) {
+                    mHandler.removeCallbacks(mSaveDirtyInfoRunner);
+                    saveDirtyInfo();
+                }
+                mShutdown.set(true);
+            }
+        }
+    };
+
     /**
      * Called when a user is unlocked.
      * - Check all known packages still exist, and otherwise perform cleanup.
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index e05ef48..8c1a90c 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -1,9 +1,7 @@
-zhanghai@google.com
+include platform/frameworks/base:/core/java/android/permission/OWNERS
+
 per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
 per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
-per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com
 per-file DefaultPermissionGrantPolicy.java = toddke@google.com
 per-file DefaultPermissionGrantPolicy.java = yamasani@google.com
 per-file DefaultPermissionGrantPolicy.java = patb@google.com
-per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com
-per-file DefaultPermissionGrantPolicy.java = zhanghai@google.com
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
new file mode 100644
index 0000000..a0771c0
--- /dev/null
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -0,0 +1,380 @@
+/*
+ * 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.policy;
+
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import com.android.internal.policy.IShortcutService;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Manages quick launch shortcuts by:
+ * <li> Keeping the local copy in sync with the database (this is an observer)
+ * <li> Returning a shortcut-matching intent to clients
+ * <li> Returning particular kind of application intent by special key.
+ */
+class ModifierShortcutManager {
+    private static final String TAG = "ShortcutManager";
+
+    private static final String TAG_BOOKMARKS = "bookmarks";
+    private static final String TAG_BOOKMARK = "bookmark";
+
+    private static final String ATTRIBUTE_PACKAGE = "package";
+    private static final String ATTRIBUTE_CLASS = "class";
+    private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+    private static final String ATTRIBUTE_CATEGORY = "category";
+    private static final String ATTRIBUTE_SHIFT = "shift";
+
+    private final SparseArray<ShortcutInfo> mIntentShortcuts = new SparseArray<>();
+    private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>();
+
+    private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
+
+    /* Table of Application Launch keys.  Maps from key codes to intent categories.
+     *
+     * These are special keys that are used to launch particular kinds of applications,
+     * such as a web browser.  HID defines nearly a hundred of them in the Consumer (0x0C)
+     * usage page.  We don't support quite that many yet...
+     */
+    static SparseArray<String> sApplicationLaunchKeyCategories;
+    static {
+        sApplicationLaunchKeyCategories = new SparseArray<String>();
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
+    }
+
+    private final Context mContext;
+    private boolean mSearchKeyShortcutPending = false;
+    private boolean mConsumeSearchKeyUp = true;
+
+    ModifierShortcutManager(Context context) {
+        mContext = context;
+        loadShortcuts();
+    }
+
+    /**
+     * Gets the shortcut intent for a given keycode+modifier. Make sure you
+     * strip whatever modifier is used for invoking shortcuts (for example,
+     * if 'Sym+A' should invoke a shortcut on 'A', you should strip the
+     * 'Sym' bit from the modifiers before calling this method.
+     * <p>
+     * This will first try an exact match (with modifiers), and then try a
+     * match without modifiers (primary character on a key).
+     *
+     * @param kcm The key character map of the device on which the key was pressed.
+     * @param keyCode The key code.
+     * @param metaState The meta state, omitting any modifiers that were used
+     * to invoke the shortcut.
+     * @return The intent that matches the shortcut, or null if not found.
+     */
+    private Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
+        ShortcutInfo shortcut = null;
+
+        // If the Shift key is pressed, then search for the shift shortcuts.
+        boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
+        SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts;
+
+        // First try the exact keycode (with modifiers).
+        int shortcutChar = kcm.get(keyCode, metaState);
+        if (shortcutChar != 0) {
+            shortcut = shortcutMap.get(shortcutChar);
+        }
+
+        // Next try the primary character on that key.
+        if (shortcut == null) {
+            shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
+            if (shortcutChar != 0) {
+                shortcut = shortcutMap.get(shortcutChar);
+            }
+        }
+
+        return (shortcut != null) ? shortcut.intent : null;
+    }
+
+    private void loadShortcuts() {
+        PackageManager packageManager = mContext.getPackageManager();
+        try {
+            XmlResourceParser parser = mContext.getResources().getXml(
+                    com.android.internal.R.xml.bookmarks);
+            XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+
+            while (true) {
+                XmlUtils.nextElement(parser);
+
+                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+                    break;
+                }
+
+                if (!TAG_BOOKMARK.equals(parser.getName())) {
+                    break;
+                }
+
+                String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
+                String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
+                String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
+                String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
+                String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
+
+                if (TextUtils.isEmpty(shortcutName)) {
+                    Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className);
+                    continue;
+                }
+
+                final int shortcutChar = shortcutName.charAt(0);
+                final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
+
+                final Intent intent;
+                final String title;
+                if (packageName != null && className != null) {
+                    ActivityInfo info = null;
+                    ComponentName componentName = new ComponentName(packageName, className);
+                    try {
+                        info = packageManager.getActivityInfo(componentName,
+                                PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                                        | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        String[] packages = packageManager.canonicalToCurrentPackageNames(
+                                new String[] { packageName });
+                        componentName = new ComponentName(packages[0], className);
+                        try {
+                            info = packageManager.getActivityInfo(componentName,
+                                    PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                                            | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+                        } catch (PackageManager.NameNotFoundException e1) {
+                            Log.w(TAG, "Unable to add bookmark: " + packageName
+                                    + "/" + className, e);
+                            continue;
+                        }
+                    }
+
+                    intent = new Intent(Intent.ACTION_MAIN);
+                    intent.addCategory(Intent.CATEGORY_LAUNCHER);
+                    intent.setComponent(componentName);
+                    title = info.loadLabel(packageManager).toString();
+                } else if (categoryName != null) {
+                    intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
+                    title = "";
+                } else {
+                    Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
+                            + ": missing package/class or category attributes");
+                    continue;
+                }
+
+                ShortcutInfo shortcut = new ShortcutInfo(title, intent);
+                if (isShiftShortcut) {
+                    mShiftShortcuts.put(shortcutChar, shortcut);
+                } else {
+                    mIntentShortcuts.put(shortcutChar, shortcut);
+                }
+            }
+        } catch (XmlPullParserException e) {
+            Log.w(TAG, "Got exception parsing bookmarks.", e);
+        } catch (IOException e) {
+            Log.w(TAG, "Got exception parsing bookmarks.", e);
+        }
+    }
+
+    void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
+            throws RemoteException {
+        IShortcutService service = mShortcutKeyServices.get(shortcutCode);
+        if (service != null && service.asBinder().pingBinder()) {
+            throw new RemoteException("Key already exists.");
+        }
+
+        mShortcutKeyServices.put(shortcutCode, shortcutService);
+    }
+
+    /**
+     * Handle the shortcut to {@link IShortcutService}
+     * @param keyCode The key code of the event.
+     * @param metaState The meta key modifier state.
+     * @return True if invoked the shortcut, otherwise false.
+     */
+    private boolean handleShortcutService(int keyCode, int metaState) {
+        long shortcutCode = keyCode;
+        if ((metaState & KeyEvent.META_CTRL_ON) != 0) {
+            shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE;
+        }
+
+        if ((metaState & KeyEvent.META_ALT_ON) != 0) {
+            shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE;
+        }
+
+        if ((metaState & KeyEvent.META_SHIFT_ON) != 0) {
+            shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE;
+        }
+
+        if ((metaState & KeyEvent.META_META_ON) != 0) {
+            shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE;
+        }
+
+        IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode);
+        if (shortcutService != null) {
+            try {
+                shortcutService.notifyShortcutKeyPressed(shortcutCode);
+            } catch (RemoteException e) {
+                mShortcutKeyServices.delete(shortcutCode);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle the shortcut to {@link Intent}
+     *
+     * @param kcm the {@link KeyCharacterMap} associated with the keyboard device.
+     * @param keyCode The key code of the event.
+     * @param metaState The meta key modifier state.
+     * @return True if invoked the shortcut, otherwise false.
+     */
+    private boolean handleIntentShortcut(KeyCharacterMap kcm, int keyCode, int metaState) {
+        // Shortcuts are invoked through Search+key, so intercept those here
+        // Any printing key that is chorded with Search should be consumed
+        // even if no shortcut was invoked.  This prevents text from being
+        // inadvertently inserted when using a keyboard that has built-in macro
+        // shortcut keys (that emit Search+x) and some of them are not registered.
+        if (mSearchKeyShortcutPending) {
+            if (kcm.isPrintingKey(keyCode)) {
+                mConsumeSearchKeyUp = true;
+                mSearchKeyShortcutPending = false;
+            } else {
+                return false;
+            }
+        } else if ((metaState & KeyEvent.META_META_MASK) != 0) {
+            // Invoke shortcuts using Meta.
+            metaState &= ~KeyEvent.META_META_MASK;
+        } else {
+            // Handle application launch keys.
+            String category = sApplicationLaunchKeyCategories.get(keyCode);
+            if (category != null) {
+                Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                try {
+                    mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+                } catch (ActivityNotFoundException ex) {
+                    Slog.w(TAG, "Dropping application launch key because "
+                            + "the activity to which it is registered was not found: "
+                            + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + ","
+                            + " category=" + category, ex);
+                }
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        final Intent shortcutIntent = getIntent(kcm, keyCode, metaState);
+        if (shortcutIntent != null) {
+            shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            try {
+                mContext.startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
+            } catch (ActivityNotFoundException ex) {
+                Slog.w(TAG, "Dropping shortcut key combination because "
+                        + "the activity to which it is registered was not found: "
+                        + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode), ex);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle the shortcut from {@link KeyEvent}
+     *
+     * @param event Description of the key event.
+     * @return True if invoked the shortcut, otherwise false.
+     */
+    boolean interceptKey(KeyEvent event) {
+        if (event.getRepeatCount() != 0) {
+            return false;
+        }
+
+        final int metaState = event.getModifiers();
+        final int keyCode = event.getKeyCode();
+        if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                mSearchKeyShortcutPending = true;
+                mConsumeSearchKeyUp = false;
+            } else {
+                mSearchKeyShortcutPending = false;
+                if (mConsumeSearchKeyUp) {
+                    mConsumeSearchKeyUp = false;
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        if (event.getAction() != KeyEvent.ACTION_DOWN) {
+            return false;
+        }
+
+        final KeyCharacterMap kcm = event.getKeyCharacterMap();
+        if (handleIntentShortcut(kcm, keyCode, metaState)) {
+            return true;
+        }
+
+        if (handleShortcutService(keyCode, metaState)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private static final class ShortcutInfo {
+        public final String title;
+        public final Intent intent;
+
+        ShortcutInfo(String title, Intent intent) {
+            this.title = title;
+            this.intent = intent;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c50efc7..bce218f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -163,7 +163,6 @@
 import android.speech.RecognizerIntent;
 import android.telecom.TelecomManager;
 import android.util.Log;
-import android.util.LongSparseArray;
 import android.util.MutableBoolean;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -318,29 +317,6 @@
      */
     private boolean mKeyguardDrawnOnce;
 
-    /* Table of Application Launch keys.  Maps from key codes to intent categories.
-     *
-     * These are special keys that are used to launch particular kinds of applications,
-     * such as a web browser.  HID defines nearly a hundred of them in the Consumer (0x0C)
-     * usage page.  We don't support quite that many yet...
-     */
-    static SparseArray<String> sApplicationLaunchKeyCategories;
-    static {
-        sApplicationLaunchKeyCategories = new SparseArray<String>();
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER);
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
-    }
-
     /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
     static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;
 
@@ -419,8 +395,6 @@
     boolean mSafeMode;
     private WindowState mKeyguardCandidate = null;
 
-    private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
-
     // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
     // This is for car dock and this is updated from resource.
     private boolean mEnableCarDockHomeCapture = true;
@@ -516,8 +490,6 @@
     Intent mCarDockIntent;
     Intent mDeskDockIntent;
     Intent mVrHeadsetHomeIntent;
-    boolean mSearchKeyShortcutPending;
-    boolean mConsumeSearchKeyUp;
     boolean mPendingMetaAction;
     boolean mPendingCapsLockToggle;
 
@@ -578,7 +550,7 @@
     private static final int BRIGHTNESS_STEPS = 10;
 
     SettingsObserver mSettingsObserver;
-    ShortcutManager mShortcutManager;
+    ModifierShortcutManager mModifierShortcutManager;
     PowerManager.WakeLock mBroadcastWakeLock;
     PowerManager.WakeLock mPowerKeyWakeLock;
     boolean mHavePendingMediaKeyRepeatWithWakeLock;
@@ -1772,7 +1744,7 @@
         mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
         mSettingsObserver.observe();
-        mShortcutManager = new ShortcutManager(context);
+        mModifierShortcutManager = new ModifierShortcutManager(context);
         mUiMode = context.getResources().getInteger(
                 com.android.internal.R.integer.config_defaultUiModeType);
         mHomeIntent =  new Intent(Intent.ACTION_MAIN, null);
@@ -2574,6 +2546,15 @@
             mPendingCapsLockToggle = false;
         }
 
+        if (isUserSetupComplete() && !keyguardOn) {
+            if (mModifierShortcutManager.interceptKey(event)) {
+                dismissKeyboardShortcutsMenu();
+                mPendingMetaAction = false;
+                mPendingCapsLockToggle = false;
+                return key_consumed;
+            }
+        }
+
         switch(keyCode) {
             case KeyEvent.KEYCODE_HOME:
                 // First we always handle the home key here, so applications
@@ -2599,20 +2580,6 @@
                     }
                 }
                 break;
-            case  KeyEvent.KEYCODE_SEARCH:
-                if (down) {
-                    if (repeatCount == 0) {
-                        mSearchKeyShortcutPending = true;
-                        mConsumeSearchKeyUp = false;
-                    }
-                } else {
-                    mSearchKeyShortcutPending = false;
-                    if (mConsumeSearchKeyUp) {
-                        mConsumeSearchKeyUp = false;
-                        return key_consumed;
-                    }
-                }
-                return 0;
             case KeyEvent.KEYCODE_APP_SWITCH:
                 if (!keyguardOn) {
                     if (down && repeatCount == 0) {
@@ -2820,114 +2787,11 @@
                 break;
         }
 
-        // Shortcuts are invoked through Search+key, so intercept those here
-        // Any printing key that is chorded with Search should be consumed
-        // even if no shortcut was invoked.  This prevents text from being
-        // inadvertently inserted when using a keyboard that has built-in macro
-        // shortcut keys (that emit Search+x) and some of them are not registered.
-        if (mSearchKeyShortcutPending) {
-            final KeyCharacterMap kcm = event.getKeyCharacterMap();
-            if (kcm.isPrintingKey(keyCode)) {
-                mConsumeSearchKeyUp = true;
-                mSearchKeyShortcutPending = false;
-                if (down && repeatCount == 0 && !keyguardOn) {
-                    Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, metaState);
-                    if (shortcutIntent != null) {
-                        shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                        try {
-                            startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
-                            dismissKeyboardShortcutsMenu();
-                        } catch (ActivityNotFoundException ex) {
-                            Slog.w(TAG, "Dropping shortcut key combination because "
-                                    + "the activity to which it is registered was not found: "
-                                    + "SEARCH+" + KeyEvent.keyCodeToString(keyCode), ex);
-                        }
-                    } else {
-                        Slog.i(TAG, "Dropping unregistered shortcut key combination: "
-                                + "SEARCH+" + KeyEvent.keyCodeToString(keyCode));
-                    }
-                }
-                return key_consumed;
-            }
-        }
-
-        // Invoke shortcuts using Meta.
-        if (down && repeatCount == 0 && !keyguardOn
-                && (metaState & KeyEvent.META_META_ON) != 0) {
-            final KeyCharacterMap kcm = event.getKeyCharacterMap();
-            if (kcm.isPrintingKey(keyCode)) {
-                Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode,
-                        metaState & ~(KeyEvent.META_META_ON
-                                | KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_RIGHT_ON));
-                if (shortcutIntent != null) {
-                    shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    try {
-                        startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
-                        dismissKeyboardShortcutsMenu();
-                    } catch (ActivityNotFoundException ex) {
-                        Slog.w(TAG, "Dropping shortcut key combination because "
-                                + "the activity to which it is registered was not found: "
-                                + "META+" + KeyEvent.keyCodeToString(keyCode), ex);
-                    }
-                    return key_consumed;
-                }
-            }
-        }
-
-        // Handle application launch keys.
-        if (down && repeatCount == 0 && !keyguardOn) {
-            String category = sApplicationLaunchKeyCategories.get(keyCode);
-            if (category != null) {
-                Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                try {
-                    startActivityAsUser(intent, UserHandle.CURRENT);
-                    dismissKeyboardShortcutsMenu();
-                } catch (ActivityNotFoundException ex) {
-                    Slog.w(TAG, "Dropping application launch key because "
-                            + "the activity to which it is registered was not found: "
-                            + "keyCode=" + keyCode + ", category=" + category, ex);
-                }
-                return key_consumed;
-            }
-        }
-
         if (isValidGlobalKey(keyCode)
                 && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
             return key_consumed;
         }
 
-        if (down) {
-            long shortcutCode = keyCode;
-            if (event.isCtrlPressed()) {
-                shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE;
-            }
-
-            if (event.isAltPressed()) {
-                shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE;
-            }
-
-            if (event.isShiftPressed()) {
-                shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE;
-            }
-
-            if (event.isMetaPressed()) {
-                shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE;
-            }
-
-            IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode);
-            if (shortcutService != null) {
-                try {
-                    if (isUserSetupComplete()) {
-                        shortcutService.notifyShortcutKeyPressed(shortcutCode);
-                    }
-                } catch (RemoteException e) {
-                    mShortcutKeyServices.delete(shortcutCode);
-                }
-                return key_consumed;
-            }
-        }
-
         // Reserve all the META modifier combos for system behavior
         if ((metaState & KeyEvent.META_META_ON) != 0) {
             return key_consumed;
@@ -3113,12 +2977,7 @@
     public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
             throws RemoteException {
         synchronized (mLock) {
-            IShortcutService service = mShortcutKeyServices.get(shortcutCode);
-            if (service != null && service.asBinder().pingBinder()) {
-                throw new RemoteException("Key already exists.");
-            }
-
-            mShortcutKeyServices.put(shortcutCode, shortcutService);
+            mModifierShortcutManager.registerShortcutKey(shortcutCode, shortcutService);
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ShortcutManager.java
deleted file mode 100644
index ab404db..0000000
--- a/services/core/java/com/android/server/policy/ShortcutManager.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.policy;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.XmlResourceParser;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-
-import com.android.internal.util.XmlUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-/**
- * Manages quick launch shortcuts by:
- * <li> Keeping the local copy in sync with the database (this is an observer)
- * <li> Returning a shortcut-matching intent to clients
- */
-class ShortcutManager {
-    private static final String TAG = "ShortcutManager";
-
-    private static final String TAG_BOOKMARKS = "bookmarks";
-    private static final String TAG_BOOKMARK = "bookmark";
-
-    private static final String ATTRIBUTE_PACKAGE = "package";
-    private static final String ATTRIBUTE_CLASS = "class";
-    private static final String ATTRIBUTE_SHORTCUT = "shortcut";
-    private static final String ATTRIBUTE_CATEGORY = "category";
-    private static final String ATTRIBUTE_SHIFT = "shift";
-
-    private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>();
-    private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>();
-
-    private final Context mContext;
-    
-    public ShortcutManager(Context context) {
-        mContext = context;
-        loadShortcuts();
-    }
-
-    /**
-     * Gets the shortcut intent for a given keycode+modifier. Make sure you
-     * strip whatever modifier is used for invoking shortcuts (for example,
-     * if 'Sym+A' should invoke a shortcut on 'A', you should strip the
-     * 'Sym' bit from the modifiers before calling this method.
-     * <p>
-     * This will first try an exact match (with modifiers), and then try a
-     * match without modifiers (primary character on a key).
-     * 
-     * @param kcm The key character map of the device on which the key was pressed.
-     * @param keyCode The key code.
-     * @param metaState The meta state, omitting any modifiers that were used
-     * to invoke the shortcut.
-     * @return The intent that matches the shortcut, or null if not found.
-     */
-    public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
-        ShortcutInfo shortcut = null;
-
-        // If the Shift key is pressed, then search for the shift shortcuts.
-        boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
-        SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mShortcuts;
-
-        // First try the exact keycode (with modifiers).
-        int shortcutChar = kcm.get(keyCode, metaState);
-        if (shortcutChar != 0) {
-            shortcut = shortcutMap.get(shortcutChar);
-        }
-
-        // Next try the primary character on that key.
-        if (shortcut == null) {
-            shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
-            if (shortcutChar != 0) {
-                shortcut = shortcutMap.get(shortcutChar);
-            }
-        }
-
-        return (shortcut != null) ? shortcut.intent : null;
-    }
-
-    private void loadShortcuts() {
-        PackageManager packageManager = mContext.getPackageManager();
-        try {
-            XmlResourceParser parser = mContext.getResources().getXml(
-                    com.android.internal.R.xml.bookmarks);
-            XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
-
-            while (true) {
-                XmlUtils.nextElement(parser);
-
-                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
-                    break;
-                }
-
-                if (!TAG_BOOKMARK.equals(parser.getName())) {
-                    break;
-                }
-
-                String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
-                String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
-                String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
-                String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
-                String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
-
-                if (TextUtils.isEmpty(shortcutName)) {
-                    Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className);
-                    continue;
-                }
-
-                final int shortcutChar = shortcutName.charAt(0);
-                final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
-
-                final Intent intent;
-                final String title;
-                if (packageName != null && className != null) {
-                    ActivityInfo info = null;
-                    ComponentName componentName = new ComponentName(packageName, className);
-                    try {
-                        info = packageManager.getActivityInfo(componentName,
-                                PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                        | PackageManager.MATCH_UNINSTALLED_PACKAGES);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        String[] packages = packageManager.canonicalToCurrentPackageNames(
-                                new String[] { packageName });
-                        componentName = new ComponentName(packages[0], className);
-                        try {
-                            info = packageManager.getActivityInfo(componentName,
-                                    PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                            | PackageManager.MATCH_UNINSTALLED_PACKAGES);
-                        } catch (PackageManager.NameNotFoundException e1) {
-                            Log.w(TAG, "Unable to add bookmark: " + packageName
-                                    + "/" + className, e);
-                            continue;
-                        }
-                    }
-
-                    intent = new Intent(Intent.ACTION_MAIN);
-                    intent.addCategory(Intent.CATEGORY_LAUNCHER);
-                    intent.setComponent(componentName);
-                    title = info.loadLabel(packageManager).toString();
-                } else if (categoryName != null) {
-                    intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
-                    title = "";
-                } else {
-                    Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
-                            + ": missing package/class or category attributes");
-                    continue;
-                }
-
-                ShortcutInfo shortcut = new ShortcutInfo(title, intent);
-                if (isShiftShortcut) {
-                    mShiftShortcuts.put(shortcutChar, shortcut);
-                } else {
-                    mShortcuts.put(shortcutChar, shortcut);
-                }
-            }
-        } catch (XmlPullParserException e) {
-            Log.w(TAG, "Got exception parsing bookmarks.", e);
-        } catch (IOException e) {
-            Log.w(TAG, "Got exception parsing bookmarks.", e);
-        }
-    }
-
-    private static final class ShortcutInfo {
-        public final String title;
-        public final Intent intent;
-
-        public ShortcutInfo(String title, Intent intent) {
-            this.title = title;
-            this.intent = intent;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/role/OWNERS b/services/core/java/com/android/server/role/OWNERS
index 31e3549..dafdf0f 100644
--- a/services/core/java/com/android/server/role/OWNERS
+++ b/services/core/java/com/android/server/role/OWNERS
@@ -1,5 +1 @@
-svetoslavganov@google.com
-zhanghai@google.com
-evanseverson@google.com
-eugenesusla@google.com
-ntmyren@google.com
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index de8823c..6366280 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -39,6 +39,7 @@
 import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundModelType;
 import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.HidlMemory;
 import android.os.HidlMemoryUtil;
 import android.os.ParcelFileDescriptor;
 
@@ -199,18 +200,7 @@
         hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type);
         hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid);
         hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid);
-
-        // Extract a dup of the underlying FileDescriptor out of aidlModel.data without changing
-        // the original.
-        FileDescriptor fd = new FileDescriptor();
-        try {
-            ParcelFileDescriptor dup = aidlModel.data.dup();
-            fd.setInt$(dup.detachFd());
-            hidlModel.data = HidlMemoryUtil.fileDescriptorToHidlMemory(fd, aidlModel.dataSize);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-
+        hidlModel.data = parcelFileDescriptorToHidlMemory(aidlModel.data, aidlModel.dataSize);
         return hidlModel;
     }
 
@@ -425,4 +415,31 @@
         }
         return aidlCapabilities;
     }
+
+    /**
+     * Convert an AIDL representation of a shared memory block (ParcelFileDescriptor + size) to the
+     * HIDL representation (HidlMemory). Will not change the incoming data or any ownership
+     * semantics, but rather duplicate the underlying FD.
+     *
+     * @param data     The incoming memory block. May be null if dataSize is 0.
+     * @param dataSize The number of bytes in the block.
+     * @return A HidlMemory representation of the memory block. Will be non-null even for an empty
+     *         block.
+     */
+    private static @NonNull
+    HidlMemory parcelFileDescriptorToHidlMemory(@Nullable ParcelFileDescriptor data, int dataSize) {
+        if (dataSize > 0) {
+            // Extract a dup of the underlying FileDescriptor out of data.
+            FileDescriptor fd = new FileDescriptor();
+            try {
+                ParcelFileDescriptor dup = data.dup();
+                fd.setInt$(dup.detachFd());
+                return HidlMemoryUtil.fileDescriptorToHidlMemory(fd, dataSize);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        } else {
+            return HidlMemoryUtil.fileDescriptorToHidlMemory(null, 0);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java
index ebe9733..212f81f 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java
@@ -35,8 +35,7 @@
  * HAL whenever they expire.
  */
 public class SoundTriggerHw2Watchdog implements ISoundTriggerHw2 {
-    // TODO(b/166328980): Reduce this to 1000 as soon as HAL is fixed.
-    private static final long TIMEOUT_MS = 10000;
+    private static final long TIMEOUT_MS = 3000;
     private static final String TAG = "SoundTriggerHw2Watchdog";
 
     private final @NonNull
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
index 43047d1..e05c468 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
@@ -62,7 +62,9 @@
         }
         validateUuid(model.uuid);
         validateUuid(model.vendorUuid);
-        Objects.requireNonNull(model.data);
+        if (model.dataSize > 0) {
+            Objects.requireNonNull(model.data);
+        }
     }
 
     static void validatePhraseModel(@Nullable PhraseSoundModel model) {
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
index b6ddd93..b2db9f5 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -65,7 +65,7 @@
     @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback();
 
     @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
-    private boolean mIsRunning = true;
+    private boolean mIsQuitting = false;
 
     @Nullable private UnderlyingNetworkRecord mCurrentRecord;
     @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;
@@ -151,7 +151,7 @@
         mVcnContext.ensureRunningOnLooperThread();
 
         // Don't bother re-filing NetworkRequests if this Tracker has been torn down.
-        if (!mIsRunning) {
+        if (mIsQuitting) {
             return;
         }
 
@@ -205,7 +205,7 @@
         }
         mCellBringupCallbacks.clear();
 
-        mIsRunning = false;
+        mIsQuitting = true;
     }
 
     /** Returns whether the currently selected Network matches the given network. */
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 6ad30b5..9d39c67 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -16,6 +16,8 @@
 
 package com.android.server.vcn;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+
 import static com.android.server.VcnManagementService.VDBG;
 
 import android.annotation.NonNull;
@@ -84,6 +86,13 @@
      */
     private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2;
 
+    /**
+     * A GatewayConnection owned by this VCN quit.
+     *
+     * @param obj VcnGatewayConnectionConfig
+     */
+    private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3;
+
     /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
     private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
 
@@ -208,6 +217,9 @@
             case MSG_EVENT_SUBSCRIPTIONS_CHANGED:
                 handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj);
                 break;
+            case MSG_EVENT_GATEWAY_CONNECTION_QUIT:
+                handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj);
+                break;
             case MSG_CMD_TEARDOWN:
                 handleTeardown();
                 break;
@@ -263,7 +275,7 @@
 
         // If preexisting VcnGatewayConnection(s) satisfy request, return
         for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
-            if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
+            if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
                 if (VDBG) {
                     Slog.v(
                             getLogTag(),
@@ -278,7 +290,7 @@
         // up
         for (VcnGatewayConnectionConfig gatewayConnectionConfig :
                 mConfig.getGatewayConnectionConfigs()) {
-            if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
+            if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
                 Slog.v(
                         getLogTag(),
                         "Bringing up new VcnGatewayConnection for request " + request.requestId);
@@ -289,12 +301,21 @@
                                 mSubscriptionGroup,
                                 mLastSnapshot,
                                 gatewayConnectionConfig,
-                                new VcnGatewayStatusCallbackImpl());
+                                new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig));
                 mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
             }
         }
     }
 
+    private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) {
+        Slog.v(getLogTag(), "VcnGatewayConnection quit: " + config);
+        mVcnGatewayConnections.remove(config);
+
+        // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
+        // start a new GatewayConnection)
+        mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
+    }
+
     private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
         mLastSnapshot = snapshot;
 
@@ -305,9 +326,10 @@
         }
     }
 
-    private boolean requestSatisfiedByGatewayConnectionConfig(
+    private boolean isRequestSatisfiedByGatewayConnectionConfig(
             @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
         final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
+        builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         for (int cap : config.getAllExposedCapabilities()) {
             builder.addCapability(cap);
         }
@@ -339,9 +361,23 @@
                 @VcnErrorCode int errorCode,
                 @Nullable String exceptionClass,
                 @Nullable String exceptionMessage);
+
+        /** Called by a VcnGatewayConnection to indicate that it has fully torn down. */
+        void onQuit();
     }
 
     private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
+        public final VcnGatewayConnectionConfig mGatewayConnectionConfig;
+
+        VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) {
+            mGatewayConnectionConfig = gatewayConnectionConfig;
+        }
+
+        @Override
+        public void onQuit() {
+            sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig));
+        }
+
         @Override
         public void onEnteredSafeMode() {
             sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE));
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 06748a3..6bc9978 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -168,6 +168,8 @@
     private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: ";
     private static final String DISCONNECT_REASON_UNDERLYING_NETWORK_LOST =
             "Underlying Network lost";
+    private static final String DISCONNECT_REASON_NETWORK_AGENT_UNWANTED =
+            "NetworkAgent was unwanted";
     private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel";
     private static final int TOKEN_ALL = Integer.MIN_VALUE;
 
@@ -379,13 +381,16 @@
         /** The reason why the disconnect was requested. */
         @NonNull public final String reason;
 
-        EventDisconnectRequestedInfo(@NonNull String reason) {
+        public final boolean shouldQuit;
+
+        EventDisconnectRequestedInfo(@NonNull String reason, boolean shouldQuit) {
             this.reason = Objects.requireNonNull(reason);
+            this.shouldQuit = shouldQuit;
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(reason);
+            return Objects.hash(reason, shouldQuit);
         }
 
         @Override
@@ -395,7 +400,7 @@
             }
 
             final EventDisconnectRequestedInfo rhs = (EventDisconnectRequestedInfo) other;
-            return reason.equals(rhs.reason);
+            return reason.equals(rhs.reason) && shouldQuit == rhs.shouldQuit;
         }
     }
 
@@ -488,8 +493,14 @@
      */
     @NonNull private final VcnWakeLock mWakeLock;
 
-    /** Running state of this VcnGatewayConnection. */
-    private boolean mIsRunning = true;
+    /**
+     * Whether the VcnGatewayConnection is in the process of irreversibly quitting.
+     *
+     * <p>This variable is false for the lifecycle of the VcnGatewayConnection, until a command to
+     * teardown has been received. This may be flipped due to events such as the Network becoming
+     * unwanted, the owning VCN entering safe mode, or an irrecoverable internal failure.
+     */
+    private boolean mIsQuitting = false;
 
     /**
      * The token used by the primary/current/active session.
@@ -622,10 +633,8 @@
      * <p>Once torn down, this VcnTunnel CANNOT be started again.
      */
     public void teardownAsynchronously() {
-        sendMessageAndAcquireWakeLock(
-                EVENT_DISCONNECT_REQUESTED,
-                TOKEN_ALL,
-                new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN));
+        sendDisconnectRequestedAndAcquireWakelock(
+                DISCONNECT_REASON_TEARDOWN, true /* shouldQuit */);
 
         // TODO: Notify VcnInstance (via callbacks) of permanent teardown of this tunnel, since this
         // is also called asynchronously when a NetworkAgent becomes unwanted
@@ -646,6 +655,8 @@
         cancelSafeModeAlarm();
 
         mUnderlyingNetworkTracker.teardown();
+
+        mGatewayStatusCallback.onQuit();
     }
 
     /**
@@ -693,7 +704,7 @@
     private void acquireWakeLock() {
         mVcnContext.ensureRunningOnLooperThread();
 
-        if (mIsRunning) {
+        if (!mIsQuitting) {
             mWakeLock.acquire();
         }
     }
@@ -892,7 +903,7 @@
                         TOKEN_ALL,
                         0 /* arg2 */,
                         new EventDisconnectRequestedInfo(
-                                DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+                                DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */));
         mDisconnectRequestAlarm =
                 createScheduledAlarm(
                         DISCONNECT_REQUEST_ALARM,
@@ -909,7 +920,8 @@
         // Cancel any existing disconnect due to previous loss of underlying network
         removeEqualMessages(
                 EVENT_DISCONNECT_REQUESTED,
-                new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+                new EventDisconnectRequestedInfo(
+                        DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */));
     }
 
     private void setRetryTimeoutAlarm(long delay) {
@@ -1041,11 +1053,8 @@
                 enterState();
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessageAndAcquireWakeLock(
-                        EVENT_DISCONNECT_REQUESTED,
-                        TOKEN_ALL,
-                        new EventDisconnectRequestedInfo(
-                                DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
+                sendDisconnectRequestedAndAcquireWakelock(
+                        DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
             }
         }
 
@@ -1083,11 +1092,8 @@
                 processStateMsg(msg);
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessageAndAcquireWakeLock(
-                        EVENT_DISCONNECT_REQUESTED,
-                        TOKEN_ALL,
-                        new EventDisconnectRequestedInfo(
-                                DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
+                sendDisconnectRequestedAndAcquireWakelock(
+                        DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
             }
 
             // Attempt to release the WakeLock - only possible if the Handler queue is empty
@@ -1104,11 +1110,8 @@
                 exitState();
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessageAndAcquireWakeLock(
-                        EVENT_DISCONNECT_REQUESTED,
-                        TOKEN_ALL,
-                        new EventDisconnectRequestedInfo(
-                                DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
+                sendDisconnectRequestedAndAcquireWakelock(
+                        DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
             }
         }
 
@@ -1141,11 +1144,11 @@
             }
         }
 
-        protected void handleDisconnectRequested(String msg) {
+        protected void handleDisconnectRequested(EventDisconnectRequestedInfo info) {
             // TODO(b/180526152): notify VcnStatusCallback for Network loss
 
-            Slog.v(TAG, "Tearing down. Cause: " + msg);
-            mIsRunning = false;
+            Slog.v(TAG, "Tearing down. Cause: " + info.reason);
+            mIsQuitting = info.shouldQuit;
 
             teardownNetwork();
 
@@ -1177,7 +1180,7 @@
     private class DisconnectedState extends BaseState {
         @Override
         protected void enterState() {
-            if (!mIsRunning) {
+            if (mIsQuitting) {
                 quitNow(); // Ignore all queued events; cleanup is complete.
             }
 
@@ -1200,9 +1203,11 @@
                     }
                     break;
                 case EVENT_DISCONNECT_REQUESTED:
-                    mIsRunning = false;
+                    if (((EventDisconnectRequestedInfo) msg.obj).shouldQuit) {
+                        mIsQuitting = true;
 
-                    quitNow();
+                        quitNow();
+                    }
                     break;
                 default:
                     logUnhandledMessage(msg);
@@ -1284,10 +1289,11 @@
 
                     break;
                 case EVENT_DISCONNECT_REQUESTED:
+                    EventDisconnectRequestedInfo info = ((EventDisconnectRequestedInfo) msg.obj);
+                    mIsQuitting = info.shouldQuit;
                     teardownNetwork();
 
-                    String reason = ((EventDisconnectRequestedInfo) msg.obj).reason;
-                    if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
+                    if (info.reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
                         // TODO(b/180526152): notify VcnStatusCallback for Network loss
 
                         // Will trigger EVENT_SESSION_CLOSED immediately.
@@ -1300,7 +1306,7 @@
                 case EVENT_SESSION_CLOSED:
                     mIkeSession = null;
 
-                    if (mIsRunning && mUnderlying != null) {
+                    if (!mIsQuitting && mUnderlying != null) {
                         transitionTo(mSkipRetryTimeout ? mConnectingState : mRetryTimeoutState);
                     } else {
                         teardownNetwork();
@@ -1391,7 +1397,7 @@
                     transitionTo(mConnectedState);
                     break;
                 case EVENT_DISCONNECT_REQUESTED:
-                    handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
+                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
                     break;
                 case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
                     mGatewayStatusCallback.onEnteredSafeMode();
@@ -1438,6 +1444,7 @@
                             mVcnContext.getVcnNetworkProvider()) {
                         @Override
                         public void unwanted() {
+                            Slog.d(TAG, "NetworkAgent was unwanted");
                             teardownAsynchronously();
                         }
 
@@ -1471,7 +1478,7 @@
                 @NonNull IpSecTransform transform,
                 int direction) {
             try {
-                // TODO(b/180163196): Set underlying network of tunnel interface
+                tunnelIface.setUnderlyingNetwork(underlyingNetwork);
 
                 // Transforms do not need to be persisted; the IkeSession will keep them alive
                 mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
@@ -1577,7 +1584,7 @@
                     setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig);
                     break;
                 case EVENT_DISCONNECT_REQUESTED:
-                    handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
+                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
                     break;
                 case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
                     mGatewayStatusCallback.onEnteredSafeMode();
@@ -1682,7 +1689,7 @@
                     transitionTo(mConnectingState);
                     break;
                 case EVENT_DISCONNECT_REQUESTED:
-                    handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
+                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
                     break;
                 case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
                     mGatewayStatusCallback.onEnteredSafeMode();
@@ -1905,13 +1912,13 @@
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    boolean isRunning() {
-        return mIsRunning;
+    boolean isQuitting() {
+        return mIsQuitting;
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setIsRunning(boolean isRunning) {
-        mIsRunning = isRunning;
+    void setIsQuitting(boolean isQuitting) {
+        mIsQuitting = isQuitting;
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -1924,6 +1931,14 @@
         mIkeSession = session;
     }
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void sendDisconnectRequestedAndAcquireWakelock(String reason, boolean shouldQuit) {
+        sendMessageAndAcquireWakeLock(
+                EVENT_DISCONNECT_REQUESTED,
+                TOKEN_ALL,
+                new EventDisconnectRequestedInfo(reason, shouldQuit));
+    }
+
     private IkeSessionParams buildIkeParams() {
         // TODO: Implement this once IkeSessionParams is persisted
         return null;
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
index bfeec01..a909695 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
@@ -67,9 +67,7 @@
         mListeners.add(listener);
 
         // Send listener all cached requests
-        for (NetworkRequestEntry entry : mRequests.values()) {
-            notifyListenerForEvent(listener, entry);
-        }
+        resendAllRequests(listener);
     }
 
     /** Unregisters the specified listener from receiving future NetworkRequests. */
@@ -78,6 +76,14 @@
         mListeners.remove(listener);
     }
 
+    /** Sends all cached NetworkRequest(s) to the specified listener. */
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public void resendAllRequests(@NonNull NetworkRequestListener listener) {
+        for (NetworkRequestEntry entry : mRequests.values()) {
+            notifyListenerForEvent(listener, entry);
+        }
+    }
+
     private void notifyListenerForEvent(
             @NonNull NetworkRequestListener listener, @NonNull NetworkRequestEntry entry) {
         listener.onNetworkRequested(entry.mRequest, entry.mScore, entry.mProviderId);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 3bbc81a..e6d37b6 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1861,7 +1861,7 @@
         }
 
         @Override
-        public boolean isEnabled() {
+        public boolean isAccessibilityTracingEnabled() {
             return mTracing.isEnabled();
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 33abcb4..5932388 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -132,7 +132,6 @@
 import static com.android.server.wm.ActivityRecordProto.DEFER_HIDING_CLIENT;
 import static com.android.server.wm.ActivityRecordProto.FILLS_PARENT;
 import static com.android.server.wm.ActivityRecordProto.FRONT_OF_TASK;
-import static com.android.server.wm.ActivityRecordProto.FROZEN_BOUNDS;
 import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING;
 import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START;
 import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN;
@@ -344,7 +343,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -732,9 +730,6 @@
     // windows, where the app hasn't had time to set a value on the window.
     int mRotationAnimationHint = -1;
 
-    ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
-    ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
-
     private AppSaturationInfo mLastAppSaturationInfo;
 
     private final ColorDisplayService.ColorTransformController mColorTransformController =
@@ -1035,10 +1030,6 @@
             pw.println(" mVisibleSetFromTransferredStartingWindow="
                     + mVisibleSetFromTransferredStartingWindow);
         }
-        if (!mFrozenBounds.isEmpty()) {
-            pw.print(prefix); pw.print("mFrozenBounds="); pw.println(mFrozenBounds);
-            pw.print(prefix); pw.print("mFrozenMergedConfig="); pw.println(mFrozenMergedConfig);
-        }
         if (mPendingRelaunchCount != 0) {
             pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount);
         }
@@ -3359,56 +3350,18 @@
         return mPendingRelaunchCount > 0;
     }
 
-    boolean shouldFreezeBounds() {
-        // For freeform windows, we can't freeze the bounds at the moment because this would make
-        // the resizing unresponsive.
-        if (task == null || task.inFreeformWindowingMode()) {
-            return false;
-        }
-
-        // We freeze the bounds while drag resizing to deal with the time between
-        // the divider/drag handle being released, and the handling it's new
-        // configuration. If we are relaunched outside of the drag resizing state,
-        // we need to be careful not to do this.
-        return task.isDragResizing();
-    }
-
     @VisibleForTesting
     void startRelaunching() {
         if (mPendingRelaunchCount == 0) {
             mRelaunchStartTime = SystemClock.elapsedRealtime();
         }
-        if (shouldFreezeBounds()) {
-            freezeBounds();
-        }
-
         clearAllDrawn();
 
         mPendingRelaunchCount++;
     }
 
-    /**
-     * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds
-     * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even
-     * if they change in the meantime. If the bounds are already frozen, the bounds will be frozen
-     * with a queue.
-     */
-    private void freezeBounds() {
-        mFrozenBounds.offer(new Rect(task.mPreparedFrozenBounds));
-
-        if (task.mPreparedFrozenMergedConfig.equals(Configuration.EMPTY)) {
-            // We didn't call prepareFreezingBounds on the task, so use the current value.
-            mFrozenMergedConfig.offer(new Configuration(task.getConfiguration()));
-        } else {
-            mFrozenMergedConfig.offer(new Configuration(task.mPreparedFrozenMergedConfig));
-        }
-        // Calling unset() to make it equal to Configuration.EMPTY.
-        task.mPreparedFrozenMergedConfig.unset();
-    }
-
     void finishRelaunching() {
         mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
-        unfreezeBounds();
 
         if (mPendingRelaunchCount > 0) {
             mPendingRelaunchCount--;
@@ -3430,30 +3383,11 @@
         if (mPendingRelaunchCount == 0) {
             return;
         }
-        unfreezeBounds();
         mPendingRelaunchCount = 0;
         mRelaunchStartTime = 0;
     }
 
     /**
-     * Unfreezes the previously frozen bounds. See {@link #freezeBounds}.
-     */
-    private void unfreezeBounds() {
-        if (mFrozenBounds.isEmpty()) {
-            return;
-        }
-        mFrozenBounds.remove();
-        if (!mFrozenMergedConfig.isEmpty()) {
-            mFrozenMergedConfig.remove();
-        }
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState win = mChildren.get(i);
-            win.onUnfreezeBounds();
-        }
-        mWmService.mWindowPlacerLocked.performSurfacePlacement();
-    }
-
-    /**
      * Perform clean-up of service connections in an activity record.
      */
     private void cleanUpActivityServices() {
@@ -4449,6 +4383,7 @@
             return;
         }
         mVisibleRequested = visible;
+        setInsetsFrozen(!visible);
         if (app != null) {
             mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
         }
@@ -8214,9 +8149,6 @@
         proto.write(STARTING_MOVED, startingMoved);
         proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW,
                 mVisibleSetFromTransferredStartingWindow);
-        for (Rect bounds : mFrozenBounds) {
-            bounds.dumpDebug(proto, FROZEN_BOUNDS);
-        }
 
         proto.write(STATE, mState.toString());
         proto.write(FRONT_OF_TASK, isRootOfTask());
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a874dee..0c77d9f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4042,17 +4042,9 @@
     int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
             boolean persistent, int userId) {
 
-        final DisplayContent defaultDisplay =
-                mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
-
         mTempConfig.setTo(getGlobalConfiguration());
         final int changes = mTempConfig.updateFrom(values);
         if (changes == 0) {
-            // Since calling to Activity.setRequestedOrientation leads to freezing the window with
-            // setting WindowManagerService.mWaitingForConfig to true, it is important that we call
-            // performDisplayOverrideConfigUpdate in order to send the new display configuration
-            // (even if there are no actual changes) to unfreeze the window.
-            defaultDisplay.performDisplayOverrideConfigUpdate(values);
             return 0;
         }
 
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 309b5ec..62a0080 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -141,14 +141,17 @@
             mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
                     mMergedOverrideConfiguration);
         }
-        dispatchConfigurationToChildren();
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
+        }
     }
 
-    void dispatchConfigurationToChildren() {
-        for (int i = getChildCount() - 1; i >= 0; --i) {
-            final ConfigurationContainer child = getChildAt(i);
-            child.onConfigurationChanged(mFullConfiguration);
-        }
+    /**
+     * Dispatches the configuration to child when {@link #onConfigurationChanged(Configuration)} is
+     * called. This allows the derived classes to override how to dispatch the configuration.
+     */
+    void dispatchConfigurationToChild(E child, Configuration config) {
+        child.onConfigurationChanged(config);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f8fdf06..84bc853 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2944,10 +2944,6 @@
         return dockFrame.bottom - imeFrame.top;
     }
 
-    void prepareFreezingTaskBounds() {
-        forAllRootTasks(Task::prepareFreezingTaskBounds);
-    }
-
     void rotateBounds(@Rotation int oldRotation, @Rotation int newRotation, Rect inOutBounds) {
         // Get display bounds on oldRotation as parent bounds for the rotation.
         getBounds(mTmpRect, oldRotation);
@@ -3704,8 +3700,8 @@
         // app.
         assignWindowLayers(true /* setLayoutNeeded */);
         // 3. The z-order of IME might have been changed. Update the above insets state.
-        mInsetsStateController.updateAboveInsetsState(
-                mInputMethodWindow, true /* notifyInsetsChange */);
+        mInsetsStateController.updateAboveInsetsState(mInputMethodWindow,
+                mInsetsStateController.getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
         // 4. Update the IME control target to apply any inset change and animation.
         // 5. Reparent the IME container surface to either the input target app, or the IME window
         // parent.
@@ -4106,13 +4102,6 @@
      */
     void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) {
         if (type == ANIMATION_TYPE_APP_TRANSITION || type == ANIMATION_TYPE_RECENTS) {
-            // Unfreeze the insets state of the frozen target when the animation finished if exists.
-            final Task task = wc.asTask();
-            if (task != null) {
-                task.forAllWindows(w -> {
-                    w.clearFrozenInsetsState();
-                }, true /* traverseTopToBottom */);
-            }
             removeImeSurfaceImmediately();
         }
     }
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index e18516d..62c155a 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -540,7 +540,7 @@
             setStatusBarState(mLockTaskModeState, userId);
             setKeyguardState(mLockTaskModeState, userId);
             if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) {
-                lockKeyguardIfNeeded();
+                lockKeyguardIfNeeded(userId);
             }
             if (getDevicePolicyManager() != null) {
                 getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
@@ -886,15 +886,15 @@
      * Helper method for locking the device immediately. This may be necessary when the device
      * leaves the pinned mode.
      */
-    private void lockKeyguardIfNeeded() {
-        if (shouldLockKeyguard()) {
+    private void lockKeyguardIfNeeded(int userId) {
+        if (shouldLockKeyguard(userId)) {
             mWindowManager.lockNow(null);
             mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
             getLockPatternUtils().requireCredentialEntry(USER_ALL);
         }
     }
 
-    private boolean shouldLockKeyguard() {
+    private boolean shouldLockKeyguard(int userId) {
         // This functionality should be kept consistent with
         // com.android.settings.security.ScreenPinningSettings (see b/127605586)
         try {
@@ -904,7 +904,7 @@
         } catch (Settings.SettingNotFoundException e) {
             // Log to SafetyNet for b/127605586
             android.util.EventLog.writeEvent(0x534e4554, "127605586", -1, "");
-            return getLockPatternUtils().isSecure(USER_CURRENT);
+            return getLockPatternUtils().isSecure(userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3f9ea1f..0e8cadb 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -650,28 +650,12 @@
     }
 
     @Override
-    void dispatchConfigurationToChildren() {
-        final Configuration configuration = getConfiguration();
-        for (int i = getChildCount() - 1; i >= 0; i--) {
-            final DisplayContent displayContent = getChildAt(i);
-            if (displayContent.isDefaultDisplay) {
-                // The global configuration is also the override configuration of default display.
-                displayContent.performDisplayOverrideConfigUpdate(configuration);
-            } else {
-                displayContent.onConfigurationChanged(configuration);
-            }
-        }
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newParentConfig) {
-        prepareFreezingTaskBounds();
-        super.onConfigurationChanged(newParentConfig);
-    }
-
-    private void prepareFreezingTaskBounds() {
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            mChildren.get(i).prepareFreezingTaskBounds();
+    void dispatchConfigurationToChild(DisplayContent child, Configuration config) {
+        if (child.isDefaultDisplay) {
+            // The global configuration is also the override configuration of default display.
+            child.performDisplayOverrideConfigUpdate(config);
+        } else {
+            child.onConfigurationChanged(config);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 80173b5..5efbb09 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -498,9 +498,6 @@
     // TODO: Make final
     int mUserId;
 
-    final Rect mPreparedFrozenBounds = new Rect();
-    final Configuration mPreparedFrozenMergedConfig = new Configuration();
-
     // Id of the previous display the root task was on.
     int mPrevDisplayId = INVALID_DISPLAY;
 
@@ -1182,10 +1179,6 @@
                 mTaskSupervisor.mNoAnimActivities.add(topActivity);
             }
 
-            // We might trigger a configuration change. Save the current task bounds for freezing.
-            // TODO: Should this call be moved inside the resize method in WM?
-            toRootTask.prepareFreezingTaskBounds();
-
             if (toRootTaskWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                     && moveRootTaskMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT) {
                 // Move recents to front so it is not behind root home task when going into docked
@@ -3363,15 +3356,6 @@
         return isResizeable();
     }
 
-    /**
-     * Prepares the task bounds to be frozen with the current size. See
-     * {@link ActivityRecord#freezeBounds}.
-     */
-    void prepareFreezingBounds() {
-        mPreparedFrozenBounds.set(getBounds());
-        mPreparedFrozenMergedConfig.setTo(getConfiguration());
-    }
-
     @Override
     void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
             Rect outSurfaceInsets) {
@@ -5058,6 +5042,10 @@
             }
         } else {
             // No longer managed by any organizer.
+            final TaskDisplayArea taskDisplayArea = getDisplayArea();
+            if (taskDisplayArea != null) {
+                taskDisplayArea.removeLaunchRootTask(this);
+            }
             setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */);
             if (mCreatedByOrganizer) {
                 removeImmediately("setTaskOrganizer");
@@ -7499,10 +7487,6 @@
         });
     }
 
-    void prepareFreezingTaskBounds() {
-        forAllLeafTasks(Task::prepareFreezingBounds, true /* traverseTopToBottom */);
-    }
-
     private int setBounds(Rect existing, Rect bounds) {
         if (equivalentBounds(existing, bounds)) {
             return BOUNDS_CHANGE_NONE;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index badd7fd..76869e5 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1135,12 +1135,7 @@
                     "Can't set not mCreatedByOrganizer as launch root tr=" + rootTask);
         }
 
-        LaunchRootTaskDef def = null;
-        for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
-            if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue;
-            def = mLaunchRootTasks.get(i);
-        }
-
+        LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask);
         if (def != null) {
             // Remove so we add to the end of the list.
             mLaunchRootTasks.remove(def);
@@ -1156,6 +1151,23 @@
         }
     }
 
+    void removeLaunchRootTask(Task rootTask) {
+        LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask);
+        if (def != null) {
+            mLaunchRootTasks.remove(def);
+        }
+    }
+
+    private @Nullable LaunchRootTaskDef getLaunchRootTaskDef(Task rootTask) {
+        LaunchRootTaskDef def = null;
+        for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
+            if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue;
+            def = mLaunchRootTasks.get(i);
+            break;
+        }
+        return def;
+    }
+
     Task getLaunchRootTask(int windowingMode, int activityType, ActivityOptions options) {
         // Try to use the launch root task in options if available.
         if (options != null) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index dd4ee877..000889a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2684,14 +2684,6 @@
             @Nullable ArrayList<WindowContainer> sources) {
         final Task task = asTask();
         if (task != null && !enter && !task.isHomeOrRecentsRootTask()) {
-            if (AppTransition.isClosingTransitOld(transit)) {
-                // Freezes the insets state when the window is in app exiting transition, to
-                // ensure the exiting window won't receive unexpected insets changes from the
-                // next window.
-                task.forAllWindows(w -> {
-                    w.freezeInsetsState();
-                }, true /* traverseTopToBottom */);
-            }
             mDisplayContent.showImeScreenshot();
         }
         final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index e183ea0..7450782 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -66,7 +66,7 @@
         /**
          * Is trace enabled or not.
          */
-        boolean isEnabled();
+        boolean isAccessibilityTracingEnabled();
 
         /**
          * Add an accessibility trace entry.
@@ -667,4 +667,13 @@
      * Moves the {@link WindowToken} {@code binder} to the display specified by {@code displayId}.
      */
     public abstract void moveWindowTokenToDisplay(IBinder binder, int displayId);
+
+    /**
+     * Checks whether the given window should restore the last IME visibility.
+     *
+     * @param imeTargetWindowToken The token of the (IME target) window
+     * @return {@code true} when the system allows to restore the IME visibility,
+     *         {@code false} otherwise.
+     */
+    public abstract boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cec0321..70b0f58 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8024,6 +8024,11 @@
                 return dc.getImeTarget(IME_TARGET_LAYERING).getWindow().getName();
             }
         }
+
+        @Override
+        public boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
+            return WindowManagerService.this.shouldRestoreImeVisibility(imeTargetWindowToken);
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
@@ -8681,6 +8686,22 @@
                 boundsInWindow, hashAlgorithm, callback);
     }
 
+    boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
+        synchronized (mGlobalLock) {
+            final WindowState imeTargetWindow = mWindowMap.get(imeTargetWindowToken);
+            if (imeTargetWindow == null) {
+                return false;
+            }
+            final Task imeTargetWindowTask = imeTargetWindow.getTask();
+            if (imeTargetWindowTask == null) {
+                return false;
+            }
+            final TaskSnapshot snapshot = mAtmService.getTaskSnapshot(imeTargetWindowTask.mTaskId,
+                    false /* isLowResolution */);
+            return snapshot != null && snapshot.hasImeSurface();
+        }
+    }
+
     private void sendDisplayHashError(RemoteCallback callback, int errorCode) {
         Bundle bundle = new Bundle();
         bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 2a9e08e..fec715e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -797,7 +797,7 @@
      * {@link InsetsStateController#notifyInsetsChanged}.
      */
     boolean isReadyToDispatchInsetsState() {
-        return isVisible() && mFrozenInsetsState == null;
+        return isVisibleRequested() && mFrozenInsetsState == null;
     }
 
     void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation,
@@ -1190,16 +1190,6 @@
             layoutYDiff = 0;
         } else {
             windowFrames.mContainingFrame.set(getBounds());
-            if (mActivityRecord != null && !mActivityRecord.mFrozenBounds.isEmpty()) {
-
-                // If the bounds are frozen, we still want to translate the window freely and only
-                // freeze the size.
-                Rect frozen = mActivityRecord.mFrozenBounds.peek();
-                windowFrames.mContainingFrame.right =
-                        windowFrames.mContainingFrame.left + frozen.width();
-                windowFrames.mContainingFrame.bottom =
-                        windowFrames.mContainingFrame.top + frozen.height();
-            }
             // IME is up and obscuring this window. Adjust the window position so it is visible.
             if (isImeTarget) {
                 if (inFreeformWindowingMode()) {
@@ -2068,23 +2058,6 @@
         super.onResize();
     }
 
-    void onUnfreezeBounds() {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
-            c.onUnfreezeBounds();
-        }
-
-        if (!mHasSurface) {
-            return;
-        }
-
-        mLayoutNeeded = true;
-        setDisplayLayoutNeeded();
-        if (!mWmService.mResizingWindows.contains(this)) {
-            mWmService.mResizingWindows.add(this);
-        }
-    }
-
     /**
      * If the window has moved due to its containing content frame changing, then notify the
      * listeners and optionally animate it. Simply checking a change of position is not enough,
@@ -3579,10 +3552,6 @@
 
     @Override
     public Configuration getConfiguration() {
-        if (mActivityRecord != null && mActivityRecord.mFrozenMergedConfig.size() > 0) {
-            return mActivityRecord.mFrozenMergedConfig.peek();
-        }
-
         // If the process has not registered to any display area to listen to the configuration
         // change, we can simply return the mFullConfiguration as default.
         if (!registeredForDisplayAreaConfigChanges()) {
@@ -3944,14 +3913,8 @@
             return true;
         }
 
-        // If the bounds are currently frozen, it means that the layout size that the app sees
-        // and the bounds we clip this window to might be different. In order to avoid holes, we
-        // simulate that we are still resizing so the app fills the hole with the resizing
-        // background.
-        return (getDisplayContent().mDividerControllerLocked.isResizing()
-                        || mActivityRecord != null && !mActivityRecord.mFrozenBounds.isEmpty()) &&
-                !task.inFreeformWindowingMode() && !isGoneForLayout();
-
+        return getDisplayContent().mDividerControllerLocked.isResizing()
+                && !task.inFreeformWindowingMode() && !isGoneForLayout();
     }
 
     void setDragResizing() {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index e87ee91..066cc1e 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -736,4 +736,13 @@
     boolean isFromClient() {
         return mFromClientToken;
     }
+
+    /** @see WindowState#freezeInsetsState() */
+    void setInsetsFrozen(boolean freeze) {
+        if (freeze) {
+            forAllWindows(WindowState::freezeInsetsState, true /* traverseTopToBottom */);
+        } else {
+            forAllWindows(WindowState::clearFrozenInsetsState, true /* traverseTopToBottom */);
+        }
+    }
 }
diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp
index c285ef5..6a8f6d4 100644
--- a/services/core/xsd/Android.bp
+++ b/services/core/xsd/Android.bp
@@ -30,7 +30,6 @@
     gen_writer: true,
 }
 
-
 xsd_config {
     name: "display-device-config",
     srcs: ["display-device-config/display-device-config.xsd"],
@@ -38,6 +37,12 @@
     package_name: "com.android.server.display.config",
 }
 
+xsd_config {
+    name: "display-layout-config",
+    srcs: ["display-layout-config/display-layout-config.xsd"],
+    api_dir: "display-layout-config/schema",
+    package_name: "com.android.server.display.config.layout",
+}
 
 xsd_config {
     name: "cec-config",
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 7d705c1..e4b9612 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -1,23 +1,24 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
+    Copyright (C) 2020 The Android Open Source Project
 
-<!-- This defines the format of the XML file generated by
-  ~ com.android.compat.annotation.ChangeIdProcessor annotation processor (from
-  ~ tools/platform-compat), and is parsed in com/android/server/compat/CompatConfig.java.
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<!--
+    This defines the format of the XML file used to provide static configuration values
+    for the displays on a device.
+    It is parsed in com/android/server/display/DisplayDeviceConfig.java
 -->
 <xs:schema version="2.0"
            elementFormDefault="qualified"
diff --git a/services/core/xsd/display-layout-config/OWNERS b/services/core/xsd/display-layout-config/OWNERS
new file mode 100644
index 0000000..20b75be
--- /dev/null
+++ b/services/core/xsd/display-layout-config/OWNERS
@@ -0,0 +1,3 @@
+include /services/core/java/com/android/server/display/OWNERS
+
+flc@google.com
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
new file mode 100644
index 0000000..c542c0d
--- /dev/null
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -0,0 +1,57 @@
+<?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.
+-->
+
+<!--
+    This defines the format of the XML file used to defines how displays are laid out
+    for a given device-state.
+    It is parsed in com/android/server/display/layout/DeviceStateToLayoutMap.java
+    More information on device-state can be found in DeviceStateManager.java
+-->
+<xs:schema version="2.0"
+           elementFormDefault="qualified"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema">
+    <xs:element name="layouts">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element type="layout" name="layout" minOccurs="1" maxOccurs="unbounded" />
+            </xs:sequence>
+        </xs:complexType>
+
+        <!-- Ensures only one layout is allowed per device state. -->
+        <xs:unique name="UniqueState">
+            <xs:selector xpath="layout" />
+            <xs:field xpath="@state" />
+        </xs:unique>
+    </xs:element>
+
+    <!-- Type definitions -->
+
+    <xs:complexType name="layout">
+        <xs:sequence>
+            <xs:element name="state" type="xs:nonNegativeInteger" />
+            <xs:element name="display" type="display" maxOccurs="unbounded"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="display">
+        <xs:sequence>
+            <xs:element name="address" type="xs:nonNegativeInteger"/>
+        </xs:sequence>
+        <xs:attribute name="enabled" type="xs:boolean" use="optional" />
+        <xs:attribute name="isDefault" type="xs:boolean" use="optional" />
+    </xs:complexType>
+</xs:schema>
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
new file mode 100644
index 0000000..8171885
--- /dev/null
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -0,0 +1,34 @@
+// Signature format: 2.0
+package com.android.server.display.config.layout {
+
+  public class Display {
+    ctor public Display();
+    method public java.math.BigInteger getAddress();
+    method public boolean getEnabled();
+    method public boolean getIsDefault();
+    method public void setAddress(java.math.BigInteger);
+    method public void setEnabled(boolean);
+    method public void setIsDefault(boolean);
+  }
+
+  public class Layout {
+    ctor public Layout();
+    method public java.util.List<com.android.server.display.config.layout.Display> getDisplay();
+    method public java.math.BigInteger getState();
+    method public void setState(java.math.BigInteger);
+  }
+
+  public class Layouts {
+    ctor public Layouts();
+    method public java.util.List<com.android.server.display.config.layout.Layout> getLayout();
+  }
+
+  public class XmlParser {
+    ctor public XmlParser();
+    method public static com.android.server.display.config.layout.Layouts read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+  }
+
+}
+
diff --git a/services/core/xsd/display-layout-config/schema/last_current.txt b/services/core/xsd/display-layout-config/schema/last_current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/services/core/xsd/display-layout-config/schema/last_current.txt
diff --git a/services/core/xsd/display-layout-config/schema/last_removed.txt b/services/core/xsd/display-layout-config/schema/last_removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/services/core/xsd/display-layout-config/schema/last_removed.txt
diff --git a/services/core/xsd/display-layout-config/schema/removed.txt b/services/core/xsd/display-layout-config/schema/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/services/core/xsd/display-layout-config/schema/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 0d878b4..a262939 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -102,7 +102,7 @@
             return false;
         }
         try {
-            return !mIProfcollect.GetSupportedProvider().isEmpty();
+            return !mIProfcollect.get_supported_provider().isEmpty();
         } catch (RemoteException e) {
             Log.e(LOG_TAG, e.getMessage());
             return false;
@@ -191,7 +191,7 @@
             }
 
             try {
-                sSelfService.mIProfcollect.ProcessProfile();
+                sSelfService.mIProfcollect.process(false);
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, e.getMessage());
             }
@@ -234,7 +234,7 @@
                 if (DEBUG) {
                     Log.d(LOG_TAG, "Tracing on app launch event: " + packageName);
                 }
-                mIProfcollect.TraceOnce("applaunch");
+                mIProfcollect.trace_once("applaunch");
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, e.getMessage());
             }
@@ -296,7 +296,7 @@
         }
 
         try {
-            mIProfcollect.CreateProfileReport();
+            mIProfcollect.report();
         } catch (RemoteException e) {
             Log.e(LOG_TAG, e.getMessage());
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 9109881..51c9b0d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -85,6 +85,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.IActivityManager;
 import android.app.IAlarmCompleteListener;
 import android.app.IAlarmListener;
@@ -1649,8 +1650,8 @@
                 eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(),
                 eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
 
-        final Bundle idleOptions = bundleCaptor.getValue();
-        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
@@ -1669,8 +1670,8 @@
                 eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
                 isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
 
-        final Bundle idleOptions = bundleCaptor.getValue();
-        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
@@ -1716,8 +1717,8 @@
                 isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE),
                 bundleCaptor.capture());
 
-        final Bundle idleOptions = bundleCaptor.getValue();
-        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
@@ -1742,8 +1743,8 @@
                 eq(FLAG_ALLOW_WHILE_IDLE | FLAG_STANDALONE), isNull(), isNull(),
                 eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
 
-        final Bundle idleOptions = bundleCaptor.getValue();
-        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
@@ -1772,8 +1773,8 @@
                 eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(),
                 eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
 
-        final Bundle idleOptions = bundleCaptor.getValue();
-        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
@@ -1797,8 +1798,8 @@
                 eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
                 isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
 
-        final Bundle idleOptions = bundleCaptor.getValue();
-        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
@@ -1822,8 +1823,8 @@
                 eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
                 isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
 
-        final Bundle idleOptions = bundleCaptor.getValue();
-        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, type);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index cfa2086..f897d5c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -160,6 +160,7 @@
     @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
     @Mock private AccessibilityWindowManager mMockA11yWindowManager;
     @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
+    @Mock private AccessibilityTrace mMockA11yTrace;
     @Mock private WindowManagerInternal mMockWindowManagerInternal;
     @Mock private SystemActionPerformer mMockSystemActionPerformer;
     @Mock private IBinder mMockService;
@@ -188,6 +189,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true);
 
+        when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false);
         // Fake a11yWindowInfo and remote a11y connection for tests.
         addA11yWindowInfo(mA11yWindowInfos, WINDOWID, false, Display.DEFAULT_DISPLAY);
         addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true, Display.DEFAULT_DISPLAY);
@@ -227,8 +229,8 @@
 
         mServiceConnection = new TestAccessibilityServiceConnection(mMockContext, COMPONENT_NAME,
                 mSpyServiceInfo, SERVICE_ID, mHandler, new Object(), mMockSecurityPolicy,
-                mMockSystemSupport, mMockWindowManagerInternal, mMockSystemActionPerformer,
-                mMockA11yWindowManager);
+                mMockSystemSupport, mMockA11yTrace, mMockWindowManagerInternal,
+                mMockSystemActionPerformer, mMockA11yWindowManager);
         // Assume that the service is connected
         mServiceConnection.mService = mMockService;
         mServiceConnection.mServiceInterface = mMockServiceInterface;
@@ -849,12 +851,13 @@
         TestAccessibilityServiceConnection(Context context, ComponentName componentName,
                 AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
                 Object lock, AccessibilitySecurityPolicy securityPolicy,
-                SystemSupport systemSupport, WindowManagerInternal windowManagerInternal,
+                SystemSupport systemSupport, AccessibilityTrace trace,
+                WindowManagerInternal windowManagerInternal,
                 SystemActionPerformer systemActionPerfomer,
                 AccessibilityWindowManager a11yWindowManager) {
             super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock,
-                    securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer,
-                    a11yWindowManager);
+                    securityPolicy, systemSupport, trace, windowManagerInternal,
+                    systemActionPerfomer, a11yWindowManager);
             mResolvedUserId = USER_ID;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
index 4b2a9fc..80e81d6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -30,7 +30,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -51,16 +50,19 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.LocalServices;
 import com.android.server.accessibility.gestures.TouchExplorer;
 import com.android.server.accessibility.magnification.FullScreenMagnificationController;
 import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
 import com.android.server.accessibility.magnification.MagnificationGestureHandler;
 import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
+import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
@@ -91,7 +93,9 @@
                     FullScreenMagnificationGestureHandler.class, TouchExplorer.class,
                     AutoclickController.class, AccessibilityInputFilter.class};
 
-    private FullScreenMagnificationController mMockFullScreenMagnificationController;
+    @Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController;
+    @Mock private WindowManagerInternal mMockWindowManagerService;
+    @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController;
     private AccessibilityManagerService mAms;
     private AccessibilityInputFilter mA11yInputFilter;
     private EventCaptor mCaptor1;
@@ -134,16 +138,21 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         Context context = InstrumentationRegistry.getContext();
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
+        LocalServices.addService(
+                WindowManagerInternal.class, mMockWindowManagerService);
+        when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
+                mMockA11yController);
+        when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false);
 
         setDisplayCount(1);
         mAms = spy(new AccessibilityManagerService(context));
-        mMockFullScreenMagnificationController = mock(FullScreenMagnificationController.class);
         mA11yInputFilter = new AccessibilityInputFilter(context, mAms, mEventHandler);
         mA11yInputFilter.onInstalled();
 
-        when(mAms.getValidDisplayList()).thenReturn(mDisplayList);
-        when(mAms.getFullScreenMagnificationController()).thenReturn(
-                mMockFullScreenMagnificationController);
+        doReturn(mDisplayList).when(mAms).getValidDisplayList();
+        doReturn(mMockFullScreenMagnificationController).when(mAms)
+                .getFullScreenMagnificationController();
     }
 
     @After
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
deleted file mode 100644
index 170f561..0000000
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.accessibility;
-
-
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS;
-import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.app.Instrumentation;
-import android.content.Context;
-import android.os.RemoteException;
-import android.view.AccessibilityInteractionController;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeIdManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-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 java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests that verify expected node and prefetched node results when finding a view by node id. We
- * send some requests to the controller via View methods to control message timing.
- */
-@RunWith(AndroidJUnit4.class)
-public class AccessibilityInteractionControllerNodeRequestsTest {
-    private AccessibilityInteractionController mAccessibilityInteractionController;
-    @Mock
-    private IAccessibilityInteractionConnectionCallback mMockClientCallback1;
-    @Mock
-    private IAccessibilityInteractionConnectionCallback mMockClientCallback2;
-
-    @Captor
-    private ArgumentCaptor<AccessibilityNodeInfo> mFindInfoCaptor;
-    @Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mPrefetchInfoListCaptor;
-
-    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
-    private static final int MOCK_CLIENT_1_THREAD_AND_PROCESS_ID = 1;
-    private static final int MOCK_CLIENT_2_THREAD_AND_PROCESS_ID = 2;
-
-    private static final String FRAME_LAYOUT_DESCRIPTION = "frameLayout";
-    private static final String TEXT_VIEW_1_DESCRIPTION = "textView1";
-    private static final String TEXT_VIEW_2_DESCRIPTION = "textView2";
-
-    private TestFrameLayout mFrameLayout;
-    private TestTextView mTextView1;
-    private TestTextView2 mTextView2;
-
-    private boolean mSendClient1RequestForTextAfterTextPrefetched;
-    private boolean mSendClient2RequestForTextAfterTextPrefetched;
-    private boolean mSendRequestForTextAndIncludeUnImportantViews;
-    private int mMockClient1InteractionId;
-    private int mMockClient2InteractionId;
-
-    @Before
-    public void setUp() throws Throwable {
-        MockitoAnnotations.initMocks(this);
-
-        mInstrumentation.runOnMainSync(() -> {
-            final Context context = mInstrumentation.getTargetContext();
-            final ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
-
-            mFrameLayout = new TestFrameLayout(context);
-            mTextView1 = new TestTextView(context);
-            mTextView2 = new TestTextView2(context);
-
-            mFrameLayout.addView(mTextView1);
-            mFrameLayout.addView(mTextView2);
-
-            // The controller retrieves views through this manager, and registration happens on
-            // when attached to a window, which we don't have. We can simply reference FrameLayout
-            // with ROOT_NODE_ID
-            AccessibilityNodeIdManager.getInstance().registerViewWithId(
-                    mTextView1, mTextView1.getAccessibilityViewId());
-            AccessibilityNodeIdManager.getInstance().registerViewWithId(
-                    mTextView2, mTextView2.getAccessibilityViewId());
-
-            try {
-                viewRootImpl.setView(mFrameLayout, new WindowManager.LayoutParams(), null);
-
-            } catch (WindowManager.BadTokenException e) {
-                // activity isn't running, we will ignore BadTokenException.
-            }
-
-            mAccessibilityInteractionController =
-                    new AccessibilityInteractionController(viewRootImpl);
-        });
-
-    }
-
-    @After
-    public void tearDown() throws Throwable {
-        AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
-                mTextView1.getAccessibilityViewId());
-        AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
-                mTextView2.getAccessibilityViewId());
-    }
-
-    /**
-     * Tests a basic request for the root node with prefetch flag
-     * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS}
-     *
-     * @throws RemoteException
-     */
-    @Test
-    public void testFindRootView_withOneClient_shouldReturnRootNodeAndPrefetchDescendants()
-            throws RemoteException {
-        // Request for our FrameLayout
-        sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
-                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
-        mInstrumentation.waitForIdleSync();
-
-        // Verify we get FrameLayout
-        verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
-                mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
-        AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
-        assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
-
-        verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
-                mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
-        // The descendants are our two TextViews
-        List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
-        assertEquals(2, prefetchedNodes.size());
-        assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
-        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
-
-    }
-
-    /**
-     * Tests a basic request for TestTextView1's node with prefetch flag
-     * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS}
-     *
-     * @throws RemoteException
-     */
-    @Test
-    public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblings()
-            throws RemoteException {
-        // Request for TextView1
-        sendNodeRequestToController(AccessibilityNodeInfo.makeNodeId(
-                mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID),
-                mMockClientCallback1, mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS);
-        mInstrumentation.waitForIdleSync();
-
-        // Verify we get TextView1
-        verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
-                mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
-        AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
-        assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
-
-        // Verify the prefetched sibling of TextView1 is TextView2
-        verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
-                mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
-        // TextView2 is the prefetched sibling
-        List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
-        assertEquals(1, prefetchedNodes.size());
-        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
-    }
-
-    /**
-     * Tests a series of controller requests to prevent prefetching.
-     *     Request 1: Client 1 requests the root node
-     *     Request 2: When the root node is initialized in
-     *     {@link TestFrameLayout#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)},
-     *     Client 2 requests TestTextView1's node
-     *
-     * Request 2 on the queue prevents prefetching for Request 1.
-     *
-     * @throws RemoteException
-     */
-    @Test
-    public void testFindRootAndTextNodes_withTwoClients_shouldPreventClient1Prefetch()
-            throws RemoteException {
-        mFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() {
-            @Override
-            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-                super.onInitializeAccessibilityNodeInfo(host, info);
-                final long nodeId = AccessibilityNodeInfo.makeNodeId(
-                        mTextView1.getAccessibilityViewId(),
-                        AccessibilityNodeProvider.HOST_VIEW_ID);
-
-                    // Enqueue a request when this node is found from a different service for
-                    // TextView1
-                    sendNodeRequestToController(nodeId, mMockClientCallback2,
-                            mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS);
-            }
-        });
-        // Client 1 request for FrameLayout
-        sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
-                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
-
-        mInstrumentation.waitForIdleSync();
-
-        // Verify client 1 gets FrameLayout
-        verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
-                mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
-        AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
-        assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
-
-        // The second request is put in the queue in the FrameLayout's onInitializeA11yNodeInfo,
-        // meaning prefetching is interrupted and does not even begin for the first request
-        verify(mMockClientCallback1, never())
-                .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt());
-
-        // Verify client 2 gets TextView1
-        verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult(
-                mFindInfoCaptor.capture(), eq(mMockClient2InteractionId));
-        infoSentToService = mFindInfoCaptor.getValue();
-        assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
-
-        // Verify the prefetched sibling of TextView1 is TextView2 (FLAG_PREFETCH_SIBLINGS)
-        verify(mMockClientCallback2).setPrefetchAccessibilityNodeInfoResult(
-                mPrefetchInfoListCaptor.capture(), eq(mMockClient2InteractionId));
-        List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
-        assertEquals(1, prefetchedNodes.size());
-        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
-    }
-
-    /**
-     * Tests a series of controller same-service requests to interrupt prefetching and satisfy a
-     * pending node request.
-     *     Request 1: Request the root node
-     *     Request 2: When TextTextView1's node is initialized as part of Request 1's prefetching,
-     *     request TestTextView1's node
-     *
-     * Request 1 prefetches TestTextView1's node, is interrupted by a pending request, and checks
-     * if its prefetched nodes satisfy any pending requests. It satisfies Request 2's request for
-     * TestTextView1's node. Request 2 is fulfilled, so it is removed from queue and does not
-     * prefetch.
-     *
-     * @throws RemoteException
-     */
-    @Test
-    public void testFindRootAndTextNode_withOneClient_shouldInterruptPrefetchAndSatisfyPendingMsg()
-            throws RemoteException {
-        mSendClient1RequestForTextAfterTextPrefetched = true;
-
-        mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){
-            @Override
-            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-                super.onInitializeAccessibilityNodeInfo(host, info);
-                info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
-                final long nodeId = AccessibilityNodeInfo.makeNodeId(
-                        mTextView1.getAccessibilityViewId(),
-                        AccessibilityNodeProvider.HOST_VIEW_ID);
-
-                if (mSendClient1RequestForTextAfterTextPrefetched) {
-                    // Prevent a loop when processing second request
-                    mSendClient1RequestForTextAfterTextPrefetched = false;
-                    // TextView1 is prefetched here after the FrameLayout is found. Now enqueue a
-                    // same-client request for TextView1
-                    sendNodeRequestToController(nodeId, mMockClientCallback1,
-                            ++mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS);
-
-                }
-            }
-        });
-        // Client 1 requests FrameLayout
-        sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
-                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
-
-        // Flush out all messages
-        mInstrumentation.waitForIdleSync();
-
-        // When TextView1 is prefetched for FrameLayout, we put a message on the queue in
-        // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus get
-        // two node results for FrameLayout and TextView1.
-        verify(mMockClientCallback1, times(2))
-                .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
-
-        List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues();
-        assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
-        assertEquals(TEXT_VIEW_1_DESCRIPTION, foundNodes.get(1).getContentDescription());
-
-        // The controller will look at FrameLayout's prefetched nodes and find matching nodes in
-        // pending requests. The prefetched TextView1 matches the second request. The second
-        // request was removed from queue and prefetching for this request never occurred.
-        verify(mMockClientCallback1, times(1))
-                .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
-                        eq(mMockClient1InteractionId - 1));
-        List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
-        assertEquals(1, prefetchedNodes.size());
-        assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
-    }
-
-    /**
-     * Like above, but tests a series of controller requests from different services to interrupt
-     * prefetching and satisfy a pending node request.
-     *
-     * @throws RemoteException
-     */
-    @Test
-    public void testFindRootAndTextNode_withTwoClients_shouldInterruptPrefetchAndSatisfyPendingMsg()
-            throws RemoteException {
-        mSendClient2RequestForTextAfterTextPrefetched = true;
-        mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){
-            @Override
-            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-                super.onInitializeAccessibilityNodeInfo(host, info);
-                info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
-                final long nodeId = AccessibilityNodeInfo.makeNodeId(
-                        mTextView1.getAccessibilityViewId(),
-                        AccessibilityNodeProvider.HOST_VIEW_ID);
-
-                if (mSendClient2RequestForTextAfterTextPrefetched) {
-                    mSendClient2RequestForTextAfterTextPrefetched = false;
-                    // TextView1 is prefetched here. Now enqueue client 2's request for
-                    // TextView1
-                    sendNodeRequestToController(nodeId, mMockClientCallback2,
-                            mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS);
-                }
-            }
-        });
-        // Client 1 requests FrameLayout
-        sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
-                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
-
-        mInstrumentation.waitForIdleSync();
-
-        // Verify client 1 gets FrameLayout
-        verify(mMockClientCallback1, times(1))
-                .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
-        assertEquals(FRAME_LAYOUT_DESCRIPTION,
-                mFindInfoCaptor.getValue().getContentDescription());
-
-        // Verify client 1 has prefetched nodes
-        verify(mMockClientCallback1, times(1))
-                .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
-                        eq(mMockClient1InteractionId));
-
-        // Verify client 1's only prefetched node is TextView1
-        List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
-        assertEquals(1, prefetchedNodes.size());
-        assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
-
-        // Verify client 2 gets TextView1
-        verify(mMockClientCallback2, times(1))
-                .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
-
-        assertEquals(TEXT_VIEW_1_DESCRIPTION, mFindInfoCaptor.getValue().getContentDescription());
-
-        // The second request was removed from queue and prefetching for this client request never
-        // occurred as it was satisfied.
-        verify(mMockClientCallback2, never())
-                .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt());
-
-    }
-
-    @Test
-    public void testFindNodeById_withTwoDifferentPrefetchFlags_shouldNotSatisfyPendingRequest()
-            throws RemoteException {
-        mSendRequestForTextAndIncludeUnImportantViews = true;
-        mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){
-            @Override
-            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-                super.onInitializeAccessibilityNodeInfo(host, info);
-                info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
-                final long nodeId = AccessibilityNodeInfo.makeNodeId(
-                        mTextView1.getAccessibilityViewId(),
-                        AccessibilityNodeProvider.HOST_VIEW_ID);
-
-                if (mSendRequestForTextAndIncludeUnImportantViews) {
-                    mSendRequestForTextAndIncludeUnImportantViews = false;
-                    // TextView1 is prefetched here for client 1. Now enqueue a request from a
-                    // different client that holds different fetch flags for TextView1
-                    sendNodeRequestToController(nodeId, mMockClientCallback2,
-                            mMockClient2InteractionId,
-                            FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);
-                }
-            }
-        });
-
-        // Mockito does not make copies of objects when called. It holds references, so
-        // the captor would point to client 2's results after all requests are processed. Verify
-        // prefetched node immediately
-        doAnswer(invocation -> {
-            List<AccessibilityNodeInfo> prefetched = invocation.getArgument(0);
-            assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetched.get(0).getContentDescription());
-            return null;
-        }).when(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(anyList(),
-                eq(mMockClient1InteractionId));
-
-        // Client 1 requests FrameLayout
-        sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
-                mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
-
-        mInstrumentation.waitForIdleSync();
-
-        // Verify client 1 gets FrameLayout
-        verify(mMockClientCallback1, times(1))
-                .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(),
-                        eq(mMockClient1InteractionId));
-
-        assertEquals(FRAME_LAYOUT_DESCRIPTION,
-                mFindInfoCaptor.getValue().getContentDescription());
-
-        // Verify client 1 has prefetched results. The only prefetched node is TextView1
-        // (from above doAnswer)
-        verify(mMockClientCallback1, times(1))
-                .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
-                        eq(mMockClient1InteractionId));
-
-        // Verify client 2 gets TextView1
-        verify(mMockClientCallback2, times(1))
-                .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(),
-                        eq(mMockClient2InteractionId));
-        assertEquals(TEXT_VIEW_1_DESCRIPTION,
-                mFindInfoCaptor.getValue().getContentDescription());
-        // Verify client 2 has TextView2 as a prefetched node
-        verify(mMockClientCallback2, times(1))
-                .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
-                        eq(mMockClient2InteractionId));
-        List<AccessibilityNodeInfo> prefetchedNode = mPrefetchInfoListCaptor.getValue();
-        assertEquals(1, prefetchedNode.size());
-        assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(0).getContentDescription());
-    }
-
-    private void sendNodeRequestToController(long requestedNodeId,
-            IAccessibilityInteractionConnectionCallback callback, int interactionId,
-            int prefetchFlags) {
-        final int processAndThreadId = callback == mMockClientCallback1
-                ? MOCK_CLIENT_1_THREAD_AND_PROCESS_ID
-                : MOCK_CLIENT_2_THREAD_AND_PROCESS_ID;
-
-        mAccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdClientThread(
-                requestedNodeId,
-                null, interactionId,
-                callback, prefetchFlags,
-                processAndThreadId,
-                processAndThreadId, null, null);
-
-    }
-
-    private class TestFrameLayout extends FrameLayout {
-
-        TestFrameLayout(Context context) {
-            super(context);
-        }
-
-        @Override
-        public int getWindowVisibility() {
-            // We aren't attached to a window so let's pretend
-            return VISIBLE;
-        }
-
-        @Override
-        public boolean isShown() {
-            // Controller check
-            return true;
-        }
-
-        @Override
-        public int getAccessibilityViewId() {
-            // static id doesn't reset after tests so return the same one
-            return 0;
-        }
-
-        @Override
-        public void addChildrenForAccessibility(ArrayList<View> outChildren) {
-            // ViewGroup#addChildrenForAccessbility sorting logic will switch these two
-            outChildren.add(mTextView1);
-            outChildren.add(mTextView2);
-        }
-
-        @Override
-        public boolean includeForAccessibility() {
-            return true;
-        }
-
-        @Override
-        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-            super.onInitializeAccessibilityNodeInfo(info);
-            info.setContentDescription(FRAME_LAYOUT_DESCRIPTION);
-        }
-    }
-
-    private class TestTextView extends TextView {
-        TestTextView(Context context) {
-            super(context);
-        }
-
-        @Override
-        public int getWindowVisibility() {
-            return VISIBLE;
-        }
-
-        @Override
-        public boolean isShown() {
-            return true;
-        }
-
-        @Override
-        public int getAccessibilityViewId() {
-            return 1;
-        }
-
-        @Override
-        public boolean includeForAccessibility() {
-            return true;
-        }
-
-        @Override
-        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-            super.onInitializeAccessibilityNodeInfo(info);
-            info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
-        }
-    }
-
-    private class TestTextView2 extends TextView {
-        TestTextView2(Context context) {
-            super(context);
-        }
-
-        @Override
-        public int getWindowVisibility() {
-            return VISIBLE;
-        }
-
-        @Override
-        public boolean isShown() {
-            return true;
-        }
-
-        @Override
-        public int getAccessibilityViewId() {
-            return 2;
-        }
-
-        @Override
-        public boolean includeForAccessibility() {
-            return true;
-        }
-
-        @Override
-        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-            super.onInitializeAccessibilityNodeInfo(info);
-            info.setContentDescription(TEXT_VIEW_2_DESCRIPTION);
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 110bb21..bcc756a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -84,6 +84,7 @@
     @Mock private AccessibilityServiceInfo mMockServiceInfo;
     @Mock private ResolveInfo mMockResolveInfo;
     @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
+    @Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController;
     @Mock private PackageManager mMockPackageManager;
     @Mock private WindowManagerInternal mMockWindowManagerService;
     @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
@@ -115,6 +116,9 @@
 
         when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn(
                 mMockWindowMagnificationMgr);
+        when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
+                mMockA11yController);
+        when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false);
         mA11yms = new AccessibilityManagerService(
             InstrumentationRegistry.getContext(),
             mMockPackageManager,
@@ -153,6 +157,7 @@
                 new Object(),
                 mMockSecurityPolicy,
                 mMockSystemSupport,
+                mA11yms,
                 mMockWindowManagerService,
                 mMockSystemActionPerformer,
                 mMockA11yWindowManager,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 6963a1a..00daa5c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -85,6 +85,7 @@
     @Mock AccessibilityWindowManager mMockA11yWindowManager;
     @Mock ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
     @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
+    @Mock AccessibilityTrace mMockA11yTrace;
     @Mock WindowManagerInternal mMockWindowManagerInternal;
     @Mock SystemActionPerformer mMockSystemActionPerformer;
     @Mock KeyEventDispatcher mMockKeyEventDispatcher;
@@ -110,12 +111,13 @@
         mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
 
         when(mMockIBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient);
+        when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false);
 
         mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext,
                 COMPONENT_NAME, mMockServiceInfo, SERVICE_ID, mHandler, new Object(),
-                mMockSecurityPolicy, mMockSystemSupport, mMockWindowManagerInternal,
-                mMockSystemActionPerformer, mMockA11yWindowManager,
-                mMockActivityTaskManagerInternal);
+                mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
+                mMockWindowManagerInternal, mMockSystemActionPerformer,
+                mMockA11yWindowManager, mMockActivityTaskManagerInternal);
         when(mMockSecurityPolicy.canPerformGestures(mConnection)).thenReturn(true);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 8062bfe..1603087 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -63,6 +63,7 @@
     @Mock AccessibilitySecurityPolicy mMockSecurityPolicy;
     @Mock AccessibilityWindowManager mMockA11yWindowManager;
     @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
+    @Mock AccessibilityTrace mMockA11yTrace;
     @Mock WindowManagerInternal mMockWindowManagerInternal;
     @Mock SystemActionPerformer mMockSystemActionPerformer;
     @Mock IBinder mMockOwner;
@@ -80,6 +81,7 @@
         mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
 
         when(mMockAccessibilityServiceClient.asBinder()).thenReturn(mMockServiceAsBinder);
+        when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false);
 
         final Context context = getInstrumentation().getTargetContext();
         when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn(
@@ -197,7 +199,7 @@
     private void register(int flags) {
         mUiAutomationManager.registerUiTestAutomationServiceLocked(mMockOwner,
                 mMockAccessibilityServiceClient, mMockContext, mMockServiceInfo, SERVICE_ID,
-                mMessageCapturingHandler, mMockSecurityPolicy, mMockSystemSupport,
+                mMessageCapturingHandler, mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
                 mMockWindowManagerInternal, mMockSystemActionPerformer,
                 mMockA11yWindowManager, flags);
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
new file mode 100644
index 0000000..bcd853c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -0,0 +1,327 @@
+/*
+ * 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.display;
+
+import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
+import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
+import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+
+import android.app.PropertyInvalidatedCache;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LogicalDisplayMapperTest {
+    private static int sUniqueTestDisplayId = 0;
+
+    private DisplayDeviceRepository mDisplayDeviceRepo;
+    private LogicalDisplayMapper mLogicalDisplayMapper;
+    private Context mContext;
+
+    @Mock LogicalDisplayMapper.Listener mListenerMock;
+
+    @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
+
+    @Before
+    public void setUp() {
+        // Share classloader to allow package private access.
+        System.setProperty("dexmaker.share_classloader", "true");
+        MockitoAnnotations.initMocks(this);
+
+        mContext = InstrumentationRegistry.getContext();
+        mDisplayDeviceRepo = new DisplayDeviceRepository(
+                new DisplayManagerService.SyncRoot(),
+                new PersistentDataStore(new PersistentDataStore.Injector() {
+                    @Override
+                    public InputStream openRead() {
+                        return null;
+                    }
+
+                    @Override
+                    public OutputStream startWrite() {
+                        return null;
+                    }
+
+                    @Override
+                    public void finishWrite(OutputStream os, boolean success) {}
+                }));
+
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+
+        mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, mListenerMock);
+    }
+
+
+    /////////////////
+    // Test Methods
+    /////////////////
+
+    @Test
+    public void testDisplayDeviceAddAndRemove_Internal() {
+        DisplayDevice device = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+                DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY);
+
+        // add
+        LogicalDisplay displayAdded = add(device);
+        assertEquals(info(displayAdded).address, info(device).address);
+        assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
+
+        // remove
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+        assertEquals(Display.DEFAULT_DISPLAY, id(displayRemoved));
+        assertEquals(displayAdded, displayRemoved);
+    }
+
+    @Test
+    public void testDisplayDeviceAddAndRemove_NonInternalTypes() {
+        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_EXTERNAL);
+        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
+        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
+        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_VIRTUAL);
+        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN);
+
+        // Call the internal test again, just to verify that adding non-internal displays
+        // doesn't affect the ability for an internal display to become the default display.
+        testDisplayDeviceAddAndRemove_Internal();
+    }
+
+    @Test
+    public void testDisplayDeviceAdd_TwoInternalOneDefault() {
+        DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0);
+        DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+                DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertNotEquals(Display.DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        assertEquals(Display.DEFAULT_DISPLAY, id(display2));
+    }
+
+    @Test
+    public void testDisplayDeviceAdd_TwoInternalBothDefault() {
+        DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+                DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+                DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertEquals(Display.DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        // Despite the flags, we can only have one default display
+        assertNotEquals(Display.DEFAULT_DISPLAY, id(display2));
+    }
+
+    @Test
+    public void testGetDisplayIdsLocked() {
+        add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+                DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+        add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0));
+        add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+
+        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID);
+        assertEquals(3, ids.length);
+        Arrays.sort(ids);
+        assertEquals(Display.DEFAULT_DISPLAY, ids[0]);
+    }
+
+    @Test
+    public void testSingleDisplayGroup() {
+        LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+                DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+        LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
+        LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+
+        assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
+        assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
+        assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+    }
+
+    @Test
+    public void testMultipleDisplayGroups() {
+        LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+                DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+        LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
+
+
+        TestDisplayDevice device3 = createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800,
+                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+        LogicalDisplay display3 = add(device3);
+
+        assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
+        assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
+        assertNotEquals(Display.DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+
+        // Now switch it back to the default group by removing the flag and issuing an update
+        DisplayDeviceInfo info = device3.getSourceInfo();
+        info.flags = info.flags & ~DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+
+        // Verify the new group is correct.
+        assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+    }
+
+
+    /////////////////
+    // Helper Methods
+    /////////////////
+
+    private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) {
+        return createDisplayDevice(new DisplayAddressImpl(), type, width, height, flags);
+    }
+
+    private TestDisplayDevice createDisplayDevice(
+            DisplayAddress address, int type, int width, int height, int flags) {
+        TestDisplayDevice device = new TestDisplayDevice();
+        DisplayDeviceInfo displayDeviceInfo = device.getSourceInfo();
+        displayDeviceInfo.type = type;
+        displayDeviceInfo.width = width;
+        displayDeviceInfo.height = height;
+        displayDeviceInfo.flags = flags;
+        displayDeviceInfo.address = new DisplayAddressImpl();
+        return device;
+    }
+
+    private DisplayDeviceInfo info(DisplayDevice device) {
+        return device.getDisplayDeviceInfoLocked();
+    }
+
+    private DisplayInfo info(LogicalDisplay display) {
+        return display.getDisplayInfoLocked();
+    }
+
+    private int id(LogicalDisplay display) {
+        return display.getDisplayIdLocked();
+    }
+
+    private LogicalDisplay add(DisplayDevice device) {
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_ADDED);
+        ArgumentCaptor<LogicalDisplay> displayCaptor =
+                ArgumentCaptor.forClass(LogicalDisplay.class);
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_ADDED));
+        clearInvocations(mListenerMock);
+        return displayCaptor.getValue();
+    }
+
+    private void testDisplayDeviceAddAndRemove_NonInternal(int type) {
+        DisplayDevice device = createDisplayDevice(type, 600, 800, 0);
+
+        // add
+        LogicalDisplay displayAdded = add(device);
+        assertEquals(info(displayAdded).address, info(device).address);
+        assertNotEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
+
+        // remove
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+        assertNotEquals(Display.DEFAULT_DISPLAY, id(displayRemoved));
+    }
+
+    /**
+     * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
+     * display-address implementation in our code. Intentionally uses default object (reference)
+     * equality rules.
+     */
+    class DisplayAddressImpl extends DisplayAddress {
+        @Override
+        public void writeToParcel(Parcel out, int flags) { }
+    }
+
+    class TestDisplayDevice extends DisplayDevice {
+        private DisplayDeviceInfo mInfo = new DisplayDeviceInfo();
+        private DisplayDeviceInfo mSentInfo;
+
+        TestDisplayDevice() {
+            super(null, null, "test_display_" + sUniqueTestDisplayId++, mContext);
+            mInfo = new DisplayDeviceInfo();
+        }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            if (mSentInfo == null) {
+                mSentInfo = new DisplayDeviceInfo();
+                mSentInfo.copyFrom(mInfo);
+            }
+            return mSentInfo;
+        }
+
+        @Override
+        public void applyPendingDisplayDeviceInfoChangesLocked() {
+            mSentInfo = null;
+        }
+
+        @Override
+        public boolean hasStableUniqueId() {
+            return true;
+        }
+
+        public DisplayDeviceInfo getSourceInfo() {
+            return mInfo;
+        }
+    }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index f87d599..7d9ab37 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -20,8 +20,10 @@
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+import static com.android.server.job.JobConcurrencyManager.workTypeToString;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -54,7 +56,7 @@
 
     private static final double[] EQUAL_PROBABILITY_CDF =
             buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
-                    1.0 / NUM_WORK_TYPES);
+                    1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES);
 
     private Random mRandom;
     private WorkCountTracker mWorkCountTracker;
@@ -66,8 +68,9 @@
     }
 
     @NonNull
-    private static double[] buildWorkTypeCdf(double pTop, double pEj, double pBg, double pBgUser) {
-        return buildCdf(pTop, pEj, pBg, pBgUser);
+    private static double[] buildWorkTypeCdf(
+            double pTop, double pFgs, double pEj, double pBg, double pBgUser) {
+        return buildCdf(pTop, pFgs, pEj, pBg, pBgUser);
     }
 
     @NonNull
@@ -102,10 +105,12 @@
             case 0:
                 return WORK_TYPE_TOP;
             case 1:
-                return WORK_TYPE_EJ;
+                return WORK_TYPE_FGS;
             case 2:
-                return WORK_TYPE_BG;
+                return WORK_TYPE_EJ;
             case 3:
+                return WORK_TYPE_BG;
+            case 4:
                 return WORK_TYPE_BGUSER;
             default:
                 throw new IllegalStateException("Unknown work type");
@@ -224,12 +229,15 @@
 
     private void startPendingJobs(Jobs jobs) {
         while (hasStartablePendingJob(jobs)) {
-            final int startingWorkType =
-                    getRandomWorkType(EQUAL_PROBABILITY_CDF, mRandom.nextDouble());
+            final int workType = getRandomWorkType(EQUAL_PROBABILITY_CDF, mRandom.nextDouble());
 
-            if (jobs.pending.get(startingWorkType) > 0
-                    && mWorkCountTracker.canJobStart(startingWorkType) != WORK_TYPE_NONE) {
-                final int pendingMultiType = getPendingMultiType(jobs, startingWorkType);
+            if (jobs.pending.get(workType) > 0) {
+                final int pendingMultiType = getPendingMultiType(jobs, workType);
+                final int startingWorkType = mWorkCountTracker.canJobStart(pendingMultiType);
+                if (startingWorkType == WORK_TYPE_NONE) {
+                    continue;
+                }
+
                 jobs.removePending(pendingMultiType);
                 jobs.running.put(startingWorkType, jobs.running.get(startingWorkType) + 1);
                 mWorkCountTracker.stageJob(startingWorkType, pendingMultiType);
@@ -304,7 +312,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final List<Pair<Integer, Integer>> minLimits = List.of();
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0);
+        final double[] cdf = buildWorkTypeCdf(0.5, 0, 0, 0.5, 0);
         final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
         final double probStart = 0.5;
 
@@ -322,7 +330,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 1.0 / 3);
         final double[] numTypesCdf = buildCdf(.75, .2, .05);
         final double probStart = 0.5;
 
@@ -340,7 +348,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final List<Pair<Integer, Integer>> minLimits = List.of();
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 1.0 / 3);
         final double[] numTypesCdf = buildCdf(.05, .95);
         final double probStart = 0.5;
 
@@ -358,7 +366,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.8, .1);
+        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.8, .1);
         final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
         final double probStart = 0.5;
 
@@ -376,7 +384,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.1, 0);
+        final double[] cdf = buildWorkTypeCdf(0.85, 0.05, 0, 0.1, 0);
         final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
@@ -394,7 +402,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.1, .8);
+        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.1, .8);
         final double[] numTypesCdf = buildCdf(0.5, 0.5);
         final double probStart = 0.5;
 
@@ -413,7 +421,7 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.4;
-        final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.05, 0.05);
+        final double[] cdf = buildWorkTypeCdf(0.8, 0.1, 0, 0.05, 0.05);
         final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
@@ -432,7 +440,7 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0, 0, 0.5, 0.5);
+        final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.5, 0.5);
         final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
@@ -451,7 +459,7 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0, 0, 0.1, 0.9);
+        final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.1, 0.9);
         final double[] numTypesCdf = buildCdf(0.9, 0.1);
         final double probStart = 0.5;
 
@@ -470,7 +478,7 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0, 0, 0.9, 0.1);
+        final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.9, 0.1);
         final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
@@ -488,7 +496,7 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildWorkTypeCdf(0.5, 0.5, 0, 0);
+        final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0, 0);
         final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2);
         final double probStart = 0.5;
 
@@ -511,7 +519,7 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1));
         final double probStop = 0.13;
-        final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.85);
+        final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.8, 0.05);
         final double probStart = 0.87;
 
         checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
@@ -528,7 +536,7 @@
                 List.of(Pair.create(WORK_TYPE_EJ, 5), Pair.create(WORK_TYPE_BG, 4));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildWorkTypeCdf(.1, 0.5, 0.35, 0.05);
+        final double[] cdf = buildWorkTypeCdf(.1, 0, 0.5, 0.35, 0.05);
         final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
@@ -548,7 +556,7 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildWorkTypeCdf(0.01, 0.49, 0.1, 0.4);
+        final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.4, 0.1, 0.4);
         final double[] numTypesCdf = buildCdf(0.7, 0.3);
         final double probStart = 0.5;
 
@@ -576,11 +584,13 @@
         startPendingJobs(jobs);
 
         for (Pair<Integer, Integer> run : resultRunning) {
-            assertWithMessage("Incorrect running result for work type " + run.first)
+            assertWithMessage(
+                    "Incorrect running result for work type " + workTypeToString(run.first))
                     .that(jobs.running.get(run.first)).isEqualTo(run.second);
         }
         for (Pair<Integer, Integer> pend : resultPending) {
-            assertWithMessage("Incorrect pending result for work type " + pend.first)
+            assertWithMessage(
+                    "Incorrect pending result for work type " + workTypeToString(pend.first))
                     .that(jobs.pending.get(pend.first)).isEqualTo(pend.second);
         }
     }
@@ -938,10 +948,15 @@
         assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6);
         assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1);
         assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
-        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4);
+        // If run the TOP jobs as TOP first, and a TOP|EJ job as EJ, then we'll have 4 TOP jobs
+        // remaining.
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isAtLeast(4);
+        // If we end up running the TOP|EJ jobs as TOP first, then we'll have 5 TOP jobs remaining.
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isAtMost(5);
         // Can't equate pending EJ since some could be running as TOP and BG
         assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
-        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(8);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(9);
 
         // Stop all jobs
         jobs.maybeFinishJobs(1);
@@ -975,7 +990,7 @@
         assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1);
         assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
         assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
-        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(1);
         assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(4);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index 2288a89..cc18317 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -18,7 +18,9 @@
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+import static com.android.server.job.JobConcurrencyManager.workTypeToString;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
@@ -44,10 +46,12 @@
 public class WorkTypeConfigTest {
     private static final String KEY_MAX_TOTAL = "concurrency_max_total_test";
     private static final String KEY_MAX_TOP = "concurrency_max_top_test";
+    private static final String KEY_MAX_FGS = "concurrency_max_fgs_test";
     private static final String KEY_MAX_EJ = "concurrency_max_ej_test";
     private static final String KEY_MAX_BG = "concurrency_max_bg_test";
     private static final String KEY_MAX_BGUSER = "concurrency_max_bguser_test";
     private static final String KEY_MIN_TOP = "concurrency_min_top_test";
+    private static final String KEY_MIN_FGS = "concurrency_min_fgs_test";
     private static final String KEY_MIN_EJ = "concurrency_min_ej_test";
     private static final String KEY_MIN_BG = "concurrency_min_bg_test";
     private static final String KEY_MIN_BGUSER = "concurrency_min_bguser_test";
@@ -59,15 +63,17 @@
 
     private void resetConfig() {
         // DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually.
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_EJ, "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_EJ, "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, null, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, null, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_FGS, null, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_EJ, null, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, null, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, null, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, null, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_FGS, null, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_EJ, null, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, null, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, null, false);
     }
 
     private void check(@Nullable DeviceConfig.Properties config,
@@ -103,10 +109,12 @@
 
         assertEquals(expectedTotal, counts.getMaxTotal());
         for (Pair<Integer, Integer> min : expectedMinLimits) {
-            assertEquals((int) min.second, counts.getMinReserved(min.first));
+            assertEquals("Incorrect min value for " + workTypeToString(min.first),
+                    (int) min.second, counts.getMinReserved(min.first));
         }
         for (Pair<Integer, Integer> max : expectedMaxLimits) {
-            assertEquals((int) max.second, counts.getMax(max.first));
+            assertEquals("Incorrect max value for " + workTypeToString(max.first),
+                    (int) max.second, counts.getMax(max.first));
         }
     }
 
@@ -193,6 +201,14 @@
                 /* min */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 3),
                         Pair.create(WORK_TYPE_BG, 1)),
                 /* max */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 1)));
+        check(null, /*default*/ 10,
+                /* min */ List.of(Pair.create(WORK_TYPE_TOP, 3), Pair.create(WORK_TYPE_FGS, 2),
+                        Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1)),
+                /* max */ List.of(Pair.create(WORK_TYPE_FGS, 3)),
+                /*expected*/ true, 10,
+                /* min */ List.of(Pair.create(WORK_TYPE_TOP, 3), Pair.create(WORK_TYPE_FGS, 2),
+                        Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1)),
+                /* max */ List.of(Pair.create(WORK_TYPE_FGS, 3)));
         check(null, /*default*/ 15,
                 /* min */ List.of(Pair.create(WORK_TYPE_BG, 15)),
                 /* max */ List.of(Pair.create(WORK_TYPE_BG, 15)),
@@ -289,5 +305,30 @@
                 /*expected*/ true, 16,
                 /* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_BG, 8)),
                 /* max */ List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_BG, 16)));
+
+        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt(KEY_MAX_TOTAL, 16)
+                        .setInt(KEY_MAX_TOP, 16)
+                        .setInt(KEY_MIN_TOP, 1)
+                        .setInt(KEY_MAX_FGS, 15)
+                        .setInt(KEY_MIN_FGS, 2)
+                        .setInt(KEY_MAX_EJ, 14)
+                        .setInt(KEY_MIN_EJ, 3)
+                        .setInt(KEY_MAX_BG, 13)
+                        .setInt(KEY_MIN_BG, 4)
+                        .setInt(KEY_MAX_BGUSER, 12)
+                        .setInt(KEY_MIN_BGUSER, 5)
+                        .build(),
+                /*default*/ 9,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 9)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 9)),
+                /*expected*/ true, 16,
+                /* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_FGS, 2),
+                        Pair.create(WORK_TYPE_EJ, 3),
+                        Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 5)),
+                /* max */
+                List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_FGS, 15),
+                        Pair.create(WORK_TYPE_EJ, 14),
+                        Pair.create(WORK_TYPE_BG, 13), Pair.create(WORK_TYPE_BGUSER, 12)));
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index d663b64..cc1869e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -454,7 +454,7 @@
         Settings.Secure.clearProviderForTest();
 
         // AND a password is set
-        when(mLockPatternUtils.isSecure(anyInt()))
+        when(mLockPatternUtils.isSecure(TEST_USER_ID))
                 .thenReturn(true);
 
         // AND there is a task record
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 99c96bd..bbb885eb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -984,6 +984,22 @@
     }
 
     @Test
+    public void testFreezeInsets() {
+        final Task stack = createTaskStackOnDisplay(mDisplayContent);
+        final ActivityRecord activity = createActivityRecord(mDisplayContent, stack);
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
+
+        // Set visibility to false, verify the main window of the task will be set the frozen
+        // insets state immediately.
+        activity.setVisibility(false);
+        assertNotNull(win.getFrozenInsetsState());
+
+        // Now make it visible again, verify that the insets are immediately unfrozen.
+        activity.setVisibility(true);
+        assertNull(win.getFrozenInsetsState());
+    }
+
+    @Test
     public void testFreezeInsetsStateWhenAppTransition() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
@@ -996,15 +1012,20 @@
         sources.add(activity);
 
         // Simulate the task applying the exit transition, verify the main window of the task
-        // will be set the frozen insets state.
+        // will be set the frozen insets state before the animation starts
+        activity.setVisibility(false);
         task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
                 false /* isVoiceInteraction */, sources);
         verify(win).freezeInsetsState();
 
-        // Simulate the task transition finished, verify the frozen insets state of the window
-        // will be reset.
+        // Simulate the task transition finished.
+        activity.commitVisibility(false, false);
         task.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION,
                 task.mSurfaceAnimator.getAnimation());
+
+        // Now make it visible again, verify that the insets are immediately unfrozen even before
+        // transition starts.
+        activity.setVisibility(true);
         verify(win).clearFrozenInsetsState();
     }
 
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
index b6244b8..8874e0a 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerService.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
@@ -171,16 +172,33 @@
         }
 
         @Override
-        public void updateUiTranslationState(@UiTranslationState int state,
+        public void updateUiTranslationStateByTaskId(@UiTranslationState int state,
                 TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
                 int taskId, int userId) {
+            // deprecated
+            enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
+            synchronized (mLock) {
+                final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
+                if (service != null && (isDefaultServiceLocked(userId)
+                        || isCalledByServiceAppLocked(userId,
+                        "updateUiTranslationStateByTaskId"))) {
+                    service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds,
+                            taskId);
+                }
+            }
+        }
+
+        @Override
+        public void updateUiTranslationState(@UiTranslationState int state,
+                TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+                IBinder token, int taskId, int userId) {
             enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
             synchronized (mLock) {
                 final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
                 if (service != null && (isDefaultServiceLocked(userId)
                         || isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) {
-                    service.updateUiTranslationState(state, sourceSpec, destSpec, viewIds,
-                            taskId);
+                    service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds,
+                            token, taskId);
                 }
             }
         }
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index 38be85c..ab6ac12 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -23,6 +23,7 @@
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.service.translation.TranslationServiceInfo;
 import android.util.Slog;
@@ -133,18 +134,40 @@
     }
 
     @GuardedBy("mLock")
-    public void updateUiTranslationState(@UiTranslationState int state,
+    public void updateUiTranslationStateLocked(@UiTranslationState int state,
             TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
             int taskId) {
-        // TODO(b/177394471): use taskId as a temporary solution. The solution may use a token to
-        //  content capture manager service find the activitytoken. Then we can use this
-        //  activitytoken to find the activity to callback. But we need to change cc API so use
-        //  temporary solution.
-        final ActivityTokens tokens = mActivityTaskManagerInternal.getTopActivityForTask(taskId);
-        if (tokens == null) {
+        // deprecated
+        final ActivityTokens taskTopActivityTokens =
+                mActivityTaskManagerInternal.getTopActivityForTask(taskId);
+        if (taskTopActivityTokens == null) {
             Slog.w(TAG, "Unknown activity to query for update translation state.");
             return;
         }
+        updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec,
+                viewIds);
+    }
+
+    @GuardedBy("mLock")
+    public void updateUiTranslationStateLocked(@UiTranslationState int state,
+            TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+            IBinder token, int taskId) {
+        // Get top activity for a given task id
+        final ActivityTokens taskTopActivityTokens =
+                mActivityTaskManagerInternal.getTopActivityForTask(taskId);
+        if (taskTopActivityTokens == null
+                || taskTopActivityTokens.getShareableActivityToken() != token) {
+            Slog.w(TAG, "Unknown activity or it was finished to query for update "
+                    + "translation state for token=" + token + " taskId=" + taskId);
+            return;
+        }
+        updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec,
+                viewIds);
+    }
+
+    private void updateUiTranslationStateByActivityTokens(ActivityTokens tokens,
+            @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec,
+            List<AutofillId> viewIds) {
         try {
             tokens.getApplicationThread().updateUiTranslationState(tokens.getActivityToken(), state,
                     sourceSpec, destSpec, viewIds);
diff --git a/telecomm/java/android/telecom/BluetoothCallQualityReport.aidl b/telecomm/java/android/telecom/BluetoothCallQualityReport.aidl
new file mode 100644
index 0000000..685fe9c
--- /dev/null
+++ b/telecomm/java/android/telecom/BluetoothCallQualityReport.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable BluetoothCallQualityReport;
diff --git a/telecomm/java/android/telecom/BluetoothCallQualityReport.java b/telecomm/java/android/telecom/BluetoothCallQualityReport.java
index 10339a8..8703d84 100644
--- a/telecomm/java/android/telecom/BluetoothCallQualityReport.java
+++ b/telecomm/java/android/telecom/BluetoothCallQualityReport.java
@@ -24,6 +24,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * This class represents the quality report that bluetooth framework sends
  * whenever there's a bad voice quality is detected from their side.
@@ -145,6 +147,26 @@
                 }
             };
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        BluetoothCallQualityReport that = (BluetoothCallQualityReport) o;
+        return mSentTimestampMillis == that.mSentTimestampMillis
+                && mChoppyVoice == that.mChoppyVoice && mRssiDbm == that.mRssiDbm
+                && mSnrDb == that.mSnrDb
+                && mRetransmittedPacketsCount == that.mRetransmittedPacketsCount
+                && mPacketsNotReceivedCount == that.mPacketsNotReceivedCount
+                && mNegativeAcknowledgementCount == that.mNegativeAcknowledgementCount;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSentTimestampMillis, mChoppyVoice, mRssiDbm, mSnrDb,
+                mRetransmittedPacketsCount, mPacketsNotReceivedCount,
+                mNegativeAcknowledgementCount);
+    }
+
     /**
      * Builder class for {@link ConnectionRequest}
      */
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 044ea80..2a5ddfd 100755
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -267,6 +267,64 @@
     public static final String EVENT_HANDOVER_FAILED =
             "android.telecom.event.HANDOVER_FAILED";
 
+    /**
+     * Event reported from the Telecom stack to report an in-call diagnostic message which the
+     * dialer app may opt to display to the user.  A diagnostic message is used to communicate
+     * scenarios the device has detected which may impact the quality of the ongoing call.
+     * <p>
+     * For example a problem with a bluetooth headset may generate a recommendation for the user to
+     * try using the speakerphone instead, or if the device detects it has entered a poor service
+     * area, the user might be warned so that they can finish their call prior to it dropping.
+     * <p>
+     * A diagnostic message is considered persistent in nature.  When the user enters a poor service
+     * area, for example, the accompanying diagnostic message persists until they leave the area
+     * of poor service.  Each message is accompanied with a {@link #EXTRA_DIAGNOSTIC_MESSAGE_ID}
+     * which uniquely identifies the diagnostic condition being reported.  The framework raises a
+     * call event of type {@link #EVENT_CLEAR_DIAGNOSTIC_MESSAGE} when the condition reported has
+     * been cleared.  The dialer app should display the diagnostic message until it is cleared.
+     * If multiple diagnostic messages are sent with different IDs (which have not yet been cleared)
+     * the dialer app should prioritize the most recently received message, but still provide the
+     * user with a means to review past messages.
+     * <p>
+     * The text of the message is found in {@link #EXTRA_DIAGNOSTIC_MESSAGE} in the form of a human
+     * readable {@link CharSequence} which is intended for display in the call UX.
+     * <p>
+     * The telecom framework audibly notifies the user of the presence of a diagnostic message, so
+     * the dialer app needs only to concern itself with visually displaying the message.
+     * <p>
+     * The dialer app receives this event via
+     * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}.
+     */
+    public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE =
+            "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE";
+
+    /**
+     * Event reported from the telecom framework when a diagnostic message previously raised with
+     * {@link #EVENT_DISPLAY_DIAGNOSTIC_MESSAGE} has cleared and is no longer pertinent.
+     * <p>
+     * The {@link #EXTRA_DIAGNOSTIC_MESSAGE_ID} indicates the diagnostic message which has been
+     * cleared.
+     * <p>
+     * The dialer app receives this event via
+     * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}.
+     */
+    public static final String EVENT_CLEAR_DIAGNOSTIC_MESSAGE =
+            "android.telecom.event.CLEAR_DIAGNOSTIC_MESSAGE";
+
+    /**
+     * Integer extra representing a message ID for a message posted via
+     * {@link #EVENT_DISPLAY_DIAGNOSTIC_MESSAGE}.  Used to ensure that the dialer app knows when
+     * the message in question has cleared via {@link #EVENT_CLEAR_DIAGNOSTIC_MESSAGE}.
+     */
+    public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID =
+            "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID";
+
+    /**
+     * {@link CharSequence} extra used with {@link #EVENT_DISPLAY_DIAGNOSTIC_MESSAGE}.  This is the
+     * diagnostic message the dialer app should display.
+     */
+    public static final String EXTRA_DIAGNOSTIC_MESSAGE =
+            "android.telecom.extra.DIAGNOSTIC_MESSAGE";
 
     /**
      * Reject reason used with {@link #reject(int)} to indicate that the user is rejecting this
diff --git a/telecomm/java/android/telecom/CallDiagnosticService.java b/telecomm/java/android/telecom/CallDiagnosticService.java
new file mode 100644
index 0000000..201c5db
--- /dev/null
+++ b/telecomm/java/android/telecom/CallDiagnosticService.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+
+import com.android.internal.telecom.ICallDiagnosticService;
+import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
+
+import java.util.Map;
+
+/**
+ * The platform supports a single OEM provided {@link CallDiagnosticService}, as defined by the
+ * {@code call_diagnostic_service_package_name} key in the
+ * {@code packages/services/Telecomm/res/values/config.xml} file.  An OEM can use this API to help
+ * provide more actionable information about calling issues the user encounters during and after
+ * a call.
+ *
+ * <h1>Manifest Declaration</h1>
+ * The following is an example of how to declare the service entry in the
+ * {@link CallDiagnosticService} manifest file:
+ * <pre>
+ * {@code
+ * <service android:name="your.package.YourCallDiagnosticServiceImplementation"
+ *          android:permission="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE">
+ *      <intent-filter>
+ *          <action android:name="android.telecom.CallDiagnosticService"/>
+ *      </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ * @hide
+ */
+@SystemApi
+public abstract class CallDiagnosticService extends Service {
+
+    /**
+     * Binder stub implementation which handles incoming requests from Telecom.
+     */
+    private final class CallDiagnosticServiceBinder extends ICallDiagnosticService.Stub {
+
+        @Override
+        public void setAdapter(ICallDiagnosticServiceAdapter adapter) throws RemoteException {
+            handleSetAdapter(adapter);
+        }
+
+        @Override
+        public void initializeDiagnosticCall(ParcelableCall call) throws RemoteException {
+            handleCallAdded(call);
+        }
+
+        @Override
+        public void updateCall(ParcelableCall call) throws RemoteException {
+            handleCallUpdated(call);
+        }
+
+        @Override
+        public void removeDiagnosticCall(String callId) throws RemoteException {
+            handleCallRemoved(callId);
+        }
+
+        @Override
+        public void updateCallAudioState(CallAudioState callAudioState) throws RemoteException {
+            onCallAudioStateChanged(callAudioState);
+        }
+
+        @Override
+        public void receiveDeviceToDeviceMessage(String callId, int message, int value) {
+            handleReceivedD2DMessage(callId, message, value);
+        }
+
+        @Override
+        public void receiveBluetoothCallQualityReport(BluetoothCallQualityReport qualityReport)
+                throws RemoteException {
+            handleBluetoothCallQualityReport(qualityReport);
+        }
+    }
+
+    /**
+     * Listens to events raised by a {@link DiagnosticCall}.
+     */
+    private android.telecom.DiagnosticCall.Listener mDiagnosticCallListener =
+            new android.telecom.DiagnosticCall.Listener() {
+
+                @Override
+                public void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall,
+                        @DiagnosticCall.MessageType int message, int value) {
+                    handleSendDeviceToDeviceMessage(diagnosticCall, message, value);
+                }
+
+                @Override
+                public void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
+                        CharSequence message) {
+                    handleDisplayDiagnosticMessage(diagnosticCall, messageId, message);
+                }
+
+                @Override
+                public void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) {
+                    handleClearDiagnosticMessage(diagnosticCall, messageId);
+                }
+            };
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService";
+
+    /**
+     * Map which tracks the Telecom calls received from the Telecom stack.
+     */
+    private final Map<String, Call.Details> mCallByTelecomCallId = new ArrayMap<>();
+    private final Map<String, DiagnosticCall> mDiagnosticCallByTelecomCallId = new ArrayMap<>();
+    private ICallDiagnosticServiceAdapter mAdapter;
+
+    @Nullable
+    @Override
+    public IBinder onBind(@NonNull Intent intent) {
+        Log.i(this, "onBind!");
+        return new CallDiagnosticServiceBinder();
+    }
+
+    /**
+     * Telecom calls this method on the {@link CallDiagnosticService} with details about a new call
+     * which was added to Telecom.
+     * <p>
+     * The {@link CallDiagnosticService} returns an implementation of {@link DiagnosticCall} to be
+     * used for the lifespan of this call.
+     *
+     * @param call The details of the new call.
+     * @return An instance of {@link DiagnosticCall} which the {@link CallDiagnosticService}
+     * provides to be used for the lifespan of the call.
+     * @throws IllegalArgumentException if a {@code null} {@link DiagnosticCall} is returned.
+     */
+    public abstract @NonNull DiagnosticCall onInitializeDiagnosticCall(@NonNull
+            android.telecom.Call.Details call);
+
+    /**
+     * Telecom calls this method when a previous created {@link DiagnosticCall} is no longer needed.
+     * This happens when Telecom is no longer tracking the call in question.
+     * @param call The diagnostic call which is no longer tracked by Telecom.
+     */
+    public abstract void onRemoveDiagnosticCall(@NonNull DiagnosticCall call);
+
+    /**
+     * Telecom calls this method when the audio routing or available audio route information
+     * changes.
+     * <p>
+     * Audio state is common to all calls.
+     *
+     * @param audioState The new audio state.
+     */
+    public abstract void onCallAudioStateChanged(
+            @NonNull CallAudioState audioState);
+
+    /**
+     * Telecom calls this method when a {@link BluetoothCallQualityReport} is received from the
+     * bluetooth stack.
+     * @param qualityReport the {@link BluetoothCallQualityReport}.
+     */
+    public abstract void onBluetoothCallQualityReportReceived(
+            @NonNull BluetoothCallQualityReport qualityReport);
+
+    /**
+     * Handles a request from Telecom to set the adapater used to communicate back to Telecom.
+     * @param adapter
+     */
+    private void handleSetAdapter(@NonNull ICallDiagnosticServiceAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    /**
+     * Handles a request from Telecom to add a new call.
+     * @param parcelableCall
+     */
+    private void handleCallAdded(@NonNull ParcelableCall parcelableCall) {
+        String telecomCallId = parcelableCall.getId();
+        Log.i(this, "handleCallAdded: callId=%s - added", telecomCallId);
+        Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
+        mCallByTelecomCallId.put(telecomCallId, newCallDetails);
+
+        DiagnosticCall diagnosticCall = onInitializeDiagnosticCall(newCallDetails);
+        if (diagnosticCall == null) {
+            throw new IllegalArgumentException("A valid DiagnosticCall instance was not provided.");
+        }
+        diagnosticCall.setListener(mDiagnosticCallListener);
+        diagnosticCall.setCallId(telecomCallId);
+        mDiagnosticCallByTelecomCallId.put(telecomCallId, diagnosticCall);
+    }
+
+    /**
+     * Handles an update to {@link Call.Details} notified by Telecom.
+     * Caches the call details and notifies the {@link DiagnosticCall} of the change via
+     * {@link DiagnosticCall#onCallDetailsChanged(Call.Details)}.
+     * @param parcelableCall the new parceled call details from Telecom.
+     */
+    private void handleCallUpdated(@NonNull ParcelableCall parcelableCall) {
+        String telecomCallId = parcelableCall.getId();
+        Log.i(this, "handleCallUpdated: callId=%s - updated", telecomCallId);
+        Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
+
+        DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(telecomCallId);
+        mCallByTelecomCallId.put(telecomCallId, newCallDetails);
+        diagnosticCall.handleCallUpdated(newCallDetails);
+    }
+
+    /**
+     * Handles a request from Telecom to remove an existing call.
+     * @param telecomCallId
+     */
+    private void handleCallRemoved(@NonNull String telecomCallId) {
+        Log.i(this, "handleCallRemoved: callId=%s - removed", telecomCallId);
+
+        if (mCallByTelecomCallId.containsKey(telecomCallId)) {
+            mCallByTelecomCallId.remove(telecomCallId);
+        }
+        if (mDiagnosticCallByTelecomCallId.containsKey(telecomCallId)) {
+            DiagnosticCall call = mDiagnosticCallByTelecomCallId.remove(telecomCallId);
+            // Inform the service of the removed call.
+            onRemoveDiagnosticCall(call);
+        }
+    }
+
+    /**
+     * Handles an incoming device to device message received from Telecom.  Notifies the
+     * {@link DiagnosticCall} via {@link DiagnosticCall#onReceiveDeviceToDeviceMessage(int, int)}.
+     * @param callId
+     * @param message
+     * @param value
+     */
+    private void handleReceivedD2DMessage(@NonNull String callId, int message, int value) {
+        Log.i(this, "handleReceivedD2DMessage: callId=%s, msg=%d/%d", callId, message, value);
+        DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId);
+        diagnosticCall.onReceiveDeviceToDeviceMessage(message, value);
+    }
+
+    /**
+     * Handles an incoming bluetooth call quality report from Telecom.  Notifies via
+     * {@link CallDiagnosticService#onBluetoothCallQualityReportReceived(
+     * BluetoothCallQualityReport)}.
+     * @param qualityReport The bluetooth call quality remote.
+     */
+    private void handleBluetoothCallQualityReport(@NonNull BluetoothCallQualityReport
+            qualityReport) {
+        Log.i(this, "handleBluetoothCallQualityReport; report=%s", qualityReport);
+        onBluetoothCallQualityReportReceived(qualityReport);
+    }
+
+    /**
+     * Handles a request from a {@link DiagnosticCall} to send a device to device message (received
+     * via {@link DiagnosticCall#sendDeviceToDeviceMessage(int, int)}.
+     * @param diagnosticCall
+     * @param message
+     * @param value
+     */
+    private void handleSendDeviceToDeviceMessage(@NonNull DiagnosticCall diagnosticCall,
+            int message, int value) {
+        String callId = diagnosticCall.getCallId();
+        try {
+            mAdapter.sendDeviceToDeviceMessage(callId, message, value);
+            Log.i(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d", callId, message,
+                    value);
+        } catch (RemoteException e) {
+            Log.w(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d failed %s",
+                    callId, message, value, e);
+        }
+    }
+
+    /**
+     * Handles a request from a {@link DiagnosticCall} to display an in-call diagnostic message.
+     * Originates from {@link DiagnosticCall#displayDiagnosticMessage(int, CharSequence)}.
+     * @param diagnosticCall
+     * @param messageId
+     * @param message
+     */
+    private void handleDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
+            CharSequence message) {
+        String callId = diagnosticCall.getCallId();
+        try {
+            mAdapter.displayDiagnosticMessage(callId, messageId, message);
+            Log.i(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s", callId, messageId,
+                    message);
+        } catch (RemoteException e) {
+            Log.w(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s failed %s",
+                    callId, messageId, message, e);
+        }
+    }
+
+    /**
+     * Handles a request from a {@link DiagnosticCall} to clear a previously shown diagnostic
+     * message.
+     * Originates from {@link DiagnosticCall#clearDiagnosticMessage(int)}.
+     * @param diagnosticCall
+     * @param messageId
+     */
+    private void handleClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) {
+        String callId = diagnosticCall.getCallId();
+        try {
+            mAdapter.clearDiagnosticMessage(callId, messageId);
+            Log.i(this, "handleClearDiagnosticMessage: call=%s; msg=%d", callId, messageId);
+        } catch (RemoteException e) {
+            Log.w(this, "handleClearDiagnosticMessage: call=%s; msg=%d failed %s",
+                    callId, messageId, e);
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 7c6253ce..335857af8 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -938,6 +938,46 @@
     public static final String EVENT_RTT_AUDIO_INDICATION_CHANGED =
             "android.telecom.event.RTT_AUDIO_INDICATION_CHANGED";
 
+    /**
+     * Connection event used to signal between the telephony {@link ConnectionService} and Telecom
+     * when device to device messages are sent/received.
+     * <p>
+     * Device to device messages originating from the network are sent by telephony using
+     * {@link Connection#sendConnectionEvent(String, Bundle)} and are routed up to any active
+     * {@link CallDiagnosticService} implementation which is active.
+     * <p>
+     * Likewise, if a {@link CallDiagnosticService} sends a message using
+     * {@link DiagnosticCall#sendDeviceToDeviceMessage(int, int)}, it will be routed to telephony
+     * via {@link Connection#onCallEvent(String, Bundle)}.  The telephony stack will relay the
+     * message to the other device.
+     * @hide
+     */
+    @SystemApi
+    public static final String EVENT_DEVICE_TO_DEVICE_MESSAGE =
+            "android.telecom.event.DEVICE_TO_DEVICE_MESSAGE";
+
+    /**
+     * Sent along with {@link #EVENT_DEVICE_TO_DEVICE_MESSAGE} to indicate the device to device
+     * message type.
+     *
+     * See {@link DiagnosticCall} for more information.
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE =
+            "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_TYPE";
+
+    /**
+     * Sent along with {@link #EVENT_DEVICE_TO_DEVICE_MESSAGE} to indicate the device to device
+     * message value.
+     * <p>
+     * See {@link DiagnosticCall} for more information.
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE =
+            "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_VALUE";
+
     // Flag controlling whether PII is emitted into the logs
     private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
 
diff --git a/telecomm/java/android/telecom/DiagnosticCall.java b/telecomm/java/android/telecom/DiagnosticCall.java
new file mode 100644
index 0000000..a495289
--- /dev/null
+++ b/telecomm/java/android/telecom/DiagnosticCall.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.telephony.Annotation;
+import android.telephony.CallQuality;
+import android.telephony.ims.ImsReasonInfo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A {@link DiagnosticCall} provides a way for a {@link CallDiagnosticService} to receive diagnostic
+ * information about a mobile call on the device.  The {@link CallDiagnosticService} can generate
+ * mid-call diagnostic messages using the {@link #displayDiagnosticMessage(int, CharSequence)} API
+ * which provides the user with valuable information about conditions impacting their call and
+ * corrective actions.  For example, if the {@link CallDiagnosticService} determines that conditions
+ * on the call are degrading, it can inform the user that the call may soon drop and that they
+ * can try using a different calling method (e.g. VOIP or WIFI).
+ * @hide
+ */
+@SystemApi
+public abstract class DiagnosticCall {
+
+    /**
+     * @hide
+     */
+    public interface Listener {
+        void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall, int message, int value);
+        void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
+                CharSequence message);
+        void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId);
+    }
+
+    /**
+     * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
+     * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the radio access type
+     * used for the current call.  Based loosely on the
+     * {@link android.telephony.TelephonyManager#getNetworkType(int)} for the call, provides a
+     * high level summary of the call radio access type.
+     * <p>
+     * Valid values:
+     * <UL>
+     *     <LI>{@link #NETWORK_TYPE_LTE}</LI>
+     *     <LI>{@link #NETWORK_TYPE_IWLAN}</LI>
+     *     <LI>{@link #NETWORK_TYPE_NR}</LI>
+     * </UL>
+     */
+    public static final int MESSAGE_CALL_NETWORK_TYPE = 1;
+
+    /**
+     * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
+     * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the call audio codec
+     * used for the current call.  Based loosely on the {@link Connection#EXTRA_AUDIO_CODEC} for a
+     * call.
+     * <p>
+     * Valid values:
+     * <UL>
+     *     <LI>{@link #AUDIO_CODEC_EVS}</LI>
+     *     <LI>{@link #AUDIO_CODEC_AMR_WB}</LI>
+     *     <LI>{@link #AUDIO_CODEC_AMR_NB}</LI>
+     * </UL>
+     */
+    public static final int MESSAGE_CALL_AUDIO_CODEC = 2;
+
+    /**
+     * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
+     * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the battery state of
+     * the device.  Will typically mirror battery state reported via intents such as
+     * {@link android.content.Intent#ACTION_BATTERY_LOW}.
+     * <p>
+     * Valid values:
+     * <UL>
+     *     <LI>{@link #BATTERY_STATE_LOW}</LI>
+     *     <LI>{@link #BATTERY_STATE_GOOD}</LI>
+     *     <LI>{@link #BATTERY_STATE_CHARGING}</LI>
+     * </UL>
+     */
+    public static final int MESSAGE_DEVICE_BATTERY_STATE = 3;
+
+    /**
+     * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
+     * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the overall network
+     * coverage as it pertains to the current call.  A {@link CallDiagnosticService} should signal
+     * poor coverage if the network coverage reaches a level where there is a high probability of
+     * the call dropping as a result.
+     * <p>
+     * Valid values:
+     * <UL>
+     *     <LI>{@link #COVERAGE_POOR}</LI>
+     *     <LI>{@link #COVERAGE_GOOD}</LI>
+     * </UL>
+     */
+    public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4;
+
+    /**@hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "MESSAGE_", value = {
+            MESSAGE_CALL_NETWORK_TYPE,
+            MESSAGE_CALL_AUDIO_CODEC,
+            MESSAGE_DEVICE_BATTERY_STATE,
+            MESSAGE_DEVICE_NETWORK_COVERAGE
+    })
+    public @interface MessageType {}
+
+    /**
+     * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate an LTE network is being used for the
+     * call.
+     */
+    public static final int NETWORK_TYPE_LTE = 1;
+
+    /**
+     * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate WIFI calling is in use for the call.
+     */
+    public static final int NETWORK_TYPE_IWLAN = 2;
+
+    /**
+     * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate a 5G NR (new radio) network is in
+     * used for the call.
+     */
+    public static final int NETWORK_TYPE_NR = 3;
+
+    /**
+     * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the
+     * Enhanced Voice Services (EVS) codec for the call.
+     */
+    public static final int AUDIO_CODEC_EVS = 1;
+
+    /**
+     * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the AMR
+     * (adaptive multi-rate) WB (wide band) audio codec.
+     */
+    public static final int AUDIO_CODEC_AMR_WB = 2;
+
+    /**
+     * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the AMR
+     * (adaptive multi-rate) NB (narrow band) audio codec.
+     */
+    public static final int AUDIO_CODEC_AMR_NB = 3;
+
+    /**
+     * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is low.
+     */
+    public static final int BATTERY_STATE_LOW = 1;
+
+    /**
+     * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is not low.
+     */
+    public static final int BATTERY_STATE_GOOD = 2;
+
+    /**
+     * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is charging.
+     */
+    public static final int BATTERY_STATE_CHARGING = 3;
+
+    /**
+     * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is poor.
+     */
+    public static final int COVERAGE_POOR = 1;
+
+    /**
+     * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is good.
+     */
+    public static final int COVERAGE_GOOD = 2;
+
+    private Listener mListener;
+    private String mCallId;
+    private Call.Details mCallDetails;
+
+    /**
+     * @hide
+     */
+    public void setListener(@NonNull Listener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Sets the call ID for this {@link DiagnosticCall}.
+     * @param callId
+     * @hide
+     */
+    public void setCallId(@NonNull String callId) {
+        mCallId = callId;
+    }
+
+    /**
+     * @return the Telecom call ID for this {@link DiagnosticCall}.
+     * @hide
+     */
+    public @NonNull String getCallId() {
+        return mCallId;
+    }
+
+    /**
+     * Returns the latest {@link Call.Details} associated with this {@link DiagnosticCall} as
+     * reported by {@link #onCallDetailsChanged(Call.Details)}.
+     * @return The latest {@link Call.Details}.
+     */
+    public @NonNull Call.Details getCallDetails() {
+        return mCallDetails;
+    }
+
+    /**
+     * Telecom calls this method when the details of a call changes.
+     */
+    public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details details);
+
+    /**
+     * The {@link CallDiagnosticService} implements this method to handle messages received via
+     * device to device communication.
+     * <p>
+     * See {@link #sendDeviceToDeviceMessage(int, int)} for background on device to device
+     * communication.
+     * <p>
+     * The underlying device to device communication protocol assumes that where there the two
+     * devices communicating are using a different version of the protocol, messages the recipient
+     * are not aware of are silently discarded.  This means an older client talking to a new client
+     * will not receive newer messages and values sent by the new client.
+     */
+    public abstract void onReceiveDeviceToDeviceMessage(
+            @MessageType int message,
+            int value);
+
+    /**
+     * Sends a device to device message to the device on the other end of this call.
+     * <p>
+     * Device to device communication is an Android platform feature which supports low bandwidth
+     * communication between Android devices while they are in a call.  The device to device
+     * communication leverages DTMF tones or RTP header extensions to pass messages.  The
+     * messages are unacknowledged and sent in a best-effort manner.  The protocols assume that the
+     * nature of the message are informational only and are used only to convey basic state
+     * information between devices.
+     * <p>
+     * Device to device messages are intentional simplifications of more rich indicators in the
+     * platform due to the extreme bandwidth constraints inherent with underlying device to device
+     * communication transports used by the telephony framework.  Device to device communication is
+     * either accomplished by adding RFC8285 compliant RTP header extensions to the audio packets
+     * for a call, or using the DTMF digits A-D as a communication pathway.  Signalling requirements
+     * for DTMF digits place a significant limitation on the amount of information which can be
+     * communicated during a call.
+     * <p>
+     * Allowed message types and values are:
+     * <ul>
+     *     <li>{@link #MESSAGE_CALL_NETWORK_TYPE}
+     *         <ul>
+     *             <li>{@link #NETWORK_TYPE_LTE}</li>
+     *             <li>{@link #NETWORK_TYPE_IWLAN}</li>
+     *             <li>{@link #NETWORK_TYPE_NR}</li>
+     *         </ul>
+     *     </li>
+     *     <li>{@link #MESSAGE_CALL_AUDIO_CODEC}
+     *         <ul>
+     *             <li>{@link #AUDIO_CODEC_EVS}</li>
+     *             <li>{@link #AUDIO_CODEC_AMR_WB}</li>
+     *             <li>{@link #AUDIO_CODEC_AMR_NB}</li>
+     *         </ul>
+     *     </li>
+     *     <li>{@link #MESSAGE_DEVICE_BATTERY_STATE}
+     *         <ul>
+     *             <li>{@link #BATTERY_STATE_LOW}</li>
+     *             <li>{@link #BATTERY_STATE_GOOD}</li>
+     *             <li>{@link #BATTERY_STATE_CHARGING}</li>
+     *         </ul>
+     *     </li>
+     *     <li>{@link #MESSAGE_DEVICE_NETWORK_COVERAGE}
+     *         <ul>
+     *             <li>{@link #COVERAGE_POOR}</li>
+     *             <li>{@link #COVERAGE_GOOD}</li>
+     *         </ul>
+     *     </li>
+     * </ul>
+     * @param message The message type to send.
+     * @param value The message value corresponding to the type.
+     */
+    public final void sendDeviceToDeviceMessage(int message, int value) {
+        if (mListener != null) {
+            mListener.onSendDeviceToDeviceMessage(this, message, value);
+        }
+    }
+
+    /**
+     * Telecom calls this method when a GSM or CDMA call disconnects.
+     * The CallDiagnosticService can return a human readable disconnect message which will be passed
+     * to the Dialer app as the {@link DisconnectCause#getDescription()}.  A dialer app typically
+     * shows this message at the termination of the call.  If {@code null} is returned, the
+     * disconnect message generated by the telephony stack will be shown instead.
+     * <p>
+     * @param disconnectCause the disconnect cause for the call.
+     * @param preciseDisconnectCause the precise disconnect cause for the call.
+     * @return the disconnect message to use in place of the default Telephony message, or
+     * {@code null} if the default message will not be overridden.
+     */
+    // TODO: Wire in Telephony support for this.
+    public abstract @Nullable CharSequence onCallDisconnected(
+            @Annotation.DisconnectCauses int disconnectCause,
+            @Annotation.PreciseDisconnectCauses int preciseDisconnectCause);
+
+    /**
+     * Telecom calls this method when an IMS call disconnects and Telephony has already
+     * provided the disconnect reason info and disconnect message for the call.  The
+     * {@link CallDiagnosticService} can intercept the raw IMS disconnect reason at this point and
+     * combine it with other call diagnostic information it is aware of to override the disconnect
+     * call message if desired.
+     *
+     * @param disconnectReason The {@link ImsReasonInfo} associated with the call disconnection.
+     * @return A user-readable call disconnect message to use in place of the platform-generated
+     * disconnect message, or {@code null} if the disconnect message should not be overridden.
+     */
+    // TODO: Wire in Telephony support for this.
+    public abstract @Nullable CharSequence onCallDisconnected(
+            @NonNull ImsReasonInfo disconnectReason);
+
+    /**
+     * Telecom calls this method when a {@link CallQuality} report is received from the telephony
+     * stack for a call.
+     * @param callQuality The call quality report for this call.
+     */
+    public abstract void onCallQualityReceived(@NonNull CallQuality callQuality);
+
+     /**
+      * Signals the active default dialer app to display a call diagnostic message.  This can be
+      * used to report problems encountered during the span of a call.
+      * <p>
+      * The {@link CallDiagnosticService} provides a unique client-specific identifier used to
+      * identify the specific diagnostic message type.
+      * <p>
+      * The {@link CallDiagnosticService} should call {@link #clearDiagnosticMessage(int)} when the
+      * diagnostic condition has cleared.
+      * @param messageId the unique message identifier.
+      * @param message a human-readable, localized message to be shown to the user indicating a
+      *                call issue which has occurred, along with potential mitigating actions.
+     */
+    public final void displayDiagnosticMessage(int messageId, @NonNull
+            CharSequence message) {
+        if (mListener != null) {
+            mListener.onDisplayDiagnosticMessage(this, messageId, message);
+        }
+    }
+
+    /**
+     * Signals to the active default dialer that the diagnostic message previously signalled using
+     * {@link #displayDiagnosticMessage(int, CharSequence)} with the specified messageId is no
+     * longer applicable (e.g. service has improved, for example.
+     * @param messageId the message identifier for a message previously shown via
+     *                  {@link #displayDiagnosticMessage(int, CharSequence)}.
+     */
+    public final void clearDiagnosticMessage(int messageId) {
+        if (mListener != null) {
+            mListener.onClearDiagnosticMessage(this, messageId);
+        }
+    }
+
+    /**
+     * Called by the {@link CallDiagnosticService} to update the call details for this
+     * {@link DiagnosticCall} based on an update received from Telecom.
+     * @param newDetails the new call details.
+     * @hide
+     */
+    public void handleCallUpdated(@NonNull Call.Details newDetails) {
+        mCallDetails = newDetails;
+        onCallDetailsChanged(newDetails);
+    }
+}
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index 2a4fdcb..922eddb 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -522,7 +522,7 @@
             return "";
         }
         return Arrays.stream(packageName.split("\\."))
-                .map(s -> s.substring(0,1))
+                .map(s -> s.length() == 0 ? "" : s.substring(0, 1))
                 .collect(Collectors.joining(""));
     }
 }
diff --git a/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
new file mode 100644
index 0000000..65b4d19
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telecom;
+
+import android.telecom.BluetoothCallQualityReport;
+import android.telecom.CallAudioState;
+import android.telecom.ParcelableCall;
+import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
+
+/**
+ * Internal remote interface for a call diagnostic service.
+ * @see android.telecom.CallDiagnosticService
+ * @hide
+ */
+oneway interface ICallDiagnosticService {
+    void setAdapter(in ICallDiagnosticServiceAdapter adapter);
+    void initializeDiagnosticCall(in ParcelableCall call);
+    void updateCall(in ParcelableCall call);
+    void updateCallAudioState(in CallAudioState callAudioState);
+    void removeDiagnosticCall(in String callId);
+    void receiveDeviceToDeviceMessage(in String callId, int message, int value);
+    void receiveBluetoothCallQualityReport(in BluetoothCallQualityReport qualityReport);
+}
diff --git a/telecomm/java/com/android/internal/telecom/ICallDiagnosticServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallDiagnosticServiceAdapter.aidl
new file mode 100644
index 0000000..92eec2a
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallDiagnosticServiceAdapter.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telecom;
+
+import android.telecom.CallAudioState;
+import android.telecom.ParcelableCall;
+
+/**
+ * Remote interface for messages from the CallDiagnosticService to the platform.
+ * @see android.telecom.CallDiagnosticService
+ * @hide
+ */
+oneway interface ICallDiagnosticServiceAdapter {
+    void displayDiagnosticMessage(in String callId, int messageId, in CharSequence message);
+    void clearDiagnosticMessage(in String callId, int messageId);
+    void sendDeviceToDeviceMessage(in String callId, int message, int value);
+    void overrideDisconnectMessage(in String callId, in CharSequence message);
+}
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index eb106b5..78283fa 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -353,4 +353,8 @@
      */
     void setTestDefaultDialer(in String packageName);
 
+    /**
+     * @see TelecomServiceImpl#setTestCallDiagnosticService
+     */
+    void setTestCallDiagnosticService(in String packageName);
 }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1473b7a..cf4e677 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -891,6 +891,14 @@
     public static final String PROFILE_CLASS = SimInfo.COLUMN_PROFILE_CLASS;
 
     /**
+     * TelephonyProvider column name for VoIMS opt-in status.
+     *
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String VOIMS_OPT_IN_STATUS = SimInfo.COLUMN_VOIMS_OPT_IN_STATUS;
+
+    /**
      * Profile class of the subscription
      * @hide
      */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 61e809b..b46440d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -9129,18 +9129,11 @@
      */
     public static final int CALL_COMPOSER_STATUS_ON = 1;
 
-    /**
-     * Call composer status indicating that sending/receiving pictures is disabled.
-     * All other attachments are still enabled in this state.
-     */
-    public static final int CALL_COMPOSER_STATUS_ON_NO_PICTURES = 2;
-
     /** @hide */
     @IntDef(prefix = {"CALL_COMPOSER_STATUS_"},
             value = {
                 CALL_COMPOSER_STATUS_ON,
                 CALL_COMPOSER_STATUS_OFF,
-                CALL_COMPOSER_STATUS_ON_NO_PICTURES,
             })
     public @interface CallComposerStatus {}
 
@@ -9148,9 +9141,8 @@
      * Set the user-set status for enriched calling with call composer.
      *
      * @param status user-set status for enriched calling with call composer;
-     *               it must be any of {@link #CALL_COMPOSER_STATUS_ON}
-     *               {@link #CALL_COMPOSER_STATUS_OFF},
-     *               or {@link #CALL_COMPOSER_STATUS_ON_NO_PICTURES}
+     *               it must be either {@link #CALL_COMPOSER_STATUS_ON} or
+     *               {@link #CALL_COMPOSER_STATUS_OFF}.
      *
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
      * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
@@ -9160,7 +9152,7 @@
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void setCallComposerStatus(@CallComposerStatus int status) {
-        if (status > CALL_COMPOSER_STATUS_ON_NO_PICTURES
+        if (status > CALL_COMPOSER_STATUS_ON
                 || status < CALL_COMPOSER_STATUS_OFF) {
             throw new IllegalArgumentException("requested status is invalid");
         }
@@ -9183,9 +9175,8 @@
      *
      * @throws SecurityException if the caller does not have the permission.
      *
-     * @return the user-set status for enriched calling with call composer, any of
-     * {@link #CALL_COMPOSER_STATUS_ON}, {@link #CALL_COMPOSER_STATUS_OFF}, or
-     * {@link #CALL_COMPOSER_STATUS_ON_NO_PICTURES}.
+     * @return the user-set status for enriched calling with call composer, either of
+     * {@link #CALL_COMPOSER_STATUS_ON} or {@link #CALL_COMPOSER_STATUS_OFF}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public @CallComposerStatus int getCallComposerStatus() {
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 6fda2e1..0ab679f 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -866,6 +866,19 @@
     public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67;
 
     /**
+     * An integer key representing the voice over IMS opt-in provisioning status for the
+     * associated subscription. Determines whether the user can see for voice services over
+     * IMS.
+     * <p>
+     * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VoIMS provisioning and
+     * {@link #PROVISIONING_VALUE_DISABLED} to disable VoIMS  provisioning.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     * @hide
+     */
+    public static final int KEY_VOIMS_OPT_IN_STATUS = 68;
+
+    /**
      * Callback for IMS provisioning changes.
      */
     public static class Callback {
diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
index 5eee389..1b5e560 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
@@ -47,4 +47,6 @@
     void removeRcsConfigCallback(IRcsConfigCallback c);
     void triggerRcsReconfiguration();
     void setRcsClientConfiguration(in RcsClientConfiguration rcc);
+    void notifyIntImsConfigChanged(int item, int value);
+    void notifyStringImsConfigChanged(int item, String value);
 }
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index 34984e0..21aeb64 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -258,6 +258,16 @@
         public void setRcsClientConfiguration(RcsClientConfiguration rcc) throws RemoteException {
             getImsConfigImpl().setRcsClientConfiguration(rcc);
         }
+
+        @Override
+        public void notifyIntImsConfigChanged(int item, int value) throws RemoteException {
+            notifyImsConfigChanged(item, value);
+        }
+
+        @Override
+        public void notifyStringImsConfigChanged(int item, String value) throws RemoteException {
+            notifyImsConfigChanged(item, value);
+        }
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 15d19a4..541292a 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_AIRPLANE_MODE_CHANGED = BASE + 57;
 
     /***** Constants *****/
 
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 60670ad..c6e2bc7 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -91,6 +91,10 @@
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED;
+import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_REMOVED;
+import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
+import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
 import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.IPPROTO_TCP;
 
@@ -218,6 +222,8 @@
 import android.net.VpnManager;
 import android.net.VpnTransportInfo;
 import android.net.metrics.IpConnectivityLog;
+import android.net.resolv.aidl.Nat64PrefixEventParcel;
+import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
 import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
@@ -244,7 +250,6 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.security.Credentials;
-import android.security.KeyStore;
 import android.system.Os;
 import android.telephony.TelephonyManager;
 import android.telephony.data.EpsBearerQosSessionAttributes;
@@ -276,6 +281,7 @@
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.QosCallbackTracker;
 import com.android.server.connectivity.Vpn;
+import com.android.server.connectivity.VpnProfileStore;
 import com.android.server.net.NetworkPinner;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.testutils.ExceptionUtils;
@@ -345,6 +351,9 @@
     private static final String TAG = "ConnectivityServiceTest";
 
     private static final int TIMEOUT_MS = 500;
+    // Broadcasts can take a long time to be delivered. The test will not wait for that long unless
+    // there is a failure, so use a long timeout.
+    private static final int BROADCAST_TIMEOUT_MS = 30_000;
     private static final int TEST_LINGER_DELAY_MS = 400;
     private static final int TEST_NASCENT_DELAY_MS = 300;
     // Chosen to be less than the linger and nascent timeout. This ensures that we can distinguish
@@ -431,7 +440,7 @@
     @Mock MockableSystemProperties mSystemProperties;
     @Mock EthernetManager mEthernetManager;
     @Mock NetworkPolicyManager mNetworkPolicyManager;
-    @Mock KeyStore mKeyStore;
+    @Mock VpnProfileStore mVpnProfileStore;
     @Mock SystemConfigManager mSystemConfigManager;
 
     private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
@@ -1115,7 +1124,7 @@
                             return mDeviceIdleInternal;
                         }
                     },
-                    mNetworkManagementService, mMockNetd, userId, mKeyStore);
+                    mNetworkManagementService, mMockNetd, userId, mVpnProfileStore);
         }
 
         public void setUids(Set<UidRange> uids) {
@@ -1294,8 +1303,9 @@
                 return mVMSHandlerThread;
             }
 
-            public KeyStore getKeyStore() {
-                return mKeyStore;
+            @Override
+            public VpnProfileStore getVpnProfileStore() {
+                return mVpnProfileStore;
             }
 
             public INetd getNetd() {
@@ -1679,7 +1689,7 @@
         }
 
         public Intent expectBroadcast() throws Exception {
-            return expectBroadcast(TIMEOUT_MS);
+            return expectBroadcast(BROADCAST_TIMEOUT_MS);
         }
 
         public void expectNoBroadcast(int timeoutMs) throws Exception {
@@ -5919,6 +5929,16 @@
         assertEquals("strict.example.com", cbi.getLp().getPrivateDnsServerName());
     }
 
+    private PrivateDnsValidationEventParcel makePrivateDnsValidationEvent(
+            final int netId, final String ipAddress, final String hostname, final int validation) {
+        final PrivateDnsValidationEventParcel event = new PrivateDnsValidationEventParcel();
+        event.netId = netId;
+        event.ipAddress = ipAddress;
+        event.hostname = hostname;
+        event.validation = validation;
+        return event;
+    }
+
     @Test
     public void testLinkPropertiesWithPrivateDnsValidationEvents() throws Exception {
         // The default on Android is opportunistic mode ("Automatic").
@@ -5949,8 +5969,9 @@
 
         // Send a validation event for a server that is not part of the current
         // resolver config. The validation event should be ignored.
-        mService.mNetdEventCallback.onPrivateDnsValidationEvent(
-                mCellNetworkAgent.getNetwork().netId, "", "145.100.185.18", true);
+        mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent(
+                makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, "",
+                        "145.100.185.18", VALIDATION_RESULT_SUCCESS));
         cellNetworkCallback.assertNoCallback();
 
         // Add a dns server to the LinkProperties.
@@ -5967,20 +5988,23 @@
 
         // Send a validation event containing a hostname that is not part of
         // the current resolver config. The validation event should be ignored.
-        mService.mNetdEventCallback.onPrivateDnsValidationEvent(
-                mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "hostname", true);
+        mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent(
+                makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId,
+                        "145.100.185.16", "hostname", VALIDATION_RESULT_SUCCESS));
         cellNetworkCallback.assertNoCallback();
 
         // Send a validation event where validation failed.
-        mService.mNetdEventCallback.onPrivateDnsValidationEvent(
-                mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", false);
+        mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent(
+                makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId,
+                        "145.100.185.16", "", VALIDATION_RESULT_FAILURE));
         cellNetworkCallback.assertNoCallback();
 
         // Send a validation event where validation succeeded for a server in
         // the current resolver config. A LinkProperties callback with updated
         // private dns fields should be sent.
-        mService.mNetdEventCallback.onPrivateDnsValidationEvent(
-                mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", true);
+        mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent(
+                makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId,
+                        "145.100.185.16", "", VALIDATION_RESULT_SUCCESS));
         cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED,
                 mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
@@ -7486,8 +7510,7 @@
     private void setupLegacyLockdownVpn() {
         final String profileName = "testVpnProfile";
         final byte[] profileTag = profileName.getBytes(StandardCharsets.UTF_8);
-        when(mKeyStore.contains(Credentials.LOCKDOWN_VPN)).thenReturn(true);
-        when(mKeyStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag);
+        when(mVpnProfileStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag);
 
         final VpnProfile profile = new VpnProfile(profileName);
         profile.name = "My VPN";
@@ -7495,7 +7518,7 @@
         profile.dnsServers = "8.8.8.8";
         profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK;
         final byte[] encodedProfile = profile.encode();
-        when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile);
+        when(mVpnProfileStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile);
     }
 
     private void establishLegacyLockdownVpn(Network underlying) throws Exception {
@@ -7824,6 +7847,16 @@
         return stacked;
     }
 
+    private Nat64PrefixEventParcel makeNat64PrefixEvent(final int netId, final int prefixOperation,
+            final String prefixAddress, final int prefixLength) {
+        final Nat64PrefixEventParcel event = new Nat64PrefixEventParcel();
+        event.netId = netId;
+        event.prefixOperation = prefixOperation;
+        event.prefixAddress = prefixAddress;
+        event.prefixLength = prefixLength;
+        return event;
+    }
+
     @Test
     public void testStackedLinkProperties() throws Exception {
         final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24");
@@ -7908,8 +7941,8 @@
         // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started.
         Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent);
         assertNull(mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getNat64Prefix());
-        mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
-                kNat64PrefixString, 96);
+        mService.mResolverUnsolEventCallback.onNat64PrefixEvent(
+                makeNat64PrefixEvent(cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96));
         LinkProperties lpBeforeClat = networkCallback.expectCallback(
                 CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent).getLp();
         assertEquals(0, lpBeforeClat.getStackedLinks().size());
@@ -7949,8 +7982,8 @@
                 .thenReturn(getClatInterfaceConfigParcel(myIpv4));
         // Change the NAT64 prefix without first removing it.
         // Expect clatd to be stopped and started with the new prefix.
-        mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
-                kOtherNat64PrefixString, 96);
+        mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent(
+                cellNetId, PREFIX_OPERATION_ADDED, kOtherNat64PrefixString, 96));
         networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
                 (lp) -> lp.getStackedLinks().size() == 0);
         verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
@@ -7998,8 +8031,8 @@
                 .thenReturn(getClatInterfaceConfigParcel(myIpv4));
 
         // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone.
-        mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */,
-                kOtherNat64PrefixString, 96);
+        mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent(
+                cellNetId, PREFIX_OPERATION_REMOVED, kOtherNat64PrefixString, 96));
         networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
                 (lp) -> lp.getNat64Prefix() == null);
 
@@ -8011,8 +8044,8 @@
         networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         assertRoutesRemoved(cellNetId, ipv4Subnet);  // Directly-connected routes auto-added.
         verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
-        mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
-                kNat64PrefixString, 96);
+        mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent(
+                cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96));
         networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
 
@@ -8024,8 +8057,8 @@
         verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME);
 
         // NAT64 prefix is removed. Expect that clat is stopped.
-        mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */,
-                kNat64PrefixString, 96);
+        mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent(
+                cellNetId, PREFIX_OPERATION_REMOVED, kNat64PrefixString, 96));
         networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
                 (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null);
         assertRoutesRemoved(cellNetId, ipv4Subnet, stackedDefault);
@@ -8113,8 +8146,8 @@
         inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
         inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
 
-        mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */,
-                pref64FromDnsStr, 96);
+        mService.mResolverUnsolEventCallback.onNat64PrefixEvent(
+                makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96));
         expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns);
         inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString());
 
@@ -8147,8 +8180,8 @@
         inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
 
         // Stopping prefix discovery results in a prefix removed notification.
-        mService.mNetdEventCallback.onNat64PrefixEvent(netId, false /* added */,
-                pref64FromDnsStr, 96);
+        mService.mResolverUnsolEventCallback.onNat64PrefixEvent(
+                makeNat64PrefixEvent(netId, PREFIX_OPERATION_REMOVED, pref64FromDnsStr, 96));
 
         inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString());
         inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString());
@@ -8186,8 +8219,8 @@
         inOrder.verify(mMockNetd).clatdStop(iface);
         inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
         inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
-        mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */,
-                pref64FromDnsStr, 96);
+        mService.mResolverUnsolEventCallback.onNat64PrefixEvent(
+                makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96));
         expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns);
         inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString());
         inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), any());
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index f5b85ca..5760211 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -22,6 +22,8 @@
 import static android.net.NetworkCapabilities.MIN_TRANSPORT;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
+import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
 import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
@@ -164,7 +166,8 @@
         mDnsManager.flushVmDnsCache();
         mDnsManager.updatePrivateDnsValidation(
                 new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_ALTERNATE,
-                InetAddress.parseNumericAddress("4.4.4.4"), "", true));
+                        InetAddress.parseNumericAddress("4.4.4.4"), "",
+                        VALIDATION_RESULT_SUCCESS));
         LinkProperties fixedLp = new LinkProperties(lp);
         mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp);
         assertFalse(fixedLp.isPrivateDnsActive());
@@ -204,7 +207,8 @@
         // Validate one.
         mDnsManager.updatePrivateDnsValidation(
                 new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
-                InetAddress.parseNumericAddress("6.6.6.6"), "strictmode.com", true));
+                        InetAddress.parseNumericAddress("6.6.6.6"), "strictmode.com",
+                        VALIDATION_RESULT_SUCCESS));
         fixedLp = new LinkProperties(lp);
         mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp);
         assertEquals(Arrays.asList(InetAddress.parseNumericAddress("6.6.6.6")),
@@ -212,7 +216,8 @@
         // Validate the 2nd one.
         mDnsManager.updatePrivateDnsValidation(
                 new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
-                InetAddress.parseNumericAddress("2001:db8:66:66::1"), "strictmode.com", true));
+                        InetAddress.parseNumericAddress("2001:db8:66:66::1"), "strictmode.com",
+                        VALIDATION_RESULT_SUCCESS));
         fixedLp = new LinkProperties(lp);
         mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp);
         assertEquals(Arrays.asList(
@@ -232,7 +237,8 @@
         mDnsManager.flushVmDnsCache();
         mDnsManager.updatePrivateDnsValidation(
                 new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
-                InetAddress.parseNumericAddress("3.3.3.3"), "", true));
+                        InetAddress.parseNumericAddress("3.3.3.3"), "",
+                        VALIDATION_RESULT_SUCCESS));
         mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
         assertFalse(lp.isPrivateDnsActive());
         assertNull(lp.getPrivateDnsServerName());
@@ -245,7 +251,8 @@
         mDnsManager.flushVmDnsCache();
         mDnsManager.updatePrivateDnsValidation(
                 new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_UNTRACKED,
-                InetAddress.parseNumericAddress("3.3.3.3"), "", true));
+                        InetAddress.parseNumericAddress("3.3.3.3"), "",
+                        VALIDATION_RESULT_SUCCESS));
         mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
         assertFalse(lp.isPrivateDnsActive());
         assertNull(lp.getPrivateDnsServerName());
@@ -253,7 +260,8 @@
         // Validation event has untracked ipAddress
         mDnsManager.updatePrivateDnsValidation(
                 new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
-                InetAddress.parseNumericAddress("4.4.4.4"), "", true));
+                        InetAddress.parseNumericAddress("4.4.4.4"), "",
+                        VALIDATION_RESULT_SUCCESS));
         mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
         assertFalse(lp.isPrivateDnsActive());
         assertNull(lp.getPrivateDnsServerName());
@@ -261,8 +269,8 @@
         // Validation event has untracked hostname
         mDnsManager.updatePrivateDnsValidation(
                 new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
-                InetAddress.parseNumericAddress("3.3.3.3"), "hostname",
-                true));
+                        InetAddress.parseNumericAddress("3.3.3.3"), "hostname",
+                        VALIDATION_RESULT_SUCCESS));
         mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
         assertFalse(lp.isPrivateDnsActive());
         assertNull(lp.getPrivateDnsServerName());
@@ -270,7 +278,8 @@
         // Validation event failed
         mDnsManager.updatePrivateDnsValidation(
                 new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
-                InetAddress.parseNumericAddress("3.3.3.3"), "", false));
+                        InetAddress.parseNumericAddress("3.3.3.3"), "",
+                        VALIDATION_RESULT_FAILURE));
         mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
         assertFalse(lp.isPrivateDnsActive());
         assertNull(lp.getPrivateDnsServerName());
@@ -279,7 +288,7 @@
         mDnsManager.removeNetwork(new Network(TEST_NETID));
         mDnsManager.updatePrivateDnsValidation(
                 new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
-                InetAddress.parseNumericAddress("3.3.3.3"), "", true));
+                        InetAddress.parseNumericAddress("3.3.3.3"), "", VALIDATION_RESULT_SUCCESS));
         mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
         assertFalse(lp.isPrivateDnsActive());
         assertNull(lp.getPrivateDnsServerName());
@@ -293,7 +302,8 @@
         mDnsManager.flushVmDnsCache();
         mDnsManager.updatePrivateDnsValidation(
                 new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
-                InetAddress.parseNumericAddress("3.3.3.3"), "", true));
+                        InetAddress.parseNumericAddress("3.3.3.3"), "",
+                        VALIDATION_RESULT_SUCCESS));
         mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
         assertFalse(lp.isPrivateDnsActive());
         assertNull(lp.getPrivateDnsServerName());
@@ -398,7 +408,8 @@
         mDnsManager.updatePrivateDns(network, mDnsManager.getPrivateDnsConfig());
         mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
         mDnsManager.updatePrivateDnsValidation(
-                new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, dnsAddr, "", true));
+                new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, dnsAddr, "",
+                        VALIDATION_RESULT_SUCCESS));
         mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
         privateDnsCfg = mDnsManager.getPrivateDnsConfig(network);
         assertTrue(privateDnsCfg.useTls);
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 7489a0f..b8f7fbc 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -91,7 +91,6 @@
 import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.security.Credentials;
-import android.security.KeyStore;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Range;
@@ -196,7 +195,7 @@
     @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
     @Mock private ConnectivityManager mConnectivityManager;
     @Mock private IpSecService mIpSecService;
-    @Mock private KeyStore mKeyStore;
+    @Mock private VpnProfileStore mVpnProfileStore;
     private final VpnProfile mVpnProfile;
 
     private IpSecManager mIpSecManager;
@@ -333,17 +332,17 @@
         assertFalse(vpn.getLockdown());
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList(), mKeyStore));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
         assertTrue(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList(), mKeyStore));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
         assertTrue(vpn.getAlwaysOn());
         assertTrue(vpn.getLockdown());
 
         // Remove always-on configuration.
-        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore));
+        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
         assertFalse(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
     }
@@ -354,17 +353,17 @@
         final UidRange user = PRI_USER_RANGE;
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
         }));
 
         // Switch to another app.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -382,14 +381,14 @@
 
         // Set always-on with lockdown and allow app PKGS[2] from lockdown.
         assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore));
+                PKGS[1], true, Collections.singletonList(PKGS[2])));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
         }));
         // Change allowed app list to PKGS[3].
         assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
+                PKGS[1], true, Collections.singletonList(PKGS[3])));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
         }));
@@ -400,7 +399,7 @@
 
         // Change the VPN app.
         assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore));
+                PKGS[0], true, Collections.singletonList(PKGS[3])));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
@@ -411,7 +410,7 @@
         }));
 
         // Remove the list of allowed packages.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -422,7 +421,7 @@
 
         // Add the list of allowed packages.
         assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore));
+                PKGS[0], true, Collections.singletonList(PKGS[1])));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.stop)
         }));
@@ -433,12 +432,12 @@
 
         // Try allowing a package with a comma, should be rejected.
         assertFalse(vpn.setAlwaysOnPackage(
-                PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore));
+                PKGS[0], true, Collections.singletonList("a.b,c.d")));
 
         // Pass a non-existent packages in the allowlist, they (and only they) should be ignored.
         // allowed package should change from PGKS[1] to PKGS[2].
         assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore));
+                PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -525,22 +524,22 @@
                 .thenReturn(Collections.singletonList(resInfo));
 
         // null package name should return false
-        assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore));
+        assertFalse(vpn.isAlwaysOnPackageSupported(null));
 
         // Pre-N apps are not supported
         appInfo.targetSdkVersion = VERSION_CODES.M;
-        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
+        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
 
         // N+ apps are supported by default
         appInfo.targetSdkVersion = VERSION_CODES.N;
-        assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
+        assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
 
         // Apps that opt out explicitly are not supported
         appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
         Bundle metaData = new Bundle();
         metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
         svcInfo.metaData = metaData;
-        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
+        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
     }
 
     @Test
@@ -556,7 +555,7 @@
         order.verify(mNotificationManager, atLeastOnce()).cancel(anyString(), anyInt());
 
         // Start showing a notification for disconnected once always-on.
-        vpn.setAlwaysOnPackage(PKGS[0], false, null, mKeyStore);
+        vpn.setAlwaysOnPackage(PKGS[0], false, null);
         order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
 
         // Stop showing the notification once connected.
@@ -568,7 +567,7 @@
         order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
 
         // Notification should be cleared after unsetting always-on package.
-        vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
+        vpn.setAlwaysOnPackage(null, false, null);
         order.verify(mNotificationManager).cancel(anyString(), anyInt());
     }
 
@@ -608,15 +607,13 @@
     }
 
     private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, String... checkedOps) {
-        assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore));
+        assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile));
 
         // The profile should always be stored, whether or not consent has been previously granted.
-        verify(mKeyStore)
+        verify(mVpnProfileStore)
                 .put(
                         eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)),
-                        eq(mVpnProfile.encode()),
-                        eq(Process.SYSTEM_UID),
-                        eq(0));
+                        eq(mVpnProfile.encode()));
 
         for (final String checkedOpStr : checkedOps) {
             verify(mAppOps).noteOpNoThrow(checkedOpStr, Process.myUid(), TEST_VPN_PKG,
@@ -671,7 +668,7 @@
         bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]);
 
         try {
-            vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile, mKeyStore);
+            vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile);
             fail("Expected IAE due to profile size");
         } catch (IllegalArgumentException expected) {
         }
@@ -684,7 +681,7 @@
                         restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         try {
-            vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore);
+            vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile);
             fail("Expected SecurityException due to restricted user");
         } catch (SecurityException expected) {
         }
@@ -694,10 +691,10 @@
     public void testDeleteVpnProfile() throws Exception {
         final Vpn vpn = createVpnAndSetupUidChecks();
 
-        vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore);
+        vpn.deleteVpnProfile(TEST_VPN_PKG);
 
-        verify(mKeyStore)
-                .delete(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), eq(Process.SYSTEM_UID));
+        verify(mVpnProfileStore)
+                .remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
     }
 
     @Test
@@ -707,7 +704,7 @@
                         restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         try {
-            vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore);
+            vpn.deleteVpnProfile(TEST_VPN_PKG);
             fail("Expected SecurityException due to restricted user");
         } catch (SecurityException expected) {
         }
@@ -717,24 +714,24 @@
     public void testGetVpnProfilePrivileged() throws Exception {
         final Vpn vpn = createVpnAndSetupUidChecks();
 
-        when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(new VpnProfile("").encode());
 
-        vpn.getVpnProfilePrivileged(TEST_VPN_PKG, mKeyStore);
+        vpn.getVpnProfilePrivileged(TEST_VPN_PKG);
 
-        verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
     }
 
     @Test
     public void testStartVpnProfile() throws Exception {
         final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
-        when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(mVpnProfile.encode());
 
-        vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
+        vpn.startVpnProfile(TEST_VPN_PKG);
 
-        verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
         verify(mAppOps)
                 .noteOpNoThrow(
                         eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
@@ -748,10 +745,10 @@
     public void testStartVpnProfileVpnServicePreconsented() throws Exception {
         final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);
 
-        when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(mVpnProfile.encode());
 
-        vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
+        vpn.startVpnProfile(TEST_VPN_PKG);
 
         // Verify that the the ACTIVATE_VPN appop was checked, but no error was thrown.
         verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
@@ -763,7 +760,7 @@
         final Vpn vpn = createVpnAndSetupUidChecks();
 
         try {
-            vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
+            vpn.startVpnProfile(TEST_VPN_PKG);
             fail("Expected failure due to no user consent");
         } catch (SecurityException expected) {
         }
@@ -780,22 +777,22 @@
                 TEST_VPN_PKG, null /* attributionTag */, null /* message */);
 
         // Keystore should never have been accessed.
-        verify(mKeyStore, never()).get(any());
+        verify(mVpnProfileStore, never()).get(any());
     }
 
     @Test
     public void testStartVpnProfileMissingProfile() throws Exception {
         final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
-        when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null);
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null);
 
         try {
-            vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
+            vpn.startVpnProfile(TEST_VPN_PKG);
             fail("Expected failure due to missing profile");
         } catch (IllegalArgumentException expected) {
         }
 
-        verify(mKeyStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG));
+        verify(mVpnProfileStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG));
         verify(mAppOps)
                 .noteOpNoThrow(
                         eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
@@ -812,7 +809,7 @@
                         restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         try {
-            vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
+            vpn.startVpnProfile(TEST_VPN_PKG);
             fail("Expected SecurityException due to restricted user");
         } catch (SecurityException expected) {
         }
@@ -938,9 +935,9 @@
     }
 
     private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
-        assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore));
+        assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null));
 
-        verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
         verify(mAppOps).setMode(
                 eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
                 eq(AppOpsManager.MODE_ALLOWED));
@@ -963,11 +960,11 @@
         final int uid = Process.myUid() + 1;
         when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
                 .thenReturn(uid);
-        when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(mVpnProfile.encode());
 
         setAndVerifyAlwaysOnPackage(vpn, uid, false);
-        assertTrue(vpn.startAlwaysOnVpn(mKeyStore));
+        assertTrue(vpn.startAlwaysOnVpn());
 
         // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
         // a subsequent CL.
@@ -984,7 +981,7 @@
                         InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
         lp.addRoute(defaultRoute);
 
-        vpn.startLegacyVpn(vpnProfile, mKeyStore, EGRESS_NETWORK, lp);
+        vpn.startLegacyVpn(vpnProfile, EGRESS_NETWORK, lp);
         return vpn;
     }
 
@@ -1186,7 +1183,7 @@
                 .thenReturn(asUserContext);
         final TestLooper testLooper = new TestLooper();
         final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService,
-                mNetd, userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
+                mNetd, userId, mVpnProfileStore, mSystemServices, mIkev2SessionCreator);
         verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat(
                 provider -> provider.getName().contains("VpnNetworkProvider")
         ));
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 69c21b9..69b2fb1 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
@@ -143,11 +144,18 @@
                 .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform());
         mTestLooper.dispatchAll();
 
+        verify(mIpSecSvc, times(2))
+                .setNetworkForTunnelInterface(
+                        eq(TEST_IPSEC_TUNNEL_RESOURCE_ID),
+                        eq(TEST_UNDERLYING_NETWORK_RECORD_1.network),
+                        any());
+
         for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) {
             verify(mIpSecSvc)
                     .applyTunnelModeTransform(
                             eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
         }
+
         assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
     }
 
@@ -290,4 +298,22 @@
         verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
                 new TemporaryFailureException("vcn test"), VCN_ERROR_CODE_INTERNAL_ERROR);
     }
+
+    @Test
+    public void testTeardown() throws Exception {
+        mGatewayConnection.teardownAsynchronously();
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        assertTrue(mGatewayConnection.isQuitting());
+    }
+
+    @Test
+    public void testNonTeardownDisconnectRequest() throws Exception {
+        mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        assertFalse(mGatewayConnection.isQuitting());
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index 17ae19e..d07d2cf 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -19,6 +19,8 @@
 import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -111,4 +113,22 @@
     public void testSafeModeTimeoutNotifiesCallback() {
         verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
     }
+
+    @Test
+    public void testTeardown() throws Exception {
+        mGatewayConnection.teardownAsynchronously();
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        assertTrue(mGatewayConnection.isQuitting());
+    }
+
+    @Test
+    public void testNonTeardownDisconnectRequest() throws Exception {
+        mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        assertFalse(mGatewayConnection.isQuitting());
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 9ea641f..5f27fab 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -21,9 +21,12 @@
 import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.net.IpSecManager;
@@ -54,7 +57,7 @@
     }
 
     @Test
-    public void testEnterWhileNotRunningTriggersQuit() throws Exception {
+    public void testEnterWhileQuittingTriggersQuit() throws Exception {
         final VcnGatewayConnection vgc =
                 new VcnGatewayConnection(
                         mVcnContext,
@@ -64,7 +67,7 @@
                         mGatewayStatusCallback,
                         mDeps);
 
-        vgc.setIsRunning(false);
+        vgc.setIsQuitting(true);
         vgc.transitionTo(vgc.mDisconnectedState);
         mTestLooper.dispatchAll();
 
@@ -101,5 +104,18 @@
         assertNull(mGatewayConnection.getCurrentState());
         verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any());
         verifySafeModeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
+        assertTrue(mGatewayConnection.isQuitting());
+        verify(mGatewayStatusCallback).onQuit();
+    }
+
+    @Test
+    public void testNonTeardownDisconnectRequest() throws Exception {
+        mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+        assertFalse(mGatewayConnection.isQuitting());
+        verify(mGatewayStatusCallback, never()).onQuit();
+        // No safe mode timer changes expected.
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
index 7385204..661e03a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -18,6 +18,8 @@
 
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
@@ -79,10 +81,20 @@
         // Should do nothing; already tearing down.
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        assertTrue(mGatewayConnection.isQuitting());
     }
 
     @Test
     public void testSafeModeTimeoutNotifiesCallback() {
         verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
     }
+
+    @Test
+    public void testNonTeardownDisconnectRequest() throws Exception {
+        mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        assertFalse(mGatewayConnection.isQuitting());
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
index 5b0850b..85a0277 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
@@ -17,6 +17,9 @@
 package com.android.server.vcn;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -96,4 +99,22 @@
     public void testSafeModeTimeoutNotifiesCallback() {
         verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
     }
+
+    @Test
+    public void testTeardownDisconnectRequest() throws Exception {
+        mGatewayConnection.teardownAsynchronously();
+        mTestLooper.dispatchAll();
+
+        assertNull(mGatewayConnection.getCurrentState());
+        assertTrue(mGatewayConnection.isQuitting());
+    }
+
+    @Test
+    public void testNonTeardownDisconnectRequest() throws Exception {
+        mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+        assertFalse(mGatewayConnection.isQuitting());
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index 9d33682..3dd710a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -16,6 +16,10 @@
 
 package com.android.server.vcn;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.mockito.Matchers.any;
@@ -33,6 +37,7 @@
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.os.ParcelUuid;
 import android.os.test.TestLooper;
+import android.util.ArraySet;
 
 import com.android.server.VcnManagementService.VcnCallback;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
@@ -51,6 +56,11 @@
     private static final ParcelUuid TEST_SUB_GROUP = new ParcelUuid(new UUID(0, 0));
     private static final int NETWORK_SCORE = 0;
     private static final int PROVIDER_ID = 5;
+    private static final int[][] TEST_CAPS =
+            new int[][] {
+                new int[] {NET_CAPABILITY_INTERNET, NET_CAPABILITY_MMS},
+                new int[] {NET_CAPABILITY_DUN}
+            };
 
     private Context mContext;
     private VcnContext mVcnContext;
@@ -91,13 +101,12 @@
         mGatewayStatusCallbackCaptor = ArgumentCaptor.forClass(VcnGatewayStatusCallback.class);
 
         final VcnConfig.Builder configBuilder = new VcnConfig.Builder(mContext);
-        for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
+        for (final int[] caps : TEST_CAPS) {
             configBuilder.addGatewayConnectionConfig(
-                    VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(capability));
+                    VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(caps));
         }
-        configBuilder.addGatewayConnectionConfig(VcnGatewayConnectionConfigTest.buildTestConfig());
-        mConfig = configBuilder.build();
 
+        mConfig = configBuilder.build();
         mVcn =
                 new Vcn(
                         mVcnContext,
@@ -130,8 +139,7 @@
     @Test
     public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
         final NetworkRequestListener requestListener = verifyAndGetRequestListener();
-        startVcnGatewayWithCapabilities(
-                requestListener, VcnGatewayConnectionConfigTest.EXPOSED_CAPS);
+        startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]);
 
         final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections();
         assertFalse(gatewayConnections.isEmpty());
@@ -153,10 +161,19 @@
         for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
             startVcnGatewayWithCapabilities(requestListener, capability);
         }
+    }
 
-        // Each Capability in EXPOSED_CAPS was split into a separate VcnGatewayConnection in #setUp.
-        // Expect one VcnGatewayConnection per capability.
-        final int numExpectedGateways = VcnGatewayConnectionConfigTest.EXPOSED_CAPS.length;
+    private void triggerVcnRequestListeners(NetworkRequestListener requestListener) {
+        for (final int[] caps : TEST_CAPS) {
+            startVcnGatewayWithCapabilities(requestListener, caps);
+        }
+    }
+
+    public Set<VcnGatewayConnection> startGatewaysAndGetGatewayConnections(
+            NetworkRequestListener requestListener) {
+        triggerVcnRequestListeners(requestListener);
+
+        final int numExpectedGateways = TEST_CAPS.length;
 
         final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections();
         assertEquals(numExpectedGateways, gatewayConnections.size());
@@ -168,7 +185,16 @@
                         any(),
                         mGatewayStatusCallbackCaptor.capture());
 
-        // Doesn't matter which callback this gets - any Gateway entering safe mode should shut down
+        return gatewayConnections;
+    }
+
+    @Test
+    public void testGatewayEnteringSafemodeNotifiesVcn() {
+        final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+        final Set<VcnGatewayConnection> gatewayConnections =
+                startGatewaysAndGetGatewayConnections(requestListener);
+
+        // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down
         // all Gateways
         final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
         statusCallback.onEnteredSafeMode();
@@ -181,4 +207,31 @@
         verify(mVcnNetworkProvider).unregisterListener(requestListener);
         verify(mVcnCallback).onEnteredSafeMode();
     }
+
+    @Test
+    public void testGatewayQuit() {
+        final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+        final Set<VcnGatewayConnection> gatewayConnections =
+                new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener));
+
+        final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
+        statusCallback.onQuit();
+        mTestLooper.dispatchAll();
+
+        // Verify that the VCN requests the networkRequests be resent
+        assertEquals(1, mVcn.getVcnGatewayConnections().size());
+        verify(mVcnNetworkProvider).resendAllRequests(requestListener);
+
+        // Verify that the VcnGatewayConnection is restarted
+        triggerVcnRequestListeners(requestListener);
+        mTestLooper.dispatchAll();
+        assertEquals(2, mVcn.getVcnGatewayConnections().size());
+        verify(mDeps, times(gatewayConnections.size() + 1))
+                .newVcnGatewayConnection(
+                        eq(mVcnContext),
+                        eq(TEST_SUB_GROUP),
+                        eq(mSubscriptionSnapshot),
+                        any(),
+                        mGatewayStatusCallbackCaptor.capture());
+    }
 }
diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp
index df64a80..c39f494 100644
--- a/tools/bit/make.cpp
+++ b/tools/bit/make.cpp
@@ -89,8 +89,9 @@
     }
 
     Json::Value json;
-    Json::Reader reader;
-    if (!reader.parse(stream, json)) {
+    Json::CharReaderBuilder builder;
+    std::string errorMessage;
+    if (!Json::parseFromStream(builder, stream, &json, &errorMessage)) {
         return;
     }
 
@@ -132,8 +133,9 @@
         return;
     }
 
-    Json::StyledStreamWriter writer("  ");
-
+    Json::StreamWriterBuilder factory;
+    factory["indentation"] = "  ";
+    std::unique_ptr<Json::StreamWriter> const writer(factory.newStreamWriter());
     Json::Value json(Json::objectValue);
 
     for (map<string,string>::const_iterator it = m_cache.begin(); it != m_cache.end(); it++) {
@@ -141,7 +143,7 @@
     }
 
     std::ofstream stream(m_filename, std::ofstream::binary);
-    writer.write(stream, json);
+    writer->write(json, &stream);
 }
 
 string
@@ -212,8 +214,9 @@
     }
 
     Json::Value json;
-    Json::Reader reader;
-    if (!reader.parse(stream, json)) {
+    Json::CharReaderBuilder builder;
+    std::string errorMessage;
+    if (!Json::parseFromStream(builder, stream, &json, &errorMessage)) {
         json_error(filename, "can't parse json format", quiet);
         return;
     }