Merge "Temperory ignore flaky visibleLayersShownMoreThanOneConsecutiveEntry" into sc-dev
diff --git a/METADATA b/METADATA
index d97975c..95577d8 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,4 @@
 third_party {
-  license_type: NOTICE
+  # would be NOTICE save for libs/usb/tests/accessorytest/f_accessory.h
+  license_type: RESTRICTED
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index cc3e9c3..c332a59 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -105,10 +105,6 @@
     @GuardedBy("mLock")
     final SparseBooleanArray mActiveUids = new SparseBooleanArray();
 
-    /** UIDs that are in the foreground. */
-    @GuardedBy("mLock")
-    final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
-
     /**
      * System except-idle + user exemption list in the device idle controller.
      */
@@ -286,13 +282,6 @@
         }
 
         /**
-         * This is called when the foreground state changed for a UID.
-         */
-        private void onUidForegroundStateChanged(AppStateTrackerImpl sender, int uid) {
-            onUidForeground(uid, sender.isUidInForeground(uid));
-        }
-
-        /**
          * This is called when the active/idle state changed for a UID.
          */
         private void onUidActiveStateChanged(AppStateTrackerImpl sender, int uid) {
@@ -416,14 +405,6 @@
         }
 
         /**
-         * Called when a UID comes into the foreground or the background.
-         *
-         * @see #isUidInForeground(int)
-         */
-        public void onUidForeground(int uid, boolean foreground) {
-        }
-
-        /**
          * Called when an ephemeral uid goes to the background, so its alarms need to be removed.
          */
         public void removeAlarmsForUid(int uid) {
@@ -460,7 +441,6 @@
                         mExemptedBucketPackages.remove(userId, pkgName);
                         mRunAnyRestrictedPackages.remove(Pair.create(uid, pkgName));
                         mActiveUids.delete(uid);
-                        mForegroundUids.delete(uid);
                     }
                     break;
             }
@@ -496,8 +476,7 @@
                 mIActivityManager.registerUidObserver(new UidObserver(),
                         ActivityManager.UID_OBSERVER_GONE
                                 | ActivityManager.UID_OBSERVER_IDLE
-                                | ActivityManager.UID_OBSERVER_ACTIVE
-                                | ActivityManager.UID_OBSERVER_PROCSTATE,
+                                | ActivityManager.UID_OBSERVER_ACTIVE,
                         ActivityManager.PROCESS_STATE_UNKNOWN, null);
                 mAppOpsService.startWatchingMode(TARGET_OP, null,
                         new AppOpsWatcher());
@@ -698,7 +677,6 @@
     private final class UidObserver extends IUidObserver.Stub {
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
-            mHandler.onUidStateChanged(uid, procState);
         }
 
         @Override
@@ -769,7 +747,6 @@
 
     private class MyHandler extends Handler {
         private static final int MSG_UID_ACTIVE_STATE_CHANGED = 0;
-        private static final int MSG_UID_FG_STATE_CHANGED = 1;
         private static final int MSG_RUN_ANY_CHANGED = 3;
         private static final int MSG_ALL_UNEXEMPTED = 4;
         private static final int MSG_ALL_EXEMPTION_LIST_CHANGED = 5;
@@ -779,7 +756,6 @@
         private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9;
         private static final int MSG_EXEMPTED_BUCKET_CHANGED = 10;
 
-        private static final int MSG_ON_UID_STATE_CHANGED = 11;
         private static final int MSG_ON_UID_ACTIVE = 12;
         private static final int MSG_ON_UID_GONE = 13;
         private static final int MSG_ON_UID_IDLE = 14;
@@ -792,10 +768,6 @@
             obtainMessage(MSG_UID_ACTIVE_STATE_CHANGED, uid, 0).sendToTarget();
         }
 
-        public void notifyUidForegroundStateChanged(int uid) {
-            obtainMessage(MSG_UID_FG_STATE_CHANGED, uid, 0).sendToTarget();
-        }
-
         public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) {
             obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget();
         }
@@ -834,10 +806,6 @@
             obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
         }
 
-        public void onUidStateChanged(int uid, int procState) {
-            obtainMessage(MSG_ON_UID_STATE_CHANGED, uid, procState).sendToTarget();
-        }
-
         public void onUidActive(int uid) {
             obtainMessage(MSG_ON_UID_ACTIVE, uid, 0).sendToTarget();
         }
@@ -875,13 +843,6 @@
                     mStatLogger.logDurationStat(Stats.UID_ACTIVE_STATE_CHANGED, start);
                     return;
 
-                case MSG_UID_FG_STATE_CHANGED:
-                    for (Listener l : cloneListeners()) {
-                        l.onUidForegroundStateChanged(sender, msg.arg1);
-                    }
-                    mStatLogger.logDurationStat(Stats.UID_FG_STATE_CHANGED, start);
-                    return;
-
                 case MSG_RUN_ANY_CHANGED:
                     for (Listener l : cloneListeners()) {
                         l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj);
@@ -944,9 +905,6 @@
                     handleUserRemoved(msg.arg1);
                     return;
 
-                case MSG_ON_UID_STATE_CHANGED:
-                    handleUidStateChanged(msg.arg1, msg.arg2);
-                    return;
                 case MSG_ON_UID_ACTIVE:
                     handleUidActive(msg.arg1);
                     return;
@@ -971,20 +929,6 @@
             }
         }
 
-        public void handleUidStateChanged(int uid, int procState) {
-            synchronized (mLock) {
-                if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                    if (removeUidFromArray(mForegroundUids, uid, false)) {
-                        mHandler.notifyUidForegroundStateChanged(uid);
-                    }
-                } else {
-                    if (addUidToArray(mForegroundUids, uid)) {
-                        mHandler.notifyUidForegroundStateChanged(uid);
-                    }
-                }
-            }
-        }
-
         public void handleUidActive(int uid) {
             synchronized (mLock) {
                 if (addUidToArray(mActiveUids, uid)) {
@@ -1007,9 +951,6 @@
                 if (removeUidFromArray(mActiveUids, uid, remove)) {
                     mHandler.notifyUidActiveStateChanged(uid);
                 }
-                if (removeUidFromArray(mForegroundUids, uid, remove)) {
-                    mHandler.notifyUidForegroundStateChanged(uid);
-                }
             }
         }
     }
@@ -1026,7 +967,6 @@
                 }
             }
             cleanUpArrayForUser(mActiveUids, removedUserId);
-            cleanUpArrayForUser(mForegroundUids, removedUserId);
             mExemptedBucketPackages.remove(removedUserId);
         }
     }
@@ -1222,22 +1162,6 @@
     }
 
     /**
-     * @return whether a UID is in the foreground or not.
-     *
-     * Note this information is based on the UID proc state callback, meaning it's updated
-     * asynchronously and may subtly be stale. If the fresh data is needed, use
-     * {@link ActivityManagerInternal#getUidProcessState} instead.
-     */
-    public boolean isUidInForeground(int uid) {
-        if (UserHandle.isCore(uid)) {
-            return true;
-        }
-        synchronized (mLock) {
-            return mForegroundUids.get(uid);
-        }
-    }
-
-    /**
      * @return whether force all apps standby is enabled or not.
      */
     public boolean isForceAllAppsStandbyEnabled() {
@@ -1315,9 +1239,6 @@
             pw.print("Active uids: ");
             dumpUids(pw, mActiveUids);
 
-            pw.print("Foreground uids: ");
-            dumpUids(pw, mForegroundUids);
-
             pw.print("Except-idle + user exemption list appids: ");
             pw.println(Arrays.toString(mPowerExemptAllAppIds));
 
@@ -1395,12 +1316,6 @@
                 }
             }
 
-            for (int i = 0; i < mForegroundUids.size(); i++) {
-                if (mForegroundUids.valueAt(i)) {
-                    proto.write(AppStateTrackerProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
-                }
-            }
-
             for (int appId : mPowerExemptAllAppIds) {
                 proto.write(AppStateTrackerProto.POWER_SAVE_EXEMPT_APP_IDS, appId);
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index aa46cfd..d44169d1 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -90,7 +90,6 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
-import android.util.SparseLongArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
@@ -152,7 +151,8 @@
     static final boolean DEBUG_BG_LIMIT = localLOGV || false;
     static final boolean DEBUG_STANDBY = localLOGV || false;
     static final boolean RECORD_ALARMS_IN_HISTORY = true;
-    static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
+    // TODO (b/178484639): Turn off once allow-while-idle revamp is completed.
+    static final boolean RECORD_DEVICE_IDLE_ALARMS = true;
     static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
     static final int TICK_HISTORY_DEPTH = 10;
@@ -208,6 +208,7 @@
             new ArrayList<>();
     AlarmHandler mHandler;
     AppWakeupHistory mAppWakeupHistory;
+    AppWakeupHistory mAllowWhileIdleHistory;
     ClockReceiver mClockReceiver;
     final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
     IBinder.DeathRecipient mListenerDeathRecipient;
@@ -230,19 +231,6 @@
      */
     int mSystemUiUid;
 
-    /**
-     * For each uid, this is the last time we dispatched an "allow while idle" alarm,
-     * used to determine the earliest we can dispatch the next such alarm. Times are in the
-     * 'elapsed' timebase.
-     */
-    final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
-
-    /**
-     * For each uid, we store whether the last allow-while-idle alarm was dispatched while
-     * the uid was in foreground or not. We will use the allow_while_idle_short_time in such cases.
-     */
-    final SparseBooleanArray mUseAllowWhileIdleShortTime = new SparseBooleanArray();
-
     static boolean isTimeTickAlarm(Alarm a) {
         return a.uid == Process.SYSTEM_UID && TIME_TICK_TAG.equals(a.listenerTag);
     }
@@ -290,9 +278,11 @@
     private boolean mAppStandbyParole;
 
     /**
-     * A rolling window history of previous times when an alarm was sent to a package.
+     * A container to keep rolling window history of previous times when an alarm was sent to
+     * a package.
      */
-    private static class AppWakeupHistory {
+    @VisibleForTesting
+    static class AppWakeupHistory {
         private ArrayMap<Pair<String, Integer>, LongArrayQueue> mPackageHistory =
                 new ArrayMap<>();
         private long mWindowSize;
@@ -353,7 +343,6 @@
         }
 
         void dump(IndentingPrintWriter pw, long nowElapsed) {
-            pw.println("App Alarm history:");
             pw.increaseIndent();
             for (int i = 0; i < mPackageHistory.size(); i++) {
                 final Pair<String, Integer> packageUser = mPackageHistory.keyAt(i);
@@ -389,10 +378,6 @@
         @VisibleForTesting
         static final String KEY_MAX_INTERVAL = "max_interval";
         @VisibleForTesting
-        static final String KEY_ALLOW_WHILE_IDLE_SHORT_TIME = "allow_while_idle_short_time";
-        @VisibleForTesting
-        static final String KEY_ALLOW_WHILE_IDLE_LONG_TIME = "allow_while_idle_long_time";
-        @VisibleForTesting
         static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
                 = "allow_while_idle_whitelist_duration";
         @VisibleForTesting
@@ -422,11 +407,12 @@
         private static final String KEY_TIME_TICK_ALLOWED_WHILE_IDLE =
                 "time_tick_allowed_while_idle";
 
+        @VisibleForTesting
+        static final String KEY_ALLOW_WHILE_IDLE_QUOTA = "allow_while_idle_quota";
+
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
         private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS;
-        private static final long DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_MIN_FUTURITY;
-        private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9 * 60 * 1000;
         private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10 * 1000;
         private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
         private static final int DEFAULT_MAX_ALARMS_PER_UID = 500;
@@ -447,6 +433,9 @@
         private static final boolean DEFAULT_LAZY_BATCHING = true;
         private static final boolean DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE = true;
 
+        private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 7;
+        public static final long ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour.
+
         // Minimum futurity of a new alarm
         public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
 
@@ -456,12 +445,6 @@
         // Maximum alarm recurrence interval
         public long MAX_INTERVAL = DEFAULT_MAX_INTERVAL;
 
-        // Minimum time between ALLOW_WHILE_IDLE alarms when system is not idle.
-        public long ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME;
-
-        // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling.
-        public long ALLOW_WHILE_IDLE_LONG_TIME = DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME;
-
         // BroadcastOptions.setTemporaryAppWhitelistDuration() to use for FLAG_ALLOW_WHILE_IDLE.
         public long ALLOW_WHILE_IDLE_WHITELIST_DURATION
                 = DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
@@ -478,6 +461,8 @@
         public boolean LAZY_BATCHING = DEFAULT_LAZY_BATCHING;
         public boolean TIME_TICK_ALLOWED_WHILE_IDLE = DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE;
 
+        public int ALLOW_WHILE_IDLE_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_QUOTA;
+
         private long mLastAllowWhileIdleWhitelistDuration = -1;
 
         Constants() {
@@ -523,15 +508,13 @@
                             MAX_INTERVAL = properties.getLong(
                                     KEY_MAX_INTERVAL, DEFAULT_MAX_INTERVAL);
                             break;
-                        case KEY_ALLOW_WHILE_IDLE_SHORT_TIME:
-                            ALLOW_WHILE_IDLE_SHORT_TIME = properties.getLong(
-                                    KEY_ALLOW_WHILE_IDLE_SHORT_TIME,
-                                    DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME);
-                            break;
-                        case KEY_ALLOW_WHILE_IDLE_LONG_TIME:
-                            ALLOW_WHILE_IDLE_LONG_TIME = properties.getLong(
-                                    KEY_ALLOW_WHILE_IDLE_LONG_TIME,
-                                    DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME);
+                        case KEY_ALLOW_WHILE_IDLE_QUOTA:
+                            ALLOW_WHILE_IDLE_QUOTA = properties.getInt(KEY_ALLOW_WHILE_IDLE_QUOTA,
+                                    DEFAULT_ALLOW_WHILE_IDLE_QUOTA);
+                            if (ALLOW_WHILE_IDLE_QUOTA <= 0) {
+                                Slog.w(TAG, "Cannot have allow-while-idle quota lower than 1.");
+                                ALLOW_WHILE_IDLE_QUOTA = 1;
+                            }
                             break;
                         case KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION:
                             ALLOW_WHILE_IDLE_WHITELIST_DURATION = properties.getLong(
@@ -660,14 +643,11 @@
             TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
             pw.println();
 
-            pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME);
-            pw.print("=");
-            TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw);
+            pw.print("allow_while_idle_window=");
+            TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WINDOW, pw);
             pw.println();
 
-            pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME);
-            pw.print("=");
-            TimeUtils.formatDuration(ALLOW_WHILE_IDLE_LONG_TIME, pw);
+            pw.print(KEY_ALLOW_WHILE_IDLE_QUOTA, ALLOW_WHILE_IDLE_QUOTA);
             pw.println();
 
             pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION);
@@ -675,9 +655,8 @@
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw);
             pw.println();
 
-            pw.print(KEY_MAX_ALARMS_PER_UID);
-            pw.print("=");
-            pw.println(MAX_ALARMS_PER_UID);
+            pw.print(KEY_MAX_ALARMS_PER_UID, MAX_ALARMS_PER_UID);
+            pw.println();
 
             pw.print(KEY_APP_STANDBY_WINDOW);
             pw.print("=");
@@ -685,14 +664,12 @@
             pw.println();
 
             for (int i = 0; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
-                pw.print(KEYS_APP_STANDBY_QUOTAS[i]);
-                pw.print("=");
-                pw.println(APP_STANDBY_QUOTAS[i]);
+                pw.print(KEYS_APP_STANDBY_QUOTAS[i], APP_STANDBY_QUOTAS[i]);
+                pw.println();
             }
 
-            pw.print(KEY_APP_STANDBY_RESTRICTED_QUOTA);
-            pw.print("=");
-            pw.println(APP_STANDBY_RESTRICTED_QUOTA);
+            pw.print(KEY_APP_STANDBY_RESTRICTED_QUOTA, APP_STANDBY_RESTRICTED_QUOTA);
+            pw.println();
 
             pw.print(KEY_APP_STANDBY_RESTRICTED_WINDOW);
             pw.print("=");
@@ -715,10 +692,6 @@
             proto.write(ConstantsProto.MIN_INTERVAL_DURATION_MS, MIN_INTERVAL);
             proto.write(ConstantsProto.MAX_INTERVAL_DURATION_MS, MAX_INTERVAL);
             proto.write(ConstantsProto.LISTENER_TIMEOUT_DURATION_MS, LISTENER_TIMEOUT);
-            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_SHORT_DURATION_MS,
-                    ALLOW_WHILE_IDLE_SHORT_TIME);
-            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_LONG_DURATION_MS,
-                    ALLOW_WHILE_IDLE_LONG_TIME);
             proto.write(ConstantsProto.ALLOW_WHILE_IDLE_WHITELIST_DURATION_MS,
                     ALLOW_WHILE_IDLE_WHITELIST_DURATION);
 
@@ -1268,6 +1241,7 @@
             mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
 
             mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
+            mAllowWhileIdleHistory = new AppWakeupHistory(Constants.ALLOW_WHILE_IDLE_WINDOW);
 
             mNextWakeup = mNextNonWakeup = 0;
 
@@ -1636,25 +1610,28 @@
             return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, nowElapsed);
         }
 
-        final long batterSaverPolicyElapsed;
+        final long batterySaverPolicyElapsed;
         if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
             // Unrestricted.
-            batterSaverPolicyElapsed = nowElapsed;
+            batterySaverPolicyElapsed = nowElapsed;
         } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
             // Allowed but limited.
-            final long minDelay;
-            if (mUseAllowWhileIdleShortTime.get(alarm.creatorUid)) {
-                minDelay = mConstants.ALLOW_WHILE_IDLE_SHORT_TIME;
+            final int userId = UserHandle.getUserId(alarm.creatorUid);
+            final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
+            final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
+                    alarm.sourcePackage, userId);
+            if (dispatchesInWindow < quota) {
+                // fine to go out immediately.
+                batterySaverPolicyElapsed = nowElapsed;
             } else {
-                minDelay = mConstants.ALLOW_WHILE_IDLE_LONG_TIME;
+                batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
+                        alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
             }
-            final long lastDispatch = mLastAllowWhileIdleDispatch.get(alarm.creatorUid, 0);
-            batterSaverPolicyElapsed = (lastDispatch == 0) ? nowElapsed : lastDispatch + minDelay;
         } else {
             // Not allowed.
-            batterSaverPolicyElapsed = nowElapsed + INDEFINITE_DELAY;
+            batterySaverPolicyElapsed = nowElapsed + INDEFINITE_DELAY;
         }
-        return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, batterSaverPolicyElapsed);
+        return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, batterySaverPolicyElapsed);
     }
 
     /**
@@ -1676,9 +1653,18 @@
             deviceIdlePolicyTime = nowElapsed;
         } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
             // Allowed but limited.
-            final long lastDispatch = mLastAllowWhileIdleDispatch.get(alarm.creatorUid, 0);
-            deviceIdlePolicyTime = (lastDispatch == 0) ? nowElapsed
-                    : lastDispatch + mConstants.ALLOW_WHILE_IDLE_LONG_TIME;
+            final int userId = UserHandle.getUserId(alarm.creatorUid);
+            final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
+            final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
+                    alarm.sourcePackage, userId);
+            if (dispatchesInWindow < quota) {
+                // fine to go out immediately.
+                deviceIdlePolicyTime = nowElapsed;
+            } else {
+                final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
+                        alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
+                deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
+            }
         } else {
             // Not allowed.
             deviceIdlePolicyTime = mPendingIdleUntil.getWhenElapsed();
@@ -1723,11 +1709,11 @@
             if (wakeupsInWindow >= quotaForBucket) {
                 final long minElapsed;
                 if (quotaForBucket <= 0) {
-                    // Just keep deferring for a day till the quota changes
-                    minElapsed = nowElapsed + MILLIS_IN_DAY;
+                    // Just keep deferring indefinitely till the quota changes.
+                    minElapsed = nowElapsed + INDEFINITE_DELAY;
                 } else {
                     // Suppose the quota for window was q, and the qth last delivery time for this
-                    // package was t(q) then the next delivery must be after t(q) + <window_size>
+                    // package was t(q) then the next delivery must be after t(q) + <window_size>.
                     final long t = mAppWakeupHistory.getNthLastWakeupForPackage(
                             sourcePackage, sourceUserId, quotaForBucket);
                     minElapsed = t + mConstants.APP_STANDBY_WINDOW;
@@ -1748,17 +1734,10 @@
                 ent.uid = a.uid;
                 ent.pkg = a.operation.getCreatorPackage();
                 ent.tag = a.operation.getTag("");
-                ent.op = "SET";
+                ent.op = "START IDLE";
                 ent.elapsedRealtime = mInjector.getElapsedRealtime();
                 ent.argRealtime = a.getWhenElapsed();
                 mAllowWhileIdleDispatches.add(ent);
-                if (mPendingIdleUntil == null) {
-                    IdleDispatchEntry ent2 = new IdleDispatchEntry();
-                    ent2.uid = 0;
-                    ent2.pkg = "START IDLE";
-                    ent2.elapsedRealtime = mInjector.getElapsedRealtime();
-                    mAllowWhileIdleDispatches.add(ent2);
-                }
             }
             if ((mPendingIdleUntil != a) && (mPendingIdleUntil != null)) {
                 Slog.wtfStack(TAG, "setImplLocked: idle until changed from " + mPendingIdleUntil
@@ -2182,6 +2161,7 @@
             pw.println("]");
             pw.println();
 
+            pw.println("App Alarm history:");
             mAppWakeupHistory.dump(pw, nowELAPSED);
 
             if (mPendingIdleUntil != null) {
@@ -2259,30 +2239,8 @@
                 pw.println();
             }
 
-            if (mLastAllowWhileIdleDispatch.size() > 0) {
-                pw.println("Last allow while idle dispatch times:");
-                pw.increaseIndent();
-                for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); i++) {
-                    pw.print("UID ");
-                    final int uid = mLastAllowWhileIdleDispatch.keyAt(i);
-                    UserHandle.formatUid(pw, uid);
-                    pw.print(": ");
-                    final long lastTime = mLastAllowWhileIdleDispatch.valueAt(i);
-                    TimeUtils.formatDuration(lastTime, nowELAPSED, pw);
-                    pw.println();
-                }
-                pw.decreaseIndent();
-            }
-
-            pw.print("mUseAllowWhileIdleShortTime: [");
-            for (int i = 0; i < mUseAllowWhileIdleShortTime.size(); i++) {
-                if (mUseAllowWhileIdleShortTime.valueAt(i)) {
-                    UserHandle.formatUid(pw, mUseAllowWhileIdleShortTime.keyAt(i));
-                    pw.print(" ");
-                }
-            }
-            pw.println("]");
-            pw.println();
+            pw.println("Allow while idle history:");
+            mAllowWhileIdleHistory.dump(pw, nowELAPSED);
 
             if (mLog.dump(pw, "Recent problems:")) {
                 pw.println();
@@ -2533,25 +2491,6 @@
                 f.dumpDebug(proto, AlarmManagerServiceDumpProto.OUTSTANDING_DELIVERIES);
             }
 
-            for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); ++i) {
-                final long token = proto.start(
-                        AlarmManagerServiceDumpProto.LAST_ALLOW_WHILE_IDLE_DISPATCH_TIMES);
-                final int uid = mLastAllowWhileIdleDispatch.keyAt(i);
-                final long lastTime = mLastAllowWhileIdleDispatch.valueAt(i);
-
-                proto.write(AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch.UID, uid);
-                proto.write(AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch.TIME_MS,
-                        lastTime);
-                proto.end(token);
-            }
-
-            for (int i = 0; i < mUseAllowWhileIdleShortTime.size(); i++) {
-                if (mUseAllowWhileIdleShortTime.valueAt(i)) {
-                    proto.write(AlarmManagerServiceDumpProto.USE_ALLOW_WHILE_IDLE_SHORT_TIME,
-                            mUseAllowWhileIdleShortTime.keyAt(i));
-                }
-            }
-
             mLog.dumpDebug(proto, AlarmManagerServiceDumpProto.RECENT_PROBLEMS);
 
             final FilterStats[] topFilters = new FilterStats[10];
@@ -3049,11 +2988,6 @@
                 mPendingBackgroundAlarms.removeAt(i);
             }
         }
-        for (int i = mLastAllowWhileIdleDispatch.size() - 1; i >= 0; i--) {
-            if (UserHandle.getUserId(mLastAllowWhileIdleDispatch.keyAt(i)) == userHandle) {
-                mLastAllowWhileIdleDispatch.removeAt(i);
-            }
-        }
         if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) {
             mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
             if (mPendingIdleUntil != null) {
@@ -3215,6 +3149,16 @@
             if (mPendingIdleUntil == alarm) {
                 mPendingIdleUntil = null;
                 mAlarmStore.updateAlarmDeliveries(a -> adjustDeliveryTimeBasedOnDeviceIdle(a));
+                if (RECORD_DEVICE_IDLE_ALARMS) {
+                    IdleDispatchEntry ent = new IdleDispatchEntry();
+                    ent.uid = alarm.uid;
+                    ent.pkg = alarm.operation.getCreatorPackage();
+                    ent.tag = alarm.operation.getTag("");
+                    ent.op = "END IDLE";
+                    ent.elapsedRealtime = mInjector.getElapsedRealtime();
+                    ent.argRealtime = alarm.getWhenElapsed();
+                    mAllowWhileIdleDispatches.add(ent);
+                }
             }
             if (mNextWakeFromIdle == alarm) {
                 mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
@@ -3829,7 +3773,6 @@
             IntentFilter sdFilter = new IntentFilter();
             sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
             sdFilter.addAction(Intent.ACTION_USER_STOPPED);
-            sdFilter.addAction(Intent.ACTION_UID_REMOVED);
             getContext().registerReceiver(this, sdFilter);
         }
 
@@ -3856,12 +3799,7 @@
                         if (userHandle >= 0) {
                             removeUserLocked(userHandle);
                             mAppWakeupHistory.removeForUser(userHandle);
-                        }
-                        return;
-                    case Intent.ACTION_UID_REMOVED:
-                        if (uid >= 0) {
-                            mLastAllowWhileIdleDispatch.delete(uid);
-                            mUseAllowWhileIdleShortTime.delete(uid);
+                            mAllowWhileIdleHistory.removeForUser(userHandle);
                         }
                         return;
                     case Intent.ACTION_PACKAGE_REMOVED:
@@ -3885,6 +3823,7 @@
                         if (uid >= 0) {
                             // package-removed and package-restarted case
                             mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
+                            mAllowWhileIdleHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
                             removeLocked(uid);
                         } else {
                             // external-applications-unavailable case
@@ -3980,23 +3919,6 @@
         }
 
         @Override
-        public void onUidForeground(int uid, boolean foreground) {
-            synchronized (mLock) {
-                if (foreground) {
-                    mUseAllowWhileIdleShortTime.put(uid, true);
-                    if (mAlarmStore.updateAlarmDeliveries(a -> {
-                        if (a.creatorUid != uid || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
-                            return false;
-                        }
-                        return adjustDeliveryTimeBasedOnBatterySaver(a);
-                    })) {
-                        rescheduleKernelAlarmsLocked();
-                    }
-                }
-            }
-        }
-
-        @Override
         public void removeAlarmsForUid(int uid) {
             synchronized (mLock) {
                 removeForStoppedLocked(uid);
@@ -4273,22 +4195,23 @@
                 notifyBroadcastAlarmPendingLocked(alarm.uid);
             }
             if (allowWhileIdle) {
-                // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
-                mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED);
-                if ((mAppStateTracker == null)
-                        || mAppStateTracker.isUidInForeground(alarm.creatorUid)) {
-                    mUseAllowWhileIdleShortTime.put(alarm.creatorUid, true);
-                } else {
-                    mUseAllowWhileIdleShortTime.put(alarm.creatorUid, false);
+                final boolean doze = (mPendingIdleUntil != null);
+                final boolean batterySaver = (mAppStateTracker != null
+                        && mAppStateTracker.isForceAllAppsStandbyEnabled());
+                if (doze || batterySaver) {
+                    // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm while the
+                    // device was in doze or battery saver.
+                    mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
+                            UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
+                    mAlarmStore.updateAlarmDeliveries(a -> {
+                        if (a.creatorUid != alarm.creatorUid
+                                || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
+                            return false;
+                        }
+                        return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a))
+                                || (batterySaver && adjustDeliveryTimeBasedOnBatterySaver(a));
+                    });
                 }
-                mAlarmStore.updateAlarmDeliveries(a -> {
-                    if (a.creatorUid != alarm.creatorUid
-                            || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
-                        return false;
-                    }
-                    return adjustDeliveryTimeBasedOnDeviceIdle(a)
-                            | adjustDeliveryTimeBasedOnBatterySaver(a);
-                });
                 if (RECORD_DEVICE_IDLE_ALARMS) {
                     IdleDispatchEntry ent = new IdleDispatchEntry();
                     ent.uid = alarm.uid;
@@ -4300,8 +4223,6 @@
                 }
             }
             if (!isExemptFromAppStandby(alarm)) {
-                final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage,
-                        UserHandle.getUserId(alarm.creatorUid));
                 mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage,
                         UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
             }
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index cb2810c..0915d07 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -160,6 +160,10 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
   }
 
+  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+    method public int getResourceId();
+  }
+
   public final class NetworkCapabilities implements android.os.Parcelable {
     field public static final int TRANSPORT_TEST = 7; // 0x7
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b3e78ab..2a2b799 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -217,6 +217,7 @@
     field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
     field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
     field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
+    field public static final String RESTART_WIFI_SUBSYSTEM = "android.permission.RESTART_WIFI_SUBSYSTEM";
     field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS";
     field public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
     field public static final String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 45f072a..fe8bf9d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -30,6 +30,7 @@
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
@@ -53,8 +54,6 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.split.DefaultSplitAssetLoader;
-import android.content.pm.split.SplitAssetDependencyLoader;
 import android.content.pm.split.SplitAssetLoader;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
@@ -80,6 +79,7 @@
 import android.util.AttributeSet;
 import android.util.Base64;
 import android.util.DisplayMetrics;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.PackageUtils;
 import android.util.Pair;
@@ -118,6 +118,7 @@
 import java.security.spec.X509EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
@@ -8573,4 +8574,410 @@
             this.error = error;
         }
     }
+
+    // Duplicate the SplitAsset related classes with PackageParser.Package/ApkLite here, and
+    // change the original one using new Package/ApkLite. The propose is that we don't want to
+    // have two branches of methods in SplitAsset related classes so we can keep real classes
+    // clean and move all the legacy code to one place.
+
+    /**
+     * A helper class that implements the dependency tree traversal for splits. Callbacks
+     * are implemented by subclasses to notify whether a split has already been constructed
+     * and is cached, and to actually create the split requested.
+     *
+     * This helper is meant to be subclassed so as to reduce the number of allocations
+     * needed to make use of it.
+     *
+     * All inputs and outputs are assumed to be indices into an array of splits.
+     *
+     * @hide
+     * @deprecated Do not use. New changes should use
+     * {@link android.content.pm.split.SplitDependencyLoader} instead.
+     */
+    @Deprecated
+    private abstract static class SplitDependencyLoader<E extends Exception> {
+        private final @NonNull SparseArray<int[]> mDependencies;
+
+        /**
+         * Construct a new SplitDependencyLoader. Meant to be called from the
+         * subclass constructor.
+         * @param dependencies The dependency tree of splits.
+         */
+        protected SplitDependencyLoader(@NonNull SparseArray<int[]> dependencies) {
+            mDependencies = dependencies;
+        }
+
+        /**
+         * Traverses the dependency tree and constructs any splits that are not already
+         * cached. This routine short-circuits and skips the creation of splits closer to the
+         * root if they are cached, as reported by the subclass implementation of
+         * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass
+         * implementation of {@link #constructSplit(int, int[], int)}.
+         * @param splitIdx The index of the split to load. 0 represents the base Application.
+         */
+        protected void loadDependenciesForSplit(@IntRange(from = 0) int splitIdx) throws E {
+            // Quick check before any allocations are done.
+            if (isSplitCached(splitIdx)) {
+                return;
+            }
+
+            // Special case the base, since it has no dependencies.
+            if (splitIdx == 0) {
+                final int[] configSplitIndices = collectConfigSplitIndices(0);
+                constructSplit(0, configSplitIndices, -1);
+                return;
+            }
+
+            // Build up the dependency hierarchy.
+            final IntArray linearDependencies = new IntArray();
+            linearDependencies.add(splitIdx);
+
+            // Collect all the dependencies that need to be constructed.
+            // They will be listed from leaf to root.
+            while (true) {
+                // Only follow the first index into the array. The others are config splits and
+                // get loaded with the split.
+                final int[] deps = mDependencies.get(splitIdx);
+                if (deps != null && deps.length > 0) {
+                    splitIdx = deps[0];
+                } else {
+                    splitIdx = -1;
+                }
+
+                if (splitIdx < 0 || isSplitCached(splitIdx)) {
+                    break;
+                }
+
+                linearDependencies.add(splitIdx);
+            }
+
+            // Visit each index, from right to left (root to leaf).
+            int parentIdx = splitIdx;
+            for (int i = linearDependencies.size() - 1; i >= 0; i--) {
+                final int idx = linearDependencies.get(i);
+                final int[] configSplitIndices = collectConfigSplitIndices(idx);
+                constructSplit(idx, configSplitIndices, parentIdx);
+                parentIdx = idx;
+            }
+        }
+
+        private @NonNull int[] collectConfigSplitIndices(int splitIdx) {
+            // The config splits appear after the first element.
+            final int[] deps = mDependencies.get(splitIdx);
+            if (deps == null || deps.length <= 1) {
+                return EmptyArray.INT;
+            }
+            return Arrays.copyOfRange(deps, 1, deps.length);
+        }
+
+        /**
+         * Subclass to report whether the split at `splitIdx` is cached and need not be constructed.
+         * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached.
+         * @param splitIdx The index of the split to check for in the cache.
+         * @return true if the split is cached and does not need to be constructed.
+         */
+        protected abstract boolean isSplitCached(@IntRange(from = 0) int splitIdx);
+
+        /**
+         * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`.
+         * The result is expected to be cached by the subclass in its own structures.
+         * @param splitIdx The index of the split to construct. 0 represents the base Application.
+         * @param configSplitIndices The array of configuration splits to load along with this
+         *                           split. May be empty (length == 0) but never null.
+         * @param parentSplitIdx The index of the parent split. -1 if there is no parent.
+         * @throws E Subclass defined exception representing failure to construct a split.
+         */
+        protected abstract void constructSplit(@IntRange(from = 0) int splitIdx,
+                @NonNull @IntRange(from = 1) int[] configSplitIndices,
+                @IntRange(from = -1) int parentSplitIdx) throws E;
+
+        public static class IllegalDependencyException extends Exception {
+            private IllegalDependencyException(String message) {
+                super(message);
+            }
+        }
+
+        private static int[] append(int[] src, int elem) {
+            if (src == null) {
+                return new int[] { elem };
+            }
+            int[] dst = Arrays.copyOf(src, src.length + 1);
+            dst[src.length] = elem;
+            return dst;
+        }
+
+        public static @NonNull SparseArray<int[]> createDependenciesFromPackage(
+                PackageLite pkg)
+                throws SplitDependencyLoader.IllegalDependencyException {
+            // The data structure that holds the dependencies. In PackageParser, splits are stored
+            // in their own array, separate from the base. We treat all paths as equals, so
+            // we need to insert the base as index 0, and shift all other splits.
+            final SparseArray<int[]> splitDependencies = new SparseArray<>();
+
+            // The base depends on nothing.
+            splitDependencies.put(0, new int[] {-1});
+
+            // First write out the <uses-split> dependencies. These must appear first in the
+            // array of ints, as is convention in this class.
+            for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
+                if (!pkg.isFeatureSplits[splitIdx]) {
+                    // Non-feature splits don't have dependencies.
+                    continue;
+                }
+
+                // Implicit dependency on the base.
+                final int targetIdx;
+                final String splitDependency = pkg.usesSplitNames[splitIdx];
+                if (splitDependency != null) {
+                    final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
+                    if (depIdx < 0) {
+                        throw new SplitDependencyLoader.IllegalDependencyException(
+                                "Split '" + pkg.splitNames[splitIdx] + "' requires split '"
+                                        + splitDependency + "', which is missing.");
+                    }
+                    targetIdx = depIdx + 1;
+                } else {
+                    // Implicitly depend on the base.
+                    targetIdx = 0;
+                }
+                splitDependencies.put(splitIdx + 1, new int[] {targetIdx});
+            }
+
+            // Write out the configForSplit reverse-dependencies. These appear after the
+            // <uses-split> dependencies and are considered leaves.
+            //
+            // At this point, all splits in splitDependencies have the first element in their
+            // array set.
+            for (int splitIdx = 0, size = pkg.splitNames.length; splitIdx < size; splitIdx++) {
+                if (pkg.isFeatureSplits[splitIdx]) {
+                    // Feature splits are not configForSplits.
+                    continue;
+                }
+
+                // Implicit feature for the base.
+                final int targetSplitIdx;
+                final String configForSplit = pkg.configForSplit[splitIdx];
+                if (configForSplit != null) {
+                    final int depIdx = Arrays.binarySearch(pkg.splitNames, configForSplit);
+                    if (depIdx < 0) {
+                        throw new SplitDependencyLoader.IllegalDependencyException(
+                                "Split '" + pkg.splitNames[splitIdx] + "' targets split '"
+                                        + configForSplit + "', which is missing.");
+                    }
+
+                    if (!pkg.isFeatureSplits[depIdx]) {
+                        throw new SplitDependencyLoader.IllegalDependencyException(
+                                "Split '" + pkg.splitNames[splitIdx] + "' declares itself as "
+                                        + "configuration split for a non-feature split '"
+                                        + pkg.splitNames[depIdx] + "'");
+                    }
+                    targetSplitIdx = depIdx + 1;
+                } else {
+                    targetSplitIdx = 0;
+                }
+                splitDependencies.put(targetSplitIdx,
+                        append(splitDependencies.get(targetSplitIdx), splitIdx + 1));
+            }
+
+            // Verify that there are no cycles.
+            final BitSet bitset = new BitSet();
+            for (int i = 0, size = splitDependencies.size(); i < size; i++) {
+                int splitIdx = splitDependencies.keyAt(i);
+
+                bitset.clear();
+                while (splitIdx != -1) {
+                    // Check if this split has been visited yet.
+                    if (bitset.get(splitIdx)) {
+                        throw new SplitDependencyLoader.IllegalDependencyException(
+                                "Cycle detected in split dependencies.");
+                    }
+
+                    // Mark the split so that if we visit it again, we no there is a cycle.
+                    bitset.set(splitIdx);
+
+                    // Follow the first dependency only, the others are leaves by definition.
+                    final int[] deps = splitDependencies.get(splitIdx);
+                    splitIdx = deps != null ? deps[0] : -1;
+                }
+            }
+            return splitDependencies;
+        }
+    }
+
+    /**
+     * Loads the base and split APKs into a single AssetManager.
+     * @hide
+     * @deprecated Do not use. New changes should use
+     * {@link android.content.pm.split.DefaultSplitAssetLoader} instead.
+     */
+    @Deprecated
+    private static class DefaultSplitAssetLoader implements SplitAssetLoader {
+        private final String mBaseCodePath;
+        private final String[] mSplitCodePaths;
+        private final @ParseFlags int mFlags;
+        private AssetManager mCachedAssetManager;
+
+        DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
+            mBaseCodePath = pkg.baseCodePath;
+            mSplitCodePaths = pkg.splitCodePaths;
+            mFlags = flags;
+        }
+
+        private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
+                throws PackageParserException {
+            if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                        "Invalid package file: " + path);
+            }
+
+            try {
+                return ApkAssets.loadFromPath(path);
+            } catch (IOException e) {
+                throw new PackageParserException(INSTALL_FAILED_INVALID_APK,
+                        "Failed to load APK at path " + path, e);
+            }
+        }
+
+        @Override
+        public AssetManager getBaseAssetManager() throws PackageParserException {
+            if (mCachedAssetManager != null) {
+                return mCachedAssetManager;
+            }
+
+            ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
+                    ? mSplitCodePaths.length : 0) + 1];
+
+            // Load the base.
+            int splitIdx = 0;
+            apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);
+
+            // Load any splits.
+            if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
+                for (String apkPath : mSplitCodePaths) {
+                    apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags);
+                }
+            }
+
+            AssetManager assets = new AssetManager();
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
+            assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
+
+            mCachedAssetManager = assets;
+            return mCachedAssetManager;
+        }
+
+        @Override
+        public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException {
+            return getBaseAssetManager();
+        }
+
+        @Override
+        public void close() throws Exception {
+            IoUtils.closeQuietly(mCachedAssetManager);
+        }
+    }
+
+    /**
+     * Loads AssetManagers for splits and their dependencies. This SplitAssetLoader implementation
+     * is to be used when an application opts-in to isolated split loading.
+     * @hide
+     * @deprecated Do not use. New changes should use
+     * {@link android.content.pm.split.SplitAssetDependencyLoader} instead.
+     */
+    @Deprecated
+    private static class SplitAssetDependencyLoader extends
+            SplitDependencyLoader<PackageParserException> implements SplitAssetLoader {
+        private final String[] mSplitPaths;
+        private final @ParseFlags int mFlags;
+        private final ApkAssets[][] mCachedSplitApks;
+        private final AssetManager[] mCachedAssetManagers;
+
+        SplitAssetDependencyLoader(PackageLite pkg,
+                SparseArray<int[]> dependencies, @ParseFlags int flags) {
+            super(dependencies);
+
+            // The base is inserted into index 0, so we need to shift all the splits by 1.
+            mSplitPaths = new String[pkg.splitCodePaths.length + 1];
+            mSplitPaths[0] = pkg.baseCodePath;
+            System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
+
+            mFlags = flags;
+            mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
+            mCachedAssetManagers = new AssetManager[mSplitPaths.length];
+        }
+
+        @Override
+        protected boolean isSplitCached(int splitIdx) {
+            return mCachedAssetManagers[splitIdx] != null;
+        }
+
+        private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
+                throws PackageParserException {
+            if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                        "Invalid package file: " + path);
+            }
+
+            try {
+                return ApkAssets.loadFromPath(path);
+            } catch (IOException e) {
+                throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK,
+                        "Failed to load APK at path " + path, e);
+            }
+        }
+
+        private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
+            final AssetManager assets = new AssetManager();
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
+            assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
+            return assets;
+        }
+
+        @Override
+        protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
+                int parentSplitIdx) throws PackageParserException {
+            final ArrayList<ApkAssets> assets = new ArrayList<>();
+
+            // Include parent ApkAssets.
+            if (parentSplitIdx >= 0) {
+                Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]);
+            }
+
+            // Include this ApkAssets.
+            assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags));
+
+            // Load and include all config splits for this feature.
+            for (int configSplitIdx : configSplitIndices) {
+                assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags));
+            }
+
+            // Cache the results.
+            mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]);
+            mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(
+                    mCachedSplitApks[splitIdx]);
+        }
+
+        @Override
+        public AssetManager getBaseAssetManager() throws PackageParserException {
+            loadDependenciesForSplit(0);
+            return mCachedAssetManagers[0];
+        }
+
+        @Override
+        public AssetManager getSplitAssetManager(int idx) throws PackageParserException {
+            // Since we insert the base at position 0, and PackageParser keeps splits separate from
+            // the base, we need to adjust the index.
+            loadDependenciesForSplit(idx + 1);
+            return mCachedAssetManagers[idx + 1];
+        }
+
+        @Override
+        public void close() throws Exception {
+            for (AssetManager assets : mCachedAssetManagers) {
+                IoUtils.closeQuietly(assets);
+            }
+        }
+    }
 }
diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java
index 982fce9..bf35c4d 100644
--- a/core/java/android/content/pm/dex/DexMetadataHelper.java
+++ b/core/java/android/content/pm/dex/DexMetadataHelper.java
@@ -17,11 +17,11 @@
 package android.content.pm.dex;
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA;
-import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
+import static android.content.pm.parsing.ApkLiteParseUtils.APK_FILE_EXTENSION;
 
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
 import android.util.ArrayMap;
 import android.util.jar.StrictJarFile;
 
@@ -87,7 +87,7 @@
      * NOTE: involves I/O checks.
      */
     private static Map<String, String> getPackageDexMetadata(PackageLite pkg) {
-        return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths());
+        return buildPackageApkToDexMetadataMap(pkg.getAllApkPaths());
     }
 
     /**
@@ -125,7 +125,7 @@
      * @throws IllegalArgumentException if the code path is not an .apk.
      */
     public static String buildDexMetadataPathForApk(String codePath) {
-        if (!PackageParser.isApkPath(codePath)) {
+        if (!ApkLiteParseUtils.isApkPath(codePath)) {
             throw new IllegalStateException(
                     "Corrupted package. Code path is not an apk " + codePath);
         }
@@ -140,7 +140,7 @@
      * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk').
      */
     private static String buildDexMetadataPathForFile(File targetFile) {
-        return PackageParser.isApkFile(targetFile)
+        return ApkLiteParseUtils.isApkFile(targetFile)
                 ? buildDexMetadataPathForApk(targetFile.getPath())
                 : targetFile.getPath() + DEX_METADATA_FILE_EXTENSION;
     }
@@ -179,7 +179,7 @@
     public static void validateDexPaths(String[] paths) {
         ArrayList<String> apks = new ArrayList<>();
         for (int i = 0; i < paths.length; i++) {
-            if (PackageParser.isApkPath(paths[i])) {
+            if (ApkLiteParseUtils.isApkPath(paths[i])) {
                 apks.add(paths[i]);
             }
         }
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 51b81b6..a3c2cbc 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -66,6 +66,8 @@
     private static final int PARSE_DEFAULT_INSTALL_LOCATION =
             PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
 
+    private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
+
     public static final String APK_FILE_EXTENSION = ".apk";
 
     /**
@@ -79,7 +81,7 @@
      *
      * @see PackageParser#parsePackage(File, int)
      */
-    public static ParseResult<PackageParser.PackageLite> parsePackageLite(ParseInput input,
+    public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
             File packageFile, int flags) {
         if (packageFile.isDirectory()) {
             return parseClusterPackageLite(input, packageFile, flags);
@@ -88,26 +90,32 @@
         }
     }
 
-    public static ParseResult<PackageParser.PackageLite> parseMonolithicPackageLite(
-            ParseInput input, File packageFile, int flags) {
+    /**
+     * Parse lightweight details about a single APK files.
+     */
+    public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
+            File packageFile, int flags) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
         try {
-            ParseResult<PackageParser.ApkLite> result = parseApkLite(input, packageFile, flags);
+            final ParseResult<ApkLite> result = parseApkLite(input, packageFile, flags);
             if (result.isError()) {
                 return input.error(result);
             }
 
-            final PackageParser.ApkLite baseApk = result.getResult();
+            final ApkLite baseApk = result.getResult();
             final String packagePath = packageFile.getAbsolutePath();
             return input.success(
-                    new PackageParser.PackageLite(packagePath, baseApk.codePath, baseApk, null,
+                    new PackageLite(packagePath, baseApk.getPath(), baseApk, null,
                             null, null, null, null, null));
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
     }
 
-    public static ParseResult<PackageParser.PackageLite> parseClusterPackageLite(ParseInput input,
+    /**
+     * Parse lightweight details about a directory of APKs.
+     */
+    public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
             File packageDir, int flags) {
         final File[] files = packageDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
@@ -122,39 +130,39 @@
         String packageName = null;
         int versionCode = 0;
 
-        final ArrayMap<String, PackageParser.ApkLite> apks = new ArrayMap<>();
+        final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
         try {
             for (File file : files) {
-                if (PackageParser.isApkFile(file)) {
-                    ParseResult<PackageParser.ApkLite> result = parseApkLite(input, file, flags);
+                if (isApkFile(file)) {
+                    final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
                     if (result.isError()) {
                         return input.error(result);
                     }
 
-                    final PackageParser.ApkLite lite = result.getResult();
+                    final ApkLite lite = result.getResult();
                     // Assert that all package names and version codes are
                     // consistent with the first one we encounter.
                     if (packageName == null) {
-                        packageName = lite.packageName;
-                        versionCode = lite.versionCode;
+                        packageName = lite.getPackageName();
+                        versionCode = lite.getVersionCode();
                     } else {
-                        if (!packageName.equals(lite.packageName)) {
+                        if (!packageName.equals(lite.getPackageName())) {
                             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                                    "Inconsistent package " + lite.packageName + " in " + file
+                                    "Inconsistent package " + lite.getPackageName() + " in " + file
                                             + "; expected " + packageName);
                         }
-                        if (versionCode != lite.versionCode) {
+                        if (versionCode != lite.getVersionCode()) {
                             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                                    "Inconsistent version " + lite.versionCode + " in " + file
+                                    "Inconsistent version " + lite.getVersionCode() + " in " + file
                                             + "; expected " + versionCode);
                         }
                     }
 
                     // Assert that each split is defined only oncuses-static-libe
-                    if (apks.put(lite.splitName, lite) != null) {
+                    if (apks.put(lite.getSplitName(), lite) != null) {
                         return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                                "Split name " + lite.splitName
+                                "Split name " + lite.getSplitName()
                                         + " defined more than once; most recent was " + file);
                     }
                 }
@@ -163,7 +171,7 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
-        final PackageParser.ApkLite baseApk = apks.remove(null);
+        final ApkLite baseApk = apks.remove(null);
         return composePackageLiteFromApks(input, packageDir, baseApk, apks);
     }
 
@@ -176,9 +184,8 @@
      * @param splitApks Parsed split APKs
      * @return PackageLite
      */
-    public static ParseResult<PackageParser.PackageLite> composePackageLiteFromApks(
-            ParseInput input, File packageDir, PackageParser.ApkLite baseApk,
-            ArrayMap<String, PackageParser.ApkLite> splitApks) {
+    public static ParseResult<PackageLite> composePackageLiteFromApks(ParseInput input,
+            File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks) {
         return composePackageLiteFromApks(input, packageDir, baseApk, splitApks, false);
     }
 
@@ -192,9 +199,9 @@
      * @param apkRenamed Indicate whether the APKs are renamed after parsed.
      * @return PackageLite
      */
-    public static ParseResult<PackageParser.PackageLite> composePackageLiteFromApks(
-            ParseInput input, File packageDir, PackageParser.ApkLite baseApk,
-            ArrayMap<String, PackageParser.ApkLite> splitApks, boolean apkRenamed) {
+    public static ParseResult<PackageLite> composePackageLiteFromApks(
+            ParseInput input, File packageDir, ApkLite baseApk,
+            ArrayMap<String, ApkLite> splitApks, boolean apkRenamed) {
         if (baseApk == null) {
             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                     "Missing base APK in " + packageDir);
@@ -217,26 +224,25 @@
             splitRevisionCodes = new int[size];
 
             splitNames = splitApks.keySet().toArray(splitNames);
-            Arrays.sort(splitNames, PackageParser.sSplitNameComparator);
+            Arrays.sort(splitNames, sSplitNameComparator);
 
             for (int i = 0; i < size; i++) {
-                final PackageParser.ApkLite apk = splitApks.get(splitNames[i]);
-                usesSplitNames[i] = apk.usesSplitName;
-                isFeatureSplits[i] = apk.isFeatureSplit;
-                configForSplits[i] = apk.configForSplit;
+                final ApkLite apk = splitApks.get(splitNames[i]);
+                usesSplitNames[i] = apk.getUsesSplitName();
+                isFeatureSplits[i] = apk.isFeatureSplit();
+                configForSplits[i] = apk.getConfigForSplit();
                 splitCodePaths[i] = apkRenamed ? new File(packageDir,
-                        splitNameToFileName(apk)).getAbsolutePath() : apk.codePath;
-                splitRevisionCodes[i] = apk.revisionCode;
+                        splitNameToFileName(apk)).getAbsolutePath() : apk.getPath();
+                splitRevisionCodes[i] = apk.getRevisionCode();
             }
         }
 
         final String codePath = packageDir.getAbsolutePath();
         final String baseCodePath = apkRenamed ? new File(packageDir,
-                splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.codePath;
+                splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.getPath();
         return input.success(
-                new PackageParser.PackageLite(codePath, baseCodePath, baseApk, splitNames,
-                        isFeatureSplits, usesSplitNames, configForSplits, splitCodePaths,
-                        splitRevisionCodes));
+                new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits,
+                        usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes));
     }
 
     /**
@@ -245,9 +251,9 @@
      * @param apk Parsed APK
      * @return The canonical file name
      */
-    public static String splitNameToFileName(@NonNull PackageParser.ApkLite apk) {
+    public static String splitNameToFileName(@NonNull ApkLite apk) {
         Objects.requireNonNull(apk);
-        final String fileName = apk.splitName == null ? "base" : "split_" + apk.splitName;
+        final String fileName = apk.getSplitName() == null ? "base" : "split_" + apk.getSplitName();
         return fileName + APK_FILE_EXTENSION;
     }
 
@@ -257,10 +263,9 @@
      *
      * @param apkFile path to a single APK
      * @param flags optional parse flags, such as
-     *            {@link PackageParser#PARSE_COLLECT_CERTIFICATES}
+     *            {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
      */
-    public static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input, File apkFile,
-            int flags) {
+    public static ParseResult<ApkLite> parseApkLite(ParseInput input, File apkFile, int flags) {
         return parseApkLiteInner(input, apkFile, null, null, flags);
     }
 
@@ -271,14 +276,14 @@
      * @param fd already open file descriptor of an apk file
      * @param debugPathName arbitrary text name for this file, for debug output
      * @param flags optional parse flags, such as
-     *            {@link PackageParser#PARSE_COLLECT_CERTIFICATES}
+     *            {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
      */
-    public static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input,
+    public static ParseResult<ApkLite> parseApkLite(ParseInput input,
             FileDescriptor fd, String debugPathName, int flags) {
         return parseApkLiteInner(input, null, fd, debugPathName, flags);
     }
 
-    private static ParseResult<PackageParser.ApkLite> parseApkLiteInner(ParseInput input,
+    private static ParseResult<ApkLite> parseApkLiteInner(ParseInput input,
             File apkFile, FileDescriptor fd, String debugPathName, int flags) {
         final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
 
@@ -294,11 +299,11 @@
                         "Failed to parse " + apkPath, e);
             }
 
-            parser = apkAssets.openXml(PackageParser.ANDROID_MANIFEST_FILENAME);
+            parser = apkAssets.openXml(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME);
 
             final PackageParser.SigningDetails signingDetails;
-            if ((flags & PackageParser.PARSE_COLLECT_CERTIFICATES) != 0) {
-                final boolean skipVerify = (flags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0;
+            if ((flags & ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES) != 0) {
+                final boolean skipVerify = (flags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
                 try {
                     ParseResult<PackageParser.SigningDetails> result =
@@ -335,9 +340,8 @@
         }
     }
 
-    private static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input,
-            String codePath, XmlPullParser parser, AttributeSet attrs,
-            PackageParser.SigningDetails signingDetails)
+    private static ParseResult<ApkLite> parseApkLite(ParseInput input, String codePath,
+            XmlPullParser parser, AttributeSet attrs, PackageParser.SigningDetails signingDetails)
             throws IOException, XmlPullParserException {
         ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser, attrs);
         if (result.isError()) {
@@ -421,12 +425,12 @@
                 continue;
             }
 
-            if (PackageParser.TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
+            if (ParsingPackageUtils.TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
                 final VerifierInfo verifier = parseVerifier(attrs);
                 if (verifier != null) {
                     verifiers.add(verifier);
                 }
-            } else if (PackageParser.TAG_APPLICATION.equals(parser.getName())) {
+            } else if (ParsingPackageUtils.TAG_APPLICATION.equals(parser.getName())) {
                 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                     final String attr = attrs.getAttributeName(i);
                     switch (attr) {
@@ -464,7 +468,7 @@
                         continue;
                     }
 
-                    if (PackageParser.TAG_PROFILEABLE.equals(parser.getName())) {
+                    if (ParsingPackageUtils.TAG_PROFILEABLE.equals(parser.getName())) {
                         for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                             final String attr = attrs.getAttributeName(i);
                             if ("shell".equals(attr)) {
@@ -474,7 +478,7 @@
                         }
                     }
                 }
-            } else if (PackageParser.TAG_OVERLAY.equals(parser.getName())) {
+            } else if (ParsingPackageUtils.TAG_OVERLAY.equals(parser.getName())) {
                 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                     final String attr = attrs.getAttributeName(i);
                     if ("requiredSystemPropertyName".equals(attr)) {
@@ -489,7 +493,7 @@
                         overlayPriority = attrs.getAttributeIntValue(i, 0);
                     }
                 }
-            } else if (PackageParser.TAG_USES_SPLIT.equals(parser.getName())) {
+            } else if (ParsingPackageUtils.TAG_USES_SPLIT.equals(parser.getName())) {
                 if (usesSplitName != null) {
                     Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
                     continue;
@@ -500,7 +504,7 @@
                     return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                             "<uses-split> tag requires 'android:name' attribute");
                 }
-            } else if (PackageParser.TAG_USES_SDK.equals(parser.getName())) {
+            } else if (ParsingPackageUtils.TAG_USES_SDK.equals(parser.getName())) {
                 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                     final String attr = attrs.getAttributeName(i);
                     if ("targetSdkVersion".equals(attr)) {
@@ -526,8 +530,8 @@
         }
 
         return input.success(
-                new PackageParser.ApkLite(codePath, packageSplit.first, packageSplit.second,
-                        isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode,
+                new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
+                        configForSplit, usesSplitName, isSplitRequired, versionCode,
                         versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
                         coreApp, debuggable, profilableByShell, multiArch, use32bitAbi,
                         useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage,
@@ -546,7 +550,7 @@
             return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                     "No start tag found");
         }
-        if (!parser.getName().equals(PackageParser.TAG_MANIFEST)) {
+        if (!parser.getName().equals(ParsingPackageUtils.TAG_MANIFEST)) {
             return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                     "No <manifest> tag");
         }
@@ -625,4 +629,24 @@
             }
         }
     }
+
+    /**
+     * Check if the given file is an APK file.
+     *
+     * @param file the file to check.
+     * @return {@code true} if the given file is an APK file.
+     */
+    public static boolean isApkFile(File file) {
+        return isApkPath(file.getName());
+    }
+
+    /**
+     * Check if the given path ends with APK file extension.
+     *
+     * @param path the path to check.
+     * @return {@code true} if the given path ends with APK file extension.
+     */
+    public static boolean isApkPath(String path) {
+        return path.endsWith(APK_FILE_EXTENSION);
+    }
 }
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index f8fd4a5..b7365b3 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -385,7 +385,7 @@
         }
 
         // CompatibilityMode is global state.
-        if (!PackageParser.sCompatibilityModeEnabled) {
+        if (!ParsingPackageUtils.sCompatibilityModeEnabled) {
             ai.disableCompatibilityMode();
         }
 
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 38d3940..51ec297 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -335,7 +335,7 @@
 
     private int fullBackupContent;
     private int iconRes;
-    private int installLocation = PackageParser.PARSE_DEFAULT_INSTALL_LOCATION;
+    private int installLocation = ParsingPackageUtils.PARSE_DEFAULT_INSTALL_LOCATION;
     private int labelRes;
     private int largestWidthLimitDp;
     private int logo;
@@ -1013,7 +1013,8 @@
         // TODO(b/135203078): See ParsingPackageImpl#getHiddenApiEnforcementPolicy
 //        appInfo.mHiddenApiPolicy
 //        appInfo.hiddenUntilInstalled
-        appInfo.icon = (PackageParser.sUseRoundIcon && roundIconRes != 0) ? roundIconRes : iconRes;
+        appInfo.icon =
+                (ParsingPackageUtils.sUseRoundIcon && roundIconRes != 0) ? roundIconRes : iconRes;
         appInfo.iconRes = iconRes;
         appInfo.roundIconRes = roundIconRes;
         appInfo.installLocation = installLocation;
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index 13ae7a2..a102e82 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -66,7 +66,7 @@
 
     /**
      * The names of packages to adopt ownership of permissions from, parsed under
-     * {@link PackageParser#TAG_ADOPT_PERMISSIONS}.
+     * {@link ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}.
      * @see R.styleable#AndroidManifestOriginalPackage_name
      */
     @NonNull
@@ -105,7 +105,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in
-     * {@link PackageParser#TAG_KEY_SETS}.
+     * {@link ParsingPackageUtils#TAG_KEY_SETS}.
      * @see R.styleable#AndroidManifestKeySet
      * @see R.styleable#AndroidManifestPublicKey
      */
@@ -816,7 +816,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in
-     * {@link PackageParser#TAG_KEY_SETS}.
+     * {@link ParsingPackageUtils#TAG_KEY_SETS}.
      * @see R.styleable#AndroidManifestUpgradeKeySet
      */
     @NonNull
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 494b3cc..b054304 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -189,6 +189,12 @@
             PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
     public static final int PARSE_DEFAULT_TARGET_SANDBOX = 1;
 
+    /** If set to true, we will only allow package files that exactly match
+     *  the DTD. Otherwise, we try to get as much from the package as we
+     *  can without failing. This should normally be set to false, to
+     *  support extensions to the DTD in future versions. */
+    public static final boolean RIGID_PARSER = false;
+
     public static final int PARSE_MUST_BE_APK = 1 << 0;
     public static final int PARSE_IGNORE_PROCESSES = 1 << 1;
     public static final int PARSE_EXTERNAL_STORAGE = 1 << 3;
@@ -220,7 +226,7 @@
      */
     @NonNull
     public static ParseResult<ParsingPackage> parseDefaultOneTime(File file,
-            @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) {
+            @ParseFlags int parseFlags, boolean collectCertificates) {
         ParseInput input = ParseTypeImpl.forDefaultParsing().reset();
         return parseDefault(input, file, parseFlags, collectCertificates);
     }
@@ -232,7 +238,7 @@
      */
     @NonNull
     public static ParseResult<ParsingPackage> parseDefault(ParseInput input, File file,
-            @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) {
+            @ParseFlags int parseFlags, boolean collectCertificates) {
         ParseResult<ParsingPackage> result;
 
         ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, new Callback() {
@@ -334,14 +340,14 @@
      */
     private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
             int flags) {
-        ParseResult<PackageParser.PackageLite> liteResult =
+        final ParseResult<PackageLite> liteResult =
                 ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0);
         if (liteResult.isError()) {
             return input.error(liteResult);
         }
 
-        final PackageParser.PackageLite lite = liteResult.getResult();
-        if (mOnlyCoreApps && !lite.coreApp) {
+        final PackageLite lite = liteResult.getResult();
+        if (mOnlyCoreApps && !lite.isCoreApp()) {
             return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
                     "Not a coreApp: " + packageDir);
         }
@@ -349,7 +355,7 @@
         // Build the split dependency tree.
         SparseArray<int[]> splitDependencies = null;
         final SplitAssetLoader assetLoader;
-        if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
+        if (lite.isIsolatedSplits() && !ArrayUtils.isEmpty(lite.getSplitNames())) {
             try {
                 splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
                 assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
@@ -362,22 +368,22 @@
 
         try {
             final AssetManager assets = assetLoader.getBaseAssetManager();
-            final File baseApk = new File(lite.baseCodePath);
-            ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
-                    lite.codePath, assets, flags);
+            final File baseApk = new File(lite.getBaseApkPath());
+            final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
+                    lite.getPath(), assets, flags);
             if (result.isError()) {
                 return input.error(result);
             }
 
             ParsingPackage pkg = result.getResult();
-            if (!ArrayUtils.isEmpty(lite.splitNames)) {
+            if (!ArrayUtils.isEmpty(lite.getSplitNames())) {
                 pkg.asSplit(
-                        lite.splitNames,
-                        lite.splitCodePaths,
-                        lite.splitRevisionCodes,
+                        lite.getSplitNames(),
+                        lite.getSplitApkPaths(),
+                        lite.getSplitRevisionCodes(),
                         splitDependencies
                 );
-                final int num = lite.splitNames.length;
+                final int num = lite.getSplitNames().length;
 
                 for (int i = 0; i < num; i++) {
                     final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
@@ -385,11 +391,11 @@
                 }
             }
 
-            pkg.setUse32BitAbi(lite.use32bitAbi);
+            pkg.setUse32BitAbi(lite.isUse32bitAbi());
             return input.success(pkg);
         } catch (PackageParserException e) {
             return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
-                    "Failed to load assets: " + lite.baseCodePath, e);
+                    "Failed to load assets: " + lite.getBaseApkPath(), e);
         } finally {
             IoUtils.closeQuietly(assetLoader);
         }
@@ -403,21 +409,21 @@
      */
     private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile,
             int flags) throws PackageParserException {
-        ParseResult<PackageParser.PackageLite> liteResult =
+        final ParseResult<PackageLite> liteResult =
                 ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags);
         if (liteResult.isError()) {
             return input.error(liteResult);
         }
 
-        final PackageParser.PackageLite lite = liteResult.getResult();
-        if (mOnlyCoreApps && !lite.coreApp) {
+        final PackageLite lite = liteResult.getResult();
+        if (mOnlyCoreApps && !lite.isCoreApp()) {
             return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
                     "Not a coreApp: " + apkFile);
         }
 
         final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
         try {
-            ParseResult<ParsingPackage> result = parseBaseApk(input,
+            final ParseResult<ParsingPackage> result = parseBaseApk(input,
                     apkFile,
                     apkFile.getCanonicalPath(),
                     assetLoader.getBaseAssetManager(), flags);
@@ -426,7 +432,7 @@
             }
 
             return input.success(result.getResult()
-                    .setUse32BitAbi(lite.use32bitAbi));
+                    .setUse32BitAbi(lite.isUse32bitAbi()));
         } catch (IOException e) {
             return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                     "Failed to get path: " + apkFile, e);
@@ -440,12 +446,12 @@
         final String apkPath = apkFile.getAbsolutePath();
 
         String volumeUuid = null;
-        if (apkPath.startsWith(PackageParser.MNT_EXPAND)) {
-            final int end = apkPath.indexOf('/', PackageParser.MNT_EXPAND.length());
-            volumeUuid = apkPath.substring(PackageParser.MNT_EXPAND.length(), end);
+        if (apkPath.startsWith(MNT_EXPAND)) {
+            final int end = apkPath.indexOf('/', MNT_EXPAND.length());
+            volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
         }
 
-        if (PackageParser.DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
+        if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
 
         final int cookie = assets.findCookieForPath(apkPath);
         if (cookie == 0) {
@@ -454,7 +460,7 @@
         }
 
         try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
-                PackageParser.ANDROID_MANIFEST_FILENAME)) {
+                ANDROID_MANIFEST_FILENAME)) {
             final Resources res = new Resources(assets, mDisplayMetrics, null);
 
             ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,
@@ -495,7 +501,7 @@
 
             pkg.setVolumeUuid(volumeUuid);
 
-            if ((flags & PackageParser.PARSE_COLLECT_CERTIFICATES) != 0) {
+            if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
                 pkg.setSigningDetails(getSigningDetails(pkg, false));
             } else {
                 pkg.setSigningDetails(SigningDetails.UNKNOWN);
@@ -512,7 +518,7 @@
             ParsingPackage pkg, int splitIndex, AssetManager assets, int flags) {
         final String apkPath = pkg.getSplitCodePaths()[splitIndex];
 
-        if (PackageParser.DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
+        if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
 
         // This must always succeed, as the path has been added to the AssetManager before.
         final int cookie = assets.findCookieForPath(apkPath);
@@ -521,7 +527,7 @@
                     "Failed adding asset path: " + apkPath);
         }
         try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
-                PackageParser.ANDROID_MANIFEST_FILENAME)) {
+                ANDROID_MANIFEST_FILENAME)) {
             Resources res = new Resources(assets, mDisplayMetrics, null);
             ParseResult<ParsingPackage> parseResult = parseSplitApk(input, pkg, res,
                     parser, flags, splitIndex);
@@ -620,9 +626,9 @@
 
             final ParseResult result;
             String tagName = parser.getName();
-            if (PackageParser.TAG_APPLICATION.equals(tagName)) {
+            if (TAG_APPLICATION.equals(tagName)) {
                 if (foundApp) {
-                    if (PackageParser.RIGID_PARSER) {
+                    if (RIGID_PARSER) {
                         result = input.error("<manifest> has more than one <application>");
                     } else {
                         Slog.w(TAG, "<manifest> has more than one <application>");
@@ -701,7 +707,7 @@
                     ParseResult<ParsedActivity> activityResult =
                             ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
                                     res,
-                                    parser, flags, PackageParser.sUseRoundIcon, input);
+                                    parser, flags, sUseRoundIcon, input);
                     if (activityResult.isSuccess()) {
                         ParsedActivity activity = activityResult.getResult();
                         if (isActivity) {
@@ -716,7 +722,7 @@
                 case "service":
                     ParseResult<ParsedService> serviceResult = ParsedServiceUtils.parseService(
                             mSeparateProcesses, pkg, res, parser, flags,
-                            PackageParser.sUseRoundIcon, input);
+                            sUseRoundIcon, input);
                     if (serviceResult.isSuccess()) {
                         ParsedService service = serviceResult.getResult();
                         pkg.addService(service);
@@ -727,7 +733,7 @@
                 case "provider":
                     ParseResult<ParsedProvider> providerResult =
                             ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
-                                    flags, PackageParser.sUseRoundIcon, input);
+                                    flags, sUseRoundIcon, input);
                     if (providerResult.isSuccess()) {
                         ParsedProvider provider = providerResult.getResult();
                         pkg.addProvider(provider);
@@ -737,7 +743,7 @@
                     break;
                 case "activity-alias":
                     activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res, parser,
-                            PackageParser.sUseRoundIcon, input);
+                            sUseRoundIcon, input);
                     if (activityResult.isSuccess()) {
                         ParsedActivity activity = activityResult.getResult();
                         pkg.addActivity(activity);
@@ -815,12 +821,12 @@
             return sharedUserResult;
         }
 
-        pkg.setInstallLocation(anInteger(PackageParser.PARSE_DEFAULT_INSTALL_LOCATION,
+        pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
                 R.styleable.AndroidManifest_installLocation, sa))
-                .setTargetSandboxVersion(anInteger(PackageParser.PARSE_DEFAULT_TARGET_SANDBOX,
+                .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
                         R.styleable.AndroidManifest_targetSandboxVersion, sa))
                 /* Set the global "on SD card" flag */
-                .setExternalStorage((flags & PackageParser.PARSE_EXTERNAL_STORAGE) != 0);
+                .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
 
         boolean foundApp = false;
         final int depth = parser.getDepth();
@@ -836,9 +842,9 @@
             final ParseResult result;
 
             // <application> has special logic, so it's handled outside the general method
-            if (PackageParser.TAG_APPLICATION.equals(tagName)) {
+            if (TAG_APPLICATION.equals(tagName)) {
                 if (foundApp) {
-                    if (PackageParser.RIGID_PARSER) {
+                    if (RIGID_PARSER) {
                         result = input.error("<manifest> has more than one <application>");
                     } else {
                         Slog.w(TAG, "<manifest> has more than one <application>");
@@ -897,51 +903,51 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
             throws IOException, XmlPullParserException {
         switch (tag) {
-            case PackageParser.TAG_OVERLAY:
+            case TAG_OVERLAY:
                 return parseOverlay(input, pkg, res, parser);
-            case PackageParser.TAG_KEY_SETS:
+            case TAG_KEY_SETS:
                 return parseKeySets(input, pkg, res, parser);
             case "feature": // TODO moltmann: Remove
-            case PackageParser.TAG_ATTRIBUTION:
+            case TAG_ATTRIBUTION:
                 return parseAttribution(input, pkg, res, parser);
-            case PackageParser.TAG_PERMISSION_GROUP:
+            case TAG_PERMISSION_GROUP:
                 return parsePermissionGroup(input, pkg, res, parser);
-            case PackageParser.TAG_PERMISSION:
+            case TAG_PERMISSION:
                 return parsePermission(input, pkg, res, parser);
-            case PackageParser.TAG_PERMISSION_TREE:
+            case TAG_PERMISSION_TREE:
                 return parsePermissionTree(input, pkg, res, parser);
-            case PackageParser.TAG_USES_PERMISSION:
-            case PackageParser.TAG_USES_PERMISSION_SDK_M:
-            case PackageParser.TAG_USES_PERMISSION_SDK_23:
+            case TAG_USES_PERMISSION:
+            case TAG_USES_PERMISSION_SDK_M:
+            case TAG_USES_PERMISSION_SDK_23:
                 return parseUsesPermission(input, pkg, res, parser);
-            case PackageParser.TAG_USES_CONFIGURATION:
+            case TAG_USES_CONFIGURATION:
                 return parseUsesConfiguration(input, pkg, res, parser);
-            case PackageParser.TAG_USES_FEATURE:
+            case TAG_USES_FEATURE:
                 return parseUsesFeature(input, pkg, res, parser);
-            case PackageParser.TAG_FEATURE_GROUP:
+            case TAG_FEATURE_GROUP:
                 return parseFeatureGroup(input, pkg, res, parser);
-            case PackageParser.TAG_USES_SDK:
+            case TAG_USES_SDK:
                 return parseUsesSdk(input, pkg, res, parser);
-            case PackageParser.TAG_SUPPORT_SCREENS:
+            case TAG_SUPPORT_SCREENS:
                 return parseSupportScreens(input, pkg, res, parser);
-            case PackageParser.TAG_PROTECTED_BROADCAST:
+            case TAG_PROTECTED_BROADCAST:
                 return parseProtectedBroadcast(input, pkg, res, parser);
-            case PackageParser.TAG_INSTRUMENTATION:
+            case TAG_INSTRUMENTATION:
                 return parseInstrumentation(input, pkg, res, parser);
-            case PackageParser.TAG_ORIGINAL_PACKAGE:
+            case TAG_ORIGINAL_PACKAGE:
                 return parseOriginalPackage(input, pkg, res, parser);
-            case PackageParser.TAG_ADOPT_PERMISSIONS:
+            case TAG_ADOPT_PERMISSIONS:
                 return parseAdoptPermissions(input, pkg, res, parser);
-            case PackageParser.TAG_USES_GL_TEXTURE:
-            case PackageParser.TAG_COMPATIBLE_SCREENS:
-            case PackageParser.TAG_SUPPORTS_INPUT:
-            case PackageParser.TAG_EAT_COMMENT:
+            case TAG_USES_GL_TEXTURE:
+            case TAG_COMPATIBLE_SCREENS:
+            case TAG_SUPPORTS_INPUT:
+            case TAG_EAT_COMMENT:
                 // Just skip this tag
                 XmlUtils.skipCurrentTag(parser);
                 return input.success(pkg);
-            case PackageParser.TAG_RESTRICT_UPDATE:
+            case TAG_RESTRICT_UPDATE:
                 return parseRestrictUpdateHash(flags, input, pkg, res, parser);
-            case PackageParser.TAG_QUERIES:
+            case TAG_QUERIES:
                 return parseQueries(input, pkg, res, parser);
             default:
                 return ParsingUtils.unknownTag("<manifest>", pkg, parser, input);
@@ -1125,7 +1131,7 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws XmlPullParserException, IOException {
         ParseResult<ParsedPermissionGroup> result = ParsedPermissionUtils.parsePermissionGroup(
-                pkg, res, parser, PackageParser.sUseRoundIcon, input);
+                pkg, res, parser, sUseRoundIcon, input);
         if (result.isError()) {
             return input.error(result);
         }
@@ -1136,7 +1142,7 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws XmlPullParserException, IOException {
         ParseResult<ParsedPermission> result = ParsedPermissionUtils.parsePermission(
-                pkg, res, parser, PackageParser.sUseRoundIcon, input);
+                pkg, res, parser, sUseRoundIcon, input);
         if (result.isError()) {
             return input.error(result);
         }
@@ -1147,7 +1153,7 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws XmlPullParserException, IOException {
         ParseResult<ParsedPermission> result = ParsedPermissionUtils.parsePermissionTree(
-                pkg, res, parser, PackageParser.sUseRoundIcon, input);
+                pkg, res, parser, sUseRoundIcon, input);
         if (result.isError()) {
             return input.error(result);
         }
@@ -1405,7 +1411,7 @@
     private static ParseResult<ParsingPackage> parseUsesSdk(ParseInput input,
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws IOException, XmlPullParserException {
-        if (PackageParser.SDK_VERSION > 0) {
+        if (SDK_VERSION > 0) {
             TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
             try {
                 int minVers = 1;
@@ -1440,7 +1446,7 @@
                 }
 
                 ParseResult<Integer> targetSdkVersionResult = computeTargetSdkVersion(
-                        targetVers, targetCode, PackageParser.SDK_CODENAMES, input);
+                        targetVers, targetCode, SDK_CODENAMES, input);
                 if (targetSdkVersionResult.isError()) {
                     return input.error(targetSdkVersionResult);
                 }
@@ -1454,7 +1460,7 @@
                 }
 
                 ParseResult<Integer> minSdkVersionResult = computeMinSdkVersion(minVers, minCode,
-                        PackageParser.SDK_VERSION, PackageParser.SDK_CODENAMES, input);
+                        SDK_VERSION, SDK_CODENAMES, input);
                 if (minSdkVersionResult.isError()) {
                     return input.error(minSdkVersionResult);
                 }
@@ -1637,7 +1643,7 @@
 
     private static ParseResult<ParsingPackage> parseRestrictUpdateHash(int flags, ParseInput input,
             ParsingPackage pkg, Resources res, XmlResourceParser parser) {
-        if ((flags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+        if ((flags & PARSE_IS_SYSTEM_DIR) != 0) {
             TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestRestrictUpdate);
             try {
                 final String hash = sa.getNonConfigurationString(
@@ -1846,7 +1852,7 @@
                         return input.error("Empty class name in package " + pkgName);
                     }
 
-                    if (PackageParser.DEBUG_BACKUP) {
+                    if (DEBUG_BACKUP) {
                         Slog.v(TAG, "android:backupAgent = " + backupAgentName
                                 + " from " + pkgName + "+" + backupAgent);
                     }
@@ -1870,7 +1876,7 @@
                     fullBackupContent = v.resourceId;
 
                     if (v.resourceId == 0) {
-                        if (PackageParser.DEBUG_BACKUP) {
+                        if (DEBUG_BACKUP) {
                             Slog.v(TAG, "fullBackupContent specified as boolean=" +
                                     (v.data == 0 ? "false" : "true"));
                         }
@@ -1880,7 +1886,7 @@
 
                     pkg.setFullBackupContent(fullBackupContent);
                 }
-                if (PackageParser.DEBUG_BACKUP) {
+                if (DEBUG_BACKUP) {
                     Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName);
                 }
             }
@@ -1994,7 +2000,7 @@
                 case "receiver":
                     ParseResult<ParsedActivity> activityResult =
                             ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
-                                    res, parser, flags, PackageParser.sUseRoundIcon, input);
+                                    res, parser, flags, sUseRoundIcon, input);
 
                     if (activityResult.isSuccess()) {
                         ParsedActivity activity = activityResult.getResult();
@@ -2012,7 +2018,7 @@
                 case "service":
                     ParseResult<ParsedService> serviceResult =
                             ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
-                                    flags, PackageParser.sUseRoundIcon, input);
+                                    flags, sUseRoundIcon, input);
                     if (serviceResult.isSuccess()) {
                         ParsedService service = serviceResult.getResult();
                         hasServiceOrder |= (service.getOrder() != 0);
@@ -2024,7 +2030,7 @@
                 case "provider":
                     ParseResult<ParsedProvider> providerResult =
                             ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
-                                    flags, PackageParser.sUseRoundIcon, input);
+                                    flags, sUseRoundIcon, input);
                     if (providerResult.isSuccess()) {
                         pkg.addProvider(providerResult.getResult());
                     }
@@ -2033,7 +2039,7 @@
                     break;
                 case "activity-alias":
                     activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
-                            parser, PackageParser.sUseRoundIcon, input);
+                            parser, sUseRoundIcon, input);
                     if (activityResult.isSuccess()) {
                         ParsedActivity activity = activityResult.getResult();
                         hasActivityOrder |= (activity.getOrder() != 0);
@@ -2505,8 +2511,7 @@
     private static void setMaxAspectRatio(ParsingPackage pkg) {
         // Default to (1.86) 16.7:9 aspect ratio for pre-O apps and unset for O and greater.
         // NOTE: 16.7:9 was the max aspect ratio Android devices can support pre-O per the CDD.
-        float maxAspectRatio = pkg.getTargetSdkVersion() < O
-                ? PackageParser.DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0;
+        float maxAspectRatio = pkg.getTargetSdkVersion() < O ? DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0;
 
         float packageMaxAspectRatio = pkg.getMaxAspectRatio();
         if (packageMaxAspectRatio != 0) {
@@ -2514,10 +2519,8 @@
             maxAspectRatio = packageMaxAspectRatio;
         } else {
             Bundle appMetaData = pkg.getMetaData();
-            if (appMetaData != null && appMetaData.containsKey(
-                    PackageParser.METADATA_MAX_ASPECT_RATIO)) {
-                maxAspectRatio = appMetaData.getFloat(PackageParser.METADATA_MAX_ASPECT_RATIO,
-                        maxAspectRatio);
+            if (appMetaData != null && appMetaData.containsKey(METADATA_MAX_ASPECT_RATIO)) {
+                maxAspectRatio = appMetaData.getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio);
             }
         }
 
@@ -2536,8 +2539,7 @@
             // process the meta data here since this method is called at the end of processing
             // the application and all meta data is guaranteed.
             final float activityAspectRatio = activity.getMetaData() != null
-                    ? activity.getMetaData().getFloat(PackageParser.METADATA_MAX_ASPECT_RATIO,
-                    maxAspectRatio)
+                    ? activity.getMetaData().getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio)
                     : maxAspectRatio;
 
             activity.setMaxAspectRatio(activity.getResizeMode(), activityAspectRatio);
@@ -2565,7 +2567,7 @@
     private void setSupportsSizeChanges(ParsingPackage pkg) {
         final Bundle appMetaData = pkg.getMetaData();
         final boolean supportsSizeChanges = appMetaData != null
-                && appMetaData.getBoolean(PackageParser.METADATA_SUPPORTS_SIZE_CHANGES, false);
+                && appMetaData.getBoolean(METADATA_SUPPORTS_SIZE_CHANGES, false);
 
         List<ParsedActivity> activities = pkg.getActivities();
         int activitiesSize = activities.size();
@@ -2573,7 +2575,7 @@
             ParsedActivity activity = activities.get(index);
             if (supportsSizeChanges || (activity.getMetaData() != null
                     && activity.getMetaData().getBoolean(
-                            PackageParser.METADATA_SUPPORTS_SIZE_CHANGES, false))) {
+                            METADATA_SUPPORTS_SIZE_CHANGES, false))) {
                 activity.setSupportsSizeChanges(true);
             }
         }
@@ -2674,7 +2676,7 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws XmlPullParserException, IOException {
         ParseResult<ParsedInstrumentation> result = ParsedInstrumentationUtils.parseInstrumentation(
-                pkg, res, parser, PackageParser.sUseRoundIcon, input);
+                pkg, res, parser, sUseRoundIcon, input);
         if (result.isError()) {
             return input.error(result);
         }
@@ -2860,7 +2862,7 @@
                     } else if (v.type == TypedValue.TYPE_FLOAT) {
                         property = new Property(name, v.getFloat(), packageName, className);
                     } else {
-                        if (!PackageParser.RIGID_PARSER) {
+                        if (!RIGID_PARSER) {
                             Slog.w(TAG,
                                     tagName + " only supports string, integer, float, color, "
                                             + "boolean, and resource reference types: "
diff --git a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
index d65f8ff..0403a25 100644
--- a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
+++ b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
@@ -22,9 +22,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Intent;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
 import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
@@ -77,7 +77,7 @@
     @NonNull
     public static ParseResult<String> buildProcessName(@NonNull String pkg, String defProc,
             CharSequence procSeq, int flags, String[] separateProcesses, ParseInput input) {
-        if ((flags & PackageParser.PARSE_IGNORE_PROCESSES) != 0 && !"system".contentEquals(
+        if ((flags & ParsingPackageUtils.PARSE_IGNORE_PROCESSES) != 0 && !"system".contentEquals(
                 procSeq)) {
             return input.success(defProc != null ? defProc : pkg);
         }
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/core/java/android/content/pm/parsing/component/ParsedActivity.java
index 1915028..2ea24f7 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivity.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivity.java
@@ -28,7 +28,6 @@
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -127,7 +126,7 @@
         activity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
         activity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE;
         activity.maxRecents = ActivityTaskManager.getDefaultAppRecentsLimitStatic();
-        activity.configChanges = PackageParser.getActivityConfigChanges(0, 0);
+        activity.configChanges = ParsedActivityUtils.getActivityConfigChanges(0, 0);
         activity.softInputMode = 0;
         activity.persistableMode = ActivityInfo.PERSIST_NEVER;
         activity.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
index f96bd54..f821e08 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
@@ -23,8 +23,8 @@
 import android.app.ActivityTaskManager;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.content.pm.PackageParser;
 import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseInput.DeferredError;
@@ -67,6 +67,12 @@
         SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED);
     }
 
+    /**
+     * Bit mask of all the valid bits that can be set in recreateOnConfigChanges.
+     */
+    private static final int RECREATE_ON_CONFIG_CHANGES_MASK =
+            ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
+
     @NonNull
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public static ParseResult<ParsedActivity> parseActivityOrReceiver(String[] separateProcesses,
@@ -153,7 +159,7 @@
                 activity.rotationAnimation = sa.getInt(R.styleable.AndroidManifestActivity_rotationAnimation, WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED);
                 activity.softInputMode = sa.getInt(R.styleable.AndroidManifestActivity_windowSoftInputMode, 0);
 
-                activity.configChanges = PackageParser.getActivityConfigChanges(
+                activity.configChanges = getActivityConfigChanges(
                         sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0),
                         sa.getInt(R.styleable.AndroidManifestActivity_recreateOnConfigChanges, 0));
 
@@ -345,7 +351,7 @@
                     if (intent != null) {
                         activity.order = Math.max(intent.getOrder(), activity.order);
                         activity.addIntent(intent);
-                        if (PackageParser.LOG_UNSAFE_BROADCASTS && isReceiver
+                        if (LOG_UNSAFE_BROADCASTS && isReceiver
                                 && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
                             int actionCount = intent.countActions();
                             for (int i = 0; i < actionCount; i++) {
@@ -354,7 +360,7 @@
                                     continue;
                                 }
 
-                                if (!PackageParser.SAFE_BROADCASTS.contains(action)) {
+                                if (!SAFE_BROADCASTS.contains(action)) {
                                     Slog.w(TAG,
                                             "Broadcast " + action + " may never be delivered to "
                                                     + pkg.getPackageName() + " as requested at: "
@@ -532,7 +538,7 @@
             ParsedActivity activity, ParseInput input) {
         // There isn't a metadata for us to fall back. Whatever is in layout is correct.
         if (activity.metaData == null || !activity.metaData.containsKey(
-                PackageParser.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
+                ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
             return input.success(activity.windowLayout);
         }
 
@@ -542,7 +548,7 @@
         }
 
         String windowLayoutAffinity = activity.metaData.getString(
-                PackageParser.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY);
+                ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY);
         ActivityInfo.WindowLayout layout = activity.windowLayout;
         if (layout == null) {
             layout = new ActivityInfo.WindowLayout(-1 /* width */, -1 /* widthFraction */,
@@ -553,4 +559,14 @@
         }
         return input.success(layout);
     }
+
+    /**
+     * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
+     * @param recreateOnConfigChanges The bit mask recreateOnConfigChanges fetched from
+     *                                AndroidManifest.xml.
+     * @hide
+     */
+    static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) {
+        return configChanges | ((~recreateOnConfigChanges) & RECREATE_ON_CONFIG_CHANGES_MASK);
+    }
 }
diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
index 368dcfd..939e77f 100644
--- a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
@@ -21,6 +21,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageParser;
 import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
@@ -65,7 +66,7 @@
                 }
             }
 
-            if (PackageParser.sUseRoundIcon) {
+            if (ParsingPackageUtils.sUseRoundIcon) {
                 intentInfo.icon = sa.getResourceId(
                         R.styleable.AndroidManifestIntentFilter_roundIcon, 0);
             }
@@ -141,7 +142,7 @@
 
         intentInfo.hasDefault = intentInfo.hasCategory(Intent.CATEGORY_DEFAULT);
 
-        if (PackageParser.DEBUG_PARSER) {
+        if (DEBUG) {
             final StringBuilder cats = new StringBuilder("Intent d=");
             cats.append(intentInfo.isHasDefault());
             cats.append(", cat=");
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
index 9e3a8f4..f3caf60 100644
--- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -18,9 +18,11 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
 
-import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser.ParseFlags;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingPackageUtils.ParseFlags;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.os.Build;
@@ -36,20 +38,21 @@
  * @hide
  */
 public class DefaultSplitAssetLoader implements SplitAssetLoader {
-    private final String mBaseCodePath;
-    private final String[] mSplitCodePaths;
+    private final String mBaseApkPath;
+    private final String[] mSplitApkPaths;
     private final @ParseFlags int mFlags;
     private AssetManager mCachedAssetManager;
 
-    public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) {
-        mBaseCodePath = pkg.baseCodePath;
-        mSplitCodePaths = pkg.splitCodePaths;
+    public DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
+        mBaseApkPath = pkg.getBaseApkPath();
+        mSplitApkPaths = pkg.getSplitApkPaths();
         mFlags = flags;
     }
 
     private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
             throws PackageParserException {
-        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+        if ((flags & ParsingPackageUtils.PARSE_MUST_BE_APK) != 0
+                && !ApkLiteParseUtils.isApkPath(path)) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                     "Invalid package file: " + path);
         }
@@ -68,16 +71,16 @@
             return mCachedAssetManager;
         }
 
-        ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
-                ? mSplitCodePaths.length : 0) + 1];
+        ApkAssets[] apkAssets = new ApkAssets[(mSplitApkPaths != null
+                ? mSplitApkPaths.length : 0) + 1];
 
         // Load the base.
         int splitIdx = 0;
-        apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);
+        apkAssets[splitIdx++] = loadApkAssets(mBaseApkPath, mFlags);
 
         // Load any splits.
-        if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
-            for (String apkPath : mSplitCodePaths) {
+        if (!ArrayUtils.isEmpty(mSplitApkPaths)) {
+            for (String apkPath : mSplitApkPaths) {
                 apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags);
             }
         }
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 58eaabf..523ca40 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -19,9 +19,11 @@
 
 import android.annotation.NonNull;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser.ParseFlags;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingPackageUtils.ParseFlags;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.os.Build;
@@ -45,14 +47,14 @@
     private final ApkAssets[][] mCachedSplitApks;
     private final AssetManager[] mCachedAssetManagers;
 
-    public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
+    public SplitAssetDependencyLoader(PackageLite pkg,
             SparseArray<int[]> dependencies, @ParseFlags int flags) {
         super(dependencies);
 
         // The base is inserted into index 0, so we need to shift all the splits by 1.
-        mSplitPaths = new String[pkg.splitCodePaths.length + 1];
-        mSplitPaths[0] = pkg.baseCodePath;
-        System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
+        mSplitPaths = new String[pkg.getSplitApkPaths().length + 1];
+        mSplitPaths[0] = pkg.getBaseApkPath();
+        System.arraycopy(pkg.getSplitApkPaths(), 0, mSplitPaths, 1, pkg.getSplitApkPaths().length);
 
         mFlags = flags;
         mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
@@ -66,7 +68,8 @@
 
     private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
             throws PackageParserException {
-        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+        if ((flags & ParsingPackageUtils.PARSE_MUST_BE_APK) != 0
+                && !ApkLiteParseUtils.isApkPath(path)) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                     "Invalid package file: " + path);
         }
diff --git a/core/java/android/content/pm/split/SplitDependencyLoader.java b/core/java/android/content/pm/split/SplitDependencyLoader.java
index 3586546..3e68132 100644
--- a/core/java/android/content/pm/split/SplitDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitDependencyLoader.java
@@ -17,7 +17,7 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.content.pm.PackageParser;
+import android.content.pm.parsing.PackageLite;
 import android.util.IntArray;
 import android.util.SparseArray;
 
@@ -149,10 +149,19 @@
         return dst;
     }
 
-    public static @NonNull SparseArray<int[]> createDependenciesFromPackage(
-            PackageParser.PackageLite pkg) throws IllegalDependencyException {
-        // The data structure that holds the dependencies. In PackageParser, splits are stored
-        // in their own array, separate from the base. We treat all paths as equals, so
+    /**
+     * Build the split dependency tree by the given package
+     *
+     * @param pkg The package to retrieve the dependency tree
+     * @return The dependency tree of splits
+     * @throws IllegalDependencyException if the requires split is missing, targets split is
+     *         missing, it declares itself as configuration split for a non-feature split, or
+     *         cycle detected in split dependencies.
+     */
+    public static @NonNull SparseArray<int[]> createDependenciesFromPackage(PackageLite pkg)
+            throws IllegalDependencyException {
+        // The data structure that holds the dependencies. In ParsingPackageUtils, splits are
+        // stored in their own array, separate from the base. We treat all paths as equals, so
         // we need to insert the base as index 0, and shift all other splits.
         final SparseArray<int[]> splitDependencies = new SparseArray<>();
 
@@ -161,19 +170,19 @@
 
         // First write out the <uses-split> dependencies. These must appear first in the
         // array of ints, as is convention in this class.
-        for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
-            if (!pkg.isFeatureSplits[splitIdx]) {
+        for (int splitIdx = 0; splitIdx < pkg.getSplitNames().length; splitIdx++) {
+            if (!pkg.getIsFeatureSplits()[splitIdx]) {
                 // Non-feature splits don't have dependencies.
                 continue;
             }
 
             // Implicit dependency on the base.
             final int targetIdx;
-            final String splitDependency = pkg.usesSplitNames[splitIdx];
+            final String splitDependency = pkg.getUsesSplitNames()[splitIdx];
             if (splitDependency != null) {
-                final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
+                final int depIdx = Arrays.binarySearch(pkg.getSplitNames(), splitDependency);
                 if (depIdx < 0) {
-                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                    throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx]
                             + "' requires split '" + splitDependency + "', which is missing.");
                 }
                 targetIdx = depIdx + 1;
@@ -188,26 +197,26 @@
         // dependencies and are considered leaves.
         //
         // At this point, all splits in splitDependencies have the first element in their array set.
-        for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
-            if (pkg.isFeatureSplits[splitIdx]) {
+        for (int splitIdx = 0, size = pkg.getSplitNames().length; splitIdx < size; splitIdx++) {
+            if (pkg.getIsFeatureSplits()[splitIdx]) {
                 // Feature splits are not configForSplits.
                 continue;
             }
 
             // Implicit feature for the base.
             final int targetSplitIdx;
-            final String configForSplit = pkg.configForSplit[splitIdx];
+            final String configForSplit = pkg.getConfigForSplit()[splitIdx];
             if (configForSplit != null) {
-                final int depIdx = Arrays.binarySearch(pkg.splitNames, configForSplit);
+                final int depIdx = Arrays.binarySearch(pkg.getSplitNames(), configForSplit);
                 if (depIdx < 0) {
-                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                    throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx]
                             + "' targets split '" + configForSplit + "', which is missing.");
                 }
 
-                if (!pkg.isFeatureSplits[depIdx]) {
-                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                if (!pkg.getIsFeatureSplits()[depIdx]) {
+                    throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx]
                             + "' declares itself as configuration split for a non-feature split '"
-                            + pkg.splitNames[depIdx] + "'");
+                            + pkg.getSplitNames()[depIdx] + "'");
                 }
                 targetSplitIdx = depIdx + 1;
             } else {
diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java
index ea6cf2f..eca56b3 100644
--- a/core/java/android/graphics/fonts/FontManager.java
+++ b/core/java/android/graphics/fonts/FontManager.java
@@ -16,6 +16,7 @@
 
 package android.graphics.fonts;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -28,6 +29,8 @@
 
 import com.android.internal.graphics.fonts.IFontManager;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -41,6 +44,116 @@
     private static final String TAG = "FontManager";
     private final @NonNull IFontManager mIFontManager;
 
+    /** @hide */
+    @IntDef(prefix = "ERROR_CODE_",
+            value = { ERROR_CODE_OK, ERROR_CODE_FAILED_TO_WRITE_FONT_FILE,
+                    ERROR_CODE_VERIFICATION_FAILURE, ERROR_CODE_FONT_NAME_MISMATCH,
+                    ERROR_CODE_INVALID_FONT_FILE, ERROR_CODE_MISSING_POST_SCRIPT_NAME,
+                    ERROR_CODE_DOWNGRADING, ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE,
+                    ERROR_CODE_FONT_UPDATER_DISABLED })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ErrorCode {}
+
+    /**
+     * Indicates an operation has processed successfully.
+     * @hide
+     */
+    public static final int ERROR_CODE_OK = 0;
+
+    /**
+     * Indicates a failure of writing font files.
+     * @hide
+     */
+    public static final int ERROR_CODE_FAILED_TO_WRITE_FONT_FILE = -1;
+
+    /**
+     * Indicates a failure of fs-verity setup.
+     * @hide
+     */
+    public static final int ERROR_CODE_VERIFICATION_FAILURE = -2;
+
+    /**
+     * Indicates a failure of verifying the font name with PostScript name.
+     * @hide
+     */
+    public static final int ERROR_CODE_FONT_NAME_MISMATCH = -3;
+
+    /**
+     * Indicates a failure of placing fonts due to unexpected font contents.
+     * @hide
+     */
+    public static final int ERROR_CODE_INVALID_FONT_FILE = -4;
+
+    /**
+     * Indicates a failure due to missing PostScript name in name table.
+     * @hide
+     */
+    public static final int ERROR_CODE_MISSING_POST_SCRIPT_NAME = -5;
+
+    /**
+     * Indicates a failure of placing fonts due to downgrading.
+     * @hide
+     */
+    public static final int ERROR_CODE_DOWNGRADING = -6;
+
+    /**
+     * Indicates a failure of writing system font configuration XML file.
+     * @hide
+     */
+    public static final int ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE = -7;
+
+    /**
+     * Indicates a failure due to disabled font updater.
+     * @hide
+     */
+    public static final int ERROR_CODE_FONT_UPDATER_DISABLED = -8;
+
+    /**
+     * Indicates a failure of opening font file.
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int ERROR_CODE_FAILED_TO_OPEN_FONT_FILE = -10001;
+
+    /**
+     * Indicates a failure of opening signature file.
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int ERROR_CODE_FAILED_TO_OPEN_SIGNATURE_FILE = -10002;
+
+    /**
+     * Indicates a failure of invalid shell command arguments.
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int ERROR_CODE_INVALID_SHELL_ARGUMENT = -10003;
+
+    /**
+     * Indicates a failure of reading signature file.
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int ERROR_CODE_INVALID_SIGNATURE_FILE = -10004;
+
+    /**
+     * Indicates a failure due to exceeding allowed signature file size (8kb).
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int ERROR_CODE_SIGNATURE_TOO_LARGE = -10005;
+
+
     private FontManager(@NonNull IFontManager iFontManager) {
         mIFontManager = iFontManager;
     }
diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
index c3b6c05..c093489 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
@@ -32,6 +32,12 @@
     // Hides the overlay.
     void hideUdfpsOverlay(int sensorId);
 
+    // Notifies of enrollment progress changes.
+    void onEnrollmentProgress(int sensorId, int remaining);
+
+    // Notifies when a non-terminal error occurs (e.g. user moved their finger too fast).
+    void onEnrollmentHelp(int sensorId);
+
     // Shows debug messages on the UDFPS overlay.
     void setDebugMessage(int sensorId, String message);
 }
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 4ec3f92..b90c728 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -503,6 +503,29 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface TvWakeOnOneTouchPlay {}
 
+    // -- Whether TV should send &lt;Standby&gt; on sleep.
+    /**
+     * Sending &lt;Standby&gt; on sleep.
+     *
+     * @hide
+     */
+    public static final int TV_SEND_STANDBY_ON_SLEEP_ENABLED = 1;
+    /**
+     * Not sending &lt;Standby&gt; on sleep.
+     *
+     * @hide
+     */
+    public static final int TV_SEND_STANDBY_ON_SLEEP_DISABLED = 0;
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "TV_SEND_STANDBY_ON_SLEEP_" }, value = {
+            TV_SEND_STANDBY_ON_SLEEP_ENABLED,
+            TV_SEND_STANDBY_ON_SLEEP_DISABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TvSendStandbyOnSleep {}
+
     // -- The RC profile of a TV panel.
     /**
      * RC profile none.
@@ -747,6 +770,14 @@
     public static final String CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY =
             "tv_wake_on_one_touch_play";
     /**
+     * Name of a setting deciding whether the device will also turn off other CEC devices
+     * when it goes to standby mode.
+     *
+     * @hide
+     */
+    public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP =
+            "tv_send_standby_on_sleep";
+    /**
      * Name of a setting representing the RC profile of a TV panel.
      *
      * @hide
@@ -805,12 +836,13 @@
         CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
         CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
         CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
+        CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
         CEC_SETTING_NAME_RC_PROFILE_TV,
         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
-        CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU
+        CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
     })
     public @interface CecSettingName {}
 
@@ -2158,4 +2190,48 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Set the current status of TV send &lt;Standby&gt; on Sleep.
+     *
+     * <p>Sets whether the device will also turn off other CEC devices
+     * when it goes to standby mode.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void setTvSendStandbyOnSleep(@NonNull @TvSendStandbyOnSleep int value) {
+        if (mService == null) {
+            Log.e(TAG, "HdmiControlService is not available");
+            throw new RuntimeException("HdmiControlService is not available");
+        }
+        try {
+            mService.setCecSettingIntValue(CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP, value);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the current status of TV send &lt;Standby&gt; on Sleep.
+     *
+     * <p>Reflects whether the device will also turn off other CEC devices
+     * when it goes to standby mode.
+     *
+     * @hide
+     */
+    @NonNull
+    @TvSendStandbyOnSleep
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public int getTvSendStandbyOnSleep() {
+        if (mService == null) {
+            Log.e(TAG, "HdmiControlService is not available");
+            throw new RuntimeException("HdmiControlService is not available");
+        }
+        try {
+            return mService.getCecSettingIntValue(CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 6fecee6..7197831 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -31,6 +31,7 @@
 import android.net.NetworkState;
 import android.net.ProxyInfo;
 import android.net.UidRange;
+import android.net.VpnInfo;
 import android.net.QosSocketInfo;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -43,7 +44,6 @@
 import com.android.connectivity.aidl.INetworkAgent;
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 
 /**
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 1a3dc97..d5aede7 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -23,11 +23,11 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.net.VpnInfo;
 import android.net.netstats.provider.INetworkStatsProvider;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.IBinder;
 import android.os.Messenger;
-import com.android.internal.net.VpnInfo;
 
 /** {@hide} */
 interface INetworkStatsService {
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index b6ae7ec..60923f5 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -15,6 +15,8 @@
  */
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.annotation.NonNull;
@@ -628,7 +630,7 @@
         }
 
         /** @hide */
-        @VisibleForTesting
+        @SystemApi(client = MODULE_LIBRARIES)
         public int getResourceId() {
             return mResourceId;
         }
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 53996a5..e829821 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -421,7 +421,7 @@
             throw new SocketException("Only AF_INET/AF_INET6 sockets supported");
         }
 
-        final int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId);
+        final int err = NetworkUtils.bindSocketToNetwork(fd, netId);
         if (err != 0) {
             // bindSocketToNetwork returns negative errno.
             throw new ErrnoException("Binding socket to network " + netId, -err)
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index b5962c5..8be4af7 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -81,11 +81,11 @@
     public native static boolean bindProcessToNetworkForHostResolution(int netId);
 
     /**
-     * Explicitly binds {@code socketfd} to the network designated by {@code netId}.  This
+     * Explicitly binds {@code fd} to the network designated by {@code netId}.  This
      * overrides any binding via {@link #bindProcessToNetwork}.
      * @return 0 on success or negative errno on failure.
      */
-    public native static int bindSocketToNetwork(int socketfd, int netId);
+    public static native int bindSocketToNetwork(FileDescriptor fd, int netId);
 
     /**
      * Protect {@code fd} from VPN connections.  After protecting, data sent through
@@ -93,9 +93,7 @@
      * forwarded through the VPN.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static boolean protectFromVpn(FileDescriptor fd) {
-        return protectFromVpn(fd.getInt$());
-    }
+    public static native boolean protectFromVpn(FileDescriptor fd);
 
     /**
      * Protect {@code socketfd} from VPN connections.  After protecting, data sent through
diff --git a/core/java/com/android/internal/net/VpnInfo.aidl b/core/java/android/net/VpnInfo.aidl
similarity index 94%
rename from core/java/com/android/internal/net/VpnInfo.aidl
rename to core/java/android/net/VpnInfo.aidl
index 6fc97be..8bcaa81 100644
--- a/core/java/com/android/internal/net/VpnInfo.aidl
+++ b/core/java/android/net/VpnInfo.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package android.net;
 
 parcelable VpnInfo;
diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/android/net/VpnInfo.java
similarity index 63%
rename from core/java/com/android/internal/net/VpnInfo.java
rename to core/java/android/net/VpnInfo.java
index e74af5e..cf58c57 100644
--- a/core/java/com/android/internal/net/VpnInfo.java
+++ b/core/java/android/net/VpnInfo.java
@@ -11,11 +11,13 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.internal.net;
+package android.net;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -23,14 +25,28 @@
 
 /**
  * A lightweight container used to carry information of the ongoing VPN.
- * Internal use only..
+ * Internal use only.
  *
  * @hide
  */
 public class VpnInfo implements Parcelable {
-    public int ownerUid;
-    public String vpnIface;
-    public String[] underlyingIfaces;
+    public final int ownerUid;
+    @Nullable
+    public final String vpnIface;
+    @Nullable
+    public final String[] underlyingIfaces;
+
+    public VpnInfo(int ownerUid, @Nullable String vpnIface, @Nullable String[] underlyingIfaces) {
+        this.ownerUid = ownerUid;
+        this.vpnIface = vpnIface;
+        this.underlyingIfaces = underlyingIfaces;
+    }
+
+    private VpnInfo(@NonNull Parcel in) {
+        this.ownerUid = in.readInt();
+        this.vpnIface = in.readString();
+        this.underlyingIfaces = in.createStringArray();
+    }
 
     @Override
     public String toString() {
@@ -47,22 +63,21 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(ownerUid);
         dest.writeString(vpnIface);
         dest.writeStringArray(underlyingIfaces);
     }
 
+    @NonNull
     public static final Parcelable.Creator<VpnInfo> CREATOR = new Parcelable.Creator<VpnInfo>() {
+        @NonNull
         @Override
-        public VpnInfo createFromParcel(Parcel source) {
-            VpnInfo info = new VpnInfo();
-            info.ownerUid = source.readInt();
-            info.vpnIface = source.readString();
-            info.underlyingIfaces = source.readStringArray();
-            return info;
+        public VpnInfo createFromParcel(@NonNull Parcel in) {
+            return new VpnInfo(in);
         }
 
+        @NonNull
         @Override
         public VpnInfo[] newArray(int size) {
             return new VpnInfo[size];
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index ede8faa..5eb4ba6 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -96,7 +96,11 @@
         return mPackageName;
     }
 
-    /** Retrieves the set of configured tunnels. */
+    /**
+     * Retrieves the set of configured tunnels.
+     *
+     * @hide
+     */
     @NonNull
     public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() {
         return Collections.unmodifiableSet(mGatewayConnectionConfigs);
@@ -146,7 +150,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeParcelable(toPersistableBundle(), flags);
     }
 
@@ -164,8 +168,12 @@
                 }
             };
 
-    /** This class is used to incrementally build {@link VcnConfig} objects. */
-    public static class Builder {
+    /**
+     * This class is used to incrementally build {@link VcnConfig} objects.
+     *
+     * @hide
+     */
+    public static final class Builder {
         @NonNull private final String mPackageName;
 
         @NonNull
@@ -182,6 +190,7 @@
          *
          * @param gatewayConnectionConfig the configuration for an individual gateway connection
          * @return this {@link Builder} instance, for chaining
+         * @hide
          */
         @NonNull
         public Builder addGatewayConnectionConfig(
@@ -196,6 +205,7 @@
          * Builds and validates the VcnConfig.
          *
          * @return an immutable VcnConfig instance
+         * @hide
          */
         @NonNull
         public VcnConfig build() {
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index d531cdb..cead2f1 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -17,6 +17,7 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -25,14 +26,19 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.vcn.util.PersistableBundleUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -97,6 +103,26 @@
         ALLOWED_CAPABILITIES = Collections.unmodifiableSet(allowedCaps);
     }
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"NET_CAPABILITY_"},
+            value = {
+                NetworkCapabilities.NET_CAPABILITY_MMS,
+                NetworkCapabilities.NET_CAPABILITY_SUPL,
+                NetworkCapabilities.NET_CAPABILITY_DUN,
+                NetworkCapabilities.NET_CAPABILITY_FOTA,
+                NetworkCapabilities.NET_CAPABILITY_IMS,
+                NetworkCapabilities.NET_CAPABILITY_CBS,
+                NetworkCapabilities.NET_CAPABILITY_IA,
+                NetworkCapabilities.NET_CAPABILITY_RCS,
+                NetworkCapabilities.NET_CAPABILITY_XCAP,
+                NetworkCapabilities.NET_CAPABILITY_EIMS,
+                NetworkCapabilities.NET_CAPABILITY_INTERNET,
+                NetworkCapabilities.NET_CAPABILITY_MCX,
+            })
+    public @interface VcnSupportedCapability {}
+
     private static final int DEFAULT_MAX_MTU = 1500;
 
     /**
@@ -128,10 +154,10 @@
             };
 
     private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities";
-    @NonNull private final Set<Integer> mExposedCapabilities;
+    @NonNull private final SortedSet<Integer> mExposedCapabilities;
 
     private static final String UNDERLYING_CAPABILITIES_KEY = "mUnderlyingCapabilities";
-    @NonNull private final Set<Integer> mUnderlyingCapabilities;
+    @NonNull private final SortedSet<Integer> mUnderlyingCapabilities;
 
     // TODO: Add Ike/ChildSessionParams as a subclass - maybe VcnIkeGatewayConnectionConfig
 
@@ -141,14 +167,14 @@
     private static final String RETRY_INTERVAL_MS_KEY = "mRetryIntervalsMs";
     @NonNull private final long[] mRetryIntervalsMs;
 
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public VcnGatewayConnectionConfig(
+    /** Builds a VcnGatewayConnectionConfig with the specified parameters. */
+    private VcnGatewayConnectionConfig(
             @NonNull Set<Integer> exposedCapabilities,
             @NonNull Set<Integer> underlyingCapabilities,
             @NonNull long[] retryIntervalsMs,
             @IntRange(from = MIN_MTU_V6) int maxMtu) {
-        mExposedCapabilities = exposedCapabilities;
-        mUnderlyingCapabilities = underlyingCapabilities;
+        mExposedCapabilities = new TreeSet(exposedCapabilities);
+        mUnderlyingCapabilities = new TreeSet(underlyingCapabilities);
         mRetryIntervalsMs = retryIntervalsMs;
         mMaxMtu = maxMtu;
 
@@ -163,9 +189,9 @@
         final PersistableBundle underlyingCapsBundle =
                 in.getPersistableBundle(UNDERLYING_CAPABILITIES_KEY);
 
-        mExposedCapabilities = new ArraySet<>(PersistableBundleUtils.toList(
+        mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
-        mUnderlyingCapabilities = new ArraySet<>(PersistableBundleUtils.toList(
+        mUnderlyingCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 underlyingCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
         mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
         mMaxMtu = in.getInt(MAX_MTU_KEY);
@@ -219,52 +245,93 @@
     /**
      * Returns all exposed capabilities.
      *
+     * <p>The returned integer-value capabilities will not contain duplicates, and will be sorted in
+     * ascending numerical order.
+     *
+     * @see Builder#addExposedCapability(int)
+     * @see Builder#clearExposedCapability(int)
      * @hide
      */
     @NonNull
+    public int[] getExposedCapabilities() {
+        // Sorted set guarantees ordering
+        return ArrayUtils.convertToIntArray(new ArrayList<>(mExposedCapabilities));
+    }
+
+    /**
+     * Returns all exposed capabilities.
+     *
+     * <p>Left to prevent the need to make major changes while changes are actively in flight.
+     *
+     * @deprecated use getExposedCapabilities() instead
+     * @hide
+     */
+    @Deprecated
+    @NonNull
     public Set<Integer> getAllExposedCapabilities() {
         return Collections.unmodifiableSet(mExposedCapabilities);
     }
 
     /**
-     * Checks if this config is configured to support/expose a specific capability.
+     * Returns all capabilities required of underlying networks.
      *
-     * @param capability the capability to check for
+     * <p>The returned integer-value capabilities will be sorted in ascending numerical order.
+     *
+     * @see Builder#addRequiredUnderlyingCapability(int)
+     * @see Builder#clearRequiredUnderlyingCapability(int)
+     * @hide
      */
-    public boolean hasExposedCapability(int capability) {
-        checkValidCapability(capability);
-
-        return mExposedCapabilities.contains(capability);
+    @NonNull
+    public int[] getRequiredUnderlyingCapabilities() {
+        // Sorted set guarantees ordering
+        return ArrayUtils.convertToIntArray(new ArrayList<>(mUnderlyingCapabilities));
     }
 
     /**
      * Returns all capabilities required of underlying networks.
      *
+     * <p>Left to prevent the need to make major changes while changes are actively in flight.
+     *
+     * @deprecated use getRequiredUnderlyingCapabilities() instead
      * @hide
      */
+    @Deprecated
     @NonNull
     public Set<Integer> getAllUnderlyingCapabilities() {
         return Collections.unmodifiableSet(mUnderlyingCapabilities);
     }
 
     /**
-     * Checks if this config requires an underlying network to have the specified capability.
+     * Retrieves the configured retry intervals.
      *
-     * @param capability the capability to check for
+     * @see Builder#setRetryInterval(long[])
+     * @hide
      */
-    public boolean requiresUnderlyingCapability(int capability) {
-        checkValidCapability(capability);
-
-        return mUnderlyingCapabilities.contains(capability);
-    }
-
-    /** Retrieves the configured retry intervals. */
     @NonNull
-    public long[] getRetryIntervalsMs() {
+    public long[] getRetryInterval() {
         return Arrays.copyOf(mRetryIntervalsMs, mRetryIntervalsMs.length);
     }
 
-    /** Retrieves the maximum MTU allowed for this Gateway Connection. */
+    /**
+     * Retrieves the configured retry intervals.
+     *
+     * <p>Left to prevent the need to make major changes while changes are actively in flight.
+     *
+     * @deprecated use getRequiredUnderlyingCapabilities() instead
+     * @hide
+     */
+    @Deprecated
+    @NonNull
+    public long[] getRetryIntervalsMs() {
+        return getRetryInterval();
+    }
+
+    /**
+     * Retrieves the maximum MTU allowed for this Gateway Connection.
+     *
+     * @see Builder.setMaxMtu(int)
+     * @hide
+     */
     @IntRange(from = MIN_MTU_V6)
     public int getMaxMtu() {
         return mMaxMtu;
@@ -319,8 +386,12 @@
                 && mMaxMtu == rhs.mMaxMtu;
     }
 
-    /** This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects. */
-    public static class Builder {
+    /**
+     * This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects.
+     *
+     * @hide
+     */
+    public static final class Builder {
         @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
         @NonNull private final Set<Integer> mUnderlyingCapabilities = new ArraySet();
         @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
@@ -338,8 +409,10 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
          *     Connection
+         * @hide
          */
-        public Builder addExposedCapability(int exposedCapability) {
+        @NonNull
+        public Builder addExposedCapability(@VcnSupportedCapability int exposedCapability) {
             checkValidCapability(exposedCapability);
 
             mExposedCapabilities.add(exposedCapability);
@@ -354,8 +427,10 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
          *     Connection
+         * @hide
          */
-        public Builder removeExposedCapability(int exposedCapability) {
+        @NonNull
+        public Builder clearExposedCapability(@VcnSupportedCapability int exposedCapability) {
             checkValidCapability(exposedCapability);
 
             mExposedCapabilities.remove(exposedCapability);
@@ -370,8 +445,11 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
          *     networks
+         * @hide
          */
-        public Builder addRequiredUnderlyingCapability(int underlyingCapability) {
+        @NonNull
+        public Builder addRequiredUnderlyingCapability(
+                @VcnSupportedCapability int underlyingCapability) {
             checkValidCapability(underlyingCapability);
 
             mUnderlyingCapabilities.add(underlyingCapability);
@@ -390,8 +468,11 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
          *     networks
+         * @hide
          */
-        public Builder removeRequiredUnderlyingCapability(int underlyingCapability) {
+        @NonNull
+        public Builder clearRequiredUnderlyingCapability(
+                @VcnSupportedCapability int underlyingCapability) {
             checkValidCapability(underlyingCapability);
 
             mUnderlyingCapabilities.remove(underlyingCapability);
@@ -420,6 +501,7 @@
          *     15m]}
          * @return this {@link Builder} instance, for chaining
          * @see VcnManager for additional discussion on fail-safe mode
+         * @hide
          */
         @NonNull
         public Builder setRetryInterval(@NonNull long[] retryIntervalsMs) {
@@ -441,6 +523,7 @@
          * @param maxMtu the maximum MTU allowed for this Gateway Connection. Must be greater than
          *     the IPv6 minimum MTU of 1280. Defaults to 1500.
          * @return this {@link Builder} instance, for chaining
+         * @hide
          */
         @NonNull
         public Builder setMaxMtu(@IntRange(from = MIN_MTU_V6) int maxMtu) {
@@ -455,6 +538,7 @@
          * Builds and validates the VcnGatewayConnectionConfig.
          *
          * @return an immutable VcnGatewayConnectionConfig instance
+         * @hide
          */
         @NonNull
         public VcnGatewayConnectionConfig build() {
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 2ccdc26..2d0a6d7 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -65,6 +65,7 @@
 public final class VcnManager {
     @NonNull private static final String TAG = VcnManager.class.getSimpleName();
 
+    /** @hide */
     @VisibleForTesting
     public static final Map<
                     VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index db55e1c..b1b2925 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -88,10 +88,8 @@
     private static final long DEFAULT_RECENT_TIME_MS = 30000L;
 
     private static boolean shouldShowIndicators() {
-        return true;
-        // TODO ntmyren: remove true set when device config is configured correctly
-        //DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
-        //PROPERTY_CAMERA_MIC_ICONS_ENABLED, true);
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                PROPERTY_CAMERA_MIC_ICONS_ENABLED, true);
     }
 
     private static boolean shouldShowLocationIndicator() {
@@ -142,7 +140,7 @@
     }
 
     private Context mContext;
-    private Map<UserHandle, Context> mUserContexts;
+    private ArrayMap<UserHandle, Context> mUserContexts;
     private PackageManager mPkgManager;
     private AppOpsManager mAppOpsManager;
 
@@ -154,7 +152,8 @@
         mContext = context;
         mPkgManager = context.getPackageManager();
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
-        mUserContexts = Map.of(Process.myUserHandle(), mContext);
+        mUserContexts = new ArrayMap<>();
+        mUserContexts.put(Process.myUserHandle(), mContext);
     }
 
     private Context getUserContext(UserHandle user) {
diff --git a/core/java/android/rotationresolver/OWNERS b/core/java/android/rotationresolver/OWNERS
new file mode 100644
index 0000000..81b6f05
--- /dev/null
+++ b/core/java/android/rotationresolver/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/rotationresolver/OWNERS
diff --git a/core/java/android/service/rotationresolver/OWNERS b/core/java/android/service/rotationresolver/OWNERS
new file mode 100644
index 0000000..e381d17
--- /dev/null
+++ b/core/java/android/service/rotationresolver/OWNERS
@@ -0,0 +1,10 @@
+# Bug component: 814982
+
+asalo@google.com
+augale@google.com
+bquezada@google.com
+eejiang@google.com
+payamp@google.com
+siddikap@google.com
+svetoslavganov@google.com
+tgadh@google.com
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 82d7399..53fe1ba 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -53,6 +53,8 @@
 public final class FontConfig implements Parcelable {
     private final @NonNull List<FontFamily> mFamilies;
     private final @NonNull List<Alias> mAliases;
+    private final long mLastModifiedDate;
+    private final int mConfigVersion;
 
     /**
      * Construct a FontConfig instance.
@@ -62,9 +64,12 @@
      *
      * @hide Only system server can create this instance and passed via IPC.
      */
-    public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases) {
+    public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases,
+            long lastModifiedDate, @IntRange(from = 0) int configVersion) {
         mFamilies = families;
         mAliases = aliases;
+        mLastModifiedDate = lastModifiedDate;
+        mConfigVersion = configVersion;
     }
 
     /**
@@ -88,6 +93,26 @@
     }
 
     /**
+     * Returns the last modified date as Java epoch seconds.
+     *
+     * If there is no update, this return 0.
+     * @hide
+     */
+    public long getLastModifiedDate() {
+        return mLastModifiedDate;
+    }
+
+    /**
+     * Returns the monotonically increasing config version value.
+     *
+     * The config version is reset to 0 when the system is restarted.
+     * @hide
+     */
+    public @IntRange(from = 0) int getConfigVersion() {
+        return mConfigVersion;
+    }
+
+    /**
      * Returns the ordered list of families included in the system fonts.
      * @deprecated Use getFontFamilies instead.
      * @hide
@@ -107,6 +132,8 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelableList(mFamilies, flags);
         dest.writeParcelableList(mAliases, flags);
+        dest.writeLong(mLastModifiedDate);
+        dest.writeInt(mConfigVersion);
     }
 
     public static final @NonNull Creator<FontConfig> CREATOR = new Creator<FontConfig>() {
@@ -116,7 +143,9 @@
                     FontFamily.class.getClassLoader());
             List<Alias> aliases = source.readParcelableList(new ArrayList<>(),
                     Alias.class.getClassLoader());
-            return new FontConfig(families, aliases);
+            long lastModifiedDate = source.readLong();
+            int configVersion = source.readInt();
+            return new FontConfig(families, aliases, lastModifiedDate, configVersion);
         }
 
         @Override
diff --git a/core/java/android/util/OWNERS b/core/java/android/util/OWNERS
index 8f3d9f6..14aa386 100644
--- a/core/java/android/util/OWNERS
+++ b/core/java/android/util/OWNERS
@@ -1,3 +1,6 @@
 per-file FeatureFlagUtils.java = sbasi@google.com
 per-file FeatureFlagUtils.java = tmfang@google.com
 per-file FeatureFlagUtils.java = asapperstein@google.com
+
+per-file TypedValue.java = file:/core/java/android/content/res/OWNERS
+per-file AttributeSet.java = file:/core/java/android/content/res/OWNERS
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 02edb7e..696271c 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.Signature;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.os.Build;
 import android.os.Trace;
 import android.util.jar.StrictJarFile;
@@ -361,7 +362,7 @@
             // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization
             // to not need to verify the whole APK when verifyFUll == false.
             final ZipEntry manifestEntry = jarFile.findEntry(
-                    PackageParser.ANDROID_MANIFEST_FILENAME);
+                    ParsingPackageUtils.ANDROID_MANIFEST_FILENAME);
             if (manifestEntry == null) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                         "Package " + apkPath + " has no manifest");
@@ -370,7 +371,7 @@
             if (ArrayUtils.isEmpty(lastCerts)) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package "
                         + apkPath + " has no certificates at entry "
-                        + PackageParser.ANDROID_MANIFEST_FILENAME);
+                        + ParsingPackageUtils.ANDROID_MANIFEST_FILENAME);
             }
             lastSigs = convertToSignatures(lastCerts);
 
@@ -383,7 +384,7 @@
 
                     final String entryName = entry.getName();
                     if (entryName.startsWith("META-INF/")) continue;
-                    if (entryName.equals(PackageParser.ANDROID_MANIFEST_FILENAME)) continue;
+                    if (entryName.equals(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME)) continue;
 
                     toVerify.add(entry);
                 }
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index fc42cd0..ab35af8 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -547,16 +547,17 @@
      * Returns the id of the "default" mode with the given refresh rate, or {@code 0} if no suitable
      * mode could be found.
      */
-    public int findDefaultModeByRefreshRate(float refreshRate) {
+    @Nullable
+    public Display.Mode findDefaultModeByRefreshRate(float refreshRate) {
         Display.Mode[] modes = supportedModes;
         Display.Mode defaultMode = getDefaultMode();
         for (int i = 0; i < modes.length; i++) {
             if (modes[i].matches(
                     defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), refreshRate)) {
-                return modes[i].getModeId();
+                return modes[i];
             }
         }
-        return 0;
+        return null;
     }
 
     /**
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index a2777fe..24bc308 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -218,6 +218,15 @@
     public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1;
 
     /**
+     * This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
+     * to operate at the exact frame rate.
+     *
+     * This is used internally by the platform and should not be used by apps.
+     * @hide
+     */
+    public static final int FRAME_RATE_COMPATIBILITY_EXACT = 100;
+
+    /**
      * Create an empty surface, which will later be filled in by readFromParcel().
      * @hide
      */
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 5140c09..90c8e17 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2307,7 +2307,9 @@
     public void removeImeSurface(IBinder windowToken) {
         synchronized (mH) {
             try {
-                mService.removeImeSurfaceFromWindow(windowToken);
+                final Completable.Void value = Completable.createVoid();
+                mService.removeImeSurfaceFromWindow(windowToken, ResultCallbacks.of(value));
+                Completable.getResult(value);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -3239,7 +3241,9 @@
     @Deprecated
     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
         try {
-            mService.setAdditionalInputMethodSubtypes(imiId, subtypes);
+            final Completable.Void value = Completable.createVoid();
+            mService.setAdditionalInputMethodSubtypes(imiId, subtypes, ResultCallbacks.of(value));
+            Completable.getResult(value);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index bbfd07b..c74c39a 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -27,9 +27,10 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.PackageLite;
-import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.SELinux;
@@ -86,17 +87,19 @@
         final boolean debuggable;
 
         public static Handle create(File packageFile) throws IOException {
-            try {
-                final PackageLite lite = PackageParser.parsePackageLite(packageFile, 0);
-                return create(lite);
-            } catch (PackageParserException e) {
-                throw new IOException("Failed to parse package: " + packageFile, e);
+            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            final ParseResult<PackageLite> ret = ApkLiteParseUtils.parsePackageLite(input.reset(),
+                    packageFile, /* flags */ 0);
+            if (ret.isError()) {
+                throw new IOException("Failed to parse package: " + packageFile,
+                        ret.getException());
             }
+            return create(ret.getResult());
         }
 
         public static Handle create(PackageLite lite) throws IOException {
-            return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs,
-                    lite.debuggable);
+            return create(lite.getAllApkPaths(), lite.isMultiArch(), lite.isExtractNativeLibs(),
+                    lite.isDebuggable());
         }
 
         public static Handle create(List<String> codePaths, boolean multiArch,
@@ -122,14 +125,14 @@
 
         public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException {
             final long[] apkHandles = new long[1];
-            final String path = lite.baseCodePath;
+            final String path = lite.getBaseApkPath();
             apkHandles[0] = nativeOpenApkFd(fd, path);
             if (apkHandles[0] == 0) {
                 throw new IOException("Unable to open APK " + path + " from fd " + fd);
             }
 
-            return new Handle(new String[]{path}, apkHandles, lite.multiArch,
-                    lite.extractNativeLibs, lite.debuggable);
+            return new Handle(new String[]{path}, apkHandles, lite.isMultiArch(),
+                    lite.isExtractNativeLibs(), lite.isDebuggable());
         }
 
         Handle(String[] apkPaths, long[] apkHandles, boolean multiArch,
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 2b78be3..c2f2052 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -24,8 +24,8 @@
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.dex.DexMetadataHelper;
+import android.content.pm.parsing.PackageLite;
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -84,8 +84,8 @@
 
     /**
      * A group of external dependencies used in
-     * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values
-     * from the system or mocked ones for testing purposes.
+     * {@link #resolveInstallVolume(Context, String, int, long, TestableInterface)}.
+     * It can be backed by real values from the system or mocked ones for testing purposes.
      */
     public static abstract class TestableInterface {
         abstract public StorageManager getStorageManager(Context context);
@@ -447,7 +447,7 @@
         long sizeBytes = 0;
 
         // Include raw APKs, and possibly unpacked resources
-        for (String codePath : pkg.getAllCodePaths()) {
+        for (String codePath : pkg.getAllApkPaths()) {
             final File codeFile = new File(codePath);
             sizeBytes += codeFile.length();
         }
diff --git a/core/java/com/android/internal/content/om/OverlayScanner.java b/core/java/com/android/internal/content/om/OverlayScanner.java
index a85cf56..6b5cb8d 100644
--- a/core/java/com/android/internal/content/om/OverlayScanner.java
+++ b/core/java/com/android/internal/content/om/OverlayScanner.java
@@ -20,7 +20,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.pm.PackageParser;
+import android.content.pm.parsing.ApkLite;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -124,15 +127,17 @@
     /** Extracts information about the overlay from its manifest. */
     @VisibleForTesting
     public ParsedOverlayInfo parseOverlayManifest(File overlayApk) {
-        try {
-            final PackageParser.ApkLite apkLite = PackageParser.parseApkLite(overlayApk, 0);
-            return apkLite.targetPackageName == null ? null :
-                    new ParsedOverlayInfo(apkLite.packageName, apkLite.targetPackageName,
-                            apkLite.targetSdkVersion, apkLite.overlayIsStatic,
-                            apkLite.overlayPriority, new File(apkLite.codePath));
-        } catch (PackageParser.PackageParserException e) {
-            Log.w(TAG, "Got exception loading overlay.", e);
+        final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
+        final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(),
+                overlayApk, /* flags */ 0);
+        if (ret.isError()) {
+            Log.w(TAG, "Got exception loading overlay.", ret.getException());
             return null;
         }
+        final ApkLite apkLite = ret.getResult();
+        return apkLite.getTargetPackageName() == null ? null :
+                new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(),
+                        apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(),
+                        apkLite.getOverlayPriority(), new File(apkLite.getPath()));
     }
 }
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index b42404f..892c5a5 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -41,22 +41,24 @@
             int untrustedDisplayId);
 
     // TODO: Use ParceledListSlice instead
-    void getInputMethodList(int userId, in IInputMethodInfoListResultCallback resultCallback);
-    // TODO: Use ParceledListSlice instead
-    void getEnabledInputMethodList(int userId,
+    oneway void getInputMethodList(int userId,
             in IInputMethodInfoListResultCallback resultCallback);
-    void getEnabledInputMethodSubtypeList(in String imiId, boolean allowsImplicitlySelectedSubtypes,
+    // TODO: Use ParceledListSlice instead
+    oneway void getEnabledInputMethodList(int userId,
+            in IInputMethodInfoListResultCallback resultCallback);
+    oneway void getEnabledInputMethodSubtypeList(in String imiId,
+            boolean allowsImplicitlySelectedSubtypes,
             in IInputMethodSubtypeListResultCallback resultCallback);
-    void getLastInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback);
+    oneway void getLastInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback);
 
-    void showSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
+    oneway void showSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
             in ResultReceiver resultReceiver, in IBooleanResultCallback resultCallback);
-    void hideSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
+    oneway void hideSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
             in ResultReceiver resultReceiver, in IBooleanResultCallback resultCallback);
     // If windowToken is null, this just does startInput().  Otherwise this reports that a window
     // has gained focus, and if 'attribute' is non-null then also does startInput.
     // @NonNull
-    void startInputOrWindowGainedFocus(
+    oneway void startInputOrWindowGainedFocus(
             /* @StartInputReason */ int startInputReason,
             in IInputMethodClient client, in IBinder windowToken,
             /* @StartInputFlags */ int startInputFlags,
@@ -66,29 +68,31 @@
             int unverifiedTargetSdkVersion,
             in IInputBindResultResultCallback inputBindResult);
 
-    void showInputMethodPickerFromClient(in IInputMethodClient client,
+    oneway void showInputMethodPickerFromClient(in IInputMethodClient client,
             int auxiliarySubtypeMode, in IVoidResultCallback resultCallback);
-    void showInputMethodPickerFromSystem(in IInputMethodClient client, int auxiliarySubtypeMode,
-            int displayId, in IVoidResultCallback resultCallback);
-    void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId,
+    oneway void showInputMethodPickerFromSystem(in IInputMethodClient client,
+            int auxiliarySubtypeMode, int displayId, in IVoidResultCallback resultCallback);
+    oneway void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client,
+            String topId, in IVoidResultCallback resultCallback);
+    oneway void isInputMethodPickerShownForTest(in IBooleanResultCallback resultCallback);
+    oneway void getCurrentInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback);
+    oneway void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes,
             in IVoidResultCallback resultCallback);
-    void isInputMethodPickerShownForTest(in IBooleanResultCallback resultCallback);
-    void getCurrentInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback);
-    void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
     // This is kept due to @UnsupportedAppUsage.
     // TODO(Bug 113914148): Consider removing this.
-    void getInputMethodWindowVisibleHeight(IIntResultCallback resultCallback);
+    oneway void getInputMethodWindowVisibleHeight(IIntResultCallback resultCallback);
 
-    void reportActivityView(in IInputMethodClient parentClient, int childDisplayId,
+    oneway void reportActivityView(in IInputMethodClient parentClient, int childDisplayId,
             in float[] matrixValues, in IVoidResultCallback resultCallback);
 
     oneway void reportPerceptible(in IBinder windowToken, boolean perceptible);
     /** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */
-    void removeImeSurface();
+    oneway void removeImeSurface(in IVoidResultCallback resultCallback);
     /** Remove the IME surface. Requires passing the currently focused window. */
-    void removeImeSurfaceFromWindow(in IBinder windowToken);
+    oneway void removeImeSurfaceFromWindow(in IBinder windowToken,
+            in IVoidResultCallback resultCallback);
     void startProtoDump(in byte[] protoDump, int source, String where);
-    void isImeTraceEnabled(in IBooleanResultCallback resultCallback);
+    oneway void isImeTraceEnabled(in IBooleanResultCallback resultCallback);
 
     // Starts an ime trace.
     void startImeTrace();
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 2155246..e2af87e 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -18,6 +18,7 @@
 
 #include <vector>
 
+#include <android/file_descriptor_jni.h>
 #include <arpa/inet.h>
 #include <linux/filter.h>
 #include <linux/if_arp.h>
@@ -83,7 +84,7 @@
         filter_code,
     };
 
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = AFileDescriptor_getFD(env, javaFd);
     if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
         jniThrowExceptionFmt(env, "java/net/SocketException",
                 "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
@@ -93,7 +94,7 @@
 static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
 {
     int optval_ignored = 0;
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = AFileDescriptor_getFD(env, javaFd);
     if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &optval_ignored, sizeof(optval_ignored)) !=
         0) {
         jniThrowExceptionFmt(env, "java/net/SocketException",
@@ -117,10 +118,9 @@
     return (jboolean) !setNetworkForResolv(netId);
 }
 
-static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket,
-        jint netId)
-{
-    return setNetworkForSocket(netId, socket);
+static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jobject javaFd,
+                                                  jint netId) {
+    return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd));
 }
 
 static jboolean android_net_utils_protectFromVpn(JNIEnv *env, jobject thiz, jint socket)
@@ -128,6 +128,10 @@
     return (jboolean) !protectFromVpn(socket);
 }
 
+static jboolean android_net_utils_protectFromVpnWithFd(JNIEnv *env, jobject thiz, jobject javaFd) {
+    return android_net_utils_protectFromVpn(env, thiz, AFileDescriptor_getFD(env, javaFd));
+}
+
 static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jint uid, jint netId)
 {
     return (jboolean) !queryUserAccess(uid, netId);
@@ -178,7 +182,7 @@
 }
 
 static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = AFileDescriptor_getFD(env, javaFd);
     int rcode;
     std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
 
@@ -205,7 +209,7 @@
 }
 
 static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = AFileDescriptor_getFD(env, javaFd);
     resNetworkCancel(fd);
     jniSetFileDescriptorOfFD(env, javaFd, -1);
 }
@@ -231,7 +235,7 @@
         return NULL;
     }
 
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = AFileDescriptor_getFD(env, javaFd);
     struct tcp_repair_window trw = {};
     socklen_t size = sizeof(trw);
 
@@ -271,8 +275,9 @@
     { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
     { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
     { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
-    { "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork },
-    { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
+    { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork },
+    { "protectFromVpn", "(I)Z", (void*) android_net_utils_protectFromVpn },
+    { "protectFromVpn", "(Ljava/io/FileDescriptor;)Z", (void*) android_net_utils_protectFromVpnWithFd },
     { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
     { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
     { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 610e0e0..c882431 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -68,7 +68,7 @@
     // Whether or not the home activity is the recents activity. This is needed for the CTS tests to
     // know what activity types to check for when invoking splitscreen multi-window.
     optional bool is_home_recents_component = 6;
-    repeated IdentifierProto pending_activities = 7;
+    repeated IdentifierProto pending_activities = 7 [deprecated=true];
 }
 
 message BarControllerProto {
@@ -307,6 +307,7 @@
     optional bool animating_bounds = 26 [deprecated = true];
     optional float minimize_amount = 27;
     optional bool created_by_organizer = 28;
+    optional string affinity = 29;
 }
 
 /* represents ActivityRecordProto */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3975529..1b9455c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1744,6 +1744,12 @@
     <permission android:name="android.permission.REQUEST_NETWORK_SCORES"
         android:protectionLevel="signature|setup" />
 
+    <!-- Allows applications to restart the Wi-Fi subsystem.
+     @SystemApi
+     <p>Not for use by third-party applications. @hide -->
+    <permission android:name="android.permission.RESTART_WIFI_SUBSYSTEM"
+                android:protectionLevel="signature|setup" />
+
     <!-- @SystemApi @hide Allows applications to toggle airplane mode.
          <p>Not for use by third-party or privileged applications.
     -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 996fbb3..98b36c5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -298,10 +298,10 @@
         <item>@string/crossSimFormat_spn_cross_sim_calling</item>
     </string-array>
 
-    <!-- Spn during Cross-SIM Calling: "<operator> " [CHAR LIMIT=NONE] -->
+    <!-- Spn during Backup Calling: "<operator> " [CHAR LIMIT=NONE] -->
     <string name="crossSimFormat_spn"><xliff:g id="spn" example="Operator">%s</xliff:g></string>
-    <!-- Spn during Cross SIM Calling: "<operator> Cross-SIM Calling" [CHAR LIMIT=NONE] -->
-    <string name="crossSimFormat_spn_cross_sim_calling"><xliff:g id="spn" example="Operator">%s</xliff:g> Cross-SIM Calling</string>
+    <!-- Spn during Backup Calling: "<operator> Backup Calling" [CHAR LIMIT=NONE] -->
+    <string name="crossSimFormat_spn_cross_sim_calling"><xliff:g id="spn" example="Operator">%s</xliff:g> Backup Calling</string>
 
     <!--
         {0} is one of "bearerServiceCode*"
diff --git a/core/sysprop/WatchdogProperties.sysprop b/core/sysprop/WatchdogProperties.sysprop
index 1bcc773..93e8b78 100644
--- a/core/sysprop/WatchdogProperties.sysprop
+++ b/core/sysprop/WatchdogProperties.sysprop
@@ -16,7 +16,7 @@
 owner: Platform
 
 # To escape the watchdog timeout loop, fatal reboot the system when
-# watchdog timed out 'fatal_count' times in 'fatal_window_second'
+# watchdog timed out 'fatal_count' times in 'fatal_window_seconds'
 # seconds, if both values are not 0. Default value of both is 0.
 prop {
     api_name: "fatal_count"
@@ -26,8 +26,9 @@
     access: Readonly
 }
 
+# See 'fatal_count' for documentation.
 prop {
-    api_name: "fatal_window_second"
+    api_name: "fatal_window_seconds"
     type: Integer
     prop_name: "framework_watchdog.fatal_window.second"
     scope: Internal
@@ -35,9 +36,9 @@
 }
 
 # The fatal counting can be disabled by setting property
-# 'is_fatal_ignore' to true.
+# 'should_ignore_fatal_count' to true.
 prop {
-    api_name: "is_fatal_ignore"
+    api_name: "should_ignore_fatal_count"
     type: Boolean
     prop_name: "persist.debug.framework_watchdog.fatal_ignore"
     scope: Internal
diff --git a/core/sysprop/api/com.android.sysprop.watchdog-latest.txt b/core/sysprop/api/com.android.sysprop.watchdog-latest.txt
index d901aef..c846211 100644
--- a/core/sysprop/api/com.android.sysprop.watchdog-latest.txt
+++ b/core/sysprop/api/com.android.sysprop.watchdog-latest.txt
@@ -7,13 +7,13 @@
     prop_name: "framework_watchdog.fatal_count"
   }
   prop {
-    api_name: "fatal_window_second"
+    api_name: "fatal_window_seconds"
     type: Integer
     scope: Internal
     prop_name: "framework_watchdog.fatal_window.second"
   }
   prop {
-    api_name: "is_fatal_ignore"
+    api_name: "should_ignore_fatal_count"
     scope: Internal
     prop_name: "persist.debug.framework_watchdog.fatal_ignore"
   }
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 65ea2a8..3df2e90 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -171,7 +171,8 @@
         FontConfig fontConfig;
         try {
             fontConfig = FontListParser.parse(
-                    TEST_FONTS_XML, TEST_FONT_DIR, oemXmlPath, TEST_OEM_DIR, updatableFontMap);
+                    TEST_FONTS_XML, TEST_FONT_DIR, oemXmlPath, TEST_OEM_DIR, updatableFontMap, 0,
+                    0);
         } catch (IOException | XmlPullParserException e) {
             throw new RuntimeException(e);
         }
@@ -199,7 +200,7 @@
         FontConfig fontConfig;
         try {
             fontConfig = FontListParser.parse(
-                    SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null);
+                    SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null, 0, 0);
         } catch (IOException | XmlPullParserException e) {
             throw new RuntimeException(e);
         }
diff --git a/core/tests/coretests/src/android/text/FontFallbackSetup.java b/core/tests/coretests/src/android/text/FontFallbackSetup.java
index e301037..90a6ca3 100644
--- a/core/tests/coretests/src/android/text/FontFallbackSetup.java
+++ b/core/tests/coretests/src/android/text/FontFallbackSetup.java
@@ -79,7 +79,7 @@
 
         FontConfig fontConfig;
         try {
-            fontConfig = FontListParser.parse(testFontsXml, mTestFontsDir, null, null, null);
+            fontConfig = FontListParser.parse(testFontsXml, mTestFontsDir, null, null, null, 0, 0);
         } catch (IOException | XmlPullParserException e) {
             throw new RuntimeException(e);
         }
diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml
index 28f99dd..734561c 100644
--- a/data/etc/com.android.emergency.xml
+++ b/data/etc/com.android.emergency.xml
@@ -19,5 +19,6 @@
         <!-- Required to place emergency calls from emergency info screen. -->
         <permission name="android.permission.CALL_PRIVILEGED"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
     </privapp-permissions>
 </permissions>
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index c8ff95d..bb795cd 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -51,7 +51,8 @@
         XmlPullParser parser = Xml.newPullParser();
         parser.setInput(in, null);
         parser.nextTag();
-        return readFamilies(parser, "/system/fonts/", new FontCustomizationParser.Result(), null);
+        return readFamilies(parser, "/system/fonts/", new FontCustomizationParser.Result(), null,
+                0, 0);
     }
 
     /**
@@ -71,7 +72,9 @@
             @NonNull String systemFontDir,
             @Nullable String oemCustomizationXmlPath,
             @Nullable String productFontDir,
-            @Nullable Map<String, File> updatableFontMap
+            @Nullable Map<String, File> updatableFontMap,
+            long lastModifiedDate,
+            int configVersion
     ) throws IOException, XmlPullParserException {
         FontCustomizationParser.Result oemCustomization;
         if (oemCustomizationXmlPath != null) {
@@ -90,7 +93,8 @@
             XmlPullParser parser = Xml.newPullParser();
             parser.setInput(is, null);
             parser.nextTag();
-            return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap);
+            return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap,
+                    lastModifiedDate, configVersion);
         }
     }
 
@@ -98,7 +102,9 @@
             @NonNull XmlPullParser parser,
             @NonNull String fontDir,
             @NonNull FontCustomizationParser.Result customization,
-            @Nullable Map<String, File> updatableFontMap)
+            @Nullable Map<String, File> updatableFontMap,
+            long lastModifiedDate,
+            int configVersion)
             throws XmlPullParserException, IOException {
         List<FontConfig.FontFamily> families = new ArrayList<>();
         List<FontConfig.Alias> aliases = new ArrayList<>(customization.getAdditionalAliases());
@@ -126,7 +132,7 @@
         }
 
         families.addAll(oemNamedFamilies.values());
-        return new FontConfig(families, aliases);
+        return new FontConfig(families, aliases, lastModifiedDate, configVersion);
     }
 
     /**
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index a41215f..c166e12 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -240,10 +240,12 @@
      * @hide
      */
     public static @NonNull FontConfig getSystemFontConfig(
-            @Nullable Map<String, File> updatableFontMap
+            @Nullable Map<String, File> updatableFontMap,
+            long lastModifiedDate,
+            int configVersion
     ) {
         return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
-                updatableFontMap);
+                updatableFontMap, lastModifiedDate, configVersion);
     }
 
     /**
@@ -251,7 +253,8 @@
      * @hide
      */
     public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
-        return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null);
+        return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null,
+                0, 0);
     }
 
     /* package */ static @NonNull FontConfig getSystemFontConfigInternal(
@@ -259,17 +262,19 @@
             @NonNull String systemFontDir,
             @Nullable String oemXml,
             @Nullable String productFontDir,
-            @Nullable Map<String, File> updatableFontMap
+            @Nullable Map<String, File> updatableFontMap,
+            long lastModifiedDate,
+            int configVersion
     ) {
         try {
             return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir,
-                                                updatableFontMap);
+                                                updatableFontMap, lastModifiedDate, configVersion);
         } catch (IOException e) {
             Log.e(TAG, "Failed to open/read system font configurations.", e);
-            return new FontConfig(Collections.emptyList(), Collections.emptyList());
+            return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
         } catch (XmlPullParserException e) {
             Log.e(TAG, "Failed to parse the system font configuration.", e);
-            return new FontConfig(Collections.emptyList(), Collections.emptyList());
+            return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
         }
     }
 
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index fcc518c..21d23b1 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -82,7 +82,7 @@
      *
      * @param locked            - whether it is a lock (true) or unlock (false) event
      * @param syntheticPassword - if it is an unlock event with the password, pass the synthetic
-     *                          password provided by the LockSettingService
+     *                            password provided by the LockSettingService
      *
      * @return 0 if successful or a {@code ResponseCode}.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
index 5cd660a..7ea4689 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
@@ -472,11 +472,6 @@
      * animator is under test.
      */
     internal fun startInternal() {
-        if (!Looper.getMainLooper().isCurrentThread) {
-            Log.e(TAG, "Animations can only be started on the main thread. If you are seeing " +
-                    "this message in a test, call PhysicsAnimatorTestUtils#prepareForTest in " +
-                    "your test setup.")
-        }
         val target = weakTarget.get()
         if (target == null) {
             Log.w(TAG, "Trying to animate a GC-ed object.")
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 3944128..9e1ea53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -42,6 +42,8 @@
 import androidx.annotation.BinderThread;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.inputmethod.Completable;
+import com.android.internal.inputmethod.ResultCallbacks;
 import com.android.internal.view.IInputMethodManager;
 
 import java.util.ArrayList;
@@ -506,7 +508,9 @@
             try {
                 // Remove the IME surface to make the insets invisible for
                 // non-client controlled insets.
-                imms.removeImeSurface();
+                final Completable.Void value = Completable.createVoid();
+                imms.removeImeSurface(ResultCallbacks.of(value));
+                Completable.getResult(value);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to remove IME surface.", e);
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index d37e628..1149cce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -17,10 +17,15 @@
 package com.android.wm.shell.common;
 
 import android.os.Looper;
+import android.os.SystemClock;
+import android.os.Trace;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
 
 /**
  * Super basic Executor interface that adds support for delayed execution and removing callbacks.
@@ -65,17 +70,17 @@
     /**
      * See {@link android.os.Handler#postDelayed(Runnable, long)}.
      */
-    void executeDelayed(Runnable r, long delayMillis);
+    void executeDelayed(Runnable runnable, long delayMillis);
 
     /**
      * See {@link android.os.Handler#removeCallbacks}.
      */
-    void removeCallbacks(Runnable r);
+    void removeCallbacks(Runnable runnable);
 
     /**
      * See {@link android.os.Handler#hasCallbacks(Runnable)}.
      */
-    boolean hasCallback(Runnable r);
+    boolean hasCallback(Runnable runnable);
 
     /**
      * Returns the looper that this executor is running on.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 1f07542..d14c3e3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -35,54 +35,17 @@
 @ExternalThread
 public interface Pip {
     /**
-     * Closes PIP (PIPed activity and PIP system UI).
-     */
-    default void closePip() {
-    }
-
-    /**
-     * Dump the current state and information if need.
-     *
-     * @param pw The stream to dump information to.
-     */
-    default void dump(PrintWriter pw) {
-    }
-
-    /**
      * Expand PIP, it's possible that specific request to activate the window via Alt-tab.
      */
     default void expandPip() {
     }
 
     /**
-     * Get the touch handler which manages all the touch handling for PIP on the Phone,
-     * including moving, dismissing and expanding the PIP. (Do not use in TV)
-     *
-     * @return
-     */
-    default @Nullable PipTouchHandler getPipTouchHandler() {
-        return null;
-    }
-
-    /**
      * Hides the PIP menu.
      */
     default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {}
 
     /**
-     * Returns {@code true} if PIP is shown.
-     */
-    default boolean isPipShown() {
-        return false;
-    }
-
-    /**
-     * Moves the PIPed activity to the fullscreen and closes PIP system UI.
-     */
-    default void movePipToFullscreen() {
-    }
-
-    /**
      * Called when configuration is changed.
      */
     default void onConfigurationChanged(Configuration newConfig) {
@@ -101,12 +64,6 @@
     }
 
     /**
-     * Registers the session listener for the current user.
-     */
-    default void registerSessionListenerForCurrentUser() {
-    }
-
-    /**
      * Called when SysUI state changed.
      *
      * @param isSysUiStateValid Is SysUI state valid or not.
@@ -116,19 +73,9 @@
     }
 
     /**
-     * Resize the Pip to the appropriate size for the input state.
-     *
-     * @param state In Pip state also used to determine the new size for the Pip.
+     * Registers the session listener for the current user.
      */
-    default void resizePinnedStack(int state) {
-    }
-
-    /**
-     * Resumes resizing operation on the Pip that was previously suspended.
-     *
-     * @param reason The reason resizing operations on the Pip was suspended.
-     */
-    default void resumePipResizing(int reason) {
+    default void registerSessionListenerForCurrentUser() {
     }
 
     /**
@@ -162,14 +109,6 @@
     default void showPictureInPictureMenu() {}
 
     /**
-     * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called.
-     *
-     * @param reason The reason for suspending resizing operations on the Pip.
-     */
-    default void suspendPipResizing(int reason) {
-    }
-
-    /**
      * Called by Launcher when swiping an auto-pip enabled Activity to home starts
      * @param componentName {@link ComponentName} represents the Activity entering PiP
      * @param activityInfo {@link ActivityInfo} tied to the Activity
@@ -199,4 +138,12 @@
      * PiP and the Back-from-Edge gesture.
      */
     default void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
+
+    /**
+     * Dump the current state and information if need.
+     *
+     * @param pw The stream to dump information to.
+     */
+    default void dump(PrintWriter pw) {
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index d96d4d0..1a4616c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -31,6 +31,7 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
+import android.os.Handler;
 import android.os.UserHandle;
 
 import androidx.annotation.Nullable;
@@ -74,6 +75,7 @@
     }
 
     private final Context mContext;
+    private final Handler mMainHandler;
 
     private final MediaSessionManager mMediaSessionManager;
     private MediaController mMediaController;
@@ -118,15 +120,16 @@
     private final ArrayList<ActionListener> mActionListeners = new ArrayList<>();
     private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>();
 
-    public PipMediaController(Context context) {
+    public PipMediaController(Context context, Handler mainHandler) {
         mContext = context;
+        mMainHandler = mainHandler;
         IntentFilter mediaControlFilter = new IntentFilter();
         mediaControlFilter.addAction(ACTION_PLAY);
         mediaControlFilter.addAction(ACTION_PAUSE);
         mediaControlFilter.addAction(ACTION_NEXT);
         mediaControlFilter.addAction(ACTION_PREV);
-        mContext.registerReceiver(mPlayPauseActionReceiver, mediaControlFilter,
-                UserHandle.USER_ALL);
+        mContext.registerReceiverForAllUsers(mPlayPauseActionReceiver, mediaControlFilter,
+                null /* permission */, mainHandler);
 
         createMediaActions();
         mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
@@ -245,7 +248,7 @@
     public void registerSessionListenerForCurrentUser() {
         mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener);
         mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsChangedListener, null,
-                UserHandle.CURRENT, null);
+                UserHandle.CURRENT, mMainHandler);
     }
 
     /**
@@ -277,7 +280,7 @@
             }
             mMediaController = controller;
             if (controller != null) {
-                controller.registerCallback(mPlaybackChangedListener);
+                controller.registerCallback(mPlaybackChangedListener, mMainHandler);
             }
             notifyActionsChanged();
             notifyMetadataChanged(getMediaMetadata());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 1279cd3..b80f285 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -50,9 +50,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Rational;
@@ -64,14 +62,14 @@
 import android.window.WindowContainerTransactionCallback;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.pip.phone.PipUpdateThread;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -98,12 +96,6 @@
     private static final String TAG = PipTaskOrganizer.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private static final int MSG_RESIZE_IMMEDIATE = 1;
-    private static final int MSG_RESIZE_ANIMATE = 2;
-    private static final int MSG_OFFSET_ANIMATE = 3;
-    private static final int MSG_FINISH_RESIZE = 4;
-    private static final int MSG_RESIZE_USER = 5;
-
     // Not a complete set of states but serves what we want right now.
     private enum State {
         UNDEFINED(0),
@@ -135,8 +127,6 @@
         }
     }
 
-    private final Handler mMainHandler;
-    private final Handler mUpdateHandler;
     private final PipBoundsState mPipBoundsState;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     private final @NonNull PipMenuController mPipMenuController;
@@ -148,6 +138,7 @@
     private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
     private final Optional<LegacySplitScreen> mSplitScreenOptional;
     protected final ShellTaskOrganizer mTaskOrganizer;
+    protected final ShellExecutor mMainExecutor;
 
     // These callbacks are called on the update thread
     private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
@@ -183,68 +174,6 @@
         }
     };
 
-    @SuppressWarnings("unchecked")
-    private final Handler.Callback mUpdateCallbacks = (msg) -> {
-        SomeArgs args = (SomeArgs) msg.obj;
-        Consumer<Rect> updateBoundsCallback = (Consumer<Rect>) args.arg1;
-        switch (msg.what) {
-            case MSG_RESIZE_IMMEDIATE: {
-                Rect toBounds = (Rect) args.arg2;
-                resizePip(toBounds);
-                if (updateBoundsCallback != null) {
-                    updateBoundsCallback.accept(toBounds);
-                }
-                break;
-            }
-            case MSG_RESIZE_ANIMATE: {
-                Rect currentBounds = (Rect) args.arg2;
-                Rect toBounds = (Rect) args.arg3;
-                Rect sourceHintRect = (Rect) args.arg4;
-                float startingAngle = (float) args.arg5;
-                int duration = args.argi2;
-                animateResizePip(currentBounds, toBounds, sourceHintRect,
-                        args.argi1 /* direction */, duration, startingAngle);
-                if (updateBoundsCallback != null) {
-                    updateBoundsCallback.accept(toBounds);
-                }
-                break;
-            }
-            case MSG_OFFSET_ANIMATE: {
-                Rect originalBounds = (Rect) args.arg2;
-                final int offset = args.argi1;
-                final int duration = args.argi2;
-                offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
-                Rect toBounds = new Rect(originalBounds);
-                toBounds.offset(0, offset);
-                if (updateBoundsCallback != null) {
-                    updateBoundsCallback.accept(toBounds);
-                }
-                break;
-            }
-            case MSG_FINISH_RESIZE: {
-                SurfaceControl.Transaction tx = (SurfaceControl.Transaction) args.arg2;
-                Rect toBounds = (Rect) args.arg3;
-                finishResize(tx, toBounds, args.argi1 /* direction */, -1);
-                if (updateBoundsCallback != null) {
-                    updateBoundsCallback.accept(toBounds);
-                }
-                break;
-            }
-            case MSG_RESIZE_USER: {
-                Rect startBounds = (Rect) args.arg2;
-                Rect toBounds = (Rect) args.arg3;
-                float degrees = (float) args.arg4;
-                userResizePip(startBounds, toBounds, degrees);
-                if (updateBoundsCallback != null) {
-                    updateBoundsCallback.accept(toBounds);
-                }
-                break;
-            }
-        }
-        args.recycle();
-        return true;
-    };
-
     private ActivityManager.RunningTaskInfo mTaskInfo;
     private WindowContainerToken mToken;
     private SurfaceControl mLeash;
@@ -276,9 +205,8 @@
             Optional<LegacySplitScreen> splitScreenOptional,
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
-            @NonNull ShellTaskOrganizer shellTaskOrganizer) {
-        mMainHandler = new Handler(Looper.getMainLooper());
-        mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
+            @NonNull ShellTaskOrganizer shellTaskOrganizer,
+            @ShellMainThread ShellExecutor mainExecutor) {
         mPipBoundsState = pipBoundsState;
         mPipBoundsAlgorithm = boundsHandler;
         mPipMenuController = pipMenuController;
@@ -290,12 +218,13 @@
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
         mSplitScreenOptional = splitScreenOptional;
         mTaskOrganizer = shellTaskOrganizer;
-        mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
-        displayController.addDisplayWindowListener(this);
-    }
+        mMainExecutor = mainExecutor;
 
-    public Handler getUpdateHandler() {
-        return mUpdateHandler;
+        // TODO: Can be removed once wm components are created on the shell-main thread
+        mMainExecutor.execute(() -> {
+            mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
+        });
+        displayController.addDisplayWindowListener(this);
     }
 
     public Rect getCurrentOrAnimatingBounds() {
@@ -428,15 +357,17 @@
             mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
                 @Override
                 public void onTransactionReady(int id, SurfaceControl.Transaction t) {
-                    t.apply();
-                    // Make sure to grab the latest source hint rect as it could have been updated
-                    // right after applying the windowing mode change.
-                    final Rect sourceHintRect = getValidSourceHintRect(mPictureInPictureParams,
-                            destinationBounds);
-                    scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds,
-                            0 /* startingAngle */, sourceHintRect, direction, animationDurationMs,
-                            null /* updateBoundsCallback */);
-                    mState = State.EXITING_PIP;
+                    mMainExecutor.execute(() -> {
+                        t.apply();
+                        // Make sure to grab the latest source hint rect as it could have been
+                        // updated right after applying the windowing mode change.
+                        final Rect sourceHintRect = getValidSourceHintRect(mPictureInPictureParams,
+                                destinationBounds);
+                        scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds,
+                                0 /* startingAngle */, sourceHintRect, direction,
+                                animationDurationMs, null /* updateBoundsCallback */);
+                        mState = State.EXITING_PIP;
+                    });
                 }
             });
         }
@@ -465,12 +396,12 @@
         }
 
         // removePipImmediately is expected when the following animation finishes.
-        mUpdateHandler.post(() -> mPipAnimationController
+        mPipAnimationController
                 .getAnimator(mLeash, mPipBoundsState.getBounds(), 1f, 0f)
                 .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(mEnterExitAnimationDuration)
-                .start());
+                .start();
         mInitialState.remove(mToken.asBinder());
         mState = State.EXITING_PIP;
     }
@@ -579,12 +510,12 @@
         tx.setAlpha(mLeash, 0f);
         tx.apply();
         applyEnterPipSyncTransaction(destinationBounds, () -> {
-            mUpdateHandler.post(() -> mPipAnimationController
+            mPipAnimationController
                     .getAnimator(mLeash, destinationBounds, 0f, 1f)
                     .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                     .setPipAnimationCallback(mPipAnimationCallback)
                     .setDuration(durationMs)
-                    .start());
+                    .start();
             // mState is set right after the animation is kicked off to block any resize
             // requests such as offsetPip that may have been called prior to the transition.
             mState = State.ENTERING_PIP;
@@ -599,13 +530,16 @@
         wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
         wct.setBounds(mToken, destinationBounds);
         wct.scheduleFinishEnterPip(mToken, destinationBounds);
+        // TODO: Migrate to SyncTransactionQueue
         mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
             @Override
             public void onTransactionReady(int id, SurfaceControl.Transaction t) {
-                t.apply();
-                if (runnable != null) {
-                    runnable.run();
-                }
+                mMainExecutor.execute(() -> {
+                    t.apply();
+                    if (runnable != null) {
+                        runnable.run();
+                    }
+                });
             }
         });
     }
@@ -621,12 +555,10 @@
             mState = State.ENTERING_PIP;
         }
         final Rect pipBounds = mPipBoundsState.getBounds();
-        runOnMainHandler(() -> {
-            for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
-                final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
-                callback.onPipTransitionStarted(componentName, direction, pipBounds);
-            }
-        });
+        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+            callback.onPipTransitionStarted(componentName, direction, pipBounds);
+        }
     }
 
     private void sendOnPipTransitionFinished(
@@ -634,29 +566,17 @@
         if (direction == TRANSITION_DIRECTION_TO_PIP) {
             mState = State.ENTERED_PIP;
         }
-        runOnMainHandler(() -> {
-            for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
-                final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
-                callback.onPipTransitionFinished(mTaskInfo.baseActivity, direction);
-            }
-        });
+        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+            callback.onPipTransitionFinished(mTaskInfo.baseActivity, direction);
+        }
     }
 
     private void sendOnPipTransitionCancelled(
             @PipAnimationController.TransitionDirection int direction) {
-        runOnMainHandler(() -> {
-            for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
-                final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
-                callback.onPipTransitionCanceled(mTaskInfo.baseActivity, direction);
-            }
-        });
-    }
-
-    private void runOnMainHandler(Runnable r) {
-        if (Looper.getMainLooper() == Looper.myLooper()) {
-            r.run();
-        } else {
-            mMainHandler.post(r);
+        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+            callback.onPipTransitionCanceled(mTaskInfo.baseActivity, direction);
         }
     }
 
@@ -872,15 +792,11 @@
             return;
         }
 
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = updateBoundsCallback;
-        args.arg2 = currentBounds;
-        args.arg3 = destinationBounds;
-        args.arg4 = sourceHintRect;
-        args.arg5 = startingAngle;
-        args.argi1 = direction;
-        args.argi2 = durationMs;
-        mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args));
+        animateResizePip(currentBounds, destinationBounds, sourceHintRect, direction, durationMs,
+                startingAngle);
+        if (updateBoundsCallback != null) {
+            updateBoundsCallback.accept(destinationBounds);
+        }
     }
 
     /**
@@ -888,10 +804,24 @@
      * {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called.
      */
     public void scheduleResizePip(Rect toBounds, Consumer<Rect> updateBoundsCallback) {
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = updateBoundsCallback;
-        args.arg2 = toBounds;
-        mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_IMMEDIATE, args));
+        // Could happen when exitPip
+        if (mToken == null || mLeash == null) {
+            Log.w(TAG, "Abort animation, invalid leash");
+            return;
+        }
+        mPipBoundsState.setBounds(toBounds);
+        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
+        mSurfaceTransactionHelper
+                .crop(tx, mLeash, toBounds)
+                .round(tx, mLeash, mState.isInPip());
+        if (mPipMenuController.isMenuVisible()) {
+            mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
+        } else {
+            tx.apply();
+        }
+        if (updateBoundsCallback != null) {
+            updateBoundsCallback.accept(toBounds);
+        }
     }
 
     /**
@@ -909,12 +839,27 @@
      */
     public void scheduleUserResizePip(Rect startBounds, Rect toBounds, float degrees,
             Consumer<Rect> updateBoundsCallback) {
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = updateBoundsCallback;
-        args.arg2 = startBounds;
-        args.arg3 = toBounds;
-        args.arg4 = degrees;
-        mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_USER, args));
+        // Could happen when exitPip
+        if (mToken == null || mLeash == null) {
+            Log.w(TAG, "Abort animation, invalid leash");
+            return;
+        }
+
+        if (startBounds.isEmpty() || toBounds.isEmpty()) {
+            Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting.");
+            return;
+        }
+
+        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
+        mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, toBounds, degrees);
+        if (mPipMenuController.isMenuVisible()) {
+            mPipMenuController.movePipMenu(mLeash, tx, toBounds);
+        } else {
+            tx.apply();
+        }
+        if (updateBoundsCallback != null) {
+            updateBoundsCallback.accept(toBounds);
+        }
     }
 
     /**
@@ -948,13 +893,11 @@
             return;
         }
 
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = updateBoundsCallback;
-        args.arg2 = createFinishResizeSurfaceTransaction(
-                destinationBounds);
-        args.arg3 = destinationBounds;
-        args.argi1 = direction;
-        mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_FINISH_RESIZE, args));
+        finishResize(createFinishResizeSurfaceTransaction(destinationBounds), destinationBounds,
+                direction, -1);
+        if (updateBoundsCallback != null) {
+            updateBoundsCallback.accept(destinationBounds);
+        }
     }
 
     private SurfaceControl.Transaction createFinishResizeSurfaceTransaction(
@@ -979,20 +922,15 @@
             Log.d(TAG, "skip scheduleOffsetPip, entering pip deferred");
             return;
         }
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = updateBoundsCallback;
-        args.arg2 = originalBounds;
-        // offset would be zero if triggered from screen rotation.
-        args.argi1 = offset;
-        args.argi2 = duration;
-        mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_OFFSET_ANIMATE, args));
+        offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
+        Rect toBounds = new Rect(originalBounds);
+        toBounds.offset(0, offset);
+        if (updateBoundsCallback != null) {
+            updateBoundsCallback.accept(toBounds);
+        }
     }
 
     private void offsetPip(Rect originalBounds, int xOffset, int yOffset, int durationMs) {
-        if (Looper.myLooper() != mUpdateHandler.getLooper()) {
-            throw new RuntimeException("Callers should call scheduleOffsetPip() instead of this "
-                    + "directly");
-        }
         if (mTaskInfo == null) {
             Log.w(TAG, "mTaskInfo is not set");
             return;
@@ -1003,62 +941,9 @@
                 TRANSITION_DIRECTION_SAME, durationMs, 0);
     }
 
-    private void resizePip(Rect destinationBounds) {
-        if (Looper.myLooper() != mUpdateHandler.getLooper()) {
-            throw new RuntimeException("Callers should call scheduleResizePip() instead of this "
-                    + "directly");
-        }
-        // Could happen when exitPip
-        if (mToken == null || mLeash == null) {
-            Log.w(TAG, "Abort animation, invalid leash");
-            return;
-        }
-        mPipBoundsState.setBounds(destinationBounds);
-        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
-        mSurfaceTransactionHelper
-                .crop(tx, mLeash, destinationBounds)
-                .round(tx, mLeash, mState.isInPip());
-        if (mPipMenuController.isMenuVisible()) {
-            runOnMainHandler(() ->
-                    mPipMenuController.resizePipMenu(mLeash, tx, destinationBounds));
-        } else {
-            tx.apply();
-        }
-    }
-
-    private void userResizePip(Rect startBounds, Rect destinationBounds, float degrees) {
-        if (Looper.myLooper() != mUpdateHandler.getLooper()) {
-            throw new RuntimeException("Callers should call scheduleUserResizePip() instead of "
-                    + "this directly");
-        }
-        // Could happen when exitPip
-        if (mToken == null || mLeash == null) {
-            Log.w(TAG, "Abort animation, invalid leash");
-            return;
-        }
-
-        if (startBounds.isEmpty() || destinationBounds.isEmpty()) {
-            Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting.");
-            return;
-        }
-
-        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
-        mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds, degrees);
-        if (mPipMenuController.isMenuVisible()) {
-            runOnMainHandler(() ->
-                    mPipMenuController.movePipMenu(mLeash, tx, destinationBounds));
-        } else {
-            tx.apply();
-        }
-    }
-
     private void finishResize(SurfaceControl.Transaction tx, Rect destinationBounds,
             @PipAnimationController.TransitionDirection int direction,
             @PipAnimationController.AnimationType int type) {
-        if (Looper.myLooper() != mUpdateHandler.getLooper()) {
-            throw new RuntimeException("Callers should call scheduleResizePip() instead of this "
-                    + "directly");
-        }
         mPipBoundsState.setBounds(destinationBounds);
         if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
             removePipImmediately();
@@ -1097,7 +982,7 @@
                     mSurfaceTransactionHelper.scale(t, snapshotSurface, snapshotSrc, snapshotDest);
                     t.apply();
 
-                    mUpdateHandler.post(() -> {
+                    mMainExecutor.execute(() -> {
                         // Start animation to fade out the snapshot.
                         final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f);
                         animator.setDuration(mEnterExitAnimationDuration);
@@ -1129,10 +1014,8 @@
     }
 
     private void finishResizeForMenu(Rect destinationBounds) {
-        runOnMainHandler(() -> {
-            mPipMenuController.movePipMenu(null, null, destinationBounds);
-            mPipMenuController.updateMenuBounds(destinationBounds);
-        });
+        mPipMenuController.movePipMenu(null, null, destinationBounds);
+        mPipMenuController.updateMenuBounds(destinationBounds);
     }
 
     private void prepareFinishResizeTransaction(Rect destinationBounds,
@@ -1185,10 +1068,6 @@
     private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect,
             @PipAnimationController.TransitionDirection int direction, int durationMs,
             float startingAngle) {
-        if (Looper.myLooper() != mUpdateHandler.getLooper()) {
-            throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of "
-                    + "this directly");
-        }
         // Could happen when exitPip
         if (mToken == null || mLeash == null) {
             Log.w(TAG, "Abort animation, invalid leash");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 5db8f3d..8bf1b46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -30,6 +30,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Debug;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -39,6 +40,7 @@
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowManagerGlobal;
 
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipMediaController.ActionListener;
@@ -97,6 +99,8 @@
     private final RectF mTmpDestinationRectF = new RectF();
     private final Context mContext;
     private final PipMediaController mMediaController;
+    private final ShellExecutor mMainExecutor;
+    private final Handler mMainHandler;
 
     private final ArrayList<Listener> mListeners = new ArrayList<>();
     private final SystemWindows mSystemWindows;
@@ -116,11 +120,14 @@
         }
     };
 
-    public PhonePipMenuController(Context context,
-            PipMediaController mediaController, SystemWindows systemWindows) {
+    public PhonePipMenuController(Context context, PipMediaController mediaController,
+            SystemWindows systemWindows, ShellExecutor mainExecutor,
+            Handler mainHandler) {
         mContext = context;
         mMediaController = mediaController;
         mSystemWindows = systemWindows;
+        mMainExecutor = mainExecutor;
+        mMainHandler = mainHandler;
     }
 
     public boolean isMenuVisible() {
@@ -156,7 +163,7 @@
         if (mPipMenuView != null) {
             detachPipMenuView();
         }
-        mPipMenuView = new PipMenuView(mContext, this);
+        mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler);
         mSystemWindows.addView(mPipMenuView,
                 getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
                 0, SHELL_ROOT_LAYER_PIP);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
index 2cd0107..d97d2d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
@@ -21,22 +21,20 @@
 
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.OnOpChangedListener;
-import android.app.IActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Handler;
 import android.util.Pair;
 
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.pip.PipUtils;
 
 public class PipAppOpsListener {
     private static final String TAG = PipAppOpsListener.class.getSimpleName();
 
     private Context mContext;
-    private Handler mHandler;
-    private IActivityManager mActivityManager;
+    private ShellExecutor mMainExecutor;
     private AppOpsManager mAppOpsManager;
     private Callback mCallback;
 
@@ -53,7 +51,7 @@
                     if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
                             mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
                                     packageName) != MODE_ALLOWED) {
-                        mHandler.post(() -> mCallback.dismissPip());
+                        mMainExecutor.execute(() -> mCallback.dismissPip());
                     }
                 }
             } catch (NameNotFoundException e) {
@@ -63,11 +61,9 @@
         }
     };
 
-    public PipAppOpsListener(Context context, IActivityManager activityManager,
-            Callback callback) {
+    public PipAppOpsListener(Context context, Callback callback, ShellExecutor mainExecutor) {
         mContext = context;
-        mHandler = new Handler(mContext.getMainLooper());
-        mActivityManager = activityManager;
+        mMainExecutor = mainExecutor;
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mCallback = callback;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index fa88084..cefeb939 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -71,11 +71,11 @@
 /**
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
-public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback {
+public class PipController implements PipTaskOrganizer.PipTransitionCallback {
     private static final String TAG = "PipController";
 
     private Context mContext;
-    private ShellExecutor mMainExecutor;
+    protected ShellExecutor mMainExecutor;
     private DisplayController mDisplayController;
     private PipInputConsumer mPipInputConsumer;
     private WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -84,6 +84,7 @@
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
     private PipBoundsState mPipBoundsState;
     private PipTouchHandler mTouchHandler;
+    protected final PipImpl mImpl = new PipImpl();
 
     private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
     private final Rect mTmpInsetBounds = new Rect();
@@ -204,6 +205,28 @@
         }
     }
 
+
+    /**
+     * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
+     */
+    @Nullable
+    public static Pip create(Context context, DisplayController displayController,
+            PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+            PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
+            PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
+            TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) {
+        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+            Slog.w(TAG, "Device doesn't support Pip feature");
+            return null;
+        }
+
+        return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
+                pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+                pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor)
+                .mImpl;
+    }
+
     protected PipController(Context context,
             DisplayController displayController,
             PipAppOpsListener pipAppOpsListener,
@@ -235,7 +258,7 @@
         mTouchHandler = pipTouchHandler;
         mAppOpsListener = pipAppOpsListener;
         mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
-                INPUT_CONSUMER_PIP);
+                INPUT_CONSUMER_PIP, mainExecutor);
         mPipTaskOrganizer.registerPipTransitionCallback(this);
         mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
             final DisplayInfo newDisplayInfo = new DisplayInfo();
@@ -288,7 +311,7 @@
             if (taskInfo != null) {
                 // If SystemUI restart, and it already existed a pinned stack,
                 // register the pip input consumer to ensure touch can send to it.
-                mPipInputConsumer.registerInputConsumer(true /* withSfVsync */);
+                mPipInputConsumer.registerInputConsumer();
             }
         } catch (RemoteException | UnsupportedOperationException e) {
             Log.e(TAG, "Failed to register pinned stack listener", e);
@@ -301,12 +324,10 @@
                     @Override
                     public void onActivityPinned(String packageName, int userId, int taskId,
                             int stackId) {
-                        mMainExecutor.execute(() -> {
-                            mTouchHandler.onActivityPinned();
-                            mMediaController.onActivityPinned();
-                            mAppOpsListener.onActivityPinned(packageName);
-                        });
-                        mPipInputConsumer.registerInputConsumer(true /* withSfVsync */);
+                        mTouchHandler.onActivityPinned();
+                        mMediaController.onActivityPinned();
+                        mAppOpsListener.onActivityPinned(packageName);
+                        mPipInputConsumer.registerInputConsumer();
                     }
 
                     @Override
@@ -314,10 +335,8 @@
                         final Pair<ComponentName, Integer> topPipActivityInfo =
                                 PipUtils.getTopPipActivity(mContext);
                         final ComponentName topActivity = topPipActivityInfo.first;
-                        mMainExecutor.execute(() -> {
-                            mTouchHandler.onActivityUnpinned(topActivity);
-                            mAppOpsListener.onActivityUnpinned();
-                        });
+                        mTouchHandler.onActivityUnpinned(topActivity);
+                        mAppOpsListener.onActivityUnpinned();
                         mPipInputConsumer.unregisterInputConsumer();
                     }
 
@@ -333,60 +352,46 @@
                 });
     }
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        mMainExecutor.execute(() -> {
-            mPipBoundsAlgorithm.onConfigurationChanged(mContext);
-            mTouchHandler.onConfigurationChanged();
-            mPipBoundsState.onConfigurationChanged();
-        });
+    private void onConfigurationChanged(Configuration newConfig) {
+        mPipBoundsAlgorithm.onConfigurationChanged(mContext);
+        mTouchHandler.onConfigurationChanged();
+        mPipBoundsState.onConfigurationChanged();
     }
 
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        mMainExecutor.execute(() -> {
-            mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
-        });
+    private void onDensityOrFontScaleChanged() {
+        mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
     }
 
-    @Override
-    public void onOverlayChanged() {
-        mMainExecutor.execute(() -> {
-            mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
-            updateMovementBounds(null /* toBounds */,
-                    false /* fromRotation */, false /* fromImeAdjustment */,
-                    false /* fromShelfAdjustment */,
-                    null /* windowContainerTransaction */);
-        });
+    private void onOverlayChanged() {
+        mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
+        updateMovementBounds(null /* toBounds */,
+                false /* fromRotation */, false /* fromImeAdjustment */,
+                false /* fromShelfAdjustment */,
+                null /* windowContainerTransaction */);
     }
 
-    @Override
-    public void registerSessionListenerForCurrentUser() {
+    private void registerSessionListenerForCurrentUser() {
         mMediaController.registerSessionListenerForCurrentUser();
     }
 
-    @Override
-    public void onSystemUiStateChanged(boolean isValidState, int flag) {
+    private void onSystemUiStateChanged(boolean isValidState, int flag) {
         mTouchHandler.onSystemUiStateChanged(isValidState);
     }
 
     /**
      * Expands the PIP.
      */
-    @Override
     public void expandPip() {
         mTouchHandler.getMotionHelper().expandLeavePip(false /* skipAnimation */);
     }
 
-    @Override
-    public PipTouchHandler getPipTouchHandler() {
+    private PipTouchHandler getPipTouchHandler() {
         return mTouchHandler;
     }
 
     /**
      * Hides the PIP menu.
      */
-    @Override
     public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
         mMenuController.hideMenu(onStartCallback, onEndCallback);
     }
@@ -408,9 +413,8 @@
     /**
      * Sets both shelf visibility and its height.
      */
-    @Override
-    public void setShelfHeight(boolean visible, int height) {
-        mMainExecutor.execute(() -> setShelfHeightLocked(visible, height));
+    private void setShelfHeight(boolean visible, int height) {
+        setShelfHeightLocked(visible, height);
     }
 
     private void setShelfHeightLocked(boolean visible, int height) {
@@ -418,18 +422,15 @@
         mPipBoundsState.setShelfVisibility(visible, shelfHeight);
     }
 
-    @Override
-    public void setPinnedStackAnimationType(int animationType) {
-        mMainExecutor.execute(() -> mPipTaskOrganizer.setOneShotAnimationType(animationType));
+    private void setPinnedStackAnimationType(int animationType) {
+        mPipTaskOrganizer.setOneShotAnimationType(animationType);
     }
 
-    @Override
-    public void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
-        mMainExecutor.execute(() -> mPinnedStackAnimationRecentsCallback = callback);
+    private void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
+        mPinnedStackAnimationRecentsCallback = callback;
     }
 
-    @Override
-    public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+    private Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
             PictureInPictureParams pictureInPictureParams,
             int launcherRotation, int shelfHeight) {
         setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
@@ -438,11 +439,19 @@
                 pictureInPictureParams);
     }
 
-    @Override
-    public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+    private void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
         mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds);
     }
 
+    /**
+     * Set a listener to watch out for PiP bounds. This is mostly used by SystemUI's
+     * Back-gesture handler, to avoid conflicting with PiP when it's stashed.
+     */
+    private void setPipExclusionBoundsChangeListener(
+            Consumer<Rect> pipExclusionBoundsChangeListener) {
+        mTouchHandler.setPipExclusionBoundsChangeListener(pipExclusionBoundsChangeListener);
+    }
+
     @Override
     public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
         if (isOutPipDirection(direction)) {
@@ -468,16 +477,6 @@
         }
     }
 
-    /**
-     * Set a listener to watch out for PiP bounds. This is mostly used by SystemUI's
-     * Back-gesture handler, to avoid conflicting with PiP when it's stashed.
-     */
-    @Override
-    public void setPipExclusionBoundsChangeListener(
-            Consumer<Rect> pipExclusionBoundsChangeListener) {
-        mTouchHandler.setPipExclusionBoundsChangeListener(pipExclusionBoundsChangeListener);
-    }
-
     @Override
     public void onPipTransitionFinished(ComponentName activity, int direction) {
         onPipTransitionFinishedOrCanceled(direction);
@@ -607,8 +606,7 @@
         }
     }
 
-    @Override
-    public void dump(PrintWriter pw) {
+    private void dump(PrintWriter pw) {
         final String innerPrefix = "  ";
         pw.println(TAG);
         mMenuController.dump(pw, innerPrefix);
@@ -619,23 +617,123 @@
         mPipInputConsumer.dump(pw, innerPrefix);
     }
 
-    /**
-     * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
-     */
-    @Nullable
-    public static PipController create(Context context, DisplayController displayController,
-            PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
-            PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
-            PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
-            TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) {
-        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
-            Slog.w(TAG, "Device doesn't support Pip feature");
-            return null;
+    private class PipImpl implements Pip {
+        @Override
+        public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
+            mMainExecutor.execute(() -> {
+                PipController.this.hidePipMenu(onStartCallback, onEndCallback);
+            });
         }
 
-        return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
-                pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
-                pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor);
+        @Override
+        public void expandPip() {
+            mMainExecutor.execute(() -> {
+                PipController.this.expandPip();
+            });
+        }
+
+        @Override
+        public void onConfigurationChanged(Configuration newConfig) {
+            mMainExecutor.execute(() -> {
+                PipController.this.onConfigurationChanged(newConfig);
+            });
+        }
+
+        @Override
+        public void onDensityOrFontScaleChanged() {
+            mMainExecutor.execute(() -> {
+                PipController.this.onDensityOrFontScaleChanged();
+            });
+        }
+
+        @Override
+        public void onOverlayChanged() {
+            mMainExecutor.execute(() -> {
+                PipController.this.onOverlayChanged();
+            });
+        }
+
+        @Override
+        public void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) {
+            mMainExecutor.execute(() -> {
+                PipController.this.onSystemUiStateChanged(isSysUiStateValid, flag);
+            });
+        }
+
+        @Override
+        public void registerSessionListenerForCurrentUser() {
+            mMainExecutor.execute(() -> {
+                PipController.this.registerSessionListenerForCurrentUser();
+            });
+        }
+
+        @Override
+        public void setShelfHeight(boolean visible, int height) {
+            mMainExecutor.execute(() -> {
+                PipController.this.setShelfHeight(visible, height);
+            });
+        }
+
+        @Override
+        public void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
+            mMainExecutor.execute(() -> {
+                PipController.this.setPinnedStackAnimationListener(callback);
+            });
+        }
+
+        @Override
+        public void setPinnedStackAnimationType(int animationType) {
+            mMainExecutor.execute(() -> {
+                PipController.this.setPinnedStackAnimationType(animationType);
+            });
+        }
+
+        @Override
+        public void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+            mMainExecutor.execute(() -> {
+                PipController.this.setPipExclusionBoundsChangeListener(listener);
+            });
+        }
+
+        @Override
+        public void showPictureInPictureMenu() {
+            mMainExecutor.execute(() -> {
+                PipController.this.showPictureInPictureMenu();
+            });
+        }
+
+        @Override
+        public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+                PictureInPictureParams pictureInPictureParams, int launcherRotation,
+                int shelfHeight) {
+            Rect[] result = new Rect[1];
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    result[0] = PipController.this.startSwipePipToHome(componentName, activityInfo,
+                            pictureInPictureParams, launcherRotation, shelfHeight);
+                });
+            } catch (InterruptedException e) {
+                Slog.e(TAG, "Failed to start swipe pip to home");
+            }
+            return result[0];
+        }
+
+        @Override
+        public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+            mMainExecutor.execute(() -> {
+                PipController.this.stopSwipePipToHome(componentName, destinationBounds);
+            });
+        }
+
+        @Override
+        public void dump(PrintWriter pw) {
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    PipController.this.dump(pw);
+                });
+            } catch (InterruptedException e) {
+                Slog.e(TAG, "Failed to dump PipController in 2s");
+            }
+        }
     }
 }
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 bebe5f9..d9a7bdb 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
@@ -37,9 +37,12 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.common.DismissCircleView;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.pip.PipUiEventLogger;
 
+import java.util.concurrent.TimeUnit;
+
 import kotlin.Unit;
 
 /**
@@ -57,36 +60,32 @@
      * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
      * PIP.
      */
-    private final MagnetizedObject<Rect> mMagnetizedPip;
+    private MagnetizedObject<Rect> mMagnetizedPip;
 
     /**
      * Container for the dismiss circle, so that it can be animated within the container via
      * translation rather than within the WindowManager via slow layout animations.
      */
-    private final ViewGroup mTargetViewContainer;
+    private ViewGroup mTargetViewContainer;
 
     /** Circle view used to render the dismiss target. */
-    private final DismissCircleView mTargetView;
+    private DismissCircleView mTargetView;
 
     /**
      * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
      */
-    private final MagnetizedObject.MagneticTarget mMagneticTarget;
+    private MagnetizedObject.MagneticTarget mMagneticTarget;
 
-    /** PhysicsAnimator instance for animating the dismiss target in/out. */
-    private final PhysicsAnimator<View> mMagneticTargetAnimator;
+    /**
+     * PhysicsAnimator instance for animating the dismiss target in/out.
+     */
+    private PhysicsAnimator<View> mMagneticTargetAnimator;
 
     /** Default configuration to use for springing the dismiss target in/out. */
     private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
             new PhysicsAnimator.SpringConfig(
                     SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
 
-    /**
-     * Runnable that can be posted delayed to show the target. This needs to be saved as a member
-     * variable so we can pass it to removeCallbacks.
-     */
-    private Runnable mShowTargetAction = this::showDismissTargetMaybe;
-
     // Allow dragging the PIP to a location to close it
     private final boolean mEnableDismissDragToEdge;
 
@@ -96,74 +95,76 @@
     private final PipMotionHelper mMotionHelper;
     private final PipUiEventLogger mPipUiEventLogger;
     private final WindowManager mWindowManager;
-    private final Handler mHandler;
+    private final ShellExecutor mMainExecutor;
 
     public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger,
-            PipMotionHelper motionHelper, Handler handler) {
+            PipMotionHelper motionHelper, ShellExecutor mainExecutor) {
         mContext = context;
         mPipUiEventLogger = pipUiEventLogger;
         mMotionHelper = motionHelper;
-        mHandler = handler;
+        mMainExecutor = mainExecutor;
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
 
         Resources res = context.getResources();
         mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
         mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
 
-        mTargetView = new DismissCircleView(context);
-        mTargetViewContainer = new FrameLayout(context);
-        mTargetViewContainer.setBackgroundDrawable(
-                context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
-        mTargetViewContainer.setClipChildren(false);
-        mTargetViewContainer.addView(mTargetView);
+        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);
 
-        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) -> {
+            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.
                     if (mEnableDismissDragToEdge) {
-                        mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
+                        showDismissTargetMaybe();
                     }
-                    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) {
-                mMotionHelper.notifyDismissalPending();
+                @Override
+                public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+                    mMainExecutor.executeDelayed(() -> {
+                        mMotionHelper.notifyDismissalPending();
+                        mMotionHelper.animateDismiss();
+                        hideDismissTargetMaybe();
 
-                handler.post(() -> {
-                    mMotionHelper.animateDismiss();
-                    hideDismissTargetMaybe();
-                });
+                        mPipUiEventLogger.log(
+                                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
+                    }, 0);
+                }
+            });
 
-                mPipUiEventLogger.log(
-                        PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
-            }
+            mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
         });
-
-        mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
     }
 
     /**
@@ -200,7 +201,6 @@
     /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
     public void createOrUpdateDismissTarget() {
         if (!mTargetViewContainer.isAttachedToWindow()) {
-            mHandler.removeCallbacks(mShowTargetAction);
             mMagneticTargetAnimator.cancel();
 
             mTargetViewContainer.setVisibility(View.INVISIBLE);
@@ -270,7 +270,6 @@
             return;
         }
 
-        mHandler.removeCallbacks(mShowTargetAction);
         mMagneticTargetAnimator
                 .spring(DynamicAnimation.TRANSLATION_Y,
                         mTargetViewContainer.getHeight(),
@@ -286,8 +285,6 @@
      * Removes the dismiss target and cancels any pending callbacks to show it.
      */
     public void cleanUpDismissTarget() {
-        mHandler.removeCallbacks(mShowTargetAction);
-
         if (mTargetViewContainer.isAttachedToWindow()) {
             mWindowManager.removeViewImmediate(mTargetViewContainer);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 0c64c8c..7a634c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -29,6 +29,8 @@
 import android.view.InputChannel;
 import android.view.InputEvent;
 
+import com.android.wm.shell.common.ShellExecutor;
+
 import java.io.PrintWriter;
 
 /**
@@ -81,6 +83,7 @@
     private final IWindowManager mWindowManager;
     private final IBinder mToken;
     private final String mName;
+    private final ShellExecutor mMainExecutor;
 
     private InputEventReceiver mInputEventReceiver;
     private InputListener mListener;
@@ -89,10 +92,12 @@
     /**
      * @param name the name corresponding to the input consumer that is defined in the system.
      */
-    public PipInputConsumer(IWindowManager windowManager, String name) {
+    public PipInputConsumer(IWindowManager windowManager, String name,
+            ShellExecutor mainExecutor) {
         mWindowManager = windowManager;
         mToken = new Binder();
         mName = name;
+        mMainExecutor = mainExecutor;
     }
 
     /**
@@ -107,9 +112,11 @@
      */
     public void setRegistrationListener(RegistrationListener listener) {
         mRegistrationListener = listener;
-        if (mRegistrationListener != null) {
-            mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null);
-        }
+        mMainExecutor.execute(() -> {
+            if (mRegistrationListener != null) {
+                mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null);
+            }
+        });
     }
 
     /**
@@ -125,14 +132,6 @@
      * Registers the input consumer.
      */
     public void registerInputConsumer() {
-        registerInputConsumer(false);
-    }
-
-    /**
-     * Registers the input consumer.
-     * @param withSfVsync the flag set using sf vsync signal or no
-     */
-    public void registerInputConsumer(boolean withSfVsync) {
         if (mInputEventReceiver != null) {
             return;
         }
@@ -144,11 +143,15 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to create input consumer", e);
         }
-        mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper(),
-                withSfVsync ? Choreographer.getSfInstance() : Choreographer.getInstance());
-        if (mRegistrationListener != null) {
-            mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
-        }
+        mMainExecutor.execute(() -> {
+            // Choreographer.getSfInstance() must be called on the thread that the input event
+            // receiver should be receiving events
+            mInputEventReceiver = new InputEventReceiver(inputChannel,
+                mMainExecutor.getLooper(), Choreographer.getSfInstance());
+            if (mRegistrationListener != null) {
+                mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
+            }
+        });
     }
 
     /**
@@ -166,9 +169,11 @@
         }
         mInputEventReceiver.dispose();
         mInputEventReceiver = null;
-        if (mRegistrationListener != null) {
-            mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
-        }
+        mMainExecutor.execute(() -> {
+            if (mRegistrationListener != null) {
+                mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
+            }
+        });
     }
 
     public void dump(PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 2e10fc9..2e515ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -63,6 +63,7 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.pip.PipUtils;
 
 import java.util.ArrayList;
@@ -116,7 +117,8 @@
                 }
             };
 
-    private Handler mHandler = new Handler();
+    private ShellExecutor mMainExecutor;
+    private Handler mMainHandler;
 
     private final Runnable mHideMenuRunnable = this::hideMenu;
 
@@ -127,10 +129,13 @@
     protected View mTopEndContainer;
     protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
 
-    public PipMenuView(Context context, PhonePipMenuController controller) {
+    public PipMenuView(Context context, PhonePipMenuController controller,
+            ShellExecutor mainExecutor, Handler mainHandler) {
         super(context, null, 0);
         mContext = context;
         mController = controller;
+        mMainExecutor = mainExecutor;
+        mMainHandler = mainHandler;
 
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         inflate(context, R.layout.pip_menu, this);
@@ -412,17 +417,15 @@
                             d.setTint(Color.WHITE);
                             actionView.setImageDrawable(d);
                         }
-                    }, mHandler);
+                    }, mMainHandler);
                     actionView.setContentDescription(action.getContentDescription());
                     if (action.isEnabled()) {
                         actionView.setOnClickListener(v -> {
-                            mHandler.post(() -> {
-                                try {
-                                    action.getActionIntent().send();
-                                } catch (CanceledException e) {
-                                    Log.w(TAG, "Failed to send action", e);
-                                }
-                            });
+                            try {
+                                action.getActionIntent().send();
+                            } catch (CanceledException e) {
+                                Log.w(TAG, "Failed to send action", e);
+                            }
                         });
                     }
                     actionView.setEnabled(action.isEnabled());
@@ -480,13 +483,13 @@
     }
 
     private void cancelDelayedHide() {
-        mHandler.removeCallbacks(mHideMenuRunnable);
+        mMainExecutor.removeCallbacks(mHideMenuRunnable);
     }
 
     private void repostDelayedHide(int delay) {
         int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
                 FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS);
-        mHandler.removeCallbacks(mHideMenuRunnable);
-        mHandler.postDelayed(mHideMenuRunnable, recommendedTimeout);
+        mMainExecutor.removeCallbacks(mHideMenuRunnable);
+        mMainExecutor.executeDelayed(mHideMenuRunnable, recommendedTimeout);
     }
 }
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 8c8f5c6..4df7cef 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
@@ -40,6 +40,7 @@
 import com.android.wm.shell.animation.FloatProperties;
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -74,8 +75,6 @@
     private PhonePipMenuController mMenuController;
     private PipSnapAlgorithm mSnapAlgorithm;
 
-    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-
     /** The region that all of PIP must stay within. */
     private final Rect mFloatingAllowedArea = new Rect();
 
@@ -130,10 +129,8 @@
                         SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
 
     private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
-        mMainHandler.post(() -> {
-            mMenuController.updateMenuLayout(newBounds);
-            mPipBoundsState.setBounds(newBounds);
-        });
+        mMenuController.updateMenuLayout(newBounds);
+        mPipBoundsState.setBounds(newBounds);
     };
 
     /**
@@ -174,7 +171,8 @@
 
     public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
             PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
-            PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator) {
+            PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator,
+            ShellExecutor mainExecutor) {
         mContext = context;
         mPipTaskOrganizer = pipTaskOrganizer;
         mPipBoundsState = pipBoundsState;
@@ -184,8 +182,12 @@
         mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
         mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
                 mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
-        mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
-                mSfAnimationHandlerThreadLocal.get());
+
+        // Need to get the shell main thread sf vsync animation handler
+        mainExecutor.execute(() -> {
+            mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
+                    mSfAnimationHandlerThreadLocal.get());
+        });
 
         mResizePipUpdateListener = (target, values) -> {
             if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
@@ -256,10 +258,8 @@
                 mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds);
                 mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
                         (Rect newBounds) -> {
-                            mMainHandler.post(() -> {
                                 mMenuController.updateMenuLayout(newBounds);
-                            });
-                    });
+                        });
             }
         } else {
             // If PIP is 'catching up' after being stuck in the dismiss target, update the animation
@@ -326,11 +326,7 @@
         }
         cancelPhysicsAnimation();
         mMenuController.hideMenuWithoutResize();
-        mPipTaskOrganizer.getUpdateHandler().post(() -> {
-            mPipTaskOrganizer.exitPip(skipAnimation
-                    ? 0
-                    : LEAVE_PIP_DURATION);
-        });
+        mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION);
     }
 
     /**
@@ -393,7 +389,8 @@
                 .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
                 .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
                 .flingThenSpring(
-                        FloatProperties.RECT_X, velocityX, isStash ? mStashConfigX : mFlingConfigX,
+                        FloatProperties.RECT_X, velocityX,
+                        isStash ? mStashConfigX : mFlingConfigX,
                         mSpringConfig, true /* flingMustReachMinOrMax */)
                 .flingThenSpring(
                         FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig);
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 762b738..41cc59d 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
@@ -29,7 +29,6 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
-import android.os.Handler;
 import android.os.Looper;
 import android.provider.DeviceConfig;
 import android.view.BatchedInputEventReceiver;
@@ -45,6 +44,7 @@
 
 import com.android.internal.policy.TaskResizingAlgorithm;
 import com.android.wm.shell.R;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
@@ -52,7 +52,7 @@
 import com.android.wm.shell.pip.PipUiEventLogger;
 
 import java.io.PrintWriter;
-import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.Function;
 
 /**
@@ -73,7 +73,7 @@
     private final PhonePipMenuController mPhonePipMenuController;
     private final PipUiEventLogger mPipUiEventLogger;
     private final int mDisplayId;
-    private final Executor mMainExecutor;
+    private final ShellExecutor mMainExecutor;
     private final Region mTmpRegion = new Region();
 
     private final PointF mDownPoint = new PointF();
@@ -91,7 +91,6 @@
     private final Rect mDisplayBounds = new Rect();
     private final Function<Rect, Rect> mMovementBoundsSupplier;
     private final Runnable mUpdateMovementBoundsRunnable;
-    private final Handler mHandler;
 
     private int mDelta;
     private float mTouchSlop;
@@ -119,10 +118,10 @@
             PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
             PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
             Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger,
-            PhonePipMenuController menuActivityController) {
+            PhonePipMenuController menuActivityController, ShellExecutor mainExecutor) {
         mContext = context;
         mDisplayId = context.getDisplayId();
-        mMainExecutor = context.getMainExecutor();
+        mMainExecutor = mainExecutor;
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipBoundsState = pipBoundsState;
         mMotionHelper = motionHelper;
@@ -131,7 +130,6 @@
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mPhonePipMenuController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
-        mHandler = new Handler(Looper.getMainLooper());
 
         context.getDisplay().getRealSize(mMaxSize);
         reloadResources();
@@ -140,7 +138,8 @@
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 PIP_PINCH_RESIZE,
                 /* defaultValue = */ false);
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                mMainExecutor,
                 new DeviceConfig.OnPropertiesChangedListener() {
                     @Override
                     public void onPropertiesChanged(DeviceConfig.Properties properties) {
@@ -213,8 +212,8 @@
             // Register input event receiver
             mInputMonitor = InputManager.getInstance().monitorGestureInput(
                     "pip-resize", mDisplayId);
-            mInputEventReceiver = new SysUiInputEventReceiver(
-                    mInputMonitor.getInputChannel(), Looper.getMainLooper());
+            mInputEventReceiver = new PipResizeInputEventReceiver(
+                    mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
         }
     }
 
@@ -523,7 +522,7 @@
 
     private void finishResize() {
         if (!mLastResizeBounds.isEmpty()) {
-            final Runnable callback = () -> {
+            final Consumer<Rect> callback = (rect) -> {
                 mUserResizeBounds.set(mLastResizeBounds);
                 mMotionHelper.synchronizePinnedStackBounds();
                 mUpdateMovementBoundsRunnable.run();
@@ -537,16 +536,10 @@
                 mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds,
                         mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()));
                 mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
-                        PINCH_RESIZE_SNAP_DURATION, mAngle,
-                        (Rect rect) -> {
-                            mHandler.post(callback);
-                        });
+                        PINCH_RESIZE_SNAP_DURATION, -mAngle, callback);
             } else {
                 mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
-                        PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE,
-                        (Rect bounds) -> {
-                            mHandler.post(callback);
-                        });
+                        PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback);
             }
             mPipUiEventLogger.log(
                     PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
@@ -593,8 +586,8 @@
         pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
     }
 
-    class SysUiInputEventReceiver extends BatchedInputEventReceiver {
-        SysUiInputEventReceiver(InputChannel channel, Looper looper) {
+    class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
+        PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
             super(channel, looper, Choreographer.getSfInstance());
         }
 
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 75d674e..c9f5ae2 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
@@ -167,18 +167,20 @@
         mGesture = new DefaultPipTouchGesture();
         mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer,
                 mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(),
-                floatingContentCoordinator);
+                floatingContentCoordinator, mainExecutor);
         mPipResizeGestureHandler =
                 new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
                         mMotionHelper, pipTaskOrganizer, this::getMovementBounds,
-                        this::updateMovementBounds, pipUiEventLogger, menuController);
+                        this::updateMovementBounds, pipUiEventLogger, menuController,
+                        mainExecutor);
         mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
-                mMotionHelper, mHandler);
-        mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
+                mMotionHelper, mainExecutor);
+        mTouchState = new PipTouchState(ViewConfiguration.get(context),
                 () -> mMenuController.showMenuWithDelay(MENU_STATE_FULL,
                         mPipBoundsState.getBounds(), true /* allowMenuTimeout */, willResizeMenu(),
                         shouldShowResizeHandle()),
-                menuController::hideMenu);
+                menuController::hideMenu,
+                mainExecutor);
 
         Resources res = context.getResources();
         mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
@@ -196,7 +198,7 @@
                 PIP_STASHING,
                 /* defaultValue = */ true);
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                context.getMainExecutor(),
+                mainExecutor,
                 properties -> {
                     if (properties.getKeyset().contains(PIP_STASHING)) {
                         mEnableStash = properties.getBoolean(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
index 5f2327c..53303ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
@@ -25,6 +25,7 @@
 import android.view.ViewConfiguration;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.common.ShellExecutor;
 
 import java.io.PrintWriter;
 
@@ -39,7 +40,7 @@
     public static final long DOUBLE_TAP_TIMEOUT = 200;
     static final long HOVER_EXIT_TIMEOUT = 50;
 
-    private final Handler mHandler;
+    private final ShellExecutor mMainExecutor;
     private final ViewConfiguration mViewConfig;
     private final Runnable mDoubleTapTimeoutCallback;
     private final Runnable mHoverExitTimeoutCallback;
@@ -67,12 +68,12 @@
     private int mActivePointerId;
     private int mLastTouchDisplayId = Display.INVALID_DISPLAY;
 
-    public PipTouchState(ViewConfiguration viewConfig, Handler handler,
-            Runnable doubleTapTimeoutCallback, Runnable hoverExitTimeoutCallback) {
+    public PipTouchState(ViewConfiguration viewConfig, Runnable doubleTapTimeoutCallback,
+            Runnable hoverExitTimeoutCallback, ShellExecutor mainExecutor) {
         mViewConfig = viewConfig;
-        mHandler = handler;
         mDoubleTapTimeoutCallback = doubleTapTimeoutCallback;
         mHoverExitTimeoutCallback = hoverExitTimeoutCallback;
+        mMainExecutor = mainExecutor;
     }
 
     /**
@@ -116,7 +117,7 @@
                 mIsDragging = false;
                 mLastDownTouchTime = mDownTouchTime;
                 if (mDoubleTapTimeoutCallback != null) {
-                    mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
+                    mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
                 }
                 break;
             }
@@ -324,8 +325,8 @@
     public void scheduleDoubleTapTimeoutCallback() {
         if (mIsWaitingForDoubleTap) {
             long delay = getDoubleTapTimeoutCallbackDelay();
-            mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
-            mHandler.postDelayed(mDoubleTapTimeoutCallback, delay);
+            mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
+            mMainExecutor.executeDelayed(mDoubleTapTimeoutCallback, delay);
         }
     }
 
@@ -342,17 +343,17 @@
      */
     public void removeDoubleTapTimeoutCallback() {
         mIsWaitingForDoubleTap = false;
-        mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
+        mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
     }
 
     @VisibleForTesting
     public void scheduleHoverExitTimeoutCallback() {
-        mHandler.removeCallbacks(mHoverExitTimeoutCallback);
-        mHandler.postDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT);
+        mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
+        mMainExecutor.executeDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT);
     }
 
     void removeHoverExitTimeoutCallback() {
-        mHandler.removeCallbacks(mHoverExitTimeoutCallback);
+        mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
     }
 
     void addMovementToVelocityTracker(MotionEvent event) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java
deleted file mode 100644
index d686cac..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip.phone;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-
-/**
- * Similar to {@link com.android.internal.os.BackgroundThread}, this is a shared singleton
- * foreground thread for each process for updating PIP.
- */
-public final class PipUpdateThread extends HandlerThread {
-    private static PipUpdateThread sInstance;
-    private static Handler sHandler;
-
-    private PipUpdateThread() {
-        super("pip");
-    }
-
-    private static void ensureThreadLocked() {
-        if (sInstance == null) {
-            sInstance = new PipUpdateThread();
-            sInstance.start();
-            sHandler = new Handler(sInstance.getLooper());
-        }
-    }
-
-    /**
-     * @return the static update thread instance
-     */
-    public static PipUpdateThread get() {
-        synchronized (PipUpdateThread.class) {
-            ensureThreadLocked();
-            return sInstance;
-        }
-    }
-    /**
-     * @return the static update thread handler instance
-     */
-    public static Handler getHandler() {
-        synchronized (PipUpdateThread.class) {
-            ensureThreadLocked();
-            return sHandler;
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 8bc60f9..61cf22b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -36,6 +36,7 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
@@ -51,7 +52,7 @@
 /**
  * Manages the picture-in-picture (PIP) UI and states.
  */
-public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallback,
+public class TvPipController implements PipTaskOrganizer.PipTransitionCallback,
         TvPipMenuController.Delegate, TvPipNotificationController.Delegate {
     private static final String TAG = "TvPipController";
     static final boolean DEBUG = true;
@@ -90,13 +91,15 @@
     private final PipMediaController mPipMediaController;
     private final TvPipNotificationController mPipNotificationController;
     private final TvPipMenuController mTvPipMenuController;
+    private final ShellExecutor mMainExecutor;
+    private final TvPipImpl mImpl = new TvPipImpl();
 
     private @State int mState = STATE_NO_PIP;
     private int mPinnedTaskId = NONEXISTENT_TASK_ID;
 
     private int mResizeAnimationDuration;
 
-    public TvPipController(
+    public static Pip create(
             Context context,
             PipBoundsState pipBoundsState,
             PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -105,8 +108,34 @@
             PipMediaController pipMediaController,
             TvPipNotificationController pipNotificationController,
             TaskStackListenerImpl taskStackListener,
-            WindowManagerShellWrapper wmShell) {
+            WindowManagerShellWrapper wmShell,
+            ShellExecutor mainExecutor) {
+        return new TvPipController(
+                context,
+                pipBoundsState,
+                pipBoundsAlgorithm,
+                pipTaskOrganizer,
+                tvPipMenuController,
+                pipMediaController,
+                pipNotificationController,
+                taskStackListener,
+                wmShell,
+                mainExecutor).mImpl;
+    }
+
+    private TvPipController(
+            Context context,
+            PipBoundsState pipBoundsState,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipTaskOrganizer pipTaskOrganizer,
+            TvPipMenuController tvPipMenuController,
+            PipMediaController pipMediaController,
+            TvPipNotificationController pipNotificationController,
+            TaskStackListenerImpl taskStackListener,
+            WindowManagerShellWrapper wmShell,
+            ShellExecutor mainExecutor) {
         mContext = context;
+        mMainExecutor = mainExecutor;
 
         mPipBoundsState = pipBoundsState;
         mPipBoundsState.setDisplayInfo(getDisplayInfo());
@@ -129,8 +158,7 @@
         registerWmShellPinnedStackListener(wmShell);
     }
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    private void onConfigurationChanged(Configuration newConfig) {
         if (DEBUG) Log.d(TAG, "onConfigurationChanged(), state=" + stateToName(mState));
 
         if (isPipShown()) {
@@ -145,8 +173,7 @@
     /**
      * Returns {@code true} if Pip is shown.
      */
-    @Override
-    public boolean isPipShown() {
+    private boolean isPipShown() {
         return mState != STATE_NO_PIP;
     }
 
@@ -211,8 +238,7 @@
      * @param state the to determine the Pip bounds. IMPORTANT: should always match the current
      *              state of the Controller.
      */
-    @Override
-    public void resizePinnedStack(@State int state) {
+    private void resizePinnedStack(@State int state) {
         if (state != mState) {
             throw new IllegalArgumentException("The passed state should match the current state!");
         }
@@ -240,8 +266,7 @@
         mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
     }
 
-    @Override
-    public void registerSessionListenerForCurrentUser() {
+    private void registerSessionListenerForCurrentUser() {
         mPipMediaController.registerSessionListenerForCurrentUser();
     }
 
@@ -418,4 +443,20 @@
                 throw new IllegalArgumentException("Unknown state " + state);
         }
     }
+
+    private class TvPipImpl implements Pip {
+        @Override
+        public void onConfigurationChanged(Configuration newConfig) {
+            mMainExecutor.execute(() -> {
+                TvPipController.this.onConfigurationChanged(newConfig);
+            });
+        }
+
+        @Override
+        public void registerSessionListenerForCurrentUser() {
+            mMainExecutor.execute(() -> {
+                TvPipController.this.registerSessionListenerForCurrentUser();
+            });
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 470ab0c..ee41b41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ParceledListSlice;
+import android.os.Handler;
 import android.util.Log;
 import android.view.SurfaceControl;
 
@@ -47,6 +48,7 @@
     private final Context mContext;
     private final SystemWindows mSystemWindows;
     private final PipBoundsState mPipBoundsState;
+    private final Handler mMainHandler;
 
     private Delegate mDelegate;
     private SurfaceControl mLeash;
@@ -56,10 +58,12 @@
     private final List<RemoteAction> mAppActions = new ArrayList<>();
 
     public TvPipMenuController(Context context, PipBoundsState pipBoundsState,
-            SystemWindows systemWindows, PipMediaController pipMediaController) {
+            SystemWindows systemWindows, PipMediaController pipMediaController,
+            Handler mainHandler) {
         mContext = context;
         mPipBoundsState = pipBoundsState;
         mSystemWindows = systemWindows;
+        mMainHandler = mainHandler;
 
         // We need to "close" the menu the platform call for all the system dialogs to close (for
         // example, on the Home button press).
@@ -69,8 +73,9 @@
                 hideMenu();
             }
         };
-        context.registerReceiver(closeSystemDialogsBroadcastReceiver,
-                new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+        context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
+                new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */,
+                mainHandler);
 
         pipMediaController.addActionListener(this::onMediaActionsChanged);
     }
@@ -199,9 +204,9 @@
             return;
         }
         if (!mAppActions.isEmpty()) {
-            mMenuView.setAdditionalActions(mAppActions);
+            mMenuView.setAdditionalActions(mAppActions, mMainHandler);
         } else {
-            mMenuView.setAdditionalActions(mMediaActions);
+            mMenuView.setAdditionalActions(mMediaActions, mMainHandler);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index e08ca52..d6cd9ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -49,7 +49,7 @@
 /**
  * A View that represents Pip Menu on TV. It's responsible for displaying 2 ever-present Pip Menu
  * actions: Fullscreen and Close, but could also display "additional" actions, that may be set via
- * a {@link #setAdditionalActions(List)} call.
+ * a {@link #setAdditionalActions(List, Handler)} call.
  */
 public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
     private static final String TAG = "TvPipMenuView";
@@ -57,7 +57,6 @@
 
     private static final float DISABLED_ACTION_ALPHA = 0.54f;
 
-    private final Handler mUiThreadHandler;
     private final Animator mFadeInAnimation;
     private final Animator mFadeOutAnimation;
     @Nullable private Listener mListener;
@@ -80,7 +79,6 @@
     public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mUiThreadHandler = new Handler(Looper.getMainLooper());
 
         inflate(context, R.layout.tv_pip_menu, this);
 
@@ -132,7 +130,7 @@
         }
     }
 
-    void setAdditionalActions(List<RemoteAction> actions) {
+    void setAdditionalActions(List<RemoteAction> actions, Handler mainHandler) {
         if (DEBUG) Log.d(TAG, "setAdditionalActions()");
 
         // Make sure we exactly as many additional buttons as we have actions to display.
@@ -176,7 +174,7 @@
             action.getIcon().loadDrawableAsync(mContext, drawable -> {
                 drawable.setTint(Color.WHITE);
                 button.setImageDrawable(drawable);
-            }, mUiThreadHandler);
+            }, mainHandler);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index ce4b608..a474831 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.media.MediaMetadata;
+import android.os.Handler;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -60,6 +61,7 @@
     private final NotificationManager mNotificationManager;
     private final Notification.Builder mNotificationBuilder;
     private final ActionBroadcastReceiver mActionBroadcastReceiver;
+    private final Handler mMainHandler;
     private Delegate mDelegate;
 
     private String mDefaultTitle;
@@ -70,10 +72,12 @@
     private String mMediaTitle;
     private Bitmap mArt;
 
-    public TvPipNotificationController(Context context, PipMediaController pipMediaController) {
+    public TvPipNotificationController(Context context, PipMediaController pipMediaController,
+            Handler mainHandler) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mNotificationManager = context.getSystemService(NotificationManager.class);
+        mMainHandler = mainHandler;
 
         mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
                 .setLocalOnly(true)
@@ -219,7 +223,8 @@
         void register() {
             if (mRegistered) return;
 
-            mContext.registerReceiver(this, mIntentFilter, UserHandle.USER_ALL);
+            mContext.registerReceiverForAllUsers(this, mIntentFilter, null /* permission */,
+                    mMainHandler);
             mRegistered = true;
         }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index 9eb13fb..5f5c30b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -46,7 +46,7 @@
 
     @Override
     public boolean hasCallback(Runnable r) {
-        return !mRunnables.isEmpty();
+        return mRunnables.contains(r);
     }
 
     @Override
@@ -55,8 +55,8 @@
     }
 
     public void flushAll() {
-        for (int i = mRunnables.size() - 1; i >= 0; --i) {
-            mRunnables.get(i).run();
+        for (Runnable r : mRunnables) {
+            r.run();
         }
         mRunnables.clear();
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 5e0d518..47104ce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -65,6 +65,8 @@
             public IInputMethodManager getImms() {
                 return mMock;
             }
+            @Override
+            void removeImeSurface() { }
         }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
     }
 
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 b5d10d7..245858d 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,9 @@
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 
@@ -71,6 +73,7 @@
     @Mock private PipUiEventLogger mMockPipUiEventLogger;
     @Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen;
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
+    private TestShellExecutor mMainExecutor;
     private PipBoundsState mPipBoundsState;
 
     private ComponentName mComponent1;
@@ -82,10 +85,12 @@
         mComponent1 = new ComponentName(mContext, "component1");
         mComponent2 = new ComponentName(mContext, "component2");
         mPipBoundsState = new PipBoundsState(mContext);
+        mMainExecutor = new TestShellExecutor();
         mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext, mPipBoundsState,
                 mMockPipBoundsAlgorithm, mMockPhonePipMenuController,
                 mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen, mMockdDisplayController,
-                mMockPipUiEventLogger, mMockShellTaskOrganizer));
+                mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor));
+        mMainExecutor.flushAll();
         preparePipTaskOrg();
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java
index 000f7e8..0d4d126 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java
@@ -24,18 +24,15 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import android.os.Handler;
-import android.os.Looper;
 import android.os.SystemClock;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -45,23 +42,22 @@
 
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
-@RunWithLooper
 public class PipTouchStateTest extends ShellTestCase {
 
     private PipTouchState mTouchState;
     private CountDownLatch mDoubleTapCallbackTriggeredLatch;
     private CountDownLatch mHoverExitCallbackTriggeredLatch;
+    private TestShellExecutor mShellMainExecutor;
 
     @Before
     public void setUp() throws Exception {
+        mShellMainExecutor = new TestShellExecutor();
         mDoubleTapCallbackTriggeredLatch = new CountDownLatch(1);
         mHoverExitCallbackTriggeredLatch = new CountDownLatch(1);
         mTouchState = new PipTouchState(ViewConfiguration.get(getContext()),
-                Handler.createAsync(Looper.myLooper()), () -> {
-            mDoubleTapCallbackTriggeredLatch.countDown();
-        }, () -> {
-            mHoverExitCallbackTriggeredLatch.countDown();
-        });
+                mDoubleTapCallbackTriggeredLatch::countDown,
+                mHoverExitCallbackTriggeredLatch::countDown,
+                mShellMainExecutor);
         assertFalse(mTouchState.isDoubleTap());
         assertFalse(mTouchState.isWaitingForDoubleTap());
     }
@@ -91,9 +87,7 @@
         assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == 10);
         mTouchState.scheduleDoubleTapTimeoutCallback();
 
-        // TODO: Remove this sleep. Its only being added because it speeds up this test a bit.
-        Thread.sleep(15);
-        TestableLooper.get(this).processAllMessages();
+        mShellMainExecutor.flushAll();
         assertTrue(mDoubleTapCallbackTriggeredLatch.getCount() == 0);
     }
 
@@ -128,17 +122,13 @@
     @Test
     public void testHoverExitTimeout_timeoutCallbackCalled() throws Exception {
         mTouchState.scheduleHoverExitTimeoutCallback();
-
-        // TODO: Remove this sleep. Its only being added because it speeds up this test a bit.
-        Thread.sleep(50);
-        TestableLooper.get(this).processAllMessages();
+        mShellMainExecutor.flushAll();
         assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 0);
     }
 
     @Test
     public void testHoverExitTimeout_timeoutCallbackNotCalled() throws Exception {
         mTouchState.scheduleHoverExitTimeoutCallback();
-        TestableLooper.get(this).processAllMessages();
         assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1);
     }
 
@@ -147,14 +137,12 @@
         mTouchState.scheduleHoverExitTimeoutCallback();
         mTouchState.onTouchEvent(createMotionEvent(ACTION_BUTTON_PRESS, SystemClock.uptimeMillis(),
                 0, 0));
-
-        // TODO: Remove this sleep. Its only being added because it speeds up this test a bit.
-        Thread.sleep(50);
-        TestableLooper.get(this).processAllMessages();
+        mShellMainExecutor.flushAll();
         assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1);
     }
 
     private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) {
         return MotionEvent.obtain(0, eventTime, action, x, y, 0);
     }
+
 }
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
new file mode 100644
index 0000000..8db8d76
--- /dev/null
+++ b/packages/Connectivity/framework/Android.bp
@@ -0,0 +1,29 @@
+//
+// 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.
+//
+
+// TODO: use a java_library in the bootclasspath instead
+filegroup {
+    name: "framework-connectivity-sources",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl",
+    ],
+    path: "src",
+    visibility: [
+        "//frameworks/base",
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
+}
\ No newline at end of file
diff --git a/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl
new file mode 100644
index 0000000..64b5567
--- /dev/null
+++ b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl
@@ -0,0 +1,49 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+package com.android.connectivity.aidl;
+
+import android.net.NattKeepalivePacketData;
+import android.net.QosFilterParcelable;
+import android.net.TcpKeepalivePacketData;
+
+import com.android.connectivity.aidl.INetworkAgentRegistry;
+
+/**
+ * Interface to notify NetworkAgent of connectivity events.
+ * @hide
+ */
+oneway interface INetworkAgent {
+    void onRegistered(in INetworkAgentRegistry registry);
+    void onDisconnected();
+    void onBandwidthUpdateRequested();
+    void onValidationStatusChanged(int validationStatus,
+            in @nullable String captivePortalUrl);
+    void onSaveAcceptUnvalidated(boolean acceptUnvalidated);
+    void onStartNattSocketKeepalive(int slot, int intervalDurationMs,
+        in NattKeepalivePacketData packetData);
+    void onStartTcpSocketKeepalive(int slot, int intervalDurationMs,
+        in TcpKeepalivePacketData packetData);
+    void onStopSocketKeepalive(int slot);
+    void onSignalStrengthThresholdsUpdated(in int[] thresholds);
+    void onPreventAutomaticReconnect();
+    void onAddNattKeepalivePacketFilter(int slot,
+        in NattKeepalivePacketData packetData);
+    void onAddTcpKeepalivePacketFilter(int slot,
+        in TcpKeepalivePacketData packetData);
+    void onRemoveKeepalivePacketFilter(int slot);
+    void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel);
+    void onQosCallbackUnregistered(int qosCallbackId);
+}
diff --git a/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl
new file mode 100644
index 0000000..f0193db
--- /dev/null
+++ b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl
@@ -0,0 +1,41 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+package com.android.connectivity.aidl;
+
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.QosSession;
+import android.telephony.data.EpsBearerQosSessionAttributes;
+
+/**
+ * Interface for NetworkAgents to send network network properties.
+ * @hide
+ */
+oneway interface INetworkAgentRegistry {
+    void sendNetworkCapabilities(in NetworkCapabilities nc);
+    void sendLinkProperties(in LinkProperties lp);
+    // TODO: consider replacing this by "markConnected()" and removing
+    void sendNetworkInfo(in NetworkInfo info);
+    void sendScore(int score);
+    void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial);
+    void sendSocketKeepaliveEvent(int slot, int reason);
+    void sendUnderlyingNetworks(in @nullable List<Network> networks);
+    void sendEpsQosSessionAvailable(int callbackId, in QosSession session, in EpsBearerQosSessionAttributes attributes);
+    void sendQosSessionLost(int qosCallbackId, in QosSession session);
+    void sendQosCallbackError(int qosCallbackId, int exceptionType);
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index c6c7142..935cb37 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -25,7 +25,10 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -143,16 +146,21 @@
 
                 File file = new File(mPackageURI.getPath());
                 try {
-                    PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0);
-                    params.setAppPackageName(pkg.packageName);
-                    params.setInstallLocation(pkg.installLocation);
-                    params.setSize(
-                            PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
-                } catch (PackageParser.PackageParserException e) {
-                    Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
-                    Log.e(LOG_TAG,
-                            "Cannot calculate installed size " + file + ". Try only apk size.");
-                    params.setSize(file.length());
+                    final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+                    final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+                            input.reset(), file, /* flags */ 0);
+                    if (result.isError()) {
+                        Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
+                        Log.e(LOG_TAG,
+                                "Cannot calculate installed size " + file + ". Try only apk size.");
+                        params.setSize(file.length());
+                    } else {
+                        final PackageLite pkg = result.getResult();
+                        params.setAppPackageName(pkg.getPackageName());
+                        params.setInstallLocation(pkg.getInstallLocation());
+                        params.setSize(
+                                PackageHelper.calculateInstalledSize(pkg, params.abiOverride));
+                    }
                 } catch (IOException e) {
                     Log.e(LOG_TAG,
                             "Cannot calculate installed size " + file + ". Try only apk size.");
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 6c51f2f..84dacfd 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -239,7 +239,7 @@
     <bool name="def_aware_lock_enabled">false</bool>
 
     <!-- Default for setting for Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED -->
-    <bool name="def_hdmiControlAutoDeviceOff">false</bool>
+    <bool name="def_hdmiControlAutoDeviceOff">true</bool>
 
     <!-- Default for Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED -->
     <bool name="def_swipe_bottom_to_notification_enabled">true</bool>
@@ -249,4 +249,7 @@
 
     <!-- Default for Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY -->
     <integer name="def_accessibility_magnification_capabilities">3</integer>
+
+    <!-- Default for Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW -->
+    <bool name="def_enable_non_resizable_multi_window">false</bool>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index edb5506..c7790fd 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3342,7 +3342,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 197;
+            private static final int SETTINGS_VERSION = 198;
 
             private final int mUserId;
 
@@ -4815,6 +4815,24 @@
                     currentVersion = 197;
                 }
 
+                if (currentVersion == 197) {
+                    // Version 197: Set the default value for Global Settings:
+                    // DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    final Setting enableNonResizableMultiWindow = globalSettings.getSettingLocked(
+                            Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW);
+                    if (enableNonResizableMultiWindow.isNull()) {
+                        final boolean defEnableNonResizableMultiWindow = getContext().getResources()
+                                .getBoolean(R.bool.def_enable_non_resizable_multi_window);
+                        globalSettings.insertSettingLocked(
+                                Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+                                defEnableNonResizableMultiWindow ? "1" : "0", null, true,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+
+                    currentVersion = 198;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/res/layout/global_screenshot_preview.xml b/packages/SystemUI/res/layout/global_screenshot_preview.xml
index 5262407..100213b 100644
--- a/packages/SystemUI/res/layout/global_screenshot_preview.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_preview.xml
@@ -25,7 +25,7 @@
     android:layout_marginBottom="@dimen/screenshot_offset_y"
     android:scaleType="fitEnd"
     android:elevation="@dimen/screenshot_preview_elevation"
-    android:visibility="gone"
+    android:visibility="invisible"
     android:background="@drawable/screenshot_rounded_corners"
     android:adjustViewBounds="true"
     android:contentDescription="@string/screenshot_edit_label"
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 905a575..6ff589c 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -16,7 +16,7 @@
   -->
 
 <resources>
-    <bool name="are_flags_overrideable">true</bool>
+    <bool name="are_flags_overrideable">false</bool>
 
     <bool name="flag_notification_pipeline2">false</bool>
     <bool name="flag_notification_pipeline2_rendering">false</bool>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 2d972e0..6eb54c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -192,7 +192,7 @@
             statusAreaLP.removeRule(RelativeLayout.ALIGN_PARENT_START);
             statusAreaLP.removeRule(RelativeLayout.START_OF);
             statusAreaLP.addRule(RelativeLayout.BELOW, R.id.clock_view);
-            statusAreaLP.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+            statusAreaLP.width = ViewGroup.LayoutParams.MATCH_PARENT;
         }
 
         requestLayout();
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 8ebcb20..756d610 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -156,9 +156,14 @@
     }
 
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        mAnimator.updateMargin((ViewGroup.MarginLayoutParams) getLayoutParams());
+    public void setLayoutParams(ViewGroup.LayoutParams params) {
+        mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
 
+        super.setLayoutParams(params);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         measureChildren(widthMeasureSpec, heightMeasureSpec);
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
index 5fc2e53..40fe7b1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
@@ -47,4 +47,9 @@
                 (int) sensorRect.right - margin,
                 (int) sensorRect.bottom - margin);
     }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mFingerprintDrawable.setAlpha(alpha);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
index 1a2a492..52662ae 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
@@ -23,10 +23,13 @@
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.RectF;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.graphics.ColorUtils;
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
 
 /**
@@ -35,8 +38,11 @@
 public class UdfpsAnimationEnroll extends UdfpsAnimation {
     private static final String TAG = "UdfpsAnimationEnroll";
 
+    private static final float SHADOW_RADIUS = 5.f;
+
     @Nullable private RectF mSensorRect;
     @NonNull private final Paint mSensorPaint;
+    private final int mNotificationShadeColor;
 
     UdfpsAnimationEnroll(@NonNull Context context) {
         super(context);
@@ -44,8 +50,11 @@
         mSensorPaint = new Paint(0 /* flags */);
         mSensorPaint.setAntiAlias(true);
         mSensorPaint.setColor(Color.WHITE);
-        mSensorPaint.setShadowLayer(UdfpsView.SENSOR_SHADOW_RADIUS, 0, 0, Color.BLACK);
+        mSensorPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, Color.BLACK);
         mSensorPaint.setStyle(Paint.Style.FILL);
+
+        mNotificationShadeColor = Utils.getColorAttr(context,
+                android.R.attr.colorBackgroundFloating).getDefaultColor();
     }
 
     @Override
@@ -73,7 +82,14 @@
 
     @Override
     public void setAlpha(int alpha) {
+        super.setAlpha(alpha);
 
+        // Gradually fade into the notification shade color. This needs to be done because the
+        // UDFPS view is drawn on a layer on top of the notification shade
+        final float percent = alpha / 255.f;
+        mSensorPaint.setColor(ColorUtils.blendARGB(mNotificationShadeColor, Color.WHITE, percent));
+        mSensorPaint.setShadowLayer(SHADOW_RADIUS, 0, 0,
+                ColorUtils.blendARGB(mNotificationShadeColor, Color.BLACK, percent));
     }
 
     @Override
@@ -85,4 +101,12 @@
     public int getOpacity() {
         return 0;
     }
+
+    public void onEnrollmentProgress(int remaining) {
+        Log.d(TAG, "Remaining: " + remaining);
+    }
+
+    public void onEnrollmentHelp() {
+        Log.d(TAG, "onEnrollmentHelp");
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java
index 7563e73..efc864a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java
@@ -44,11 +44,6 @@
     }
 
     @Override
-    public void setAlpha(int alpha) {
-
-    }
-
-    @Override
     public void setColorFilter(@Nullable ColorFilter colorFilter) {
 
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
index 3e46a65..501de9d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
@@ -99,11 +99,6 @@
     }
 
     @Override
-    public void setAlpha(int alpha) {
-
-    }
-
-    @Override
     public void setColorFilter(@Nullable ColorFilter colorFilter) {
 
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index a2b2bea8..b373cff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -47,9 +47,11 @@
 import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.settings.SystemSettings;
 
@@ -70,7 +72,8 @@
  * {@code sensorId} parameters.
  */
 @SuppressWarnings("deprecation")
-class UdfpsController implements DozeReceiver {
+@SysUISingleton
+public class UdfpsController implements DozeReceiver {
     private static final String TAG = "UdfpsController";
     // Gamma approximation for the sRGB color space.
     private static final float DISPLAY_GAMMA = 2.2f;
@@ -131,6 +134,16 @@
         }
 
         @Override
+        public void onEnrollmentProgress(int sensorId, int remaining) {
+            mView.onEnrollmentProgress(remaining);
+        }
+
+        @Override
+        public void onEnrollmentHelp(int sensorId) {
+            mView.onEnrollmentHelp();
+        }
+
+        @Override
         public void setDebugMessage(int sensorId, String message) {
             mView.setDebugMessage(message);
         }
@@ -166,7 +179,7 @@
     };
 
     @Inject
-    UdfpsController(@NonNull Context context,
+    public UdfpsController(@NonNull Context context,
             @Main Resources resources,
             LayoutInflater inflater,
             @Nullable FingerprintManager fingerprintManager,
@@ -174,7 +187,8 @@
             WindowManager windowManager,
             SystemSettings systemSettings,
             @NonNull StatusBarStateController statusBarStateController,
-            @Main DelayableExecutor fgExecutor) {
+            @Main DelayableExecutor fgExecutor,
+            @NonNull ScrimController scrimController) {
         mContext = context;
         // The fingerprint manager is queried for UDFPS before this class is constructed, so the
         // fingerprint manager should never be null.
@@ -210,6 +224,8 @@
 
         mHbmSupported = !TextUtils.isEmpty(mHbmPath);
         mView.setHbmSupported(mHbmSupported);
+        scrimController.addScrimChangedListener(mView);
+        statusBarStateController.addCallback(mView);
 
         // This range only consists of the minimum and maximum values, which only cover
         // non-high-brightness mode.
@@ -450,7 +466,7 @@
         onFingerUp();
     }
 
-    private void onFingerDown(int x, int y, float minor, float major) {
+    protected void onFingerDown(int x, int y, float minor, float major) {
         if (mHbmSupported) {
             try {
                 FileWriter fw = new FileWriter(mHbmPath);
@@ -468,7 +484,7 @@
         mView.showScrimAndDot();
     }
 
-    private void onFingerUp() {
+    protected void onFingerUp() {
         mFingerprintManager.onPointerUp(mSensorProps.sensorId);
         // Hiding the scrim before disabling HBM results in less noticeable flicker.
         mView.hideScrimAndDot();
@@ -507,4 +523,8 @@
         }
         return normalizedBacklight;
     }
+
+    protected UdfpsView getView() {
+        return mView;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 4cb8101..96ecc7b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.biometrics;
 
+import static com.android.systemui.statusbar.StatusBarState.FULLSCREEN_USER_SWITCHER;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -23,6 +27,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -30,20 +35,24 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Surface;
-import android.view.View;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
 import android.view.ViewTreeObserver;
 
 import com.android.systemui.R;
 import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.ScrimController;
 
 /**
  * A full screen view with a configurable illumination dot and scrim.
  */
-public class UdfpsView extends View implements DozeReceiver {
+public class UdfpsView extends SurfaceView implements DozeReceiver,
+        StatusBarStateController.StateListener,  ScrimController.ScrimChangedListener {
     private static final String TAG = "UdfpsView";
 
     // Values in pixels.
-    public static final float SENSOR_SHADOW_RADIUS = 2.0f;
+    private static final float SENSOR_SHADOW_RADIUS = 2.0f;
 
     private static final int DEBUG_TEXT_SIZE_PX = 32;
 
@@ -55,7 +64,6 @@
     @NonNull private final Paint mSensorPaint;
     private final float mSensorTouchAreaCoefficient;
 
-
     // Stores rounded up values from mSensorRect. Necessary for APIs that only take Rect (not RecF).
     @NonNull private final Rect mTouchableRegion;
     // mInsetsListener is used to set the touchable region for our window. Our window covers the
@@ -71,11 +79,37 @@
     private boolean mShowScrimAndDot;
     private boolean mIsHbmSupported;
     @Nullable private String mDebugMessage;
+    private int mStatusBarState;
+    private boolean mNotificationShadeExpanded;
+    private int mNotificationPanelAlpha;
 
     // Runnable that will be run after the illumination dot and scrim are shown.
     // The runnable is reset to null after it's executed once.
     @Nullable private Runnable mRunAfterShowingScrimAndDot;
 
+    @NonNull private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceCreated(@NonNull SurfaceHolder holder) {
+            Log.d(TAG, "Surface created");
+            // SurfaceView sets this to true by default. We must set it to false to allow
+            // onDraw to be called
+            setWillNotDraw(false);
+        }
+
+        @Override
+        public void surfaceChanged(@NonNull SurfaceHolder holder, int format,
+                int width, int height) {
+
+        }
+
+        @Override
+        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+            Log.d(TAG, "Surface destroyed");
+            // Must not draw when the surface is destroyed
+            setWillNotDraw(true);
+        }
+    };
+
     public UdfpsView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -92,6 +126,8 @@
             a.recycle();
         }
 
+        getHolder().addCallback(mSurfaceCallback);
+        getHolder().setFormat(PixelFormat.TRANSLUCENT);
 
         mScrimRect = new Rect();
         mScrimPaint = new Paint(0 /* flags */);
@@ -137,6 +173,22 @@
         }
     }
 
+    @Override
+    public void onExpandedChanged(boolean isExpanded) {
+        mNotificationShadeExpanded = isExpanded;
+    }
+
+    @Override
+    public void onStateChanged(int newState) {
+        mStatusBarState = newState;
+    }
+
+    @Override
+    public void onAlphaChanged(float alpha) {
+        mNotificationPanelAlpha = (int) (alpha * 255);
+        postInvalidate();
+    }
+
     // The "h" and "w" are the display's height and width relative to its current rotation.
     protected void updateSensorRect(int h, int w) {
         // mSensorProps coordinates assume portrait mode.
@@ -148,10 +200,12 @@
         // Transform mSensorRect if the device is in landscape mode.
         switch (mContext.getDisplay().getRotation()) {
             case Surface.ROTATION_90:
+                //noinspection SuspiciousNameCombination
                 mSensorRect.set(mSensorRect.top, h - mSensorRect.right, mSensorRect.bottom,
                         h - mSensorRect.left);
                 break;
             case Surface.ROTATION_270:
+                //noinspection SuspiciousNameCombination
                 mSensorRect.set(w - mSensorRect.bottom, mSensorRect.left, w - mSensorRect.top,
                         mSensorRect.right);
                 break;
@@ -223,6 +277,8 @@
             canvas.drawOval(mSensorRect, mSensorPaint);
         } else {
             if (mUdfpsAnimation != null) {
+                final int alpha = shouldPauseAuth() ? 255 - mNotificationPanelAlpha : 255;
+                mUdfpsAnimation.setAlpha(alpha);
                 mUdfpsAnimation.draw(canvas);
             }
         }
@@ -261,7 +317,19 @@
         return x > (cx - rx * mSensorTouchAreaCoefficient)
                 && x < (cx + rx * mSensorTouchAreaCoefficient)
                 && y > (cy - ry * mSensorTouchAreaCoefficient)
-                && y < (cy + ry * mSensorTouchAreaCoefficient);
+                && y < (cy + ry * mSensorTouchAreaCoefficient)
+                && !shouldPauseAuth();
+    }
+
+    /**
+     * States where UDFPS should temporarily not be authenticating. Instead of completely stopping
+     * authentication which would cause the UDFPS icons to abruptly disappear, do it here by not
+     * sending onFingerDown and smoothly animating away.
+     */
+    private boolean shouldPauseAuth() {
+        return (mNotificationShadeExpanded && mStatusBarState != KEYGUARD)
+                || mStatusBarState == SHADE_LOCKED
+                || mStatusBarState == FULLSCREEN_USER_SWITCHER;
     }
 
     void setScrimAlpha(int alpha) {
@@ -281,4 +349,16 @@
         mShowScrimAndDot = false;
         invalidate();
     }
+
+    void onEnrollmentProgress(int remaining) {
+        if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) {
+            ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentProgress(remaining);
+        }
+    }
+
+    void onEnrollmentHelp() {
+        if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) {
+            ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentHelp();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b71036c..4431b69 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -49,7 +49,6 @@
 import android.hardware.display.DisplayManager;
 import android.media.MediaActionSound;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -67,6 +66,7 @@
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.View;
+import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -80,13 +80,15 @@
 import com.android.internal.policy.PhoneWindow;
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
+import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
 import com.android.systemui.util.DeviceConfigProxy;
 
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -173,7 +175,7 @@
     private final UiEventLogger mUiEventLogger;
     private final ImageExporter mImageExporter;
     private final Executor mMainExecutor;
-    private final Executor mBgExecutor;
+    private final ExecutorService mBgExecutor;
 
     private final WindowManager mWindowManager;
     private final WindowManager.LayoutParams mWindowLayoutParams;
@@ -182,17 +184,14 @@
     private final ScrollCaptureClient mScrollCaptureClient;
     private final DeviceConfigProxy mConfigProxy;
     private final PhoneWindow mWindow;
-    private final View mDecorView;
     private final DisplayManager mDisplayManager;
 
-    private final Binder mWindowToken;
     private ScreenshotView mScreenshotView;
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
 
     private Animator mScreenshotAnimation;
-
-    private Runnable mOnCompleteRunnable;
+    private RequestCallback mCurrentRequestCallback;
 
     private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) {
         @Override
@@ -229,15 +228,14 @@
             UiEventLogger uiEventLogger,
             DeviceConfigProxy configProxy,
             ImageExporter imageExporter,
-            @Main Executor mainExecutor,
-            @Background Executor bgExecutor) {
+            @Main Executor mainExecutor) {
         mScreenshotSmartActions = screenshotSmartActions;
         mNotificationsController = screenshotNotificationsController;
         mScrollCaptureClient = scrollCaptureClient;
         mUiEventLogger = uiEventLogger;
         mImageExporter = imageExporter;
         mMainExecutor = mainExecutor;
-        mBgExecutor = bgExecutor;
+        mBgExecutor = Executors.newSingleThreadExecutor();
 
         mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
         final Context displayContext = context.createDisplayContext(getDefaultDisplay());
@@ -247,9 +245,6 @@
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
         mConfigProxy = configProxy;
 
-        mWindowToken = new Binder("ScreenshotController");
-        mScrollCaptureClient.setHostWindowToken(mWindowToken);
-
         // Setup the window that we are going to use
         mWindowLayoutParams = new WindowManager.LayoutParams(
                 MATCH_PARENT, MATCH_PARENT, /* xpos */ 0, /* ypos */ 0, TYPE_SCREENSHOT,
@@ -263,7 +258,6 @@
         mWindowLayoutParams.setTitle("ScreenshotAnimation");
         mWindowLayoutParams.layoutInDisplayCutoutMode =
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        mWindowLayoutParams.token = mWindowToken;
         // This is needed to let touches pass through outside the touchable areas
         mWindowLayoutParams.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 
@@ -272,8 +266,8 @@
         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
         mWindow.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
         mWindow.setBackgroundDrawableResource(android.R.color.transparent);
-        mDecorView = mWindow.getDecorView();
 
+        mConfigChanges.applyNewConfig(context.getResources());
         reloadAssets();
 
         // Setup the Camera shutter sound
@@ -281,9 +275,8 @@
         mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
     }
 
-    void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) {
-        mOnCompleteRunnable = onComplete;
-
+    void takeScreenshotFullscreen(Consumer<Uri> finisher, RequestCallback requestCallback) {
+        mCurrentRequestCallback = requestCallback;
         DisplayMetrics displayMetrics = new DisplayMetrics();
         getDefaultDisplay().getRealMetrics(displayMetrics);
         takeScreenshotInternal(
@@ -293,16 +286,14 @@
 
     void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
             Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
-            Consumer<Uri> finisher, Runnable onComplete) {
+            Consumer<Uri> finisher, RequestCallback requestCallback) {
         // TODO: use task Id, userId, topComponent for smart handler
-        mOnCompleteRunnable = onComplete;
 
         if (screenshot == null) {
             Log.e(TAG, "Got null bitmap from screenshot message");
             mNotificationsController.notifyScreenshotError(
                     R.string.screenshot_failed_to_capture_text);
-            finisher.accept(null);
-            mOnCompleteRunnable.run();
+            requestCallback.reportError();
             return;
         }
 
@@ -312,17 +303,19 @@
             visibleInsets = Insets.NONE;
             screenshotScreenBounds.set(0, 0, screenshot.getWidth(), screenshot.getHeight());
         }
+        mCurrentRequestCallback = requestCallback;
         saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, showFlash);
     }
 
     /**
      * Displays a screenshot selector
      */
-    void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) {
-        dismissScreenshot(true);
-        mOnCompleteRunnable = onComplete;
+    void takeScreenshotPartial(final Consumer<Uri> finisher, RequestCallback requestCallback) {
+        mScreenshotView.reset();
+        mCurrentRequestCallback = requestCallback;
 
-        mWindowManager.addView(mScreenshotView, mWindowLayoutParams);
+        attachWindow();
+        mWindow.setContentView(mScreenshotView);
 
         mScreenshotView.takePartialScreenshot(
                 rect -> takeScreenshotInternal(finisher, rect));
@@ -343,9 +336,9 @@
             }
             return;
         }
-        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+        cancelTimeout();
         if (immediate) {
-            resetScreenshotView();
+            finishDismiss();
         } else {
             mScreenshotView.animateDismissal();
         }
@@ -360,6 +353,8 @@
      */
     void releaseContext() {
         mContext.release();
+        mCameraSound.release();
+        mBgExecutor.shutdownNow();
     }
 
     /**
@@ -369,12 +364,8 @@
         if (DEBUG_UI) {
             Log.d(TAG, "reloadAssets()");
         }
-        boolean wasAttached = mDecorView.isAttachedToWindow();
-        if (wasAttached) {
-            if (DEBUG_WINDOW) {
-                Log.d(TAG, "Removing screenshot window");
-            }
-            mWindowManager.removeView(mDecorView);
+        if (mScreenshotView != null && mScreenshotView.isAttachedToWindow()) {
+            mWindow.clearContentView(); // Is there a simpler way to say "remove screenshotView?"
         }
 
         // respect the display cutout in landscape (since we'd otherwise overlap) but not portrait
@@ -382,12 +373,6 @@
         mWindowLayoutParams.setFitInsetsTypes(
                 orientation == ORIENTATION_PORTRAIT ? 0 : WindowInsets.Type.displayCutout());
 
-        // ignore system bar insets for the purpose of window layout
-        mDecorView.setOnApplyWindowInsetsListener((v, insets) -> v.onApplyWindowInsets(
-                new WindowInsets.Builder(insets)
-                        .setInsets(WindowInsets.Type.all(), Insets.NONE)
-                        .build()));
-
         // Inflate the screenshot layout
         mScreenshotView = (ScreenshotView)
                 LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null);
@@ -399,12 +384,18 @@
 
             @Override
             public void onDismiss() {
-                resetScreenshotView();
+                finishDismiss();
             }
         });
 
+        // ignore system bar insets for the purpose of window layout
+        mScreenshotView.setOnApplyWindowInsetsListener((v, insets) -> v.onApplyWindowInsets(
+                new WindowInsets.Builder(insets)
+                        .setInsets(WindowInsets.Type.all(), Insets.NONE)
+                        .build()));
+
         // TODO(159460485): Remove this when focus is handled properly in the system
-        mDecorView.setOnTouchListener((v, event) -> {
+        mScreenshotView.setOnTouchListener((v, event) -> {
             if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) {
                 if (DEBUG_INPUT) {
                     Log.d(TAG, "onTouch: ACTION_OUTSIDE");
@@ -426,8 +417,10 @@
             return false;
         });
 
-        // view is added to window manager in startAnimation
-        mWindow.setContentView(mScreenshotView, mWindowLayoutParams);
+        if (DEBUG_WINDOW) {
+            Log.d(TAG, "adding OnComputeInternalInsetsListener");
+        }
+        mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
     }
 
     /**
@@ -464,14 +457,9 @@
             Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
             mNotificationsController.notifyScreenshotError(
                     R.string.screenshot_failed_to_capture_text);
-            if (DEBUG_CALLBACK) {
-                Log.d(TAG, "Supplying null to Consumer<Uri>");
+            if (mCurrentRequestCallback != null) {
+                mCurrentRequestCallback.reportError();
             }
-            finisher.accept(null);
-            if (DEBUG_CALLBACK) {
-                Log.d(TAG, "Calling mOnCompleteRunnable.run()");
-            }
-            mOnCompleteRunnable.run();
             return;
         }
 
@@ -488,6 +476,13 @@
             mAccessibilityManager.sendAccessibilityEvent(event);
         }
 
+        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+            if (DEBUG_UI) {
+                Log.d(TAG, "saveScreenshot: reloading assets");
+            }
+            reloadAssets();
+        }
+
         if (mScreenshotView.isAttachedToWindow()) {
             // if we didn't already dismiss for another reason
             if (!mScreenshotView.isDismissing()) {
@@ -514,33 +509,101 @@
         mScreenBitmap.setHasAlpha(false);
         mScreenBitmap.prepareToDraw();
 
-        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
-            if (DEBUG_UI) {
-                Log.d(TAG, "saveScreenshot: reloading assets");
-            }
-            reloadAssets();
-        }
+        saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady);
 
         // The window is focusable by default
         setWindowFocusable(true);
 
-        // Start the post-screenshot animation
-        startAnimation(finisher, screenRect, screenInsets, showFlash);
-
         if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) {
-            mScrollCaptureClient.request(DEFAULT_DISPLAY, (connection) ->
-                    mScreenshotView.showScrollChip(() ->
-                            runScrollCapture(connection,
-                                    () -> mScreenshotHandler.post(
-                                            () -> dismissScreenshot(false)))));
+            View decorView = mWindow.getDecorView();
+
+            // Wait until this window is attached to request because it is
+            // the reference used to locate the target window (below).
+            withWindowAttached(() -> {
+                mScrollCaptureClient.setHostWindowToken(decorView.getWindowToken());
+                mScrollCaptureClient.request(DEFAULT_DISPLAY,
+                        /* onConnection */
+                        (connection) -> mScreenshotHandler.post(() ->
+                                mScreenshotView.showScrollChip(() ->
+                                        /* onClick */
+                                        runScrollCapture(connection))));
+            });
+        }
+
+
+        attachWindow();
+        if (DEBUG_WINDOW) {
+            Log.d(TAG, "setContentView: " + mScreenshotView);
+        }
+        mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        if (DEBUG_WINDOW) {
+                            Log.d(TAG, "onPreDraw: startAnimation");
+                        }
+                        mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
+                        startAnimation(screenRect, showFlash);
+                        return true;
+                    }
+                });
+        mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
+        setContentView(mScreenshotView);
+        cancelTimeout(); // restarted after animation
+    }
+
+    private void withWindowAttached(Runnable action) {
+        View decorView = mWindow.getDecorView();
+        if (decorView.isAttachedToWindow()) {
+            action.run();
+        } else {
+            decorView.getViewTreeObserver().addOnWindowAttachListener(
+                    new ViewTreeObserver.OnWindowAttachListener() {
+                        @Override
+                        public void onWindowAttached() {
+                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+                            action.run();
+                        }
+
+                        @Override
+                        public void onWindowDetached() { }
+                    });
+
         }
     }
 
-    private void runScrollCapture(ScrollCaptureClient.Connection connection, Runnable andThen) {
+    private void setContentView(View contentView) {
+        mWindow.setContentView(contentView);
+    }
+
+    private void attachWindow() {
+        View decorView = mWindow.getDecorView();
+        if (decorView.isAttachedToWindow()) {
+            return;
+        }
+        if (DEBUG_WINDOW) {
+            Log.d(TAG, "attachWindow");
+        }
+        mWindowManager.addView(decorView, mWindowLayoutParams);
+        decorView.requestApplyInsets();
+    }
+
+    void removeWindow() {
+        final View decorView = mWindow.peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            if (DEBUG_WINDOW) {
+                Log.d(TAG, "Removing screenshot window");
+            }
+            mWindowManager.removeViewImmediate(decorView);
+        }
+    }
+
+    private void runScrollCapture(ScrollCaptureClient.Connection connection) {
+        cancelTimeout();
         ScrollCaptureController controller = new ScrollCaptureController(mContext, connection,
                 mMainExecutor, mBgExecutor, mImageExporter);
-        controller.run(andThen);
+        controller.start(/* onDismiss */ () -> dismissScreenshot(false));
     }
 
     /**
@@ -549,11 +612,11 @@
      */
     private void saveScreenshotAndToast(Consumer<Uri> finisher) {
         // Play the shutter sound to notify that we've taken a screenshot
-        mScreenshotHandler.post(() -> {
-            mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
-        });
+        mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
 
-        saveScreenshotInWorkerThread(finisher, imageData -> {
+        saveScreenshotInWorkerThread(
+                /* onComplete */ finisher,
+                /* actionsReadyListener */ imageData -> {
             if (DEBUG_CALLBACK) {
                 Log.d(TAG, "returning URI to finisher (Consumer<URI>): " + imageData.uri);
             }
@@ -573,55 +636,35 @@
     /**
      * Starts the animation after taking the screenshot
      */
-    private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
-            boolean showFlash) {
-        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
-        mScreenshotHandler.post(() -> {
-            if (!mScreenshotView.isAttachedToWindow()) {
-                if (DEBUG_WINDOW) {
-                    Log.d(TAG, "Adding screenshot window");
-                }
-                mWindowManager.addView(mWindow.getDecorView(), mWindowLayoutParams);
-            }
+    private void startAnimation(Rect screenRect, boolean showFlash) {
+        if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
+            mScreenshotAnimation.cancel();
+        }
 
-            mScreenshotView.prepareForAnimation(mScreenBitmap, screenInsets);
+        mScreenshotAnimation =
+                mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
 
-            mScreenshotHandler.post(() -> {
-                if (DEBUG_WINDOW) {
-                    Log.d(TAG, "adding OnComputeInternalInsetsListener");
-                }
-                mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(
-                        mScreenshotView);
+        // Play the shutter sound to notify that we've taken a screenshot
+        mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
 
-                mScreenshotAnimation =
-                        mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
-
-                saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady);
-
-                // Play the shutter sound to notify that we've taken a screenshot
-                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
-
-                if (DEBUG_ANIM) {
-                    Log.d(TAG, "starting post-screenshot animation");
-                }
-                mScreenshotAnimation.start();
-            });
-        });
+        if (DEBUG_ANIM) {
+            Log.d(TAG, "starting post-screenshot animation");
+        }
+        mScreenshotAnimation.start();
     }
 
     /** Reset screenshot view and then call onCompleteRunnable */
-    private void resetScreenshotView() {
+    private void finishDismiss() {
         if (DEBUG_UI) {
-            Log.d(TAG, "resetScreenshotView");
+            Log.d(TAG, "finishDismiss");
         }
-        if (mScreenshotView.isAttachedToWindow()) {
-            if (DEBUG_WINDOW) {
-                Log.d(TAG, "Removing screenshot window");
-            }
-            mWindowManager.removeView(mDecorView);
-        }
+        cancelTimeout();
+        removeWindow();
         mScreenshotView.reset();
-        mOnCompleteRunnable.run();
+        if (mCurrentRequestCallback != null) {
+            mCurrentRequestCallback.onFinish();
+            mCurrentRequestCallback = null;
+        }
     }
 
     /**
@@ -645,8 +688,12 @@
         mSaveInBgTask.execute();
     }
 
-    private void resetTimeout() {
+    private void cancelTimeout() {
         mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+    }
+
+    private void resetTimeout() {
+        cancelTimeout();
 
         AccessibilityManager accessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -705,7 +752,7 @@
 
                 @Override
                 public void hideSharedElements() {
-                    resetScreenshotView();
+                    finishDismiss();
                 }
 
                 @Override
@@ -753,13 +800,21 @@
         if (DEBUG_WINDOW) {
             Log.d(TAG, "setWindowFocusable: " + focusable);
         }
+        int flags = mWindowLayoutParams.flags;
         if (focusable) {
             mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
         } else {
             mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
         }
-        if (mDecorView.isAttachedToWindow()) {
-            mWindowManager.updateViewLayout(mDecorView, mWindowLayoutParams);
+        if (mWindowLayoutParams.flags == flags) {
+            if (DEBUG_WINDOW) {
+                Log.d(TAG, "setWindowFocusable: skipping, already " + focusable);
+            }
+            return;
+        }
+        final View decorView = mWindow.peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 211f507..bf86b68 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -316,21 +316,21 @@
         mScreenshotSelectorView.requestFocus();
     }
 
-    void prepareForAnimation(Bitmap bitmap, Insets screenInsets) {
+    void setScreenshot(Bitmap bitmap, Insets screenInsets) {
         mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
-        // make static preview invisible (from gone) so we can query its location on screen
-        mScreenshotPreview.setVisibility(View.INVISIBLE);
     }
 
     AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
-        mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-        mScreenshotPreview.buildLayer();
+        if (DEBUG_ANIM) {
+            Log.d(TAG, "createAnim: bounds=" + bounds + " showFlash=" + showFlash);
+        }
 
         Rect previewBounds = new Rect();
         mScreenshotPreview.getBoundsOnScreen(previewBounds);
-        int[] previewLocation = new int[2];
-        mScreenshotPreview.getLocationInWindow(previewLocation);
+        Rect targetPosition = new Rect();
+        mScreenshotPreview.getHitRect(targetPosition);
 
+        // ratio of preview width, end vs. start size
         float cornerScale =
                 mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height());
         final float currentScale = 1 / cornerScale;
@@ -358,8 +358,13 @@
 
         // animate from the current location, to the static preview location
         final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
-        final PointF finalPos = new PointF(previewLocation[0] + previewBounds.width() / 2f,
-                previewLocation[1] + previewBounds.height() / 2f);
+        final PointF finalPos = new PointF(targetPosition.exactCenterX(),
+                targetPosition.exactCenterY());
+
+        if (DEBUG_ANIM) {
+            Log.d(TAG, "toCorner: startPos=" + startPos);
+            Log.d(TAG, "toCorner: finalPos=" + finalPos);
+        }
 
         ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
         toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
@@ -427,7 +432,7 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 if (DEBUG_ANIM) {
-                    Log.d(TAG, "drop-in animation completed");
+                    Log.d(TAG, "drop-in animation ended");
                 }
                 mDismissButton.setOnClickListener(view -> {
                     if (DEBUG_INPUT) {
@@ -653,13 +658,12 @@
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
         // Clear any references to the bitmap
         mScreenshotPreview.setImageDrawable(null);
+        mScreenshotPreview.setVisibility(View.INVISIBLE);
         mPendingSharedTransition = false;
         mActionsContainerBackground.setVisibility(View.GONE);
         mActionsContainer.setVisibility(View.GONE);
         mBackgroundProtection.setAlpha(0f);
         mDismissButton.setVisibility(View.GONE);
-        mScreenshotPreview.setVisibility(View.GONE);
-        mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null);
         mScreenshotStatic.setTranslationX(0);
         mScreenshotPreview.setTranslationY(0);
         mScreenshotPreview.setContentDescription(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 54b1b2c..825c857 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -72,7 +72,7 @@
      *
      * @param after action to take after the flow is complete
      */
-    public void run(final Runnable after) {
+    public void start(final Runnable after) {
         mCaptureTime = ZonedDateTime.now();
         mRequestId = UUID.randomUUID();
         mConnection.start((session) -> startCapture(session, after));
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 144ad39..daa9d09 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -25,6 +25,7 @@
 import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
 import static com.android.systemui.screenshot.LogConfig.logTag;
 
+import android.annotation.MainThread;
 import android.app.Service;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -59,7 +60,8 @@
 public class TakeScreenshotService extends Service {
     private static final String TAG = logTag(TakeScreenshotService.class);
 
-    private final ScreenshotController mScreenshot;
+    private ScreenshotController mScreenshot;
+
     private final UserManager mUserManager;
     private final UiEventLogger mUiEventLogger;
     private final ScreenshotNotificationsController mNotificationsController;
@@ -79,6 +81,15 @@
         }
     };
 
+    /** Informs about coarse grained state of the Controller. */
+    interface RequestCallback {
+        /** Respond to the current request indicating the screenshot request failed.*/
+        void reportError();
+
+        /** The controller has completed handling this request UI has been removed */
+        void onFinish();
+    }
+
     @Inject
     public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager,
             UiEventLogger uiEventLogger,
@@ -116,7 +127,8 @@
             Log.d(TAG, "onUnbind");
         }
         if (mScreenshot != null) {
-            mScreenshot.dismissScreenshot(true);
+            mScreenshot.removeWindow();
+            mScreenshot = null;
         }
         unregisterReceiver(mCloseSystemDialogs);
         return false;
@@ -126,18 +138,39 @@
     public void onDestroy() {
         super.onDestroy();
         if (mScreenshot != null) {
+            mScreenshot.removeWindow();
             mScreenshot.releaseContext();
+            mScreenshot = null;
         }
         if (DEBUG_SERVICE) {
             Log.d(TAG, "onDestroy");
         }
     }
 
+    static class RequestCallbackImpl implements RequestCallback {
+        private final Messenger mReplyTo;
+
+        RequestCallbackImpl(Messenger replyTo) {
+            mReplyTo = replyTo;
+        }
+
+        public void reportError() {
+            reportUri(mReplyTo, null);
+            sendComplete(mReplyTo);
+        }
+
+        @Override
+        public void onFinish() {
+            sendComplete(mReplyTo);
+        }
+    }
+
     /** Respond to incoming Message via Binder (Messenger) */
+    @MainThread
     private boolean handleMessage(Message msg) {
         final Messenger replyTo = msg.replyTo;
-        final Runnable onComplete = () -> sendComplete(replyTo);
         final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri);
+        RequestCallback requestCallback = new RequestCallbackImpl(replyTo);
 
         // If the storage for this user is locked, we have no place to store
         // the screenshot, so skip taking it instead of showing a misleading
@@ -146,14 +179,7 @@
             Log.w(TAG, "Skipping screenshot because storage is locked!");
             mNotificationsController.notifyScreenshotError(
                     R.string.screenshot_failed_to_save_user_locked_text);
-            if (DEBUG_CALLBACK) {
-                Log.d(TAG, "handleMessage: calling uriConsumer.accept(null)");
-            }
-            uriConsumer.accept(null);
-            if (DEBUG_CALLBACK) {
-                Log.d(TAG, "handleMessage: calling onComplete.run()");
-            }
-            onComplete.run();
+            requestCallback.reportError();
             return true;
         }
 
@@ -167,13 +193,13 @@
                 if (DEBUG_SERVICE) {
                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
                 }
-                mScreenshot.takeScreenshotFullscreen(uriConsumer, onComplete);
+                mScreenshot.takeScreenshotFullscreen(uriConsumer, requestCallback);
                 break;
             case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
                 if (DEBUG_SERVICE) {
                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION");
                 }
-                mScreenshot.takeScreenshotPartial(uriConsumer, onComplete);
+                mScreenshot.takeScreenshotPartial(uriConsumer, requestCallback);
                 break;
             case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
                 if (DEBUG_SERVICE) {
@@ -186,8 +212,16 @@
                 int taskId = screenshotRequest.getTaskId();
                 int userId = screenshotRequest.getUserId();
                 ComponentName topComponent = screenshotRequest.getTopComponent();
-                mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
-                        taskId, userId, topComponent, uriConsumer, onComplete);
+
+                if (screenshot == null) {
+                    Log.e(TAG, "Got null bitmap from screenshot message");
+                    mNotificationsController.notifyScreenshotError(
+                            R.string.screenshot_failed_to_capture_text);
+                    requestCallback.reportError();
+                } else {
+                    mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
+                            taskId, userId, topComponent, uriConsumer, requestCallback);
+                }
                 break;
             default:
                 Log.w(TAG, "Invalid screenshot option: " + msg.what);
@@ -196,7 +230,7 @@
         return true;
     };
 
-    private void sendComplete(Messenger target) {
+    private static void sendComplete(Messenger target) {
         try {
             if (DEBUG_CALLBACK) {
                 Log.d(TAG, "sendComplete: " + target);
@@ -207,7 +241,7 @@
         }
     }
 
-    private void reportUri(Messenger target, Uri uri) {
+    private static void reportUri(Messenger target, Uri uri) {
         try {
             if (DEBUG_CALLBACK) {
                 Log.d(TAG, "reportUri: " + target + " -> " + uri);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 03d17e5..19c0b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -34,6 +34,7 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -62,6 +63,8 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -195,6 +198,16 @@
     private boolean mWakeLockHeld;
     private boolean mKeyguardOccluded;
 
+    /**
+     * Notifies listeners of animation-related changes (currently just opacity changes).
+     */
+    public interface ScrimChangedListener {
+        void onAlphaChanged(float alpha);
+    }
+
+    @NonNull
+    private final List<ScrimChangedListener> mScrimChangedListeners;
+
     @Inject
     public ScrimController(LightBarController lightBarController, DozeParameters dozeParameters,
             AlarmManager alarmManager, KeyguardStateController keyguardStateController,
@@ -208,6 +221,7 @@
         ScrimState.BUBBLE_EXPANDED.setBubbleAlpha(featureFlags.isShadeOpaque()
                 ? BUSY_SCRIM_ALPHA : GAR_SCRIM_ALPHA);
         mBlurUtils = blurUtils;
+        mScrimChangedListeners = new ArrayList<>();
 
         mKeyguardStateController = keyguardStateController;
         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -284,6 +298,10 @@
         mScrimVisibleListener = listener;
     }
 
+    public void addScrimChangedListener(@NonNull ScrimChangedListener listener) {
+        mScrimChangedListeners.add(listener);
+    }
+
     public void transitionTo(ScrimState state) {
         transitionTo(state, null);
     }
@@ -559,6 +577,10 @@
             throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
                     + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha);
         }
+
+        for (ScrimChangedListener listener : mScrimChangedListeners) {
+            listener.onAlphaChanged(mBehindAlpha);
+        }
     }
 
     private void applyAndDispatchExpansion() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index d097cfa..499d1e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -316,6 +316,7 @@
     @Override
     public void onDarkChanged(Rect area, float darkIntensity, int tint) {
         mNonAdaptedColor = DarkIconDispatcher.getTint(area, this, tint);
+        setTextColor(mNonAdaptedColor);
     }
 
     // Update text color based when shade scrim changes color.
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 8a79ace..416de04 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -17,13 +17,16 @@
 package com.android.systemui.wmshell;
 
 import android.content.Context;
+import android.os.Handler;
 
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -57,9 +60,10 @@
             PipMediaController pipMediaController,
             TvPipNotificationController tvPipNotificationController,
             TaskStackListenerImpl taskStackListener,
-            WindowManagerShellWrapper windowManagerShellWrapper) {
+            WindowManagerShellWrapper windowManagerShellWrapper,
+            @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.of(
-                new TvPipController(
+                TvPipController.create(
                         context,
                         pipBoundsState,
                         pipBoundsAlgorithm,
@@ -68,7 +72,8 @@
                         pipMediaController,
                         tvPipNotificationController,
                         taskStackListener,
-                        windowManagerShellWrapper));
+                        windowManagerShellWrapper,
+                        mainExecutor));
     }
 
     @WMSingleton
@@ -84,21 +89,26 @@
         return new PipBoundsState(context);
     }
 
+    // Handler needed for loadDrawableAsync() in PipControlsViewController
     @WMSingleton
     @Provides
     static TvPipMenuController providesTvPipMenuController(
             Context context,
             PipBoundsState pipBoundsState,
             SystemWindows systemWindows,
-            PipMediaController pipMediaController) {
-        return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController);
+            PipMediaController pipMediaController,
+            @ShellMainThread Handler mainHandler) {
+        return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController,
+                mainHandler);
     }
 
+    // Handler needed for registerReceiverForAllUsers()
     @WMSingleton
     @Provides
     static TvPipNotificationController provideTvPipNotificationController(Context context,
-            PipMediaController pipMediaController) {
-        return new TvPipNotificationController(context, pipMediaController);
+            PipMediaController pipMediaController,
+            @ShellMainThread Handler mainHandler) {
+        return new TvPipNotificationController(context, pipMediaController, mainHandler);
     }
 
     @WMSingleton
@@ -109,9 +119,10 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
-            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
+            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
+            @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
                 tvPipMenuController, pipSurfaceTransactionHelper, splitScreenOptional,
-                displayController, pipUiEventLogger, shellTaskOrganizer);
+                displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index bbc238a..103e6bb 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -250,14 +250,17 @@
     @Provides
     static PipAppOpsListener providePipAppOpsListener(Context context,
             IActivityManager activityManager,
-            PipTouchHandler pipTouchHandler) {
-        return new PipAppOpsListener(context, activityManager, pipTouchHandler.getMotionHelper());
+            PipTouchHandler pipTouchHandler,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
     }
 
+    // Needs handler for registering broadcast receivers
     @WMSingleton
     @Provides
-    static PipMediaController providePipMediaController(Context context) {
-        return new PipMediaController(context);
+    static PipMediaController providePipMediaController(Context context,
+            @ShellMainThread Handler mainHandler) {
+        return new PipMediaController(context, mainHandler);
     }
 
     @WMSingleton
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 8105250..12a3b5d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -19,6 +19,7 @@
 import android.animation.AnimationHandler;
 import android.app.ActivityTaskManager;
 import android.content.Context;
+import android.os.Handler;
 import android.view.IWindowManager;
 
 import com.android.systemui.dagger.WMSingleton;
@@ -125,11 +126,15 @@
         return new PipBoundsAlgorithm(context, pipBoundsState);
     }
 
+    // Handler is used by Icon.loadDrawableAsync
     @WMSingleton
     @Provides
     static PhonePipMenuController providesPipPhoneMenuController(Context context,
-            PipMediaController pipMediaController, SystemWindows systemWindows) {
-        return new PhonePipMenuController(context, pipMediaController, systemWindows);
+            PipMediaController pipMediaController, SystemWindows systemWindows,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return new PhonePipMenuController(context, pipMediaController, systemWindows,
+                mainExecutor, mainHandler);
     }
 
     @WMSingleton
@@ -154,9 +159,10 @@
             PhonePipMenuController menuPhoneController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
-            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
+            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
+            @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
                 menuPhoneController, pipSurfaceTransactionHelper, splitScreenOptional,
-                displayController, pipUiEventLogger, shellTaskOrganizer);
+                displayController, pipUiEventLogger, shellTaskOrganizer, 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 ed3cf9a..dd145e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -90,6 +91,8 @@
     private WindowManager mWindowManager;
     @Mock
     private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private ScrimController mScrimController;
 
     private FakeSettings mSystemSettings;
     private FakeExecutor mFgExecutor;
@@ -130,7 +133,8 @@
                 mWindowManager,
                 mSystemSettings,
                 mStatusBarStateController,
-                mFgExecutor);
+                mFgExecutor,
+                mScrimController);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
 
@@ -245,4 +249,10 @@
         // THEN the scrim and dot is hidden
         verify(mUdfpsView).hideScrimAndDot();
     }
+
+    @Test
+    public void registersViewForCallbacks() throws RemoteException {
+        verify(mStatusBarStateController).addCallback(mUdfpsView);
+        verify(mScrimController).addScrimChangedListener(mUdfpsView);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 8b86403..4078d4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -63,7 +63,6 @@
     @Mock ScreenLifecycle mScreenLifecycle;
     @Mock SysUiState mSysUiState;
     @Mock Pip mPip;
-    @Mock PipTouchHandler mPipTouchHandler;
     @Mock LegacySplitScreen mLegacySplitScreen;
     @Mock OneHanded mOneHanded;
     @Mock HideDisplayCutout mHideDisplayCutout;
@@ -80,8 +79,6 @@
                 Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
                 mKeyguardUpdateMonitor, mNavigationModeController,
                 mScreenLifecycle, mSysUiState, mProtoTracer, mSysUiMainExecutor);
-
-        when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
     }
 
     @Test
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 2f3ad19..6de227e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -87,6 +87,7 @@
         ":storaged_aidl",
         ":vold_aidl",
         ":platform-compat-config",
+        ":platform-compat-overrides",
         ":display-device-config",
         ":cec-config",
         ":device-state-config",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 9aadcf8..74a6e07 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -133,6 +133,7 @@
 import android.net.UidRange;
 import android.net.UidRangeParcel;
 import android.net.Uri;
+import android.net.VpnInfo;
 import android.net.VpnManager;
 import android.net.VpnService;
 import android.net.metrics.INetdEventListener;
@@ -184,7 +185,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.AsyncChannel;
@@ -1458,9 +1458,8 @@
             return;
         }
         final String action = blocked ? "BLOCKED" : "UNBLOCKED";
-        final NetworkRequest satisfiedRequest = nri.getSatisfiedRequest();
-        final int requestId =  satisfiedRequest != null
-                ? satisfiedRequest.requestId : nri.mRequests.get(0).requestId;
+        final int requestId = nri.getActiveRequest() != null
+                ? nri.getActiveRequest().requestId : nri.mRequests.get(0).requestId;
         mNetworkInfoBlockingLogs.log(String.format(
                 "%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId()));
     }
@@ -2730,7 +2729,7 @@
     @VisibleForTesting
     NetworkRequestInfo[] requestsSortedById() {
         NetworkRequestInfo[] requests = new NetworkRequestInfo[0];
-        requests = mNetworkRequests.values().toArray(requests);
+        requests = getNrisFromGlobalRequests().toArray(requests);
         // Sort the array based off the NRI containing the min requestId in its requests.
         Arrays.sort(requests,
                 Comparator.comparingInt(nri -> Collections.min(nri.mRequests,
@@ -3435,10 +3434,10 @@
         for (int i = 0; i < nai.numNetworkRequests(); i++) {
             NetworkRequest request = nai.requestAt(i);
             final NetworkRequestInfo nri = mNetworkRequests.get(request);
-            final NetworkAgentInfo currentNetwork = nri.mSatisfier;
+            final NetworkAgentInfo currentNetwork = nri.getSatisfier();
             if (currentNetwork != null
                     && currentNetwork.network.getNetId() == nai.network.getNetId()) {
-                nri.mSatisfier = null;
+                nri.setSatisfier(null, null);
                 sendUpdatedScoreToFactories(request, null);
             }
         }
@@ -3516,42 +3515,63 @@
         return null;
     }
 
-    private void handleRegisterNetworkRequestWithIntent(Message msg) {
+    private void handleRegisterNetworkRequestWithIntent(@NonNull final Message msg) {
         final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
-
-        NetworkRequestInfo existingRequest = findExistingNetworkRequestInfo(nri.mPendingIntent);
+        // handleRegisterNetworkRequestWithIntent() doesn't apply to multilayer requests.
+        ensureNotMultilayerRequest(nri, "handleRegisterNetworkRequestWithIntent");
+        final NetworkRequestInfo existingRequest =
+                findExistingNetworkRequestInfo(nri.mPendingIntent);
         if (existingRequest != null) { // remove the existing request.
-            if (DBG) log("Replacing " + existingRequest.request + " with "
-                    + nri.request + " because their intents matched.");
-            handleReleaseNetworkRequest(existingRequest.request, getCallingUid(),
+            if (DBG) {
+                log("Replacing " + existingRequest.mRequests.get(0) + " with "
+                        + nri.mRequests.get(0) + " because their intents matched.");
+            }
+            handleReleaseNetworkRequest(existingRequest.mRequests.get(0), getCallingUid(),
                     /* callOnUnavailable */ false);
         }
         handleRegisterNetworkRequest(nri);
     }
 
-    private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
+    private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) {
         ensureRunningOnConnectivityServiceThread();
-        mNetworkRequests.put(nri.request, nri);
         mNetworkRequestInfoLogs.log("REGISTER " + nri);
-        if (nri.request.isListen()) {
-            for (NetworkAgentInfo network : mNetworkAgentInfos) {
-                if (nri.request.networkCapabilities.hasSignalStrength() &&
-                        network.satisfiesImmutableCapabilitiesOf(nri.request)) {
-                    updateSignalStrengthThresholds(network, "REGISTER", nri.request);
+        for (final NetworkRequest req : nri.mRequests) {
+            mNetworkRequests.put(req, nri);
+            if (req.isListen()) {
+                for (final NetworkAgentInfo network : mNetworkAgentInfos) {
+                    if (req.networkCapabilities.hasSignalStrength()
+                            && network.satisfiesImmutableCapabilitiesOf(req)) {
+                        updateSignalStrengthThresholds(network, "REGISTER", req);
+                    }
                 }
             }
         }
         rematchAllNetworksAndRequests();
-        if (nri.request.isRequest() && nri.mSatisfier == null) {
-            sendUpdatedScoreToFactories(nri.request, null);
+        // If an active request exists, return as its score has already been sent if needed.
+        if (null != nri.getActiveRequest()) {
+            return;
+        }
+
+        // As this request was not satisfied on rematch and thus never had any scores sent to the
+        // factories, send null now for each request of type REQUEST.
+        for (final NetworkRequest req : nri.mRequests) {
+            if (!req.isRequest()) {
+                continue;
+            }
+            sendUpdatedScoreToFactories(req, null);
         }
     }
 
-    private void handleReleaseNetworkRequestWithIntent(PendingIntent pendingIntent,
-            int callingUid) {
-        NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
+    private void handleReleaseNetworkRequestWithIntent(@NonNull final PendingIntent pendingIntent,
+            final int callingUid) {
+        final NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
         if (nri != null) {
-            handleReleaseNetworkRequest(nri.request, callingUid, /* callOnUnavailable */ false);
+            // handleReleaseNetworkRequestWithIntent() paths don't apply to multilayer requests.
+            ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequestWithIntent");
+            handleReleaseNetworkRequest(
+                    nri.mRequests.get(0),
+                    callingUid,
+                    /* callOnUnavailable */ false);
         }
     }
 
@@ -3605,6 +3625,11 @@
             return false;
         }
         for (final NetworkRequest req : nri.mRequests) {
+            // This multilayer listen request is satisfied therefore no further requests need to be
+            // evaluated deeming this network not a potential satisfier.
+            if (req.isListen() && nri.getActiveRequest() == req) {
+                return false;
+            }
             // As non-multilayer listen requests have already returned, the below would only happen
             // for a multilayer request therefore continue to the next request if available.
             if (req.isListen()) {
@@ -3625,7 +3650,7 @@
                         // 2. Unvalidated WiFi will not be reaped when validated cellular
                         //    is currently satisfying the request.  This is desirable when
                         //    WiFi ends up validating and out scoring cellular.
-                        || nri.mSatisfier.getCurrentScore()
+                        || nri.getSatisfier().getCurrentScore()
                         < candidate.getCurrentScoreAsValidated();
                 return isNetworkNeeded;
             }
@@ -3650,30 +3675,45 @@
         return nri;
     }
 
-    private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) {
-        ensureRunningOnConnectivityServiceThread();
-        if (mNetworkRequests.get(nri.request) == null) {
-            return;
+    private void ensureNotMultilayerRequest(@NonNull final NetworkRequestInfo nri,
+            final String callingMethod) {
+        if (nri.isMultilayerRequest()) {
+            throw new IllegalStateException(
+                    callingMethod + " does not support multilayer requests.");
         }
-        if (nri.mSatisfier != null) {
-            return;
-        }
-        if (VDBG || (DBG && nri.request.isRequest())) {
-            log("releasing " + nri.request + " (timeout)");
-        }
-        handleRemoveNetworkRequest(nri);
-        callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
     }
 
-    private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid,
-            boolean callOnUnavailable) {
+    private void handleTimedOutNetworkRequest(@NonNull final NetworkRequestInfo nri) {
+        ensureRunningOnConnectivityServiceThread();
+        // handleTimedOutNetworkRequest() is part of the requestNetwork() flow which works off of a
+        // single NetworkRequest and thus does not apply to multilayer requests.
+        ensureNotMultilayerRequest(nri, "handleTimedOutNetworkRequest");
+        if (mNetworkRequests.get(nri.mRequests.get(0)) == null) {
+            return;
+        }
+        if (nri.getSatisfier() != null) {
+            return;
+        }
+        if (VDBG || (DBG && nri.mRequests.get(0).isRequest())) {
+            log("releasing " + nri.mRequests.get(0) + " (timeout)");
+        }
+        handleRemoveNetworkRequest(nri);
+        callCallbackForRequest(
+                nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
+    }
+
+    private void handleReleaseNetworkRequest(@NonNull final NetworkRequest request,
+            final int callingUid,
+            final boolean callOnUnavailable) {
         final NetworkRequestInfo nri =
                 getNriForAppRequest(request, callingUid, "release NetworkRequest");
         if (nri == null) {
             return;
         }
-        if (VDBG || (DBG && nri.request.isRequest())) {
-            log("releasing " + nri.request + " (release request)");
+        // handleReleaseNetworkRequest() paths don't apply to multilayer requests.
+        ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequest");
+        if (VDBG || (DBG && request.isRequest())) {
+            log("releasing " + request + " (release request)");
         }
         handleRemoveNetworkRequest(nri);
         if (callOnUnavailable) {
@@ -3681,42 +3721,88 @@
         }
     }
 
-    private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) {
+    private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) {
         ensureRunningOnConnectivityServiceThread();
 
         nri.unlinkDeathRecipient();
-        mNetworkRequests.remove(nri.request);
-
+        for (final NetworkRequest req : nri.mRequests) {
+            mNetworkRequests.remove(req);
+            if (req.isListen()) {
+                removeListenRequestFromNetworks(req);
+            }
+        }
         mNetworkRequestCounter.decrementCount(nri.mUid);
-
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
-        if (nri.request.isRequest()) {
-            boolean wasKept = false;
-            final NetworkAgentInfo nai = nri.mSatisfier;
-            if (nai != null) {
-                boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
-                nai.removeRequest(nri.request.requestId);
-                if (VDBG || DDBG) {
-                    log(" Removing from current network " + nai.toShortString()
-                            + ", leaving " + nai.numNetworkRequests() + " requests.");
-                }
-                // If there are still lingered requests on this network, don't tear it down,
-                // but resume lingering instead.
-                final long now = SystemClock.elapsedRealtime();
-                if (updateLingerState(nai, now)) {
-                    notifyNetworkLosing(nai, now);
-                }
-                if (unneeded(nai, UnneededFor.TEARDOWN)) {
-                    if (DBG) log("no live requests for " + nai.toShortString() + "; disconnecting");
-                    teardownUnneededNetwork(nai);
-                } else {
-                    wasKept = true;
-                }
-                nri.mSatisfier = null;
-                if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) {
-                    // Went from foreground to background.
-                    updateCapabilitiesForNetwork(nai);
-                }
+
+        if (null != nri.getActiveRequest()) {
+            if (nri.getActiveRequest().isRequest()) {
+                removeSatisfiedNetworkRequestFromNetwork(nri);
+            } else {
+                nri.setSatisfier(null, null);
+            }
+        }
+
+        cancelNpiRequests(nri);
+    }
+
+    private void cancelNpiRequests(@NonNull final NetworkRequestInfo nri) {
+        for (final NetworkRequest req : nri.mRequests) {
+            cancelNpiRequest(req);
+        }
+    }
+
+    private void cancelNpiRequest(@NonNull final NetworkRequest req) {
+        if (req.isRequest()) {
+            for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+                npi.cancelRequest(req);
+            }
+        }
+    }
+
+    private void removeListenRequestFromNetworks(@NonNull final NetworkRequest req) {
+        // listens don't have a singular affected Network. Check all networks to see
+        // if this listen request applies and remove it.
+        for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+            nai.removeRequest(req.requestId);
+            if (req.networkCapabilities.hasSignalStrength()
+                    && nai.satisfiesImmutableCapabilitiesOf(req)) {
+                updateSignalStrengthThresholds(nai, "RELEASE", req);
+            }
+        }
+    }
+
+    /**
+     * Remove a NetworkRequestInfo's satisfied request from its 'satisfier' (NetworkAgentInfo) and
+     * manage the necessary upkeep (linger, teardown networks, etc.) when doing so.
+     * @param nri the NetworkRequestInfo to disassociate from its current NetworkAgentInfo
+     */
+    private void removeSatisfiedNetworkRequestFromNetwork(@NonNull final NetworkRequestInfo nri) {
+        boolean wasKept = false;
+        final NetworkAgentInfo nai = nri.getSatisfier();
+        if (nai != null) {
+            final int requestLegacyType = nri.getActiveRequest().legacyType;
+            final boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
+            nai.removeRequest(nri.getActiveRequest().requestId);
+            if (VDBG || DDBG) {
+                log(" Removing from current network " + nai.toShortString()
+                        + ", leaving " + nai.numNetworkRequests() + " requests.");
+            }
+            // If there are still lingered requests on this network, don't tear it down,
+            // but resume lingering instead.
+            final long now = SystemClock.elapsedRealtime();
+            if (updateLingerState(nai, now)) {
+                notifyNetworkLosing(nai, now);
+            }
+            if (unneeded(nai, UnneededFor.TEARDOWN)) {
+                if (DBG) log("no live requests for " + nai.toShortString() + "; disconnecting");
+                teardownUnneededNetwork(nai);
+            } else {
+                wasKept = true;
+            }
+            nri.setSatisfier(null, null);
+            if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) {
+                // Went from foreground to background.
+                updateCapabilitiesForNetwork(nai);
             }
 
             // Maintain the illusion.  When this request arrived, we might have pretended
@@ -3724,15 +3810,15 @@
             // connected.  Now that this request has gone away, we might have to pretend
             // that the network disconnected.  LegacyTypeTracker will generate that
             // phantom disconnect for this type.
-            if (nri.request.legacyType != TYPE_NONE && nai != null) {
+            if (requestLegacyType != TYPE_NONE) {
                 boolean doRemove = true;
                 if (wasKept) {
                     // check if any of the remaining requests for this network are for the
                     // same legacy type - if so, don't remove the nai
                     for (int i = 0; i < nai.numNetworkRequests(); i++) {
                         NetworkRequest otherRequest = nai.requestAt(i);
-                        if (otherRequest.legacyType == nri.request.legacyType &&
-                                otherRequest.isRequest()) {
+                        if (otherRequest.legacyType == requestLegacyType
+                                && otherRequest.isRequest()) {
                             if (DBG) log(" still have other legacy request - leaving");
                             doRemove = false;
                         }
@@ -3740,21 +3826,7 @@
                 }
 
                 if (doRemove) {
-                    mLegacyTypeTracker.remove(nri.request.legacyType, nai, false);
-                }
-            }
-
-            for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
-                npi.cancelRequest(nri.request);
-            }
-        } else {
-            // listens don't have a singular affectedNetwork.  Check all networks to see
-            // if this listen request applies and remove it.
-            for (NetworkAgentInfo nai : mNetworkAgentInfos) {
-                nai.removeRequest(nri.request.requestId);
-                if (nri.request.networkCapabilities.hasSignalStrength() &&
-                        nai.satisfiesImmutableCapabilitiesOf(nri.request)) {
-                    updateSignalStrengthThresholds(nai, "RELEASE", nri.request);
+                    mLegacyTypeTracker.remove(requestLegacyType, nai, false);
                 }
             }
         }
@@ -4832,16 +4904,14 @@
 
         if (interfaces.isEmpty()) return null;
 
-        VpnInfo info = new VpnInfo();
-        info.ownerUid = nai.networkCapabilities.getOwnerUid();
-        info.vpnIface = nai.linkProperties.getInterfaceName();
         // Must be non-null or NetworkStatsService will crash.
         // Cannot happen in production code because Vpn only registers the NetworkAgent after the
         // tun or ipsec interface is created.
-        if (info.vpnIface == null) return null;
-        info.underlyingIfaces = interfaces.toArray(new String[0]);
+        if (nai.linkProperties.getInterfaceName() == null) return null;
 
-        return info;
+        return new VpnInfo(nai.networkCapabilities.getOwnerUid(),
+                nai.linkProperties.getInterfaceName(),
+                interfaces.toArray(new String[0]));
     }
 
     /**
@@ -5417,18 +5487,38 @@
 
     /**
      * Tracks info about the requester.
-     * Also used to notice when the calling process dies so we can self-expire
+     * Also used to notice when the calling process dies so as to self-expire
      */
     @VisibleForTesting
     protected class NetworkRequestInfo implements IBinder.DeathRecipient {
         final List<NetworkRequest> mRequests;
-        final NetworkRequest request;
+
+        // mSatisfier and mActiveRequest rely on one another therefore set them together.
+        void setSatisfier(
+                @Nullable final NetworkAgentInfo satisfier,
+                @Nullable final NetworkRequest activeRequest) {
+            mSatisfier = satisfier;
+            mActiveRequest = activeRequest;
+        }
 
         // The network currently satisfying this request, or null if none. Must only be touched
         // on the handler thread. This only makes sense for network requests and not for listens,
         // as defined by NetworkRequest#isRequest(). For listens, this is always null.
         @Nullable
-        NetworkAgentInfo mSatisfier;
+        private NetworkAgentInfo mSatisfier;
+        NetworkAgentInfo getSatisfier() {
+            return mSatisfier;
+        }
+
+        // The request in mRequests assigned to a network agent. This is null if none of the
+        // requests in mRequests can be satisfied. This member has the constraint of only being
+        // accessible on the handler thread.
+        @Nullable
+        private NetworkRequest mActiveRequest;
+        NetworkRequest getActiveRequest() {
+            return mActiveRequest;
+        }
+
         final PendingIntent mPendingIntent;
         boolean mPendingIntentSent;
         private final IBinder mBinder;
@@ -5437,7 +5527,6 @@
         final Messenger messenger;
 
         NetworkRequestInfo(NetworkRequest r, PendingIntent pi) {
-            request = r;
             mRequests = initializeRequests(r);
             ensureAllNetworkRequestsHaveType(mRequests);
             mPendingIntent = pi;
@@ -5451,7 +5540,6 @@
         NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder) {
             super();
             messenger = m;
-            request = r;
             mRequests = initializeRequests(r);
             ensureAllNetworkRequestsHaveType(mRequests);
             mBinder = binder;
@@ -5481,20 +5569,6 @@
             return Collections.unmodifiableList(tempRequests);
         }
 
-        private NetworkRequest getSatisfiedRequest() {
-            if (mSatisfier == null) {
-                return null;
-            }
-
-            for (NetworkRequest req : mRequests) {
-                if (mSatisfier.isSatisfyingRequest(req.requestId)) {
-                    return req;
-                }
-            }
-
-            return null;
-        }
-
         void unlinkDeathRecipient() {
             if (mBinder != null) {
                 mBinder.unlinkToDeath(this, 0);
@@ -5541,6 +5615,10 @@
     private int[] getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) {
         final SortedSet<Integer> thresholds = new TreeSet<>();
         synchronized (nai) {
+            // mNetworkRequests may contain the same value multiple times in case of
+            // multilayer requests. It won't matter in this case because the thresholds
+            // will then be the same and be deduplicated as they enter the `thresholds` set.
+            // TODO : have mNetworkRequests be a Set<NetworkRequestInfo> or the like.
             for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
                 for (final NetworkRequest req : nri.mRequests) {
                     if (req.networkCapabilities.hasSignalStrength()
@@ -5916,13 +5994,19 @@
     }
 
     @Override
-    public void declareNetworkRequestUnfulfillable(NetworkRequest request) {
+    public void declareNetworkRequestUnfulfillable(@NonNull final NetworkRequest request) {
         if (request.hasTransport(TRANSPORT_TEST)) {
             enforceNetworkFactoryOrTestNetworksPermission();
         } else {
             enforceNetworkFactoryPermission();
         }
-        mHandler.post(() -> handleReleaseNetworkRequest(request, mDeps.getCallingUid(), true));
+        final NetworkRequestInfo nri = mNetworkRequests.get(request);
+        if (nri != null) {
+            // declareNetworkRequestUnfulfillable() paths don't apply to multilayer requests.
+            ensureNotMultilayerRequest(nri, "declareNetworkRequestUnfulfillable");
+            mHandler.post(() -> handleReleaseNetworkRequest(
+                    nri.mRequests.get(0), mDeps.getCallingUid(), true));
+        }
     }
 
     // NOTE: Accessed on multiple threads, must be synchronized on itself.
@@ -6847,6 +6931,39 @@
         }
     }
 
+    private void sendUpdatedScoreToFactories(
+            @NonNull final NetworkReassignment.RequestReassignment event) {
+        // If a request of type REQUEST is now being satisfied by a new network.
+        if (null != event.mNewNetworkRequest && event.mNewNetworkRequest.isRequest()) {
+            sendUpdatedScoreToFactories(event.mNewNetworkRequest, event.mNewNetwork);
+        }
+
+        // If a previously satisfied request of type REQUEST is no longer being satisfied.
+        if (null != event.mOldNetworkRequest && event.mOldNetworkRequest.isRequest()
+                && event.mOldNetworkRequest != event.mNewNetworkRequest) {
+            sendUpdatedScoreToFactories(event.mOldNetworkRequest, null);
+        }
+
+        cancelMultilayerLowerPriorityNpiRequests(event.mNetworkRequestInfo);
+    }
+
+    /**
+     *  Cancel with all NPIs the given NRI's multilayer requests that are a lower priority than
+     *  its currently satisfied active request.
+     * @param nri the NRI to cancel lower priority requests for.
+     */
+    private void cancelMultilayerLowerPriorityNpiRequests(
+            @NonNull final NetworkRequestInfo nri) {
+        if (!nri.isMultilayerRequest() || null == nri.mActiveRequest) {
+            return;
+        }
+
+        final int indexOfNewRequest = nri.mRequests.indexOf(nri.mActiveRequest);
+        for (int i = indexOfNewRequest + 1; i < nri.mRequests.size(); i++) {
+            cancelNpiRequest(nri.mRequests.get(i));
+        }
+    }
+
     private void sendUpdatedScoreToFactories(@NonNull NetworkRequest networkRequest,
             @Nullable NetworkAgentInfo nai) {
         final int score;
@@ -6867,21 +6984,35 @@
     }
 
     /** Sends all current NetworkRequests to the specified factory. */
-    private void sendAllRequestsToProvider(NetworkProviderInfo npi) {
+    private void sendAllRequestsToProvider(@NonNull final NetworkProviderInfo npi) {
         ensureRunningOnConnectivityServiceThread();
-        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-            if (nri.request.isListen()) continue;
-            NetworkAgentInfo nai = nri.mSatisfier;
-            final int score;
-            final int serial;
-            if (nai != null) {
-                score = nai.getCurrentScore();
-                serial = nai.factorySerialNumber;
-            } else {
-                score = 0;
-                serial = NetworkProvider.ID_NONE;
+        for (final NetworkRequestInfo nri : getNrisFromGlobalRequests()) {
+            for (final NetworkRequest req : nri.mRequests) {
+                if (req.isListen() && nri.getActiveRequest() == req) {
+                    break;
+                }
+                if (req.isListen()) {
+                    continue;
+                }
+                // Only set the nai for the request it is satisfying.
+                final NetworkAgentInfo nai =
+                        nri.getActiveRequest() == req ? nri.getSatisfier() : null;
+                final int score;
+                final int serial;
+                if (null != nai) {
+                    score = nai.getCurrentScore();
+                    serial = nai.factorySerialNumber;
+                } else {
+                    score = 0;
+                    serial = NetworkProvider.ID_NONE;
+                }
+                npi.requestNetwork(req, score, serial);
+                // For multilayer requests, don't send lower priority requests if a higher priority
+                // request is already satisfied.
+                if (null != nai) {
+                    break;
+                }
             }
-            npi.requestNetwork(nri.request, score, serial);
         }
     }
 
@@ -6890,7 +7021,12 @@
         if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE && !nri.mPendingIntentSent) {
             Intent intent = new Intent();
             intent.putExtra(ConnectivityManager.EXTRA_NETWORK, networkAgent.network);
-            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, nri.request);
+            // If apps could file multi-layer requests with PendingIntents, they'd need to know
+            // which of the layer is satisfied alongside with some ID for the request. Hence, if
+            // such an API is ever implemented, there is no doubt the right request to send in
+            // EXTRA_NETWORK_REQUEST is mActiveRequest, and whatever ID would be added would need to
+            // be sent as a separate extra.
+            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, nri.getActiveRequest());
             nri.mPendingIntentSent = true;
             sendIntent(nri.mPendingIntent, intent);
         }
@@ -6920,8 +7056,9 @@
         releasePendingNetworkRequestWithDelay(pendingIntent);
     }
 
-    private void callCallbackForRequest(NetworkRequestInfo nri,
-            NetworkAgentInfo networkAgent, int notificationType, int arg1) {
+    private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri,
+            @NonNull final NetworkAgentInfo networkAgent, final int notificationType,
+            final int arg1) {
         if (nri.messenger == null) {
             // Default request has no msgr. Also prevents callbacks from being invoked for
             // NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks
@@ -6929,8 +7066,14 @@
             return;
         }
         Bundle bundle = new Bundle();
+        // In the case of multi-layer NRIs, the first request is not necessarily the one that
+        // is satisfied. This is vexing, but the ConnectivityManager code that receives this
+        // callback is only using the request as a token to identify the callback, so it doesn't
+        // matter too much at this point as long as the callback can be found.
+        // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
         // TODO: check if defensive copies of data is needed.
-        putParcelable(bundle, new NetworkRequest(nri.request));
+        final NetworkRequest nrForCallback = new NetworkRequest(nri.mRequests.get(0));
+        putParcelable(bundle, nrForCallback);
         Message msg = Message.obtain();
         if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
             putParcelable(bundle, networkAgent.network);
@@ -6943,7 +7086,7 @@
                 putParcelable(
                         bundle,
                         createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-                                nc, nri.mUid, nri.request.getRequestorPackageName()));
+                                nc, nri.mUid, nrForCallback.getRequestorPackageName()));
                 putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
                         networkAgent.linkProperties, nri.mPid, nri.mUid));
                 // For this notification, arg1 contains the blocked status.
@@ -6962,7 +7105,7 @@
                 putParcelable(
                         bundle,
                         createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-                                netCap, nri.mUid, nri.request.getRequestorPackageName()));
+                                netCap, nri.mUid, nrForCallback.getRequestorPackageName()));
                 break;
             }
             case ConnectivityManager.CALLBACK_IP_CHANGED: {
@@ -6981,12 +7124,12 @@
         try {
             if (VDBG) {
                 String notification = ConnectivityManager.getCallbackName(notificationType);
-                log("sending notification " + notification + " for " + nri.request);
+                log("sending notification " + notification + " for " + nrForCallback);
             }
             nri.messenger.send(msg);
         } catch (RemoteException e) {
             // may occur naturally in the race of binder death.
-            loge("RemoteException caught trying to send a callback msg for " + nri.request);
+            loge("RemoteException caught trying to send a callback msg for " + nrForCallback);
         }
     }
 
@@ -7062,19 +7205,25 @@
     }
 
     private void processNewlyLostListenRequests(@NonNull final NetworkAgentInfo nai) {
-        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-            NetworkRequest nr = nri.request;
+        for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
+            if (nri.isMultilayerRequest()) {
+                continue;
+            }
+            final NetworkRequest nr = nri.mRequests.get(0);
             if (!nr.isListen()) continue;
             if (nai.isSatisfyingRequest(nr.requestId) && !nai.satisfies(nr)) {
-                nai.removeRequest(nri.request.requestId);
+                nai.removeRequest(nr.requestId);
                 callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0);
             }
         }
     }
 
     private void processNewlySatisfiedListenRequests(@NonNull final NetworkAgentInfo nai) {
-        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-            NetworkRequest nr = nri.request;
+        for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
+            if (nri.isMultilayerRequest()) {
+                continue;
+            }
+            final NetworkRequest nr = nri.mRequests.get(0);
             if (!nr.isListen()) continue;
             if (nai.satisfies(nr) && !nai.isSatisfyingRequest(nr.requestId)) {
                 nai.addRequest(nr);
@@ -7086,19 +7235,25 @@
     // An accumulator class to gather the list of changes that result from a rematch.
     private static class NetworkReassignment {
         static class RequestReassignment {
-            @NonNull public final NetworkRequestInfo mRequest;
+            @NonNull public final NetworkRequestInfo mNetworkRequestInfo;
+            @NonNull public final NetworkRequest mOldNetworkRequest;
+            @NonNull public final NetworkRequest mNewNetworkRequest;
             @Nullable public final NetworkAgentInfo mOldNetwork;
             @Nullable public final NetworkAgentInfo mNewNetwork;
-            RequestReassignment(@NonNull final NetworkRequestInfo request,
+            RequestReassignment(@NonNull final NetworkRequestInfo networkRequestInfo,
+                    @NonNull final NetworkRequest oldNetworkRequest,
+                    @NonNull final NetworkRequest newNetworkRequest,
                     @Nullable final NetworkAgentInfo oldNetwork,
                     @Nullable final NetworkAgentInfo newNetwork) {
-                mRequest = request;
+                mNetworkRequestInfo = networkRequestInfo;
+                mOldNetworkRequest = oldNetworkRequest;
+                mNewNetworkRequest = newNetworkRequest;
                 mOldNetwork = oldNetwork;
                 mNewNetwork = newNetwork;
             }
 
             public String toString() {
-                return mRequest.mRequests.get(0).requestId + " : "
+                return mNetworkRequestInfo.mRequests.get(0).requestId + " : "
                         + (null != mOldNetwork ? mOldNetwork.network.getNetId() : "null")
                         + " → " + (null != mNewNetwork ? mNewNetwork.network.getNetId() : "null");
             }
@@ -7116,7 +7271,7 @@
                 // sure this stays true, but without imposing this expensive check on all
                 // reassignments on all user devices.
                 for (final RequestReassignment existing : mReassignments) {
-                    if (existing.mRequest.equals(reassignment.mRequest)) {
+                    if (existing.mNetworkRequestInfo.equals(reassignment.mNetworkRequestInfo)) {
                         throw new IllegalStateException("Trying to reassign ["
                                 + reassignment + "] but already have ["
                                 + existing + "]");
@@ -7131,7 +7286,7 @@
         @Nullable
         private RequestReassignment getReassignment(@NonNull final NetworkRequestInfo nri) {
             for (final RequestReassignment event : getRequestReassignments()) {
-                if (nri == event.mRequest) return event;
+                if (nri == event.mNetworkRequestInfo) return event;
             }
             return null;
         }
@@ -7158,6 +7313,8 @@
     }
 
     private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri,
+            @NonNull final NetworkRequest previousRequest,
+            @NonNull final NetworkRequest newRequest,
             @Nullable final NetworkAgentInfo previousSatisfier,
             @Nullable final NetworkAgentInfo newSatisfier,
             final long now) {
@@ -7167,58 +7324,98 @@
                 if (VDBG || DDBG) {
                     log("   accepting network in place of " + previousSatisfier.toShortString());
                 }
-                previousSatisfier.removeRequest(nri.request.requestId);
-                previousSatisfier.lingerRequest(nri.request.requestId, now, mLingerDelayMs);
+                previousSatisfier.removeRequest(previousRequest.requestId);
+                previousSatisfier.lingerRequest(previousRequest.requestId, now, mLingerDelayMs);
             } else {
                 if (VDBG || DDBG) log("   accepting network in place of null");
             }
-            newSatisfier.unlingerRequest(nri.request.requestId);
-            if (!newSatisfier.addRequest(nri.request)) {
+            newSatisfier.unlingerRequest(newRequest.requestId);
+            if (!newSatisfier.addRequest(newRequest)) {
                 Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has "
-                        + nri.request);
+                        + newRequest);
             }
         } else {
             if (DBG) {
                 log("Network " + previousSatisfier.toShortString() + " stopped satisfying"
-                        + " request " + nri.request.requestId);
+                        + " request " + previousRequest.requestId);
             }
-            previousSatisfier.removeRequest(nri.request.requestId);
+            previousSatisfier.removeRequest(previousRequest.requestId);
         }
-        nri.mSatisfier = newSatisfier;
+        nri.setSatisfier(newSatisfier, newRequest);
     }
 
+    /**
+     * This function is triggered when something can affect what network should satisfy what
+     * request, and it computes the network reassignment from the passed collection of requests to
+     * network match to the one that the system should now have. That data is encoded in an
+     * object that is a list of changes, each of them having an NRI, and old satisfier, and a new
+     * satisfier.
+     *
+     * After the reassignment is computed, it is applied to the state objects.
+     *
+     * @param networkRequests the nri objects to evaluate for possible network reassignment
+     * @return NetworkReassignment listing of proposed network assignment changes
+     */
     @NonNull
-    private NetworkReassignment computeNetworkReassignment() {
-        ensureRunningOnConnectivityServiceThread();
+    private NetworkReassignment computeNetworkReassignment(
+            @NonNull final Collection<NetworkRequestInfo> networkRequests) {
         final NetworkReassignment changes = new NetworkReassignment();
 
         // Gather the list of all relevant agents and sort them by score.
         final ArrayList<NetworkAgentInfo> nais = new ArrayList<>();
         for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
-            if (!nai.everConnected) continue;
+            if (!nai.everConnected) {
+                continue;
+            }
             nais.add(nai);
         }
 
-        for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
-            if (nri.request.isListen()) continue;
-            final NetworkAgentInfo bestNetwork = mNetworkRanker.getBestNetwork(nri.request, nais);
+        for (final NetworkRequestInfo nri : networkRequests) {
+            // Non-multilayer listen requests can be ignored.
+            if (!nri.isMultilayerRequest() && nri.mRequests.get(0).isListen()) {
+                continue;
+            }
+            NetworkAgentInfo bestNetwork = null;
+            NetworkRequest bestRequest = null;
+            for (final NetworkRequest req : nri.mRequests) {
+                bestNetwork = mNetworkRanker.getBestNetwork(req, nais);
+                // Stop evaluating as the highest possible priority request is satisfied.
+                if (null != bestNetwork) {
+                    bestRequest = req;
+                    break;
+                }
+            }
             if (bestNetwork != nri.mSatisfier) {
                 // bestNetwork may be null if no network can satisfy this request.
                 changes.addRequestReassignment(new NetworkReassignment.RequestReassignment(
-                        nri, nri.mSatisfier, bestNetwork));
+                        nri, nri.mActiveRequest, bestRequest, nri.getSatisfier(), bestNetwork));
             }
         }
         return changes;
     }
 
+    private Set<NetworkRequestInfo> getNrisFromGlobalRequests() {
+        return new HashSet<>(mNetworkRequests.values());
+    }
+
     /**
-     * Attempt to rematch all Networks with NetworkRequests.  This may result in Networks
+     * Attempt to rematch all Networks with all NetworkRequests.  This may result in Networks
      * being disconnected.
      */
     private void rematchAllNetworksAndRequests() {
+        rematchNetworksAndRequests(getNrisFromGlobalRequests());
+    }
+
+    /**
+     * Attempt to rematch all Networks with given NetworkRequests.  This may result in Networks
+     * being disconnected.
+     */
+    private void rematchNetworksAndRequests(
+            @NonNull final Set<NetworkRequestInfo> networkRequests) {
+        ensureRunningOnConnectivityServiceThread();
         // TODO: This may be slow, and should be optimized.
         final long now = SystemClock.elapsedRealtime();
-        final NetworkReassignment changes = computeNetworkReassignment();
+        final NetworkReassignment changes = computeNetworkReassignment(networkRequests);
         if (VDBG || DDBG) {
             log(changes.debugString());
         } else if (DBG) {
@@ -7243,8 +7440,10 @@
         // the linger status.
         for (final NetworkReassignment.RequestReassignment event :
                 changes.getRequestReassignments()) {
-            updateSatisfiersForRematchRequest(event.mRequest, event.mOldNetwork,
-                    event.mNewNetwork, now);
+            updateSatisfiersForRematchRequest(event.mNetworkRequestInfo,
+                    event.mOldNetworkRequest, event.mNewNetworkRequest,
+                    event.mOldNetwork, event.mNewNetwork,
+                    now);
         }
 
         final NetworkAgentInfo oldDefaultNetwork = getDefaultNetwork();
@@ -7296,12 +7495,12 @@
             // trying to connect if they know they cannot match it.
             // TODO - this could get expensive if there are a lot of outstanding requests for this
             // network. Think of a way to reduce this. Push netid->request mapping to each factory?
-            sendUpdatedScoreToFactories(event.mRequest.request, event.mNewNetwork);
+            sendUpdatedScoreToFactories(event);
 
             if (null != event.mNewNetwork) {
-                notifyNetworkAvailable(event.mNewNetwork, event.mRequest);
+                notifyNetworkAvailable(event.mNewNetwork, event.mNetworkRequestInfo);
             } else {
-                callCallbackForRequest(event.mRequest, event.mOldNetwork,
+                callCallbackForRequest(event.mNetworkRequestInfo, event.mOldNetwork,
                         ConnectivityManager.CALLBACK_LOST, 0);
             }
         }
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index f2b63a6..88ce220 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -22,7 +22,6 @@
 import android.gsi.GsiProgress;
 import android.gsi.IGsiService;
 import android.gsi.IGsiServiceCallback;
-import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -30,7 +29,7 @@
 import android.os.UserHandle;
 import android.os.image.IDynamicSystemService;
 import android.os.storage.StorageManager;
-import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
 import android.util.Slog;
 
 import java.io.File;
@@ -88,16 +87,17 @@
         String path = SystemProperties.get("os.aot.path");
         if (path.isEmpty()) {
             final int userId = UserHandle.myUserId();
-            final StorageVolume[] volumes =
-                    StorageManager.getVolumeList(userId, StorageManager.FLAG_FOR_WRITE);
-            for (StorageVolume volume : volumes) {
-                if (volume.isEmulated()) continue;
-                if (!volume.isRemovable()) continue;
-                if (!Environment.MEDIA_MOUNTED.equals(volume.getState())) continue;
-                File sdCard = volume.getPathFile();
-                if (sdCard.isDirectory()) {
-                    path = new File(sdCard, dsuSlot).getPath();
-                    break;
+            final StorageManager sm = mContext.getSystemService(StorageManager.class);
+            for (VolumeInfo volume : sm.getVolumes()) {
+                if (volume.getType() != volume.TYPE_PUBLIC) {
+                    continue;
+                }
+                if (!volume.isMountedWritable()) {
+                    continue;
+                }
+                File sd_internal = volume.getInternalPathForUser(userId);
+                if (sd_internal != null) {
+                    path = new File(sd_internal, dsuSlot).getPath();
                 }
             }
             if (path.isEmpty()) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index ba1eda9..efe82df 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -715,7 +715,7 @@
                 WatchdogDiagnostics.diagnoseCheckers(blockedCheckers);
                 Slog.w(TAG, "*** GOODBYE!");
                 if (!Build.IS_USER && isCrashLoopFound()
-                        && !WatchdogProperties.is_fatal_ignore().orElse(false)) {
+                        && !WatchdogProperties.should_ignore_fatal_count().orElse(false)) {
                     breakCrashLoop();
                 }
                 Process.killProcess(Process.myPid());
@@ -794,7 +794,7 @@
     private boolean isCrashLoopFound() {
         int fatalCount = WatchdogProperties.fatal_count().orElse(0);
         long fatalWindowMs = TimeUnit.SECONDS.toMillis(
-                WatchdogProperties.fatal_window_second().orElse(0));
+                WatchdogProperties.fatal_window_seconds().orElse(0));
         if (fatalCount == 0 || fatalWindowMs == 0) {
             if (fatalCount != fatalWindowMs) {
                 Slog.w(TAG, String.format("sysprops '%s' and '%s' should be set or unset together",
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index dd09a1c..b40df95 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -80,20 +80,22 @@
 
     // Phenotype sends int configurations and we map them to the strings we'll use on device,
     // preventing a weird string value entering the kernel.
+    private static final int COMPACT_ACTION_NONE = 0;
+    private static final int COMPACT_ACTION_FILE = 1;
+    private static final int COMPACT_ACTION_ANON = 2;
+    private static final int COMPACT_ACTION_FULL = 3;
+
+    private static final String COMPACT_ACTION_STRING[] = {"", "file", "anon", "all"};
+
+    // Keeps these flags in sync with services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
     private static final int COMPACT_ACTION_FILE_FLAG = 1;
     private static final int COMPACT_ACTION_ANON_FLAG = 2;
-    private static final int COMPACT_ACTION_FULL_FLAG = 3;
-    private static final int COMPACT_ACTION_NONE_FLAG = 4;
-    private static final String COMPACT_ACTION_NONE = "";
-    private static final String COMPACT_ACTION_FILE = "file";
-    private static final String COMPACT_ACTION_ANON = "anon";
-    private static final String COMPACT_ACTION_FULL = "all";
 
     // Defaults for phenotype flags.
     @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
     @VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = false;
-    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
-    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;
+    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE;
+    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL;
     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
@@ -406,6 +408,14 @@
     private native void compactSystem();
 
     /**
+     * Compacts a process or app
+     * @param pid pid of process to compact
+     * @param compactionFlags selects the compaction type as defined by COMPACT_ACTION_{TYPE}_FLAG
+     *         constants
+     */
+    static private native void compactProcess(int pid, int compactionFlags);
+
+    /**
      * Reads the flag value from DeviceConfig to determine whether app compaction
      * should be enabled, and starts the freeze/compaction thread if needed.
      */
@@ -706,18 +716,11 @@
 
     @VisibleForTesting
     static String compactActionIntToString(int action) {
-        switch(action) {
-            case COMPACT_ACTION_NONE_FLAG:
-                return COMPACT_ACTION_NONE;
-            case COMPACT_ACTION_FILE_FLAG:
-                return COMPACT_ACTION_FILE;
-            case COMPACT_ACTION_ANON_FLAG:
-                return COMPACT_ACTION_ANON;
-            case COMPACT_ACTION_FULL_FLAG:
-                return COMPACT_ACTION_FULL;
-            default:
-                return COMPACT_ACTION_NONE;
+        if (action < 0 || action >= COMPACT_ACTION_STRING.length) {
+            return "";
         }
+
+        return COMPACT_ACTION_STRING[action];
     }
 
     // This will ensure app will be out of the freezer for at least FREEZE_TIMEOUT_MS
@@ -950,11 +953,11 @@
                             action = mCompactActionFull;
                             break;
                         default:
-                            action = COMPACT_ACTION_NONE;
+                            action = COMPACT_ACTION_STRING[COMPACT_ACTION_NONE];
                             break;
                     }
 
-                    if (COMPACT_ACTION_NONE.equals(action)) {
+                    if (COMPACT_ACTION_STRING[COMPACT_ACTION_NONE].equals(action)) {
                         return;
                     }
 
@@ -978,7 +981,8 @@
                         return;
                     }
 
-                    if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) {
+                    if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])
+                            || action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
                         if (mFullAnonRssThrottleKb > 0L
                                 && anonRssBefore < mFullAnonRssThrottleKb) {
                             if (DEBUG_COMPACTION) {
@@ -1054,8 +1058,8 @@
                             proc.lastCompactTime = end;
                             proc.lastCompactAction = pendingAction;
                         }
-                        if (action.equals(COMPACT_ACTION_FULL)
-                                || action.equals(COMPACT_ACTION_ANON)) {
+                        if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])
+                                || action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
                             // Remove entry and insert again to update insertion order.
                             mLastCompactionStats.remove(pid);
                             mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
@@ -1229,8 +1233,12 @@
         // Compact process.
         @Override
         public void performCompaction(String action, int pid) throws IOException {
-            try (FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim")) {
-                fos.write(action.getBytes());
+            if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])) {
+                compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG);
+            } else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FILE])) {
+                compactProcess(pid, COMPACT_ACTION_FILE_FLAG);
+            } else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
+                compactProcess(pid, COMPACT_ACTION_ANON_FLAG);
             }
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index c86bfcb..271537a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -563,14 +563,26 @@
         final boolean isAuthenticating =
                 mCurrentOperation.mClientMonitor instanceof AuthenticationConsumer;
         final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
-        if (!isAuthenticating || !tokenMatches) {
-            Slog.w(getTag(), "Not cancelling authentication"
-                    + ", current operation : " + mCurrentOperation
-                    + ", tokenMatches: " + tokenMatches);
-            return;
-        }
 
-        cancelInternal(mCurrentOperation);
+        if (isAuthenticating && tokenMatches) {
+            Slog.d(getTag(), "Cancelling authentication: " + mCurrentOperation);
+            cancelInternal(mCurrentOperation);
+        } else if (!isAuthenticating) {
+            // Look through the current queue for all authentication clients for the specified
+            // token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
+            // all of them, instead of just the first one, since the API surface currently doesn't
+            // allow us to distinguish between multiple authentication requests from the same
+            // process. However, this generally does not happen anyway, and would be a class of
+            // bugs on its own.
+            for (Operation operation : mPendingOperations) {
+                if (operation.mClientMonitor instanceof AuthenticationConsumer
+                        && operation.mClientMonitor.getToken() == token) {
+                    Slog.d(getTag(), "Marking " + operation
+                            + " as STATE_WAITING_IN_QUEUE_CANCELING");
+                    operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+                }
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
index 01a620f..d092e86 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
@@ -16,8 +16,11 @@
 
 package com.android.server.biometrics.sensors.fingerprint;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -85,4 +88,33 @@
             Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
         }
     }
+
+    public static void onEnrollmentProgress(int sensorId, int remaining,
+            @Nullable IUdfpsOverlayController udfpsOverlayController) {
+        if (udfpsOverlayController == null) {
+            return;
+        }
+        try {
+            udfpsOverlayController.onEnrollmentProgress(sensorId, remaining);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception when sending onEnrollmentProgress", e);
+        }
+    }
+
+    public static void onEnrollmentHelp(int sensorId,
+            @Nullable IUdfpsOverlayController udfpsOverlayController) {
+        if (udfpsOverlayController == null) {
+            return;
+        }
+        try {
+            udfpsOverlayController.onEnrollmentHelp(sensorId);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception when sending onEnrollmentHelp", e);
+        }
+    }
+
+    public static boolean isValidAcquisitionMessage(@NonNull Context context,
+            int acquireInfo, int vendorCode) {
+        return FingerprintManager.getAcquiredString(context, acquireInfo, vendorCode) != null;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 0864c1a..08cc464 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -65,12 +65,23 @@
     public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
         super.onEnrollResult(identifier, remaining);
 
+        UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController);
+
         if (remaining == 0) {
             UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
         }
     }
 
     @Override
+    public void onAcquired(int acquiredInfo, int vendorCode) {
+        super.onAcquired(acquiredInfo, vendorCode);
+
+        if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
+            UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController);
+        }
+    }
+
+    @Override
     public void onError(int errorCode, int vendorCode) {
         super.onError(errorCode, vendorCode);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index acc575f..a4a8401 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -47,6 +47,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
@@ -397,7 +398,8 @@
         });
     }
 
-    private synchronized IBiometricsFingerprint getDaemon() {
+    @VisibleForTesting
+    synchronized IBiometricsFingerprint getDaemon() {
         if (mTestHalEnabled) {
             final TestHal testHal = new TestHal();
             testHal.setNotify(mHalResultController);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 8493af1..d927aa7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -107,12 +107,23 @@
     public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
         super.onEnrollResult(identifier, remaining);
 
+        UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController);
+
         if (remaining == 0) {
             UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
         }
     }
 
     @Override
+    public void onAcquired(int acquiredInfo, int vendorCode) {
+        super.onAcquired(acquiredInfo, vendorCode);
+
+        if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
+            UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController);
+        }
+    }
+
+    @Override
     public void onError(int errorCode, int vendorCode) {
         super.onError(errorCode, vendorCode);
 
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index 9ba957e..e3757df 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -23,8 +23,11 @@
 
 import com.android.internal.compat.CompatibilityChangeInfo;
 import com.android.server.compat.config.Change;
+import com.android.server.compat.overrides.ChangeOverrides;
+import com.android.server.compat.overrides.OverrideValue;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -253,6 +256,71 @@
         return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName);
     }
 
+    /**
+     * Checks whether a change has any package overrides.
+     * @return true if the change has at least one deferred override
+     */
+    boolean hasAnyPackageOverride() {
+        return mDeferredOverrides != null && !mDeferredOverrides.isEmpty();
+    }
+
+    /**
+     * Checks whether a change has any deferred overrides.
+     * @return true if the change has at least one deferred override
+     */
+    boolean hasAnyDeferredOverride() {
+        return mPackageOverrides != null && !mPackageOverrides.isEmpty();
+    }
+
+    void loadOverrides(ChangeOverrides changeOverrides) {
+        if (mDeferredOverrides == null) {
+            mDeferredOverrides = new HashMap<>();
+        }
+        mDeferredOverrides.clear();
+        for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
+            mDeferredOverrides.put(override.getPackageName(), override.getEnabled());
+        }
+
+        if (mPackageOverrides == null) {
+            mPackageOverrides = new HashMap<>();
+        }
+        mPackageOverrides.clear();
+        for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) {
+            mPackageOverrides.put(override.getPackageName(), override.getEnabled());
+        }
+    }
+
+    ChangeOverrides saveOverrides() {
+        if (!hasAnyDeferredOverride() && !hasAnyPackageOverride()) {
+            return null;
+        }
+        ChangeOverrides changeOverrides = new ChangeOverrides();
+        changeOverrides.setChangeId(getId());
+        ChangeOverrides.Deferred deferredOverrides = new ChangeOverrides.Deferred();
+        List<OverrideValue> deferredList = deferredOverrides.getOverrideValue();
+        if (mDeferredOverrides != null) {
+            for (Map.Entry<String, Boolean> entry : mDeferredOverrides.entrySet()) {
+                OverrideValue override = new OverrideValue();
+                override.setPackageName(entry.getKey());
+                override.setEnabled(entry.getValue());
+                deferredList.add(override);
+            }
+        }
+        changeOverrides.setDeferred(deferredOverrides);
+        ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated();
+        List<OverrideValue> validatedList = validatedOverrides.getOverrideValue();
+        if (mPackageOverrides != null) {
+            for (Map.Entry<String, Boolean> entry : mPackageOverrides.entrySet()) {
+                OverrideValue override = new OverrideValue();
+                override.setPackageName(entry.getKey());
+                override.setEnabled(entry.getValue());
+                validatedList.add(override);
+            }
+        }
+        changeOverrides.setValidated(validatedOverrides);
+        return changeOverrides;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("ChangeId(")
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 69686a2..6b77b9d 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -34,7 +34,10 @@
 import com.android.internal.compat.IOverrideValidator;
 import com.android.internal.compat.OverrideAllowedState;
 import com.android.server.compat.config.Change;
-import com.android.server.compat.config.XmlParser;
+import com.android.server.compat.config.Config;
+import com.android.server.compat.overrides.ChangeOverrides;
+import com.android.server.compat.overrides.Overrides;
+import com.android.server.compat.overrides.XmlWriter;
 import com.android.server.pm.ApexManager;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -60,11 +63,14 @@
 final class CompatConfig {
 
     private static final String TAG = "CompatConfig";
+    private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat";
+    private static final String OVERRIDES_FILE = "compat_framework_overrides.xml";
 
     @GuardedBy("mChanges")
     private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
 
     private final OverrideValidatorImpl mOverrideValidator;
+    private File mOverridesFile;
 
     @VisibleForTesting
     CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
@@ -83,6 +89,8 @@
             config.initConfigFromLib(Environment.buildPath(
                     apex.apexDirectory, "etc", "compatconfig"));
         }
+        File overridesFile = new File(APP_COMPAT_DATA_DIR, OVERRIDES_FILE);
+        config.initOverrides(overridesFile);
         config.invalidateCache();
         return config;
     }
@@ -202,6 +210,17 @@
      * @throws IllegalStateException if overriding is not allowed
      */
     boolean addOverride(long changeId, String packageName, boolean enabled) {
+        boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, enabled);
+        saveOverrides();
+        invalidateCache();
+        return alreadyKnown;
+    }
+
+    /**
+     * Unsafe version of {@link #addOverride(long, String, boolean)}.
+     * It does not invalidate the cache nor save the overrides.
+     */
+    private boolean addOverrideUnsafe(long changeId, String packageName, boolean enabled) {
         boolean alreadyKnown = true;
         OverrideAllowedState allowedState =
                 mOverrideValidator.getOverrideAllowedState(changeId, packageName);
@@ -224,7 +243,6 @@
                     throw new IllegalStateException("Should only be able to override changes that "
                             + "are allowed or can be deferred.");
             }
-            invalidateCache();
         }
         return alreadyKnown;
     }
@@ -282,6 +300,17 @@
      * @return {@code true} if an override existed;
      */
     boolean removeOverride(long changeId, String packageName) {
+        boolean overrideExists = removeOverrideUnsafe(changeId, packageName);
+        saveOverrides();
+        invalidateCache();
+        return overrideExists;
+    }
+
+    /**
+     * Unsafe version of {@link #removeOverride(long, String)}.
+     * It does not invalidate the cache nor save the overrides.
+     */
+    private boolean removeOverrideUnsafe(long changeId, String packageName) {
         boolean overrideExists = false;
         synchronized (mChanges) {
             CompatChange c = mChanges.get(changeId);
@@ -300,7 +329,6 @@
                 }
             }
         }
-        invalidateCache();
         return overrideExists;
     }
 
@@ -315,12 +343,13 @@
     void addOverrides(CompatibilityChangeConfig overrides, String packageName) {
         synchronized (mChanges) {
             for (Long changeId : overrides.enabledChanges()) {
-                addOverride(changeId, packageName, true);
+                addOverrideUnsafe(changeId, packageName, true);
             }
             for (Long changeId : overrides.disabledChanges()) {
-                addOverride(changeId, packageName, false);
+                addOverrideUnsafe(changeId, packageName, false);
 
             }
+            saveOverrides();
             invalidateCache();
         }
     }
@@ -337,8 +366,9 @@
         synchronized (mChanges) {
             for (int i = 0; i < mChanges.size(); ++i) {
                 CompatChange change = mChanges.valueAt(i);
-                removeOverride(change.getId(), packageName);
+                removeOverrideUnsafe(change.getId(), packageName);
             }
+            saveOverrides();
             invalidateCache();
         }
     }
@@ -372,8 +402,10 @@
     int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
         long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
         for (long changeId : changes) {
-            addOverride(changeId, packageName, true);
+            addOverrideUnsafe(changeId, packageName, true);
         }
+        saveOverrides();
+        invalidateCache();
         return changes.length;
     }
 
@@ -386,8 +418,10 @@
     int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
         long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
         for (long changeId : changes) {
-            addOverride(changeId, packageName, false);
+            addOverrideUnsafe(changeId, packageName, false);
         }
+        saveOverrides();
+        invalidateCache();
         return changes.length;
     }
 
@@ -494,7 +528,8 @@
 
     private void readConfig(File configFile) {
         try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
-            for (Change change : XmlParser.read(in).getCompatChange()) {
+            Config config = com.android.server.compat.config.XmlParser.read(in);
+            for (Change change : config.getCompatChange()) {
                 Slog.d(TAG, "Adding: " + change.toString());
                 addChange(new CompatChange(change));
             }
@@ -503,6 +538,65 @@
         }
     }
 
+    void initOverrides(File overridesFile) {
+        if (!overridesFile.exists()) {
+            mOverridesFile = overridesFile;
+            // There have not been any overrides added yet.
+            return;
+        }
+
+        try (InputStream in = new BufferedInputStream(new FileInputStream(overridesFile))) {
+            Overrides overrides = com.android.server.compat.overrides.XmlParser.read(in);
+            for (ChangeOverrides changeOverrides : overrides.getChangeOverrides()) {
+                long changeId = changeOverrides.getChangeId();
+                CompatChange compatChange = mChanges.get(changeId);
+                if (compatChange == null) {
+                    Slog.w(TAG, "Change ID " + changeId + " not found. "
+                            + "Skipping overrides for it.");
+                    continue;
+                }
+                compatChange.loadOverrides(changeOverrides);
+            }
+        } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
+            Slog.w(TAG, "Error processing " + overridesFile + " " + e.toString());
+            return;
+        }
+        mOverridesFile = overridesFile;
+    }
+
+    /**
+     * Persist compat framework overrides to /data/misc/appcompat/compat_framework_overrides.xml
+     */
+    void saveOverrides() {
+        if (mOverridesFile == null) {
+            return;
+        }
+        synchronized (mChanges) {
+            // Create the file if it doesn't already exist
+            try {
+                mOverridesFile.createNewFile();
+            } catch (IOException e) {
+                Slog.e(TAG, "Could not create override config file: " + e.toString());
+                return;
+            }
+            try (PrintWriter out = new PrintWriter(mOverridesFile)) {
+                XmlWriter writer = new XmlWriter(out);
+                Overrides overrides = new Overrides();
+                List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides();
+                for (int idx = 0; idx < mChanges.size(); ++idx) {
+                    CompatChange c = mChanges.valueAt(idx);
+                    ChangeOverrides changeOverrides = c.saveOverrides();
+                    if (changeOverrides != null) {
+                        changeOverridesList.add(changeOverrides);
+                    }
+                }
+                XmlWriter.write(writer, overrides);
+            } catch (IOException e) {
+                Slog.e(TAG, e.toString());
+            }
+        }
+    }
+
     IOverrideValidator getOverrideValidator() {
         return mOverrideValidator;
     }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index fb1e819..8ce6746 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -70,6 +70,7 @@
 import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
+import android.net.VpnInfo;
 import android.net.VpnManager;
 import android.net.VpnService;
 import android.net.ipsec.ike.ChildSessionCallback;
@@ -109,7 +110,6 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
@@ -1816,18 +1816,15 @@
     }
 
     /**
-     * This method should only be called by ConnectivityService because it doesn't
-     * have enough data to fill VpnInfo.primaryUnderlyingIface field.
+     * This method should not be called if underlying interfaces field is needed, because it doesn't
+     * have enough data to fill VpnInfo.underlyingIfaces field.
      */
     public synchronized VpnInfo getVpnInfo() {
         if (!isRunningLocked()) {
             return null;
         }
 
-        VpnInfo info = new VpnInfo();
-        info.ownerUid = mOwnerUID;
-        info.vpnIface = mInterface;
-        return info;
+        return new VpnInfo(mOwnerUID, mInterface, null);
     }
 
     public synchronized boolean appliesToUid(int uid) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 55103ca..c3f8d8c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1357,7 +1357,7 @@
                 // Scan supported modes returned by display.getInfo() to find a mode with the same
                 // size as the default display mode but with the specified refresh rate instead.
                 requestedModeId = display.getDisplayInfoLocked().findDefaultModeByRefreshRate(
-                        requestedRefreshRate);
+                        requestedRefreshRate).getModeId();
             }
             mDisplayModeDirector.getAppRequestObserver().setAppRequestedMode(
                     displayId, requestedModeId);
@@ -1538,6 +1538,14 @@
         }
     }
 
+    void setDisplayModeDirectorLoggingEnabled(boolean enabled) {
+        synchronized (mSyncRoot) {
+            if (mDisplayModeDirector != null) {
+                mDisplayModeDirector.setLoggingEnabled(enabled);
+            }
+        }
+    }
+
     void setAmbientColorTemperatureOverride(float cct) {
         synchronized (mSyncRoot) {
             final DisplayPowerController displayPowerController = mDisplayPowerControllers.get(
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 111664a..aaea15a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -54,6 +54,10 @@
                 return setDisplayWhiteBalanceLoggingEnabled(true);
             case "dwb-logging-disable":
                 return setDisplayWhiteBalanceLoggingEnabled(false);
+            case "dmd-logging-enable":
+                return setDisplayModeDirectorLoggingEnabled(true);
+            case "dmd-logging-disable":
+                return setDisplayModeDirectorLoggingEnabled(false);
             case "dwb-set-cct":
                 return setAmbientColorTemperatureOverride();
             case "set-fold":
@@ -82,6 +86,10 @@
         pw.println("    Enable display white-balance logging.");
         pw.println("  dwb-logging-disable");
         pw.println("    Disable display white-balance logging.");
+        pw.println("  dmd-logging-enable");
+        pw.println("    Enable display mode director logging.");
+        pw.println("  dmd-logging-disable");
+        pw.println("    Disable display mode director logging.");
         pw.println("  dwb-set-cct CCT");
         pw.println("    Sets the ambient color temperature override to CCT (use -1 to disable).");
         pw.println("  set-fold [fold|unfold|reset]");
@@ -136,6 +144,11 @@
         return 0;
     }
 
+    private int setDisplayModeDirectorLoggingEnabled(boolean enabled) {
+        mService.setDisplayModeDirectorLoggingEnabled(enabled);
+        return 0;
+    }
+
     private int setAmbientColorTemperatureOverride() {
         String cctText = getNextArg();
         if (cctText == null) {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 006f875..dce6bd8 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -67,7 +67,7 @@
  */
 public class DisplayModeDirector {
     private static final String TAG = "DisplayModeDirector";
-    private static final boolean DEBUG = false;
+    private boolean mLoggingEnabled;
 
     private static final int MSG_REFRESH_RATE_RANGE_CHANGED = 1;
     private static final int MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED = 2;
@@ -155,6 +155,14 @@
         }
     }
 
+    public void setLoggingEnabled(boolean loggingEnabled) {
+        if (mLoggingEnabled == loggingEnabled) {
+            return;
+        }
+        mLoggingEnabled = loggingEnabled;
+        mBrightnessObserver.setLoggingEnabled(loggingEnabled);
+    }
+
     @NonNull
     private SparseArray<Vote> getVotesLocked(int displayId) {
         SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
@@ -269,7 +277,7 @@
 
                 availableModes = filterModes(modes, primarySummary);
                 if (availableModes.length > 0) {
-                    if (DEBUG) {
+                    if (mLoggingEnabled) {
                         Slog.w(TAG, "Found available modes=" + Arrays.toString(availableModes)
                                 + " with lowest priority considered "
                                 + Vote.priorityToString(lowestConsideredPriority)
@@ -282,7 +290,7 @@
                     break;
                 }
 
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.w(TAG, "Couldn't find available modes with lowest priority set to "
                             + Vote.priorityToString(lowestConsideredPriority)
                             + " and with the following constraints: "
@@ -307,7 +315,7 @@
                     Math.min(appRequestSummary.minRefreshRate, primarySummary.minRefreshRate);
             appRequestSummary.maxRefreshRate =
                     Math.max(appRequestSummary.maxRefreshRate, primarySummary.maxRefreshRate);
-            if (DEBUG) {
+            if (mLoggingEnabled) {
                 Slog.i(TAG,
                         String.format("App request range: [%.0f %.0f]",
                                 appRequestSummary.minRefreshRate,
@@ -357,7 +365,7 @@
         for (Display.Mode mode : supportedModes) {
             if (mode.getPhysicalWidth() != summary.width
                     || mode.getPhysicalHeight() != summary.height) {
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size"
                             + ": desiredWidth=" + summary.width
                             + ": desiredHeight=" + summary.height
@@ -372,7 +380,7 @@
             // comparison.
             if (refreshRate < (summary.minRefreshRate - FLOAT_TOLERANCE)
                     || refreshRate > (summary.maxRefreshRate + FLOAT_TOLERANCE)) {
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.w(TAG, "Discarding mode " + mode.getModeId()
                             + ", outside refresh rate bounds"
                             + ": minRefreshRate=" + summary.minRefreshRate
@@ -516,7 +524,7 @@
     }
 
     private void updateVoteLocked(int displayId, int priority, Vote vote) {
-        if (DEBUG) {
+        if (mLoggingEnabled) {
             Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
                     + ", priority=" + Vote.priorityToString(priority)
                     + ", vote=" + vote + ")");
@@ -537,7 +545,7 @@
         }
 
         if (votes.size() == 0) {
-            if (DEBUG) {
+            if (mLoggingEnabled) {
                 Slog.i(TAG, "No votes left for display " + displayId + ", removing.");
             }
             mVotesByDisplay.remove(displayId);
@@ -1287,6 +1295,7 @@
         private boolean mShouldObserveAmbientLowChange;
         private boolean mShouldObserveDisplayHighChange;
         private boolean mShouldObserveAmbientHighChange;
+        private boolean mLoggingEnabled;
 
         private SensorManager mSensorManager;
         private Sensor mLightSensor;
@@ -1303,7 +1312,6 @@
         // changeable and low power mode off. After initialization, these states will
         // be updated from the same handler thread.
         private int mDefaultDisplayState = Display.STATE_UNKNOWN;
-        private boolean mIsDeviceActive = false;
         private boolean mRefreshRateChangeable = false;
         private boolean mLowPowerModeEnabled = false;
 
@@ -1415,6 +1423,14 @@
             mDeviceConfigDisplaySettings.startListening();
         }
 
+        public void setLoggingEnabled(boolean loggingEnabled) {
+            if (mLoggingEnabled == loggingEnabled) {
+                return;
+            }
+            mLoggingEnabled = loggingEnabled;
+            mLightSensorListener.setLoggingEnabled(loggingEnabled);
+        }
+
         public void onRefreshRateSettingChangedLocked(float min, float max) {
             boolean changeable = (max - min > 1f && max > 60f);
             if (mRefreshRateChangeable != changeable) {
@@ -1485,7 +1501,6 @@
             pw.println("    mAmbientLux: " + mAmbientLux);
             pw.println("    mBrightness: " + mBrightness);
             pw.println("    mDefaultDisplayState: " + mDefaultDisplayState);
-            pw.println("    mIsDeviceActive: " + mIsDeviceActive);
             pw.println("    mLowPowerModeEnabled: " + mLowPowerModeEnabled);
             pw.println("    mRefreshRateChangeable: " + mRefreshRateChangeable);
             pw.println("    mShouldObserveDisplayLowChange: " + mShouldObserveDisplayLowChange);
@@ -1691,7 +1706,7 @@
                 vote = Vote.forRefreshRates(mRefreshRateInHighZone, mRefreshRateInHighZone);
             }
 
-            if (DEBUG) {
+            if (mLoggingEnabled) {
                 Slog.d(TAG, "Display brightness " + mBrightness + ", ambient lux " +  mAmbientLux
                         + ", Vote " + vote);
             }
@@ -1720,6 +1735,11 @@
 
         @VisibleForTesting
         public void setDefaultDisplayState(int state) {
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "setDefaultDisplayState: mDefaultDisplayState = "
+                        + mDefaultDisplayState + ", state = " + state);
+            }
+
             if (mDefaultDisplayState != state) {
                 mDefaultDisplayState = state;
                 updateSensorStatus();
@@ -1731,36 +1751,58 @@
                 return;
             }
 
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "updateSensorStatus: mShouldObserveAmbientLowChange = "
+                        + mShouldObserveAmbientLowChange + ", mShouldObserveAmbientHighChange = "
+                        + mShouldObserveAmbientHighChange);
+                Slog.d(TAG, "updateSensorStatus: mLowPowerModeEnabled = "
+                        + mLowPowerModeEnabled + ", mRefreshRateChangeable = "
+                        + mRefreshRateChangeable);
+            }
+
             if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
                      && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
                 mSensorManager.registerListener(mLightSensorListener,
                         mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
+                if (mLoggingEnabled) {
+                    Slog.d(TAG, "updateSensorStatus: registerListener");
+                }
             } else {
                 mLightSensorListener.removeCallbacks();
                 mSensorManager.unregisterListener(mLightSensorListener);
+                if (mLoggingEnabled) {
+                    Slog.d(TAG, "updateSensorStatus: unregisterListener");
+                }
             }
         }
 
         private boolean isDeviceActive() {
-            mIsDeviceActive = mInjector.isDeviceInteractive(mContext);
-            return (mDefaultDisplayState == Display.STATE_ON)
-                    && mIsDeviceActive;
+            return mDefaultDisplayState == Display.STATE_ON;
         }
 
         private final class LightSensorEventListener implements SensorEventListener {
             final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
             private float mLastSensorData;
             private long mTimestamp;
+            private boolean mLoggingEnabled;
 
             public void dumpLocked(PrintWriter pw) {
                 pw.println("    mLastSensorData: " + mLastSensorData);
                 pw.println("    mTimestamp: " + formatTimestamp(mTimestamp));
             }
 
+
+            public void setLoggingEnabled(boolean loggingEnabled) {
+                if (mLoggingEnabled == loggingEnabled) {
+                    return;
+                }
+                mLoggingEnabled = loggingEnabled;
+            }
+
             @Override
             public void onSensorChanged(SensorEvent event) {
                 mLastSensorData = event.values[0];
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.d(TAG, "On sensor changed: " + mLastSensorData);
                 }
 
@@ -2009,8 +2051,6 @@
 
         void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer);
-
-        boolean isDeviceInteractive(@NonNull Context context);
     }
 
     @VisibleForTesting
@@ -2041,11 +2081,6 @@
             cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
                     observer, UserHandle.USER_SYSTEM);
         }
-
-        @Override
-        public boolean isDeviceInteractive(@NonNull Context ctx) {
-            return ctx.getSystemService(PowerManager.class).isInteractive();
-        }
     }
 
 }
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index bda4240..5b3db01 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -22,12 +22,15 @@
 import android.graphics.Typeface;
 import android.graphics.fonts.FontFamily;
 import android.graphics.fonts.FontFileUtil;
+import android.graphics.fonts.FontManager;
 import android.graphics.fonts.SystemFonts;
-import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.SharedMemory;
+import android.os.ShellCallback;
 import android.system.ErrnoException;
 import android.text.FontConfig;
+import android.util.AndroidException;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
@@ -48,6 +51,7 @@
 import java.nio.NioUtils;
 import java.nio.channels.FileChannel;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Map;
 
 /** A service for managing system fonts. */
@@ -55,8 +59,6 @@
 public final class FontManagerService extends IFontManager.Stub {
     private static final String TAG = "FontManagerService";
 
-    // TODO: make this a DeviceConfig flag.
-    private static final boolean ENABLE_FONT_UPDATES = false;
     private static final String FONT_FILES_DIR = "/data/fonts/files";
 
     @Override
@@ -64,6 +66,24 @@
         return getCurrentFontSettings().getSystemFontConfig();
     }
 
+    /* package */ static class SystemFontException extends AndroidException {
+        private final int mErrorCode;
+
+        SystemFontException(@FontManager.ErrorCode int errorCode, String msg, Throwable cause) {
+            super(msg, cause);
+            mErrorCode = errorCode;
+        }
+
+        SystemFontException(int errorCode, String msg) {
+            super(msg);
+            mErrorCode = errorCode;
+        }
+
+        @FontManager.ErrorCode int getErrorCode() {
+            return mErrorCode;
+        }
+    }
+
     /** Class to manage FontManagerService's lifecycle. */
     public static final class Lifecycle extends SystemService {
         private final FontManagerService mService;
@@ -151,7 +171,6 @@
 
     @Nullable
     private static UpdatableFontDir createUpdatableFontDir() {
-        if (!ENABLE_FONT_UPDATES) return null;
         // If apk verity is supported, fs-verity should be available.
         if (!FileIntegrityService.isApkVeritySupported()) return null;
         return new UpdatableFontDir(new File(FONT_FILES_DIR),
@@ -178,19 +197,34 @@
         }
     }
 
-    // TODO(b/173619554): Expose as API.
-    private boolean installFontFile(FileDescriptor fd, byte[] pkcs7Signature) {
-        if (mUpdatableFontDir == null) return false;
+    /* package */ void installFontFile(FileDescriptor fd, byte[] pkcs7Signature)
+            throws SystemFontException {
+        if (mUpdatableFontDir == null) {
+            throw new SystemFontException(
+                    FontManager.ERROR_CODE_FONT_UPDATER_DISABLED,
+                    "The font updater is disabled.");
+        }
         synchronized (FontManagerService.this) {
-            try {
-                mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to install font file");
-                return false;
-            }
+            mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
             // Create updated font map in the next getSerializedSystemFontMap() call.
             mCurrentFontSettings = null;
-            return true;
+        }
+    }
+
+    /* package */ void clearUpdates() throws SystemFontException {
+        if (mUpdatableFontDir == null) {
+            throw new SystemFontException(
+                    FontManager.ERROR_CODE_FONT_UPDATER_DISABLED,
+                    "The font updater is disabled.");
+        }
+        mUpdatableFontDir.clearUpdates();
+    }
+
+    /* package */ Map<String, File> getFontFileMap() {
+        if (mUpdatableFontDir == null) {
+            return Collections.emptyMap();
+        } else {
+            return mUpdatableFontDir.getFontFileMap();
         }
     }
 
@@ -202,11 +236,13 @@
     }
 
     @Override
-    public int handleShellCommand(@NonNull ParcelFileDescriptor in,
-            @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
-            @NonNull String[] args) {
-        return new FontManagerShellCommand(this).exec(this,
-                in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args);
+    public void onShellCommand(@Nullable FileDescriptor in,
+            @Nullable FileDescriptor out,
+            @Nullable FileDescriptor err,
+            @NonNull String[] args,
+            @Nullable ShellCallback callback,
+            @NonNull ResultReceiver result) throws RemoteException {
+        new FontManagerShellCommand(this).exec(this, in, out, err, args, callback, result);
     }
 
     /* package */ static class SystemFontSettings {
@@ -245,8 +281,7 @@
         public static @Nullable SystemFontSettings create(
                 @Nullable UpdatableFontDir updatableFontDir) {
             if (updatableFontDir != null) {
-                final FontConfig fontConfig = SystemFonts.getSystemFontConfig(
-                        updatableFontDir.getFontFileMap());
+                final FontConfig fontConfig = updatableFontDir.getSystemFontConfig();
                 final Map<String, FontFamily[]> fallback =
                         SystemFonts.buildSystemFallback(fontConfig);
                 final Map<String, Typeface> typefaceMap =
@@ -274,4 +309,5 @@
             return null;
         }
     }
+
 }
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index acb5826..fd5c020 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -16,19 +16,33 @@
 
 package com.android.server.graphics.fonts;
 
+import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.fonts.Font;
 import android.graphics.fonts.FontFamily;
+import android.graphics.fonts.FontManager;
 import android.graphics.fonts.FontVariationAxis;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.ShellCommand;
 import android.text.FontConfig;
 import android.util.IndentingPrintWriter;
+import android.util.Slog;
 
 import com.android.internal.util.DumpUtils;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -38,6 +52,13 @@
 public class FontManagerShellCommand extends ShellCommand {
     private static final String TAG = "FontManagerShellCommand";
 
+    /**
+     * The maximum size of signature file.  This is just to avoid potential abuse.
+     *
+     * This is copied from VerityUtils.java.
+     */
+    private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192;
+
     @NonNull private final FontManagerService mService;
 
     FontManagerShellCommand(@NonNull FontManagerService service) {
@@ -46,12 +67,31 @@
 
     @Override
     public int onCommand(String cmd) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
+            // Do not change this string since this string is expected in the CTS.
+            getErrPrintWriter().println("Only shell or root user can execute font command.");
+            return 1;
+        }
         return execCommand(this, cmd);
     }
 
     @Override
     public void onHelp() {
-        dumpHelp(getOutPrintWriter());
+        PrintWriter w = getOutPrintWriter();
+        w.println("Font service (font) commands");
+        w.println("help");
+        w.println("    Print this help text.");
+        w.println();
+        w.println("dump [family name]");
+        w.println("    Dump all font files in the specified family name.");
+        w.println("    Dump current system font configuration if no family name was specified.");
+        w.println();
+        w.println("update [font file path] [signature file path]");
+        w.println("    Update installed font files with new font file.");
+        w.println();
+        w.println("clear");
+        w.println("    Remove all installed font files and reset to the initial state.");
     }
 
     /* package */ void dumpAll(@NonNull IndentingPrintWriter w) {
@@ -165,16 +205,6 @@
         w.decreaseIndent();
     }
 
-    private static void dumpHelp(@NonNull PrintWriter w) {
-        w.println("Font service (font) commands");
-        w.println("help");
-        w.println("    Print this help text.");
-        w.println();
-        w.println("dump [family name]");
-        w.println("    Dump all font files in the specified family name.");
-        w.println("    Dump current system font configuration if no family name was specified.");
-    }
-
     private void dumpFallback(@NonNull IndentingPrintWriter writer,
             @NonNull FontFamily[] families) {
         for (FontFamily family : families) {
@@ -233,37 +263,143 @@
         writer.println(sb.toString());
     }
 
-    private int execCommand(@NonNull ShellCommand shell, @NonNull String cmd) {
+    private void writeCommandResult(ShellCommand shell, SystemFontException e) {
+        // Print short summary to the stderr.
+        PrintWriter pw = shell.getErrPrintWriter();
+        pw.println(e.getErrorCode());
+        pw.println(e.getMessage());
+
+        // Dump full stack trace to logcat.
+
+        Slog.e(TAG, "Command failed: " + Arrays.toString(shell.getAllArgs()), e);
+    }
+
+    private int dump(ShellCommand shell) {
         final Context ctx = mService.getContext();
+        final FontManagerService.SystemFontSettings settings =
+                mService.getCurrentFontSettings();
+        if (!DumpUtils.checkDumpPermission(ctx, TAG, shell.getErrPrintWriter())) {
+            return 1;
+        }
+        final IndentingPrintWriter writer =
+                new IndentingPrintWriter(shell.getOutPrintWriter(), "  ");
+        String nextArg = shell.getNextArg();
+        if (nextArg == null) {
+            dumpFontConfig(writer, settings.getSystemFontConfig());
+        } else {
+            final Map<String, FontFamily[]> fallbackMap =
+                    settings.getSystemFallbackMap();
+            FontFamily[] families = fallbackMap.get(nextArg);
+            if (families == null) {
+                writer.println("Font Family \"" + nextArg + "\" not found");
+            } else {
+                dumpFallback(writer, families);
+            }
+        }
+        return 0;
+    }
+
+    private int update(ShellCommand shell) throws SystemFontException {
+        String fontPath = shell.getNextArg();
+        if (fontPath == null) {
+            throw new SystemFontException(
+                    FontManager.ERROR_CODE_INVALID_SHELL_ARGUMENT,
+                    "Font file path argument is required.");
+        }
+        String signaturePath = shell.getNextArg();
+        if (signaturePath == null) {
+            throw new SystemFontException(
+                    FontManager.ERROR_CODE_INVALID_SHELL_ARGUMENT,
+                    "Signature file argument is required.");
+        }
+
+        ParcelFileDescriptor fontFd = shell.openFileForSystem(fontPath, "r");
+        if (fontFd == null) {
+            throw new SystemFontException(
+                    FontManager.ERROR_CODE_FAILED_TO_OPEN_FONT_FILE,
+                    "Failed to open font file");
+        }
+
+        ParcelFileDescriptor sigFd = shell.openFileForSystem(signaturePath, "r");
+        if (sigFd == null) {
+            throw new SystemFontException(
+                    FontManager.ERROR_CODE_FAILED_TO_OPEN_SIGNATURE_FILE,
+                    "Failed to open signature file");
+        }
+
+        try (FileInputStream sigFis = new FileInputStream(sigFd.getFileDescriptor())) {
+            try (FileInputStream fontFis = new FileInputStream(fontFd.getFileDescriptor())) {
+                int len = sigFis.available();
+                if (len > MAX_SIGNATURE_FILE_SIZE_BYTES) {
+                    throw new SystemFontException(
+                            FontManager.ERROR_CODE_SIGNATURE_TOO_LARGE,
+                            "Signature file is too large");
+                }
+                byte[] signature = new byte[len];
+                if (sigFis.read(signature, 0, len) != len) {
+                    throw new SystemFontException(
+                            FontManager.ERROR_CODE_INVALID_SIGNATURE_FILE,
+                            "Invalid read length");
+                }
+                mService.installFontFile(fontFis.getFD(), signature);
+            } catch (IOException e) {
+                throw new SystemFontException(
+                        FontManager.ERROR_CODE_INVALID_SIGNATURE_FILE,
+                        "Failed to read signature file.", e);
+            }
+        } catch (IOException e) {
+            throw new SystemFontException(
+                    FontManager.ERROR_CODE_INVALID_FONT_FILE,
+                    "Failed to read font files.", e);
+        }
+
+        shell.getOutPrintWriter().println("Success");  // TODO: Output more details.
+        return 0;
+    }
+
+    private int clear(ShellCommand shell) throws SystemFontException {
+        mService.clearUpdates();
+        shell.getOutPrintWriter().println("Success");
+        return 0;
+    }
+
+    private int status(ShellCommand shell) throws SystemFontException {
+        final FontManagerService.SystemFontSettings settings = mService.getCurrentFontSettings();
+        final IndentingPrintWriter writer =
+                new IndentingPrintWriter(shell.getOutPrintWriter(), "  ");
+        FontConfig config = settings.getSystemFontConfig();
+
+        writer.println("Current Version: " + config.getConfigVersion());
+        LocalDateTime dt = LocalDateTime.ofEpochSecond(config.getLastModifiedDate(), 0,
+                ZoneOffset.UTC);
+        writer.println("Last Modified Date: " + dt.format(DateTimeFormatter.ISO_DATE_TIME));
+
+        Map<String, File> fontFileMap = mService.getFontFileMap();
+        writer.println("Number of updated font files: " + fontFileMap.size());
+        return 0;
+    }
+
+    private int execCommand(@NonNull ShellCommand shell, @Nullable String cmd) {
         if (cmd == null) {
             return shell.handleDefaultCommands(null);
         }
 
-        final FontManagerService.SystemFontSettings settings = mService.getCurrentFontSettings();
-
-        switch (cmd) {
-            case "dump":
-                if (!DumpUtils.checkDumpPermission(ctx, TAG, shell.getErrPrintWriter())) {
-                    return 1;
-                }
-                final IndentingPrintWriter writer =
-                        new IndentingPrintWriter(shell.getOutPrintWriter(), "  ");
-                String nextArg = shell.getNextArg();
-                if (nextArg == null) {
-                    dumpFontConfig(writer, settings.getSystemFontConfig());
-                } else {
-                    final Map<String, FontFamily[]> fallbackMap = settings.getSystemFallbackMap();
-                    FontFamily[] families = fallbackMap.get(nextArg);
-                    if (families == null) {
-                        writer.println("Font Family \"" + nextArg + "\" not found");
-                    } else {
-                        dumpFallback(writer, families);
-                    }
-                }
-                return 0;
-            default:
-                shell.handleDefaultCommands(cmd);
+        try {
+            switch (cmd) {
+                case "dump":
+                    return dump(shell);
+                case "update":
+                    return update(shell);
+                case "clear":
+                    return clear(shell);
+                case "status":
+                    return status(shell);
+                default:
+                    return shell.handleDefaultCommands(cmd);
+            }
+        } catch (SystemFontException e) {
+            writeCommandResult(shell, e);
+            return 1;
         }
-        return 0;
     }
 }
diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
new file mode 100644
index 0000000..f0d14ba
--- /dev/null
+++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
@@ -0,0 +1,114 @@
+/*
+ * 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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/* package */ class PersistentSystemFontConfig {
+    private static final String TAG = "PersistentSystemFontConfig";
+
+    private static final String TAG_ROOT = "fontConfig";
+    private static final String TAG_LAST_MODIFIED_DATE = "lastModifiedDate";
+    private static final String TAG_VALUE = "value";
+
+    /* package */ static class Config {
+        public long lastModifiedDate;
+
+        public void reset() {
+            lastModifiedDate = 0;
+        }
+
+        public void copyTo(@NonNull Config out) {
+            out.lastModifiedDate = lastModifiedDate;
+        }
+    }
+
+    /**
+     * Read config XML and write to out argument.
+     */
+    public static void loadFromXml(@NonNull InputStream is, @NonNull Config out)
+            throws XmlPullParserException, IOException {
+        out.reset();
+        TypedXmlPullParser parser = Xml.resolvePullParser(is);
+
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+            final int depth = parser.getDepth();
+            final String tag = parser.getName();
+            if (depth == 1) {
+                if (!TAG_ROOT.equals(tag)) {
+                    Slog.e(TAG, "Invalid root tag: " + tag);
+                    return;
+                }
+            } else if (depth == 2) {
+                switch (tag) {
+                    case TAG_LAST_MODIFIED_DATE:
+                        out.lastModifiedDate = parseLongAttribute(parser, TAG_VALUE, 0);
+                        break;
+                    default:
+                        Slog.w(TAG, "Skipping unknown tag: " + tag);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Write config to OutputStream as XML file.
+     */
+    public static void writeToXml(@NonNull OutputStream os, @NonNull Config config)
+            throws IOException {
+        TypedXmlSerializer out = Xml.resolveSerializer(os);
+        out.startDocument(null /* encoding */, true /* standalone */);
+
+        out.startTag(null, TAG_ROOT);
+        out.startTag(null, TAG_LAST_MODIFIED_DATE);
+        out.attribute(null, TAG_VALUE, Long.toString(config.lastModifiedDate));
+        out.endTag(null, TAG_LAST_MODIFIED_DATE);
+        out.endTag(null, TAG_ROOT);
+
+        out.endDocument();
+    }
+
+    private static long parseLongAttribute(TypedXmlPullParser parser, String attr, long defValue) {
+        final String value = parser.getAttributeValue(null /* namespace */, attr);
+        if (TextUtils.isEmpty(value)) {
+            return defValue;
+        }
+        try {
+            return Long.parseLong(value);
+        } catch (NumberFormatException e) {
+            return defValue;
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 8da579f..8ec7277 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -16,18 +16,28 @@
 
 package com.android.server.graphics.fonts;
 
+import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.fonts.FontManager;
+import android.graphics.fonts.SystemFonts;
 import android.os.FileUtils;
+import android.text.FontConfig;
 import android.util.Base64;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.security.SecureRandom;
+import java.time.Instant;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -39,6 +49,8 @@
     // TODO: Support .otf
     private static final String ALLOWED_EXTENSION = ".ttf";
 
+    private static final String CONFIG_XML_FILE = "/data/fonts/config/config.xml";
+
     /** Interface to mock font file access in tests. */
     interface FontFileParser {
         String getPostScriptName(File file) throws IOException;
@@ -55,6 +67,15 @@
         boolean rename(File src, File dest);
     }
 
+    /** Interface to mock persistent configuration */
+    interface PersistentConfig {
+        void loadFromXml(PersistentSystemFontConfig.Config out)
+                throws XmlPullParserException, IOException;
+        void writeToXml(PersistentSystemFontConfig.Config config)
+                throws IOException;
+        boolean rename(File src, File dest);
+    }
+
     /** Data class to hold font file path and revision. */
     private static final class FontFileInfo {
         private final File mFile;
@@ -87,6 +108,16 @@
     private final List<File> mPreinstalledFontDirs;
     private final FontFileParser mParser;
     private final FsverityUtil mFsverityUtil;
+    private final File mConfigFile;
+    private final File mTmpConfigFile;
+
+    @GuardedBy("UpdatableFontDir.this")
+    private final PersistentSystemFontConfig.Config mConfig =
+            new PersistentSystemFontConfig.Config();
+
+    @GuardedBy("UpdatableFontDir.this")
+    private int mConfigVersion = 1;
+
     /**
      * A mutable map containing mapping from font file name (e.g. "NotoColorEmoji.ttf") to {@link
      * FontFileInfo}. All files in this map are validated, and have higher revision numbers than
@@ -101,6 +132,20 @@
         mPreinstalledFontDirs = preinstalledFontDirs;
         mParser = parser;
         mFsverityUtil = fsverityUtil;
+        mConfigFile = new File(CONFIG_XML_FILE);
+        mTmpConfigFile = new File(CONFIG_XML_FILE + ".tmp");
+        loadFontFileMap();
+    }
+
+    // For unit testing
+    UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
+            FsverityUtil fsverityUtil, File configFile) {
+        mFilesDir = filesDir;
+        mPreinstalledFontDirs = preinstalledFontDirs;
+        mParser = parser;
+        mFsverityUtil = fsverityUtil;
+        mConfigFile = configFile;
+        mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp");
         loadFontFileMap();
     }
 
@@ -108,6 +153,13 @@
         // TODO: SIGBUS crash protection
         synchronized (UpdatableFontDir.this) {
             boolean success = false;
+
+            try (FileInputStream fis = new FileInputStream(mConfigFile)) {
+                PersistentSystemFontConfig.loadFromXml(fis, mConfig);
+            } catch (IOException | XmlPullParserException e) {
+                mConfig.reset();
+            }
+
             mFontFileInfoMap.clear();
             try {
                 File[] dirs = mFilesDir.listFiles();
@@ -117,13 +169,13 @@
                     File[] files = dir.listFiles();
                     if (files == null || files.length != 1) return;
                     FontFileInfo fontFileInfo = validateFontFile(files[0]);
-                    if (fontFileInfo == null) {
-                        Slog.w(TAG, "Broken file is found. Clearing files.");
-                        return;
-                    }
-                    addFileToMapLocked(fontFileInfo, true /* deleteOldFile */);
+                    addFileToMapIfNewerLocked(fontFileInfo, true /* deleteOldFile */);
                 }
                 success = true;
+            } catch (Throwable t) {
+                // If something happened during loading system fonts, clear all contents in finally
+                // block. Here, just dumping errors.
+                Slog.e(TAG, "Failed to load font mappings.", t);
             } finally {
                 // Delete all files just in case if we find a problematic file.
                 if (!success) {
@@ -134,6 +186,24 @@
         }
     }
 
+    /* package */ void clearUpdates() throws SystemFontException {
+        synchronized (UpdatableFontDir.this) {
+            mFontFileInfoMap.clear();
+            FileUtils.deleteContents(mFilesDir);
+
+            mConfig.reset();
+            mConfig.lastModifiedDate = Instant.now().getEpochSecond();
+            try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
+                PersistentSystemFontConfig.writeToXml(fos, mConfig);
+            } catch (Exception e) {
+                throw new SystemFontException(
+                        FontManager.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE,
+                        "Failed to write config XML.", e);
+            }
+            mConfigVersion++;
+        }
+    }
+
     /**
      * Installs a new font file, or updates an existing font file.
      *
@@ -143,38 +213,92 @@
      *
      * @param fd             A file descriptor to the font file.
      * @param pkcs7Signature A PKCS#7 detached signature to enable fs-verity for the font file.
+     * @throws SystemFontException if error occurs.
      */
-    void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws IOException {
+    void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws SystemFontException {
         synchronized (UpdatableFontDir.this) {
             File newDir = getRandomDir(mFilesDir);
             if (!newDir.mkdir()) {
-                // TODO: Define and return an error code for API
-                throw new IOException("Failed to create a new dir");
+                throw new SystemFontException(
+                        FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE,
+                        "Failed to create font directory.");
             }
             boolean success = false;
             try {
                 File tempNewFontFile = new File(newDir, "font.ttf");
                 try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) {
                     FileUtils.copy(fd, out.getFD());
+                } catch (IOException e) {
+                    throw new SystemFontException(
+                            FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE,
+                            "Failed to write font file to storage.", e);
                 }
-                // Do not parse font file before setting up fs-verity.
-                // setUpFsverity throws IOException if failed.
-                mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(), pkcs7Signature);
-                String postScriptName = mParser.getPostScriptName(tempNewFontFile);
+                try {
+                    // Do not parse font file before setting up fs-verity.
+                    // setUpFsverity throws IOException if failed.
+                    mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(),
+                            pkcs7Signature);
+                } catch (IOException e) {
+                    throw new SystemFontException(
+                            FontManager.ERROR_CODE_VERIFICATION_FAILURE,
+                            "Failed to setup fs-verity.", e);
+                }
+                String postScriptName;
+                try {
+                    postScriptName = mParser.getPostScriptName(tempNewFontFile);
+                } catch (IOException e) {
+                    throw new SystemFontException(
+                            FontManager.ERROR_CODE_INVALID_FONT_FILE,
+                            "Failed to read PostScript name from font file", e);
+                }
+                if (postScriptName == null) {
+                    throw new SystemFontException(
+                            FontManager.ERROR_CODE_MISSING_POST_SCRIPT_NAME,
+                            "Failed to read PostScript name from font file");
+                }
                 File newFontFile = new File(newDir, postScriptName + ALLOWED_EXTENSION);
                 if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) {
-                    // TODO: Define and return an error code for API
-                    throw new IOException("Failed to rename");
+                    throw new SystemFontException(
+                            FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE,
+                            "Failed to move verified font file.");
                 }
                 FontFileInfo fontFileInfo = validateFontFile(newFontFile);
-                if (fontFileInfo == null) {
-                    // TODO: Define and return an error code for API
-                    throw new IllegalArgumentException("Invalid file");
+
+                // Write config file.
+                PersistentSystemFontConfig.Config copied = new PersistentSystemFontConfig.Config();
+                mConfig.copyTo(copied);
+
+                copied.lastModifiedDate = Instant.now().getEpochSecond();
+                try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
+                    PersistentSystemFontConfig.writeToXml(fos, copied);
+                } catch (Exception e) {
+                    throw new SystemFontException(
+                            FontManager.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE,
+                            "Failed to write config XML.", e);
                 }
-                if (!addFileToMapLocked(fontFileInfo, false)) {
-                    // TODO: Define and return an error code for API
-                    throw new IllegalArgumentException("Version downgrade");
+
+                // Backup the mapping for rollback.
+                HashMap<String, FontFileInfo> backup = new HashMap<>(mFontFileInfoMap);
+                if (!addFileToMapIfNewerLocked(fontFileInfo, false)) {
+                    throw new SystemFontException(
+                            FontManager.ERROR_CODE_DOWNGRADING,
+                            "Downgrading font file is forbidden.");
                 }
+
+                if (!mFsverityUtil.rename(mTmpConfigFile, mConfigFile)) {
+                    // If we fail to stage the config file, need to rollback the config.
+                    mFontFileInfoMap.clear();
+                    mFontFileInfoMap.putAll(backup);
+                    throw new SystemFontException(
+                            FontManager.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE,
+                            "Failed to stage the config file.");
+                }
+
+
+                // Now font update is succeeded. Update config version.
+                copied.copyTo(mConfig);
+                mConfigVersion++;
+
                 success = true;
             } finally {
                 if (!success) {
@@ -207,7 +331,7 @@
      * higher than the currently used font file (either in {@link #mFontFileInfoMap} or {@link
      * #mPreinstalledFontDirs}).
      */
-    private boolean addFileToMapLocked(FontFileInfo fontFileInfo, boolean deleteOldFile) {
+    private boolean addFileToMapIfNewerLocked(FontFileInfo fontFileInfo, boolean deleteOldFile) {
         String name = fontFileInfo.getFile().getName();
         FontFileInfo existingInfo = mFontFileInfoMap.get(name);
         final boolean shouldAddToMap;
@@ -224,13 +348,12 @@
                 FileUtils.deleteContentsAndDir(existingInfo.getRandomizedFontDir());
             }
             mFontFileInfoMap.put(name, fontFileInfo);
-            return true;
         } else {
             if (deleteOldFile) {
                 FileUtils.deleteContentsAndDir(fontFileInfo.getRandomizedFontDir());
             }
-            return false;
         }
+        return shouldAddToMap;
     }
 
     private long getPreinstalledFontRevision(String name) {
@@ -255,20 +378,23 @@
      * returns a {@link FontFileInfo} on success. This method does not check if the font revision
      * is higher than the currently used font.
      */
-    @Nullable
-    private FontFileInfo validateFontFile(File file) {
+    @NonNull
+    private FontFileInfo validateFontFile(File file) throws SystemFontException {
         if (!mFsverityUtil.hasFsverity(file.getAbsolutePath())) {
-            Slog.w(TAG, "Font validation failed. Fs-verity is not enabled: " + file);
-            return null;
+            throw new SystemFontException(
+                    FontManager.ERROR_CODE_VERIFICATION_FAILURE,
+                    "Font validation failed. Fs-verity is not enabled: " + file);
         }
         if (!validateFontFileName(file)) {
-            Slog.w(TAG, "Font validation failed. Could not validate font file name: " + file);
-            return null;
+            throw new SystemFontException(
+                    FontManager.ERROR_CODE_FONT_NAME_MISMATCH,
+                    "Font validation failed. Could not validate font file name: " + file);
         }
         long revision = getFontRevision(file);
         if (revision == -1) {
-            Slog.w(TAG, "Font validation failed. Could not read font revision: " + file);
-            return null;
+            throw new SystemFontException(
+                    FontManager.ERROR_CODE_INVALID_FONT_FILE,
+                    "Font validation failed. Could not read font revision: " + file);
         }
         return new FontFileInfo(file, revision);
     }
@@ -318,4 +444,14 @@
         }
         return map;
     }
+
+    /* package */ FontConfig getSystemFontConfig() {
+        synchronized (UpdatableFontDir.this) {
+            return SystemFonts.getSystemFontConfig(
+                    getFontFileMap(),
+                    mConfig.lastModifiedDate,
+                    mConfigVersion
+            );
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index d66bf63..6918064 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -303,6 +303,8 @@
                 return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY:
                 return STORAGE_GLOBAL_SETTINGS;
+            case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
+                return STORAGE_GLOBAL_SETTINGS;
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
                 return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
@@ -338,6 +340,8 @@
                 return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY:
                 return Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED;
+            case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
+                return Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED;
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
                 return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index ccce9dc..382f0f9 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -956,8 +956,6 @@
         }
     }
 
-    void setAutoDeviceOff(boolean enabled) {}
-
     /**
      * Called when a hot-plug event issued.
      *
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index e6cf18b..75b52f9 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -24,7 +24,6 @@
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.SystemProperties;
-import android.provider.Settings.Global;
 import android.sysprop.HdmiProperties;
 import android.util.Slog;
 
@@ -56,9 +55,6 @@
     // Lazily initialized - should call getWakeLock() to get the instance.
     private ActiveWakeLock mWakeLock;
 
-    // If true, turn off TV upon standby. False by default.
-    private boolean mAutoTvOff;
-
     // Determines what action should be taken upon receiving Routing Control messages.
     @VisibleForTesting
     protected HdmiProperties.playback_device_action_on_routing_control_values
@@ -68,12 +64,6 @@
 
     HdmiCecLocalDevicePlayback(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
-
-        mAutoTvOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, false);
-
-        // The option is false by default. Update settings db as well to have the right
-        // initial setting on UI.
-        mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, mAutoTvOff);
     }
 
     @Override
@@ -154,7 +144,10 @@
         // Invalidate the internal active source record when goes to standby
         mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS,
                 "HdmiCecLocalDevicePlayback#onStandby()");
-        if (initiatedByCec || !mAutoTvOff || !wasActiveSource) {
+        boolean mTvSendStandbyOnSleep = mService.getHdmiCecConfig().getIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
+                    == HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED;
+        if (initiatedByCec || !mTvSendStandbyOnSleep || !wasActiveSource) {
             return;
         }
         switch (standbyAction) {
@@ -201,13 +194,6 @@
     }
 
     @Override
-    @ServiceThreadOnly
-    void setAutoDeviceOff(boolean enabled) {
-        assertRunOnServiceThread();
-        mAutoTvOff = enabled;
-    }
-
-    @Override
     @CallSuper
     @ServiceThreadOnly
     @VisibleForTesting
@@ -425,7 +411,6 @@
     protected void dump(final IndentingPrintWriter pw) {
         super.dump(pw);
         pw.println("isActiveSource(): " + isActiveSource());
-        pw.println("mAutoTvOff:" + mAutoTvOff);
     }
 
     // Wrapper interface over PowerManager.WakeLock
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 5ef3738..a3e18d1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -89,9 +89,6 @@
     @GuardedBy("mLock")
     private boolean mSystemAudioMute = false;
 
-    // If true, TV going to standby mode puts other devices also to standby.
-    private boolean mAutoDeviceOff;
-
     private final HdmiCecStandbyModeHandler mStandbyHandler;
 
     // If true, do not do routing control/send active source for internal source.
@@ -156,8 +153,6 @@
     HdmiCecLocalDeviceTv(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_TV);
         mPrevPortId = Constants.INVALID_PORT_ID;
-        mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
-                true);
         mSystemAudioControlFeatureEnabled =
                 mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);
         mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
@@ -1202,13 +1197,6 @@
         }
     }
 
-    @Override
-    @ServiceThreadOnly
-    void setAutoDeviceOff(boolean enabled) {
-        assertRunOnServiceThread();
-        mAutoDeviceOff = enabled;
-    }
-
     @ServiceThreadOnly
     boolean getAutoWakeup() {
         assertRunOnServiceThread();
@@ -1286,7 +1274,11 @@
         if (!mService.isControlEnabled()) {
             return;
         }
-        if (!initiatedByCec && mAutoDeviceOff) {
+        boolean sendStandbyOnSleep =
+                mService.getHdmiCecConfig().getIntValue(
+                    HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
+                        == HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED;
+        if (!initiatedByCec && sendStandbyOnSleep) {
             mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
                     mAddress, Constants.ADDR_BROADCAST));
         }
@@ -1545,7 +1537,6 @@
         pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
         pw.println("mSystemAudioMute: " + mSystemAudioMute);
         pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
-        pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
         pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
         pw.println("mPrevPortId: " + mPrevPortId);
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 8febd4f..0ae1994 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -661,7 +661,6 @@
         ContentResolver resolver = getContext().getContentResolver();
         String[] settings = new String[] {
                 Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
-                Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
                 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
                 Global.MHL_INPUT_SWITCHING_ENABLED,
                 Global.MHL_POWER_CHARGE_ENABLED,
@@ -689,15 +688,6 @@
                     setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue(
                             HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE));
                     break;
-                case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
-                    for (int type : mLocalDevices) {
-                        HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
-                        if (localDevice != null) {
-                            localDevice.setAutoDeviceOff(enabled);
-                        }
-                    }
-                    // No need to propagate to HAL.
-                    break;
                 case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED:
                     if (isTvDeviceEnabled()) {
                         tv().setSystemAudioControlFeatureEnabled(enabled);
diff --git a/services/core/java/com/android/server/hdmi/cec_config.xml b/services/core/java/com/android/server/hdmi/cec_config.xml
index a751fd7..191e725 100644
--- a/services/core/java/com/android/server/hdmi/cec_config.xml
+++ b/services/core/java/com/android/server/hdmi/cec_config.xml
@@ -64,6 +64,15 @@
     </allowed-values>
     <default-value int-value="1" />
   </setting>
+  <setting name="tv_send_standby_on_sleep"
+      value-type="int"
+      user-configurable="true">
+    <allowed-values>
+      <value int-value="0" />
+      <value int-value="1" />
+    </allowed-values>
+    <default-value int-value="1" />
+  </setting>
   <setting name="rc_profile_tv"
       value-type="int"
       user-configurable="false">
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 143ec15..6308ace 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3947,58 +3947,61 @@
     }
 
     @Override
-    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
-        // By this IPC call, only a process which shares the same uid with the IME can add
-        // additional input method subtypes to the IME.
-        if (TextUtils.isEmpty(imiId) || subtypes == null) return;
-        final ArrayList<InputMethodSubtype> toBeAdded = new ArrayList<>();
-        for (InputMethodSubtype subtype : subtypes) {
-            if (!toBeAdded.contains(subtype)) {
-                toBeAdded.add(subtype);
-            } else {
-                Slog.w(TAG, "Duplicated subtype definition found: "
-                        + subtype.getLocale() + ", " + subtype.getMode());
+    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
+            IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            // By this IPC call, only a process which shares the same uid with the IME can add
+            // additional input method subtypes to the IME.
+            if (TextUtils.isEmpty(imiId) || subtypes == null) return;
+            final ArrayList<InputMethodSubtype> toBeAdded = new ArrayList<>();
+            for (InputMethodSubtype subtype : subtypes) {
+                if (!toBeAdded.contains(subtype)) {
+                    toBeAdded.add(subtype);
+                } else {
+                    Slog.w(TAG, "Duplicated subtype definition found: "
+                            + subtype.getLocale() + ", " + subtype.getMode());
+                }
             }
-        }
-        synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked()) {
-                return;
-            }
-            if (!mSystemReady) {
-                return;
-            }
-            final InputMethodInfo imi = mMethodMap.get(imiId);
-            if (imi == null) return;
-            final String[] packageInfos;
-            try {
-                packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to get package infos");
-                return;
-            }
-            if (packageInfos != null) {
-                final int packageNum = packageInfos.length;
-                for (int i = 0; i < packageNum; ++i) {
-                    if (packageInfos[i].equals(imi.getPackageName())) {
-                        if (subtypes.length > 0) {
-                            mAdditionalSubtypeMap.put(imi.getId(), toBeAdded);
-                        } else {
-                            mAdditionalSubtypeMap.remove(imi.getId());
+            synchronized (mMethodMap) {
+                if (!calledFromValidUserLocked()) {
+                    return;
+                }
+                if (!mSystemReady) {
+                    return;
+                }
+                final InputMethodInfo imi = mMethodMap.get(imiId);
+                if (imi == null) return;
+                final String[] packageInfos;
+                try {
+                    packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to get package infos");
+                    return;
+                }
+                if (packageInfos != null) {
+                    final int packageNum = packageInfos.length;
+                    for (int i = 0; i < packageNum; ++i) {
+                        if (packageInfos[i].equals(imi.getPackageName())) {
+                            if (subtypes.length > 0) {
+                                mAdditionalSubtypeMap.put(imi.getId(), toBeAdded);
+                            } else {
+                                mAdditionalSubtypeMap.remove(imi.getId());
+                            }
+                            AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap,
+                                    mSettings.getCurrentUserId());
+                            final long ident = Binder.clearCallingIdentity();
+                            try {
+                                buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+                            } finally {
+                                Binder.restoreCallingIdentity(ident);
+                            }
+                            return;
                         }
-                        AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap,
-                                mSettings.getCurrentUserId());
-                        final long ident = Binder.clearCallingIdentity();
-                        try {
-                            buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
-                        } finally {
-                            Binder.restoreCallingIdentity(ident);
-                        }
-                        return;
                     }
                 }
             }
-        }
-        return;
+            return;
+        });
     }
 
     /**
@@ -4103,16 +4106,21 @@
     }
 
     @Override
-    public void removeImeSurface() {
-        mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null);
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
+    public void removeImeSurface(IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null);
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
+        });
     }
 
     @Override
-    public void removeImeSurfaceFromWindow(IBinder windowToken) {
-        // No permission check, because we'll only execute the request if the calling window is
-        // also the current IME client.
-        mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
+    public void removeImeSurfaceFromWindow(IBinder windowToken,
+            IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            // No permission check, because we'll only execute the request if the calling window is
+            // also the current IME client.
+            mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
+        });
     }
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 2dd7096..7f9c766 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1502,14 +1502,17 @@
 
         @BinderThread
         @Override
-        public void removeImeSurface() {
+        public void removeImeSurface(IVoidResultCallback resultCallback) {
             reportNotSupported();
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
 
         @BinderThread
         @Override
-        public void removeImeSurfaceFromWindow(IBinder windowToken) {
+        public void removeImeSurfaceFromWindow(IBinder windowToken,
+                IVoidResultCallback resultCallback) {
             reportNotSupported();
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
 
         @BinderThread
@@ -1815,8 +1818,10 @@
 
         @BinderThread
         @Override
-        public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+        public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
+                IVoidResultCallback resultCallback) {
             reportNotSupported();
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
 
         @BinderThread
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 7dd961a..b92a83f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -89,6 +89,7 @@
     private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
 
     private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key";
+    private static final String REBOOT_ESCROW_SERVER_BLOB = "reboot.escrow.server.blob.key";
 
     private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
 
@@ -318,6 +319,22 @@
         deleteFile(getRebootEscrowFile(userId));
     }
 
+    public void writeRebootEscrowServerBlob(byte[] serverBlob) {
+        writeFile(getRebootEscrowServerBlob(), serverBlob);
+    }
+
+    public byte[] readRebootEscrowServerBlob() {
+        return readFile(getRebootEscrowServerBlob());
+    }
+
+    public boolean hasRebootEscrowServerBlob() {
+        return hasFile(getRebootEscrowServerBlob());
+    }
+
+    public void removeRebootEscrowServerBlob() {
+        deleteFile(getRebootEscrowServerBlob());
+    }
+
     public boolean hasPassword(int userId) {
         return hasFile(getLockPasswordFilename(userId));
     }
@@ -446,6 +463,12 @@
         return getLockCredentialFilePathForUser(userId, REBOOT_ESCROW_FILE);
     }
 
+    @VisibleForTesting
+    String getRebootEscrowServerBlob() {
+        // There is a single copy of server blob for all users.
+        return getLockCredentialFilePathForUser(UserHandle.USER_SYSTEM, REBOOT_ESCROW_SERVER_BLOB);
+    }
+
     private String getLockCredentialFilePathForUser(int userId, String basename) {
         String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
                         SYSTEM_DIRECTORY;
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index fbec915..06962d4 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -124,26 +124,28 @@
     static class Injector {
         protected Context mContext;
         private final RebootEscrowKeyStoreManager mKeyStoreManager;
-        private final RebootEscrowProviderInterface mRebootEscrowProvider;
+        private final LockSettingsStorage mStorage;
+        private RebootEscrowProviderInterface mRebootEscrowProvider;
 
-        Injector(Context context) {
+        Injector(Context context, LockSettingsStorage storage) {
             mContext = context;
+            mStorage = storage;
             mKeyStoreManager = new RebootEscrowKeyStoreManager();
+        }
 
-            RebootEscrowProviderInterface rebootEscrowProvider = null;
-            // TODO(xunchang) add implementation for server based ror.
+        private RebootEscrowProviderInterface createRebootEscrowProvider() {
+            RebootEscrowProviderInterface rebootEscrowProvider;
             if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
                     "server_based_ror_enabled", false)) {
-                Slog.e(TAG, "Server based ror isn't implemented yet.");
+                rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage);
             } else {
                 rebootEscrowProvider = new RebootEscrowProviderHalImpl();
             }
 
-            if (rebootEscrowProvider != null && rebootEscrowProvider.hasRebootEscrowSupport()) {
-                mRebootEscrowProvider = rebootEscrowProvider;
-            } else {
-                mRebootEscrowProvider = null;
+            if (rebootEscrowProvider.hasRebootEscrowSupport()) {
+                return rebootEscrowProvider;
             }
+            return null;
         }
 
         public Context getContext() {
@@ -159,6 +161,12 @@
         }
 
         public RebootEscrowProviderInterface getRebootEscrowProvider() {
+            // Initialize for the provider lazily. Because the device_config and service
+            // implementation apps may change when system server is running.
+            if (mRebootEscrowProvider == null) {
+                mRebootEscrowProvider = createRebootEscrowProvider();
+            }
+
             return mRebootEscrowProvider;
         }
 
@@ -177,7 +185,7 @@
     }
 
     RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
-        this(new Injector(context), callbacks, storage);
+        this(new Injector(context, storage), callbacks, storage);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
new file mode 100644
index 0000000..ba1a680
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
@@ -0,0 +1,202 @@
+/*
+ * 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.locksettings;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import javax.crypto.SecretKey;
+
+/**
+ * An implementation of the {@link RebootEscrowProviderInterface} by communicating with server to
+ * encrypt & decrypt the blob.
+ */
+class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface {
+    private static final String TAG = "RebootEscrowProvider";
+
+    // Timeout for service binding
+    private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10;
+
+    /**
+     * Use the default lifetime of 10 minutes. The lifetime covers the following activities:
+     * Server wrap secret -> device reboot -> server unwrap blob.
+     */
+    private static final long DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS = 600_1000;
+
+    private final LockSettingsStorage mStorage;
+
+    private final Injector mInjector;
+
+    static class Injector {
+        private ResumeOnRebootServiceConnection mServiceConnection = null;
+
+        Injector(Context context) {
+            mServiceConnection = new ResumeOnRebootServiceProvider(context).getServiceConnection();
+            if (mServiceConnection == null) {
+                Slog.e(TAG, "Failed to resolve resume on reboot server service.");
+            }
+        }
+
+        Injector(ResumeOnRebootServiceConnection serviceConnection) {
+            mServiceConnection = serviceConnection;
+        }
+
+        @Nullable
+        private ResumeOnRebootServiceConnection getServiceConnection() {
+            return mServiceConnection;
+        }
+
+        long getServiceTimeoutInSeconds() {
+            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA,
+                    "server_based_service_timeout_in_seconds",
+                    DEFAULT_SERVICE_TIMEOUT_IN_SECONDS);
+        }
+
+        long getServerBlobLifetimeInMillis() {
+            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA,
+                    "server_based_server_blob_lifetime_in_millis",
+                    DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS);
+        }
+    }
+
+    RebootEscrowProviderServerBasedImpl(Context context, LockSettingsStorage storage) {
+        this(storage, new Injector(context));
+    }
+
+    @VisibleForTesting
+    RebootEscrowProviderServerBasedImpl(LockSettingsStorage storage, Injector injector) {
+        mStorage = storage;
+        mInjector = injector;
+    }
+
+    @Override
+    public boolean hasRebootEscrowSupport() {
+        return mInjector.getServiceConnection() != null;
+    }
+
+    private byte[] unwrapServerBlob(byte[] serverBlob, SecretKey decryptionKey) throws
+            TimeoutException, RemoteException, IOException {
+        ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection();
+        if (serviceConnection == null) {
+            Slog.w(TAG, "Had reboot escrow data for users, but resume on reboot server"
+                    + " service is unavailable");
+            return null;
+        }
+
+        // Decrypt with k_k from the key store first.
+        byte[] decryptedBlob = AesEncryptionUtil.decrypt(decryptionKey, serverBlob);
+        if (decryptedBlob == null) {
+            Slog.w(TAG, "Decrypted server blob should not be null");
+            return null;
+        }
+
+        // Ask the server connection service to decrypt the inner layer, to get the reboot
+        // escrow key (k_s).
+        serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds());
+        byte[] escrowKeyBytes = serviceConnection.unwrap(decryptedBlob,
+                mInjector.getServiceTimeoutInSeconds());
+        serviceConnection.unbindService();
+
+        return escrowKeyBytes;
+    }
+
+    @Override
+    public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) {
+        byte[] serverBlob = mStorage.readRebootEscrowServerBlob();
+        // Delete the server blob in storage.
+        mStorage.removeRebootEscrowServerBlob();
+        if (serverBlob == null) {
+            Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
+            return null;
+        }
+
+        try {
+            byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey);
+            if (escrowKeyBytes == null) {
+                Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null");
+                return null;
+            } else if (escrowKeyBytes.length != 32) {
+                Slog.e(TAG, "Decrypted reboot escrow key has incorrect size "
+                        + escrowKeyBytes.length);
+                return null;
+            }
+
+            return RebootEscrowKey.fromKeyBytes(escrowKeyBytes);
+        } catch (TimeoutException | RemoteException | IOException e) {
+            Slog.w(TAG, "Failed to decrypt the server blob ", e);
+            return null;
+        }
+    }
+
+    @Override
+    public void clearRebootEscrowKey() {
+        mStorage.removeRebootEscrowServerBlob();
+    }
+
+    private byte[] wrapEscrowKey(byte[] escrowKeyBytes, SecretKey encryptionKey) throws
+            TimeoutException, RemoteException, IOException {
+        ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection();
+        if (serviceConnection == null) {
+            Slog.w(TAG, "Failed to encrypt the reboot escrow key: resume on reboot server"
+                    + " service is unavailable");
+            return null;
+        }
+
+        serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds());
+        // Ask the server connection service to encrypt the reboot escrow key.
+        byte[] serverEncryptedBlob = serviceConnection.wrapBlob(escrowKeyBytes,
+                mInjector.getServerBlobLifetimeInMillis(), mInjector.getServiceTimeoutInSeconds());
+        serviceConnection.unbindService();
+
+        if (serverEncryptedBlob == null) {
+            Slog.w(TAG, "Server encrypted reboot escrow key cannot be null");
+            return null;
+        }
+
+        // Additionally wrap the server blob with a local key.
+        return AesEncryptionUtil.encrypt(encryptionKey, serverEncryptedBlob);
+    }
+
+    @Override
+    public boolean storeRebootEscrowKey(RebootEscrowKey escrowKey, SecretKey encryptionKey) {
+        mStorage.removeRebootEscrowServerBlob();
+        try {
+            byte[] wrappedBlob = wrapEscrowKey(escrowKey.getKeyBytes(), encryptionKey);
+            if (wrappedBlob == null) {
+                Slog.w(TAG, "Failed to encrypt the reboot escrow key");
+                return false;
+            }
+            mStorage.writeRebootEscrowServerBlob(wrappedBlob);
+
+            Slog.i(TAG, "Reboot escrow key encrypted and stored.");
+            return true;
+        } catch (TimeoutException | RemoteException | IOException e) {
+            Slog.w(TAG, "Failed to encrypt the reboot escrow key ", e);
+        }
+
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
index e9868fd..4faa790 100644
--- a/services/core/java/com/android/server/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -27,6 +27,7 @@
 import android.annotation.Nullable;
 import android.net.INetd;
 import android.net.NetworkStats;
+import android.net.VpnInfo;
 import android.net.util.NetdService;
 import android.os.RemoteException;
 import android.os.StrictMode;
@@ -34,7 +35,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ProcFileReader;
 
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 81a6641..4be7b48 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -105,6 +105,7 @@
 import android.net.NetworkTemplate;
 import android.net.TrafficStats;
 import android.net.Uri;
+import android.net.VpnInfo;
 import android.net.netstats.provider.INetworkStatsProvider;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.net.netstats.provider.NetworkStatsProvider;
@@ -143,7 +144,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FileRotator;
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index a12932a8..de85d9e 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -30,9 +30,9 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.parsing.PackageInfoWithoutStateUtils;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.RemoteException;
@@ -508,7 +508,8 @@
 
             for (ApexInfo ai : allPkgs) {
                 File apexFile = new File(ai.modulePath);
-                parallelPackageParser.submit(apexFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
+                parallelPackageParser.submit(apexFile,
+                        ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
                 parsingApexInfo.put(apexFile, ai);
             }
 
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index ff3a12a..5373f99 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -24,7 +24,7 @@
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA256;
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
 import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
-import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
+import static android.content.pm.parsing.ApkLiteParseUtils.APK_FILE_EXTENSION;
 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
@@ -39,6 +39,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
 import android.content.pm.Signature;
+import android.content.pm.parsing.ApkLiteParseUtils;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.os.incremental.IncrementalManager;
@@ -171,7 +172,7 @@
      * @throws IllegalArgumentException if the code path is not an .apk.
      */
     public static String buildDigestsPathForApk(String codePath) {
-        if (!PackageParser.isApkPath(codePath)) {
+        if (!ApkLiteParseUtils.isApkPath(codePath)) {
             throw new IllegalStateException("Code path is not an apk " + codePath);
         }
         return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length())
diff --git a/services/core/java/com/android/server/pm/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java
index 780c522..f5ec595 100644
--- a/services/core/java/com/android/server/pm/IncrementalStates.java
+++ b/services/core/java/com/android/server/pm/IncrementalStates.java
@@ -61,12 +61,12 @@
 
     public IncrementalStates() {
         // By default the package is not startable and not fully loaded (i.e., is loading)
-        this(false, true);
+        this(false, true, 0);
     }
 
-    public IncrementalStates(boolean isStartable, boolean isLoading) {
+    public IncrementalStates(boolean isStartable, boolean isLoading, float loadingProgress) {
         mStartableState = new StartableState(isStartable);
-        mLoadingState = new LoadingState(isLoading);
+        mLoadingState = new LoadingState(isLoading, loadingProgress);
         mStatusConsumer = new StatusConsumer();
     }
 
@@ -405,9 +405,10 @@
         private boolean mIsLoading;
         private float mProgress;
 
-        LoadingState(boolean isLoading) {
+        LoadingState(boolean isLoading, float loadingProgress) {
             mIsLoading = isLoading;
-            mProgress = isLoading ? 0 : 1;
+            // loading progress is reset to 1 if loading has finished
+            mProgress = isLoading ? loadingProgress : 1;
         }
 
         public boolean isLoading() {
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 71b99bd..13fe8a0 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -17,7 +17,7 @@
 package com.android.server.pm;
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-import static android.content.pm.PackageParser.isApkFile;
+import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.incremental.IncrementalManager.isIncrementalPath;
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index e143bd0..e218dc1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -81,11 +81,12 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.dex.DexMetadataHelper;
+import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.graphics.Bitmap;
@@ -2671,16 +2672,17 @@
 
         // Populate package name of the apex session
         mPackageName = null;
-        final ApkLite apk;
-        try {
-            apk = PackageParser.parseApkLite(
-                    mResolvedBaseFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
-        } catch (PackageParserException e) {
-            throw PackageManagerException.from(e);
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(),
+                mResolvedBaseFile, ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
+        if (ret.isError()) {
+            throw new PackageManagerException(ret.getErrorCode(), ret.getErrorMessage(),
+                    ret.getException());
         }
+        final ApkLite apk = ret.getResult();
 
         if (mPackageName == null) {
-            mPackageName = apk.packageName;
+            mPackageName = apk.getPackageName();
             mVersionCode = apk.getLongVersionCode();
         }
     }
@@ -2745,29 +2747,29 @@
 
         // Verify that all staged packages are internally consistent
         final ArraySet<String> stagedSplits = new ArraySet<>();
-        final ArrayMap<String, PackageParser.ApkLite> splitApks = new ArrayMap<>();
-        ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ArrayMap<String, ApkLite> splitApks = new ArrayMap<>();
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
         for (File addedFile : addedFiles) {
-            ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),
-                    addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
+            final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),
+                    addedFile, ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
             if (result.isError()) {
                 throw new PackageManagerException(result.getErrorCode(),
                         result.getErrorMessage(), result.getException());
             }
 
             final ApkLite apk = result.getResult();
-            if (!stagedSplits.add(apk.splitName)) {
+            if (!stagedSplits.add(apk.getSplitName())) {
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
-                        "Split " + apk.splitName + " was defined multiple times");
+                        "Split " + apk.getSplitName() + " was defined multiple times");
             }
 
             // Use first package to define unknown values
             if (mPackageName == null) {
-                mPackageName = apk.packageName;
+                mPackageName = apk.getPackageName();
                 mVersionCode = apk.getLongVersionCode();
             }
             if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
-                mSigningDetails = apk.signingDetails;
+                mSigningDetails = apk.getSigningDetails();
             }
 
             assertApkConsistentLocked(String.valueOf(addedFile), apk);
@@ -2780,10 +2782,10 @@
             }
 
             // Yell loudly if installers drop attribute installLocation when apps explicitly set.
-            if (apk.installLocation != PackageInfo.INSTALL_LOCATION_UNSPECIFIED) {
+            if (apk.getInstallLocation() != PackageInfo.INSTALL_LOCATION_UNSPECIFIED) {
                 final String installerPackageName = getInstallerPackageName();
                 if (installerPackageName != null
-                        && (params.installLocation != apk.installLocation)) {
+                        && (params.installLocation != apk.getInstallLocation())) {
                     Slog.wtf(TAG, installerPackageName
                             + " drops manifest attribute android:installLocation in " + targetName
                             + " for " + mPackageName);
@@ -2791,14 +2793,14 @@
             }
 
             final File targetFile = new File(stageDir, targetName);
-            resolveAndStageFileLocked(addedFile, targetFile, apk.splitName);
+            resolveAndStageFileLocked(addedFile, targetFile, apk.getSplitName());
 
             // Base is coming from session
-            if (apk.splitName == null) {
+            if (apk.getSplitName() == null) {
                 mResolvedBaseFile = targetFile;
                 baseApk = apk;
             } else {
-                splitApks.put(apk.splitName, apk);
+                splitApks.put(apk.getSplitName(), apk);
             }
         }
 
@@ -2854,7 +2856,7 @@
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Full install must include a base package");
             }
-            if (baseApk.isSplitRequired && stagedSplits.size() <= 1) {
+            if (baseApk.isSplitRequired() && stagedSplits.size() <= 1) {
                 throw new PackageManagerException(INSTALL_FAILED_MISSING_SPLIT,
                         "Missing split for " + mPackageName);
             }
@@ -2879,10 +2881,10 @@
             }
             final PackageLite existing = pkgLiteResult.getResult();
             packageLite = existing;
-            assertPackageConsistentLocked("Existing", existing.packageName,
+            assertPackageConsistentLocked("Existing", existing.getPackageName(),
                     existing.getLongVersionCode());
             final PackageParser.SigningDetails signingDetails =
-                    unsafeGetCertsWithoutVerification(existing.baseCodePath);
+                    unsafeGetCertsWithoutVerification(existing.getBaseApkPath());
             if (!mSigningDetails.signaturesMatchExactly(signingDetails)) {
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Existing signatures are inconsistent");
@@ -2895,10 +2897,10 @@
             }
 
             // Inherit splits if not overridden.
-            if (!ArrayUtils.isEmpty(existing.splitNames)) {
-                for (int i = 0; i < existing.splitNames.length; i++) {
-                    final String splitName = existing.splitNames[i];
-                    final File splitFile = new File(existing.splitCodePaths[i]);
+            if (!ArrayUtils.isEmpty(existing.getSplitNames())) {
+                for (int i = 0; i < existing.getSplitNames().length; i++) {
+                    final String splitName = existing.getSplitNames()[i];
+                    final File splitFile = new File(existing.getSplitApkPaths()[i]);
                     final boolean splitRemoved = removeSplitList.contains(splitName);
                     if (!stagedSplits.contains(splitName) && !splitRemoved) {
                         inheritFileLocked(splitFile);
@@ -2978,8 +2980,8 @@
                 }
             }
             // For the case of split required, failed if no splits existed
-            if (packageLite.isSplitRequired) {
-                final int existingSplits = ArrayUtils.size(existing.splitNames);
+            if (packageLite.isSplitRequired()) {
+                final int existingSplits = ArrayUtils.size(existing.getSplitNames());
                 final boolean allSplitsRemoved = (existingSplits == removeSplitList.size());
                 final boolean onlyBaseFileStaged = (stagedSplits.size() == 1
                         && stagedSplits.contains(null));
@@ -2989,7 +2991,7 @@
                 }
             }
         }
-        if (packageLite.useEmbeddedDex) {
+        if (packageLite.isUseEmbeddedDex()) {
             for (File file : mResolvedStagedFiles) {
                 if (file.getName().endsWith(".apk")
                         && !DexManager.auditUncompressedDexInApk(file.getPath())) {
@@ -3002,7 +3004,7 @@
 
         final boolean isInstallerShell = (mInstallerUid == Process.SHELL_UID);
         if (isInstallerShell && isIncrementalInstallation() && mIncrementalFileStorages != null) {
-            if (!packageLite.debuggable && !packageLite.profilableByShell) {
+            if (!packageLite.isDebuggable() && !packageLite.isProfileableByShell()) {
                 mIncrementalFileStorages.disallowReadLogs();
             }
         }
@@ -3174,8 +3176,8 @@
     @GuardedBy("mLock")
     private void assertApkConsistentLocked(String tag, ApkLite apk)
             throws PackageManagerException {
-        assertPackageConsistentLocked(tag, apk.packageName, apk.getLongVersionCode());
-        if (!mSigningDetails.signaturesMatchExactly(apk.signingDetails)) {
+        assertPackageConsistentLocked(tag, apk.getPackageName(), apk.getLongVersionCode());
+        if (!mSigningDetails.signaturesMatchExactly(apk.getSigningDetails())) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     tag + " signatures are inconsistent");
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 68b0698..50aacd6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -101,7 +101,7 @@
 import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
 import static android.content.pm.PackageManagerInternal.LAST_KNOWN_PACKAGE;
 import static android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
-import static android.content.pm.PackageParser.isApkFile;
+import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.incremental.IncrementalManager.isIncrementalPath;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
@@ -211,9 +211,7 @@
 import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PackageManagerInternal.PrivateResolveFlags;
 import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser.ParseFlags;
 import android.content.pm.PackageParser.SigningDetails;
 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.PackagePartitions;
@@ -241,7 +239,9 @@
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.dex.IArtManager;
 import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingPackageUtils.ParseFlags;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedInstrumentation;
 import android.content.pm.parsing.component.ParsedIntentInfo;
@@ -6251,7 +6251,7 @@
 
         if (separateProcesses != null && separateProcesses.length() > 0) {
             if ("*".equals(separateProcesses)) {
-                mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES;
+                mDefParseFlags = ParsingPackageUtils.PARSE_IGNORE_PROCESSES;
                 mSeparateProcesses = null;
                 Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
             } else {
@@ -6451,7 +6451,7 @@
                 scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
             }
 
-            final int systemParseFlags = mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR;
+            final int systemParseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
             final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM;
 
             PackageParser2 packageParser = injector.getScanningCachingPackageParser();
@@ -7091,7 +7091,7 @@
      */
     private boolean enableCompressedPackage(AndroidPackage stubPkg,
             @NonNull PackageSetting stubPkgSetting) {
-        final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
+        final int parseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_CHATTY
                 | PackageParser.PARSE_ENFORCE_CODE;
         synchronized (mInstallLock) {
             final AndroidPackage pkg;
@@ -11317,7 +11317,8 @@
             @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
             @Nullable UserHandle user)
                     throws PackageManagerException {
-        final boolean scanSystemPartition = (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0;
+        final boolean scanSystemPartition =
+                (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
         final String renamedPkgName;
         final PackageSetting disabledPkgSetting;
         final boolean isSystemPkgUpdated;
@@ -11360,7 +11361,7 @@
                             0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true)
                     : null;
             if (DEBUG_PACKAGE_SCANNING
-                    && (parseFlags & PackageParser.PARSE_CHATTY) != 0
+                    && (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0
                     && sharedUserSetting != null) {
                 Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId()
                         + " (uid=" + sharedUserSetting.userId + "):"
@@ -13179,10 +13180,11 @@
                 sharedUserSetting = mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(),
                         0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
                 if (DEBUG_PACKAGE_SCANNING) {
-                    if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
+                    if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) {
                         Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId()
                                 + " (uid=" + sharedUserSetting.userId + "):"
                                 + " packages=" + sharedUserSetting.packages);
+                    }
                 }
             }
             String platformPackageName = mPlatformPackage == null
@@ -13339,7 +13341,7 @@
         final int userId = user == null ? 0 : user.getIdentifier();
         // Modify state for the given package setting
         commitPackageSettings(pkg, oldPkg, pkgSetting, scanFlags,
-                (parseFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
+                (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
         if (pkgSetting.getInstantApp(userId)) {
             mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId);
         }
@@ -13548,8 +13550,9 @@
         List<String> changedAbiCodePath = null;
 
         if (DEBUG_PACKAGE_SCANNING) {
-            if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
+            if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) {
                 Log.d(TAG, "Scanning package " + parsedPackage.getPackageName());
+            }
         }
 
         // Initialize package source and resource directories
@@ -13816,7 +13819,7 @@
         } else if (pkgSetting.firstInstallTime == 0) {
             // We need *something*.  Take time time stamp of the file.
             pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime;
-        } else if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+        } else if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0) {
             if (scanFileTime != pkgSetting.timeStamp) {
                 // A package on the system image has changed; consider this
                 // to be an update.
@@ -14013,7 +14016,7 @@
     private void assertPackageIsValid(AndroidPackage pkg, final @ParseFlags int parseFlags,
             final @ScanFlags int scanFlags)
                     throws PackageManagerException {
-        if ((parseFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
+        if ((parseFlags & ParsingPackageUtils.PARSE_ENFORCE_CODE) != 0) {
             assertCodePolicy(pkg);
         }
 
@@ -14270,7 +14273,7 @@
                 if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
                     // We are scanning a system overlay. This can be the first scan of the
                     // system/vendor/oem partition, or an update to the system overlay.
-                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
                         // This must be an update to a system overlay. Immutable overlays cannot be
                         // upgraded.
                         Objects.requireNonNull(mOverlayConfig,
@@ -14350,7 +14353,7 @@
 
             // If the package is not on a system partition ensure it is signed with at least the
             // minimum signature scheme version required for its target SDK.
-            if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+            if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
                 int minSignatureSchemeVersion =
                         ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
                                 pkg.getTargetSdkVersion());
@@ -18015,11 +18018,12 @@
             // Try enumerating all code paths before deleting
             List<String> allCodePaths = Collections.EMPTY_LIST;
             if (codeFile != null && codeFile.exists()) {
-                try {
-                    final PackageLite pkg = PackageParser.parsePackageLite(codeFile, 0);
-                    allCodePaths = pkg.getAllCodePaths();
-                } catch (PackageParserException e) {
-                    // Ignored; we tried our best
+                final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+                final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+                        input.reset(), codeFile, /* flags */ 0);
+                if (result.isSuccess()) {
+                    // Ignore error; we tried our best
+                    allCodePaths = result.getResult().getAllApkPaths();
                 }
             }
 
@@ -18637,7 +18641,7 @@
                     // We just determined the app is signed correctly, so bring
                     // over the latest parsed certs.
                 } else {
-                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
                         throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                                 "Package " + parsedPackage.getPackageName()
                                         + " upgrade keys do not match the previously installed"
@@ -18687,7 +18691,7 @@
                         }
                     }
                 } catch (PackageManagerException e) {
-                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
                         throw new ReconcileFailure(e);
                     }
                     signingDetails = parsedPackage.getSigningDetails();
@@ -18766,7 +18770,8 @@
             // apps are scanned to avoid dependency based scanning.
             final ScanResult scanResult = scannedPackages.get(installPackageName);
             if ((scanResult.request.scanFlags & SCAN_BOOTING) != 0
-                    || (scanResult.request.parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+                    || (scanResult.request.parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+                    != 0) {
                 continue;
             }
             try {
@@ -19652,9 +19657,9 @@
         }
 
         // Retrieve PackageSettings and parse package
-        @ParseFlags final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
-                | PackageParser.PARSE_ENFORCE_CODE
-                | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
+        @ParseFlags final int parseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_CHATTY
+                | ParsingPackageUtils.PARSE_ENFORCE_CODE
+                | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0);
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
         final ParsedPackage parsedPackage;
@@ -21384,8 +21389,8 @@
         final File codePath = new File(codePathString);
         @ParseFlags int parseFlags =
                 mDefParseFlags
-                | PackageParser.PARSE_MUST_BE_APK
-                | PackageParser.PARSE_IS_SYSTEM_DIR;
+                | ParsingPackageUtils.PARSE_MUST_BE_APK
+                | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
         @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
         for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
             ScanPartition partition = mDirsToScanAsSystem.get(i);
@@ -24837,7 +24842,7 @@
 
         final ArrayList<PackageFreezer> freezers = new ArrayList<>();
         final ArrayList<AndroidPackage> loaded = new ArrayList<>();
-        final int parseFlags = mDefParseFlags | PackageParser.PARSE_EXTERNAL_STORAGE;
+        final int parseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_EXTERNAL_STORAGE;
 
         final VersionInfo ver;
         final List<PackageSetting> packages;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index d3d7c60..ee94b85 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -35,9 +35,12 @@
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Build;
 import android.os.Debug;
 import android.os.Environment;
@@ -819,8 +822,8 @@
     /**
      * Parse given package and return minimal details.
      */
-    public static PackageInfoLite getMinimalPackageInfo(Context context,
-            PackageParser.PackageLite pkg, String packagePath, int flags, String abiOverride) {
+    public static PackageInfoLite getMinimalPackageInfo(Context context, PackageLite pkg,
+            String packagePath, int flags, String abiOverride) {
         final PackageInfoLite ret = new PackageInfoLite();
         if (packagePath == null || pkg == null) {
             Slog.i(TAG, "Invalid package file " + packagePath);
@@ -843,19 +846,19 @@
         }
 
         final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
-                pkg.packageName, pkg.installLocation, sizeBytes, flags);
+                pkg.getPackageName(), pkg.getInstallLocation(), sizeBytes, flags);
 
-        ret.packageName = pkg.packageName;
-        ret.splitNames = pkg.splitNames;
-        ret.versionCode = pkg.versionCode;
-        ret.versionCodeMajor = pkg.versionCodeMajor;
-        ret.baseRevisionCode = pkg.baseRevisionCode;
-        ret.splitRevisionCodes = pkg.splitRevisionCodes;
-        ret.installLocation = pkg.installLocation;
-        ret.verifiers = pkg.verifiers;
+        ret.packageName = pkg.getPackageName();
+        ret.splitNames = pkg.getSplitNames();
+        ret.versionCode = pkg.getVersionCode();
+        ret.versionCodeMajor = pkg.getVersionCodeMajor();
+        ret.baseRevisionCode = pkg.getBaseRevisionCode();
+        ret.splitRevisionCodes = pkg.getSplitRevisionCodes();
+        ret.installLocation = pkg.getInstallLocation();
+        ret.verifiers = pkg.getVerifiers();
         ret.recommendedInstallLocation = recommendedInstallLocation;
-        ret.multiArch = pkg.multiArch;
-        ret.debuggable = pkg.debuggable;
+        ret.multiArch = pkg.isMultiArch();
+        ret.debuggable = pkg.isDebuggable();
 
         return ret;
     }
@@ -868,11 +871,16 @@
      */
     public static long calculateInstalledSize(String packagePath, String abiOverride) {
         final File packageFile = new File(packagePath);
-        final PackageParser.PackageLite pkg;
         try {
-            pkg = PackageParser.parsePackageLite(packageFile, 0);
-            return PackageHelper.calculateInstalledSize(pkg, abiOverride);
-        } catch (PackageParserException | IOException e) {
+            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+                    input.reset(), packageFile, /* flags */ 0);
+            if (result.isError()) {
+                throw new PackageManagerException(result.getErrorCode(),
+                        result.getErrorMessage(), result.getException());
+            }
+            return PackageHelper.calculateInstalledSize(result.getResult(), abiOverride);
+        } catch (PackageManagerException | IOException e) {
             Slog.w(TAG, "Failed to calculate installed size: " + e);
             return -1;
         }
@@ -931,16 +939,23 @@
 
         try {
             final File packageFile = new File(packagePath);
-            final PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packageFile, 0);
-            copyFile(pkg.baseCodePath, targetDir, "base.apk");
-            if (!ArrayUtils.isEmpty(pkg.splitNames)) {
-                for (int i = 0; i < pkg.splitNames.length; i++) {
-                    copyFile(pkg.splitCodePaths[i], targetDir,
-                            "split_" + pkg.splitNames[i] + ".apk");
+            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+                    input.reset(), packageFile, /* flags */ 0);
+            if (result.isError()) {
+                Slog.w(TAG, "Failed to parse package at " + packagePath);
+                return result.getErrorCode();
+            }
+            final PackageLite pkg = result.getResult();
+            copyFile(pkg.getBaseApkPath(), targetDir, "base.apk");
+            if (!ArrayUtils.isEmpty(pkg.getSplitNames())) {
+                for (int i = 0; i < pkg.getSplitNames().length; i++) {
+                    copyFile(pkg.getSplitApkPaths()[i], targetDir,
+                            "split_" + pkg.getSplitNames()[i] + ".apk");
                 }
             }
             return PackageManager.INSTALL_SUCCEEDED;
-        } catch (PackageParserException | IOException | ErrnoException e) {
+        } catch (IOException | ErrnoException e) {
             Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
             return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 446342a..3207d56a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -49,8 +49,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
@@ -61,7 +59,9 @@
 import android.content.pm.dex.ArtManager;
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
+import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.AssetManager;
@@ -555,8 +555,8 @@
                             apkLiteResult.getException());
                 }
                 final ApkLite apkLite = apkLiteResult.getResult();
-                PackageLite pkgLite = new PackageLite(null, apkLite.codePath, apkLite, null, null,
-                        null, null, null, null);
+                final PackageLite pkgLite = new PackageLite(null, apkLite.getPath(), apkLite, null,
+                        null, null, null, null, null);
                 sessionSize += PackageHelper.calculateInstalledSize(pkgLite,
                         params.sessionParams.abiOverride, fd.getFileDescriptor());
             } catch (IOException e) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3369a4f..2929568 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -73,6 +73,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.incremental.IncrementalManager;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.service.pm.PackageServiceDumpProto;
@@ -2957,6 +2958,8 @@
         if (pkg.isPackageLoading()) {
             serializer.attributeBoolean(null, "isLoading", true);
         }
+        serializer.attributeFloat(null, "loadingProgress",
+                pkg.getIncrementalStates().getProgress());
 
         writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
 
@@ -3699,6 +3702,7 @@
         boolean installedForceQueryable = false;
         boolean isStartable = false;
         boolean isLoading = false;
+        float loadingProgress = 0;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
@@ -3717,6 +3721,7 @@
             installedForceQueryable = parser.getAttributeBoolean(null, "forceQueryable", false);
             isStartable = parser.getAttributeBoolean(null, "isStartable", false);
             isLoading = parser.getAttributeBoolean(null, "isLoading", false);
+            loadingProgress = parser.getAttributeFloat(null, "loadingProgress", 0);
 
             if (primaryCpuAbiString == null && legacyCpuAbiString != null) {
                 primaryCpuAbiString = legacyCpuAbiString;
@@ -3864,7 +3869,8 @@
             packageSetting.secondaryCpuAbiString = secondaryCpuAbiString;
             packageSetting.updateAvailable = updateAvailable;
             packageSetting.forceQueryableOverride = installedForceQueryable;
-            packageSetting.incrementalStates = new IncrementalStates(isStartable, isLoading);
+            packageSetting.incrementalStates = new IncrementalStates(isStartable, isLoading,
+                    loadingProgress);
             // Handle legacy string here for single-user mode
             final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
             if (enabledStr != null) {
@@ -4814,6 +4820,10 @@
             pw.print(prefix); pw.print("  installerAttributionTag=");
             pw.println(ps.installSource.installerAttributionTag);
         }
+        if (IncrementalManager.isIncrementalPath(ps.getPathString())) {
+            pw.print(prefix); pw.println("  loadingProgress="
+                    + (int) (ps.getIncrementalStates().getProgress() * 100) + "%");
+        }
         if (ps.volumeUuid != null) {
             pw.print(prefix); pw.print("  volumeUuid=");
                     pw.println(ps.volumeUuid);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 314510b..f43240b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -47,6 +47,7 @@
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.os.Binder;
@@ -92,6 +93,7 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.util.TypedValue;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
@@ -139,6 +141,7 @@
 import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Service for {@link UserManager}.
@@ -461,6 +464,26 @@
         }
     };
 
+    /**
+     * Cache the owner name string, since it could be read repeatedly on a critical code path
+     * but hit by slow IO. This could be eliminated once we have the cached UserInfo in place.
+     */
+    private final AtomicReference<String> mOwnerName = new AtomicReference<>();
+
+    private final TypedValue mOwnerNameTypedValue = new TypedValue();
+
+    private final Configuration mLastConfiguration = new Configuration();
+
+    private final BroadcastReceiver mConfigurationChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+            invalidateOwnerNameIfNecessary(context.getResources(), false /* forceUpdate */);
+        }
+    };
+
     // TODO(b/161915546): remove once userWithName() is fixed / removed
     // Use to debug / dump when user 0 is allocated at userWithName()
     public static final boolean DBG_ALLOCATION = false; // DO NOT SUBMIT WITH TRUE
@@ -636,6 +659,7 @@
         mHandler = new MainHandler();
         mUserDataPreparer = userDataPreparer;
         mUserTypes = UserTypeFactory.getUserTypes();
+        invalidateOwnerNameIfNecessary(context.getResources(), true /* forceUpdate */);
         synchronized (mPackagesLock) {
             mUsersDir = new File(dataDir, USER_INFO_DIR);
             mUsersDir.mkdirs();
@@ -669,6 +693,10 @@
         mContext.registerReceiver(mDisableQuietModeCallback,
                 new IntentFilter(ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK),
                 null, mHandler);
+
+        mContext.registerReceiver(mConfigurationChangeReceiver,
+                new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED),
+                null, mHandler);
     }
 
     /**
@@ -2851,7 +2879,16 @@
     }
 
     private String getOwnerName() {
-        return mContext.getResources().getString(com.android.internal.R.string.owner_name);
+        return mOwnerName.get();
+    }
+
+    private void invalidateOwnerNameIfNecessary(@NonNull Resources res, boolean forceUpdate) {
+        final int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
+        if (forceUpdate || (configChanges & mOwnerNameTypedValue.changingConfigurations) != 0) {
+            res.getValue(com.android.internal.R.string.owner_name, mOwnerNameTypedValue, true);
+            final CharSequence ownerName = mOwnerNameTypedValue.coerceToString();
+            mOwnerName.set(ownerName != null ? ownerName.toString() : null);
+        }
     }
 
     private void scheduleWriteUser(UserData userData) {
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java
index a13680a..471a4d3 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageParser;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.parsing.ParsingPackageRead;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.component.ParsedAttribution;
 import android.content.pm.parsing.component.ParsedIntentInfo;
 import android.content.pm.parsing.component.ParsedPermissionGroup;
@@ -56,7 +57,7 @@
 
     /**
      * The names of packages to adopt ownership of permissions from, parsed under
-     * {@link PackageParser#TAG_ADOPT_PERMISSIONS}.
+     * {@link ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}.
      * @see R.styleable#AndroidManifestOriginalPackage_name
      */
     @NonNull
@@ -84,7 +85,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in
-     * {@link PackageParser#TAG_KEY_SETS}.
+     * {@link ParsingPackageUtils#TAG_KEY_SETS}.
      * @see R.styleable#AndroidManifestKeySet
      * @see R.styleable#AndroidManifestPublicKey
      */
@@ -230,7 +231,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in
-     * {@link PackageParser#TAG_KEY_SETS}.
+     * {@link ParsingPackageUtils#TAG_KEY_SETS}.
      * @see R.styleable#AndroidManifestUpgradeKeySet
      */
     @NonNull
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index ab25a7c..37dfea4 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -21,12 +21,12 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.VersionedPackage;
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.parsing.ParsingPackageRead;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedInstrumentation;
 import android.content.pm.parsing.component.ParsedProvider;
@@ -233,7 +233,7 @@
     }
 
     public static int getIcon(ParsingPackageRead pkg) {
-        return (PackageParser.sUseRoundIcon && pkg.getRoundIconRes() != 0)
+        return (ParsingPackageUtils.sUseRoundIcon && pkg.getRoundIconRes() != 0)
                 ? pkg.getRoundIconRes() : pkg.getIconRes();
     }
 
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index bd66aa3..a4459d0 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -33,9 +33,9 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.VersionedPackage;
+import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
@@ -831,24 +831,24 @@
         }
 
         // Get information about the package to be installed.
-        ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-        ParseResult<PackageParser.ApkLite> parseResult = ApkLiteParseUtils.parseApkLite(
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ParseResult<ApkLite> parseResult = ApkLiteParseUtils.parseApkLite(
                 input.reset(), new File(session.resolvedBaseCodePath), 0);
         if (parseResult.isError()) {
             Slog.e(TAG, "Unable to parse new package: " + parseResult.getErrorMessage(),
                     parseResult.getException());
             return false;
         }
-        PackageParser.ApkLite newPackage = parseResult.getResult();
+        final ApkLite newPackage = parseResult.getResult();
 
-        String packageName = newPackage.packageName;
-        int rollbackDataPolicy = computeRollbackDataPolicy(
-                session.rollbackDataPolicy, newPackage.rollbackDataPolicy);
+        final String packageName = newPackage.getPackageName();
+        final int rollbackDataPolicy = computeRollbackDataPolicy(
+                session.rollbackDataPolicy, newPackage.getRollbackDataPolicy());
         Slog.i(TAG, "Enabling rollback for install of " + packageName
                 + ", session:" + session.sessionId
                 + ", rollbackDataPolicy=" + rollbackDataPolicy);
 
-        String installerPackageName = session.getInstallerPackageName();
+        final String installerPackageName = session.getInstallerPackageName();
         if (!enableRollbackAllowed(installerPackageName, packageName)) {
             Slog.e(TAG, "Installer " + installerPackageName
                     + " is not allowed to enable rollback on " + packageName);
@@ -900,7 +900,7 @@
          * a rollback object is inconsistent because it doesn't count apk-in-apex.
          */
         ApplicationInfo appInfo = pkgInfo.applicationInfo;
-        return rollback.enableForPackage(packageName, newPackage.versionCode,
+        return rollback.enableForPackage(packageName, newPackage.getVersionCode(),
                 pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
                 appInfo.splitSourceDirs, rollbackDataPolicy);
     }
diff --git a/services/core/java/com/android/server/rotationresolver/OWNERS b/services/core/java/com/android/server/rotationresolver/OWNERS
new file mode 100644
index 0000000..81b6f05
--- /dev/null
+++ b/services/core/java/com/android/server/rotationresolver/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/rotationresolver/OWNERS
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
index 90ac69a..cf7460b 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
@@ -42,7 +42,7 @@
     static final String TAG = "SoundTriggerHw2Enforcer";
 
     final ISoundTriggerHw2 mUnderlying;
-    final Map<Integer, Boolean> mModelStates = new HashMap<>();
+    Map<Integer, Boolean> mModelStates = new HashMap<>();
 
     public SoundTriggerHw2Enforcer(
             ISoundTriggerHw2 underlying) {
@@ -62,12 +62,12 @@
     public int loadSoundModel(ISoundTriggerHw.SoundModel soundModel, Callback callback,
             int cookie) {
         try {
+            int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback),
+                    cookie);
             synchronized (mModelStates) {
-                int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback),
-                        cookie);
                 mModelStates.put(handle, false);
-                return handle;
             }
+            return handle;
         } catch (RuntimeException e) {
             throw handleException(e);
         }
@@ -77,13 +77,13 @@
     public int loadPhraseSoundModel(ISoundTriggerHw.PhraseSoundModel soundModel, Callback callback,
             int cookie) {
         try {
+            int handle = mUnderlying.loadPhraseSoundModel(soundModel,
+                    new CallbackEnforcer(callback),
+                    cookie);
             synchronized (mModelStates) {
-                int handle = mUnderlying.loadPhraseSoundModel(soundModel,
-                        new CallbackEnforcer(callback),
-                        cookie);
                 mModelStates.put(handle, false);
-                return handle;
             }
+            return handle;
         } catch (RuntimeException e) {
             throw handleException(e);
         }
@@ -92,8 +92,8 @@
     @Override
     public void unloadSoundModel(int modelHandle) {
         try {
+            mUnderlying.unloadSoundModel(modelHandle);
             synchronized (mModelStates) {
-                mUnderlying.unloadSoundModel(modelHandle);
                 mModelStates.remove(modelHandle);
             }
         } catch (RuntimeException e) {
@@ -104,8 +104,8 @@
     @Override
     public void stopRecognition(int modelHandle) {
         try {
+            mUnderlying.stopRecognition(modelHandle);
             synchronized (mModelStates) {
-                mUnderlying.stopRecognition(modelHandle);
                 mModelStates.replace(modelHandle, false);
             }
         } catch (RuntimeException e) {
@@ -116,8 +116,8 @@
     @Override
     public void stopAllRecognitions() {
         try {
+            mUnderlying.stopAllRecognitions();
             synchronized (mModelStates) {
-                mUnderlying.stopAllRecognitions();
                 for (Map.Entry<Integer, Boolean> entry : mModelStates.entrySet()) {
                     entry.setValue(false);
                 }
@@ -130,12 +130,14 @@
     @Override
     public void startRecognition(int modelHandle, RecognitionConfig config, Callback callback,
             int cookie) {
+        // It is possible that an event will be sent before the HAL returns from the
+        // startRecognition call, thus it is important to set the state to active before the call.
+        synchronized (mModelStates) {
+            mModelStates.replace(modelHandle, true);
+        }
         try {
-            synchronized (mModelStates) {
-                mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback),
-                        cookie);
-                mModelStates.replace(modelHandle, true);
-            }
+            mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback),
+                    cookie);
         } catch (RuntimeException e) {
             throw handleException(e);
         }
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 0fa97a2..8805fa2 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -122,7 +122,9 @@
     private static final int TOKEN_ALL = Integer.MIN_VALUE;
 
     private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
-    private static final int TEARDOWN_TIMEOUT_SECONDS = 5;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int TEARDOWN_TIMEOUT_SECONDS = 5;
 
     private interface EventInfo {}
 
@@ -413,13 +415,6 @@
     private int mCurrentToken = -1;
 
     /**
-     * The next usable token.
-     *
-     * <p>A new token MUST be used for all new IKE sessions.
-     */
-    private int mNextToken = 0;
-
-    /**
      * The number of unsuccessful attempts since the last successful connection.
      *
      * <p>This number MUST be incremented each time the RetryTimeout state is entered, and cleared
@@ -440,7 +435,7 @@
      * <p>Set in Connecting or Migrating States, always @NonNull in Connecting, Connected, and
      * Migrating states, null otherwise.
      */
-    private IkeSession mIkeSession;
+    private VcnIkeSession mIkeSession;
 
     /**
      * The last known child configuration.
@@ -774,7 +769,70 @@
      */
     private class DisconnectingState extends ActiveBaseState {
         @Override
-        protected void processStateMsg(Message msg) {}
+        protected void enterState() throws Exception {
+            if (mIkeSession == null) {
+                Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state.");
+                sendMessage(EVENT_SESSION_CLOSED, mCurrentToken);
+                return;
+            }
+
+            // If underlying network has already been lost, save some time and just kill the session
+            if (mUnderlying == null) {
+                // Will trigger a EVENT_SESSION_CLOSED as IkeSession shuts down.
+                mIkeSession.kill();
+                return;
+            }
+
+            sendMessageDelayed(
+                    EVENT_TEARDOWN_TIMEOUT_EXPIRED,
+                    mCurrentToken,
+                    TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+        }
+
+        @Override
+        protected void processStateMsg(Message msg) {
+            switch (msg.what) {
+                case EVENT_UNDERLYING_NETWORK_CHANGED: // Fallthrough
+                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
+
+                    // If we received a new underlying network, continue.
+                    if (mUnderlying != null) {
+                        break;
+                    }
+
+                    // Fallthrough; no network exists to send IKE close session requests.
+                case EVENT_TEARDOWN_TIMEOUT_EXPIRED:
+                    // Grace period ended. Kill session, triggering EVENT_SESSION_CLOSED
+                    mIkeSession.kill();
+
+                    break;
+                case EVENT_DISCONNECT_REQUESTED:
+                    teardownNetwork();
+
+                    String reason = ((EventDisconnectRequestedInfo) msg.obj).reason;
+                    if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
+                        // Will trigger EVENT_SESSION_CLOSED immediately.
+                        mIkeSession.kill();
+                        break;
+                    }
+
+                    // Otherwise we are already in the process of shutting down.
+                    break;
+                case EVENT_SESSION_CLOSED:
+                    mIkeSession = null;
+
+                    if (mIsRunning && mUnderlying != null) {
+                        transitionTo(mRetryTimeoutState);
+                    } else {
+                        teardownNetwork();
+                        transitionTo(mDisconnectedState);
+                    }
+                    break;
+                default:
+                    logUnhandledMessage(msg);
+                    break;
+            }
+        }
     }
 
     /**
@@ -946,6 +1004,38 @@
         mIsRunning = isRunning;
     }
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    VcnIkeSession getIkeSession() {
+        return mIkeSession;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setIkeSession(@Nullable VcnIkeSession session) {
+        mIkeSession = session;
+    }
+
+    private IkeSessionParams buildIkeParams() {
+        // TODO: Implement this with ConnectingState
+        return null;
+    }
+
+    private ChildSessionParams buildChildParams() {
+        // TODO: Implement this with ConnectingState
+        return null;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    VcnIkeSession buildIkeSession() {
+        final int token = ++mCurrentToken;
+
+        return mDeps.newIkeSession(
+                mVcnContext,
+                buildIkeParams(),
+                buildChildParams(),
+                new IkeSessionCallbackImpl(token),
+                new ChildSessionCallbackImpl(token));
+    }
+
     /** External dependencies used by VcnGatewayConnection, for injection in tests */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public static class Dependencies {
@@ -958,19 +1048,67 @@
         }
 
         /** Builds a new IkeSession. */
-        public IkeSession newIkeSession(
+        public VcnIkeSession newIkeSession(
                 VcnContext vcnContext,
                 IkeSessionParams ikeSessionParams,
                 ChildSessionParams childSessionParams,
                 IkeSessionCallback ikeSessionCallback,
                 ChildSessionCallback childSessionCallback) {
-            return new IkeSession(
-                    vcnContext.getContext(),
+            return new VcnIkeSession(
+                    vcnContext,
                     ikeSessionParams,
                     childSessionParams,
-                    new HandlerExecutor(new Handler(vcnContext.getLooper())),
                     ikeSessionCallback,
                     childSessionCallback);
         }
     }
+
+    /** Proxy implementation of IKE session, used for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnIkeSession {
+        private final IkeSession mImpl;
+
+        public VcnIkeSession(
+                VcnContext vcnContext,
+                IkeSessionParams ikeSessionParams,
+                ChildSessionParams childSessionParams,
+                IkeSessionCallback ikeSessionCallback,
+                ChildSessionCallback childSessionCallback) {
+            mImpl =
+                    new IkeSession(
+                            vcnContext.getContext(),
+                            ikeSessionParams,
+                            childSessionParams,
+                            new HandlerExecutor(new Handler(vcnContext.getLooper())),
+                            ikeSessionCallback,
+                            childSessionCallback);
+        }
+
+        /** Creates a new IKE Child session. */
+        public void openChildSession(
+                @NonNull ChildSessionParams childSessionParams,
+                @NonNull ChildSessionCallback childSessionCallback) {
+            mImpl.openChildSession(childSessionParams, childSessionCallback);
+        }
+
+        /** Closes an IKE session as identified by the ChildSessionCallback. */
+        public void closeChildSession(@NonNull ChildSessionCallback childSessionCallback) {
+            mImpl.closeChildSession(childSessionCallback);
+        }
+
+        /** Gracefully closes this IKE Session, waiting for remote acknowledgement. */
+        public void close() {
+            mImpl.close();
+        }
+
+        /** Forcibly kills this IKE Session, without waiting for a closure confirmation. */
+        public void kill() {
+            mImpl.kill();
+        }
+
+        /** Sets the underlying network used by the IkeSession. */
+        public void setNetwork(@NonNull Network network) {
+            mImpl.setNetwork(network);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index ae20a72..7257478 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -37,15 +37,11 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.proto.ProtoOutputStream;
 import android.view.RemoteAnimationAdapter;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -55,10 +51,8 @@
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityStarter.DefaultFactory;
 import com.android.server.wm.ActivityStarter.Factory;
-import com.android.server.wm.ActivityTaskSupervisor.PendingActivityLaunch;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -87,35 +81,12 @@
     /** The result of the last home activity we attempted to start. */
     private int mLastHomeActivityStartResult;
 
-    /** A list of activities that are waiting to launch. */
-    private final ArrayList<ActivityTaskSupervisor.PendingActivityLaunch>
-            mPendingActivityLaunches = new ArrayList<>();
-
     private final Factory mFactory;
 
-    private final Handler mHandler;
-
     private final PendingRemoteAnimationRegistry mPendingRemoteAnimationRegistry;
 
     boolean mCheckedForSetup = false;
 
-    private final class StartHandler extends Handler {
-        public StartHandler(Looper looper) {
-            super(looper, null, true);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch(msg.what) {
-                case DO_PENDING_ACTIVITY_LAUNCHES_MSG:
-                    synchronized (mService.mGlobalLock) {
-                        doPendingActivityLaunches(true);
-                    }
-                    break;
-            }
-        }
-    }
-
     /**
      * TODO(b/64750076): Capture information necessary for dump and
      * {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object
@@ -134,7 +105,6 @@
             Factory factory) {
         mService = service;
         mSupervisor = supervisor;
-        mHandler = new StartHandler(mService.mH.getLooper());
         mFactory = factory;
         mFactory.setController(this);
         mPendingRemoteAnimationRegistry = new PendingRemoteAnimationRegistry(service.mGlobalLock,
@@ -514,45 +484,6 @@
         return START_SUCCESS;
     }
 
-    void schedulePendingActivityLaunches(long delayMs) {
-        mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
-        Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
-        mHandler.sendMessageDelayed(msg, delayMs);
-    }
-
-    void doPendingActivityLaunches(boolean doResume) {
-        while (!mPendingActivityLaunches.isEmpty()) {
-            final PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
-            final boolean resume = doResume && mPendingActivityLaunches.isEmpty();
-            final ActivityStarter starter = obtainStarter(null /* intent */,
-                    "pendingActivityLaunch");
-            try {
-                starter.startResolvedActivity(pal.r, pal.sourceRecord, null, null, pal.startFlags,
-                        resume, pal.r.getOptions(), null, pal.intentGrants);
-            } catch (Exception e) {
-                Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e);
-                pal.sendErrorResult(e.getMessage());
-            }
-        }
-    }
-
-    void addPendingActivityLaunch(PendingActivityLaunch launch) {
-        mPendingActivityLaunches.add(launch);
-    }
-
-    boolean clearPendingActivityLaunches(String packageName) {
-        final int pendingLaunches = mPendingActivityLaunches.size();
-
-        for (int palNdx = pendingLaunches - 1; palNdx >= 0; --palNdx) {
-            final PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
-            final ActivityRecord r = pal.r;
-            if (r != null && r.packageName.equals(packageName)) {
-                mPendingActivityLaunches.remove(palNdx);
-            }
-        }
-        return mPendingActivityLaunches.size() < pendingLaunches;
-    }
-
     void registerRemoteAnimationForNextActivityStart(String packageName,
             RemoteAnimationAdapter adapter) {
         mPendingRemoteAnimationRegistry.addPendingAnimation(packageName, adapter);
@@ -609,10 +540,4 @@
             pw.println("(nothing)");
         }
     }
-
-    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        for (PendingActivityLaunch activity: mPendingActivityLaunches) {
-            activity.r.writeIdentifierToProto(proto, fieldId);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 4fa4a67..c6ed16c 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -122,7 +122,6 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
-import com.android.server.wm.ActivityTaskSupervisor.PendingActivityLaunch;
 import com.android.server.wm.LaunchParamsController.LaunchParams;
 
 import java.io.PrintWriter;
@@ -1171,42 +1170,19 @@
             r.appTimeTracker = sourceRecord.appTimeTracker;
         }
 
-        final Task rootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
-
-        // If we are starting an activity that is not from the same uid as the currently resumed
-        // one, check whether app switches are allowed.
-        if (voiceSession == null && rootTask != null && (rootTask.getResumedActivity() == null
-                || rootTask.getResumedActivity().info.applicationInfo.uid != realCallingUid)) {
-            if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
-                    realCallingPid, realCallingUid, "Activity start")) {
-                if (!(restrictedBgActivity && handleBackgroundActivityAbort(r))) {
-                    mController.addPendingActivityLaunch(new PendingActivityLaunch(r,
-                            sourceRecord, startFlags, rootTask, callerApp, intentGrants));
-                }
-                ActivityOptions.abort(checkedOptions);
-                return ActivityManager.START_SWITCHES_CANCELED;
-            }
+        // Only allow app switching to be resumed if activity is not a restricted background
+        // activity and target app is not home process, otherwise any background activity
+        // started in background task can stop home button protection mode.
+        // As the targeted app is not a home process and we don't need to wait for the 2nd
+        // activity to be started to resume app switching, we can just enable app switching
+        // directly.
+        WindowProcessController homeProcess = mService.mHomeProcess;
+        boolean isHomeProcess = homeProcess != null
+                && aInfo.applicationInfo.uid == homeProcess.mUid;
+        if (!restrictedBgActivity && !isHomeProcess) {
+            mService.resumeAppSwitches();
         }
 
-        if (mService.getBalAppSwitchesProtectionEnabled()) {
-            // Only allow app switching to be resumed if activity is not a restricted background
-            // activity and target app is not home process, otherwise any background activity
-            // started in background task can stop home button protection mode.
-            // As the targeted app is not a home process and we don't need to wait for the 2nd
-            // activity to be started to resume app switching, we can just enable app switching
-            // directly.
-            WindowProcessController homeProcess = mService.mHomeProcess;
-            boolean isHomeProcess = homeProcess != null
-                    && aInfo.applicationInfo.uid == homeProcess.mUid;
-            if (!restrictedBgActivity && !isHomeProcess) {
-                mService.resumeAppSwitches();
-            }
-        } else {
-            mService.onStartActivitySetDidAppSwitch();
-        }
-
-        mController.doPendingActivityLaunches(false);
-
         mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
                 request.voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask,
                 restrictedBgActivity, intentGrants);
@@ -1286,8 +1262,6 @@
             return false;
         }
 
-        // App switching will be allowed if BAL app switching flag is not enabled, or if
-        // its app switching rule allows it.
         // This is used to block background activity launch even if the app is still
         // visible to user after user clicking home button.
         final boolean appSwitchAllowed = mService.getBalAppSwitchesAllowed();
@@ -1438,7 +1412,6 @@
         Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
                 + "; callingUid: " + callingUid
                 + "; appSwitchAllowed: " + appSwitchAllowed
-                + "; balAppSwitchEnabled: " + mService.getBalAppSwitchesProtectionEnabled()
                 + "; isCallingUidForeground: " + isCallingUidForeground
                 + "; callingUidHasAnyVisibleWindow: " + callingUidHasAnyVisibleWindow
                 + "; callingUidProcState: " + DebugUtils.valueToString(ActivityManager.class,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 80add64..5610573 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -202,7 +202,6 @@
 import android.os.WorkSource;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.dreams.DreamActivity;
 import android.service.voice.IVoiceInteractionSession;
@@ -324,12 +323,6 @@
     /** This activity is being relaunched due to a free-resize operation. */
     public static final int RELAUNCH_REASON_FREE_RESIZE = 2;
 
-    /**
-     * Apps are blocked from starting activities in the foreground after the user presses home.
-     */
-    public static final String BLOCK_ACTIVITY_STARTS_AFTER_HOME_FLAG =
-            "am_block_activity_starts_after_home";
-
     Context mContext;
 
     /**
@@ -386,7 +379,6 @@
     volatile WindowProcessController mHeavyWeightProcess;
     boolean mHasHeavyWeightFeature;
     boolean mHasLeanbackFeature;
-    boolean mBlockActivityAfterHomeEnabled;
     /** The process of the top most activity. */
     volatile WindowProcessController mTopApp;
     /**
@@ -490,20 +482,11 @@
     /** Temporary to avoid allocations. */
     final StringBuilder mStringBuilder = new StringBuilder(256);
 
-    // Amount of time after a call to stopAppSwitches() during which we will
-    // prevent further untrusted switches from happening.
-    private static final long APP_SWITCH_DELAY_TIME = 5 * 1000;
-
     /**
-     * The time at which we will allow normal application switches again,
-     * after a call to {@link #stopAppSwitches()}.
+     * Whether normal application switches are allowed; a call to {@link #stopAppSwitches()
+     * disables this.
      */
-    private long mAppSwitchesAllowedTime;
-    /**
-     * This is set to true after the first switch after mAppSwitchesAllowedTime
-     * is set; any switches after that will clear the time.
-     */
-    private boolean mDidAppSwitch;
+    private boolean mAppSwitchesAllowed = true;
 
     /**
      * Last stop app switches time, apps finished before this time cannot start background activity
@@ -749,9 +732,6 @@
             mRecentTasks.onSystemReadyLocked();
             mTaskSupervisor.onSystemReady();
             mActivityClientController.onSystemReady();
-            mBlockActivityAfterHomeEnabled = DeviceConfig.getBoolean(
-                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    BLOCK_ACTIVITY_STARTS_AFTER_HOME_FLAG, true);
         }
     }
 
@@ -1146,7 +1126,7 @@
             if (topFocusedRootTask != null && topFocusedRootTask.getResumedActivity() != null
                     && topFocusedRootTask.getResumedActivity().info.applicationInfo.uid
                     == Binder.getCallingUid()) {
-                mAppSwitchesAllowedTime = 0;
+                mAppSwitchesAllowed = true;
             }
         }
         return pir.sendInner(0, fillInIntent, resolvedType, allowlistToken, null, null,
@@ -2002,10 +1982,7 @@
         final int callingPid = Binder.getCallingPid();
         final int callingUid = Binder.getCallingUid();
         assertPackageMatchesCallingUid(callingPackage);
-        if (!checkAppSwitchAllowedLocked(callingPid, callingUid, -1, -1, "Task to front")) {
-            SafeActivityOptions.abort(options);
-            return;
-        }
+
         final long origId = Binder.clearCallingIdentity();
         WindowProcessController callerApp = null;
         if (appThread != null) {
@@ -2086,76 +2063,10 @@
     }
 
     /**
-     * Return true if app switch protection will be handled by background activity launch logic.
-     */
-    boolean getBalAppSwitchesProtectionEnabled() {
-         return mBlockActivityAfterHomeEnabled;
-    }
-
-    /**
      * Return true if app switching is allowed.
      */
     boolean getBalAppSwitchesAllowed() {
-        if (getBalAppSwitchesProtectionEnabled()) {
-            // Apps no longer able to start BAL again until app switching is resumed.
-            return mAppSwitchesAllowedTime == 0;
-        } else {
-            // Legacy behavior, BAL logic won't block app switching.
-            return true;
-        }
-    }
-
-    boolean checkAppSwitchAllowedLocked(int sourcePid, int sourceUid,
-            int callingPid, int callingUid, String name) {
-
-        // Background activity launch logic replaces app switching protection, so allow
-        // apps to start activity here now.
-        if (getBalAppSwitchesProtectionEnabled()) {
-            return true;
-        }
-
-        if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
-            return true;
-        }
-
-        if (getRecentTasks().isCallerRecents(sourceUid)) {
-            return true;
-        }
-
-        int perm = checkComponentPermission(STOP_APP_SWITCHES, sourcePid, sourceUid, -1, true);
-        if (perm == PackageManager.PERMISSION_GRANTED) {
-            return true;
-        }
-        if (checkAllowAppSwitchUid(sourceUid)) {
-            return true;
-        }
-
-        // If the actual IPC caller is different from the logical source, then
-        // also see if they are allowed to control app switches.
-        if (callingUid != -1 && callingUid != sourceUid) {
-            perm = checkComponentPermission(STOP_APP_SWITCHES, callingPid, callingUid, -1, true);
-            if (perm == PackageManager.PERMISSION_GRANTED) {
-                return true;
-            }
-            if (checkAllowAppSwitchUid(callingUid)) {
-                return true;
-            }
-        }
-
-        Slog.w(TAG, name + " request from " + sourceUid + " stopped");
-        return false;
-    }
-
-    private boolean checkAllowAppSwitchUid(int uid) {
-        ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(UserHandle.getUserId(uid));
-        if (types != null) {
-            for (int i = types.size() - 1; i >= 0; i--) {
-                if (types.valueAt(i).intValue() == uid) {
-                    return true;
-                }
-            }
-        }
-        return false;
+        return mAppSwitchesAllowed;
     }
 
     @Override
@@ -3663,13 +3574,8 @@
     public void stopAppSwitches() {
         enforceCallerIsRecentsOrHasPermission(STOP_APP_SWITCHES, "stopAppSwitches");
         synchronized (mGlobalLock) {
-            mAppSwitchesAllowedTime = SystemClock.uptimeMillis() + APP_SWITCH_DELAY_TIME;
+            mAppSwitchesAllowed = false;
             mLastStopAppSwitchesTime = SystemClock.uptimeMillis();
-            mDidAppSwitch = false;
-            // If BAL app switching enabled, app switches are blocked not delayed.
-            if (!getBalAppSwitchesProtectionEnabled()) {
-                getActivityStartController().schedulePendingActivityLaunches(APP_SWITCH_DELAY_TIME);
-            }
         }
     }
 
@@ -3677,10 +3583,7 @@
     public void resumeAppSwitches() {
         enforceCallerIsRecentsOrHasPermission(STOP_APP_SWITCHES, "resumeAppSwitches");
         synchronized (mGlobalLock) {
-            // Note that we don't execute any pending app switches... we will
-            // let those wait until either the timeout, or the next start
-            // activity request.
-            mAppSwitchesAllowedTime = 0;
+            mAppSwitchesAllowed = true;
         }
     }
 
@@ -3688,19 +3591,6 @@
         return mLastStopAppSwitchesTime;
     }
 
-    void onStartActivitySetDidAppSwitch() {
-        if (mDidAppSwitch) {
-            // This is the second allowed switch since we stopped switches, so now just generally
-            // allow switches. Use case:
-            // - user presses home (switches disabled, switch to home, mDidAppSwitch now true);
-            // - user taps a home icon (coming from home so allowed, we hit here and now allow
-            // anyone to switch again).
-            mAppSwitchesAllowedTime = 0;
-        } else {
-            mDidAppSwitch = true;
-        }
-    }
-
     /** @return whether the system should disable UI modes incompatible with VR mode. */
     boolean shouldDisableNonVrUiLocked() {
         return mVrController.shouldDisableNonVrUiLocked();
@@ -5822,15 +5712,12 @@
                 int userId) {
             synchronized (mGlobalLock) {
 
-                boolean didSomething =
-                        getActivityStartController().clearPendingActivityLaunches(packageName);
-                didSomething |= mRootWindowContainer.finishDisabledPackageActivities(packageName,
+                return mRootWindowContainer.finishDisabledPackageActivities(packageName,
                         null /* filterByClasses */, doit, evenPersistent, userId,
                         // Only remove the activities without process because the activities with
                         // attached process will be removed when handling process died with
                         // WindowProcessController#isRemoved == true.
                         true /* onlyRemoveNoProcess */);
-                return didSomething;
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 0ad392b..de43643 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -142,7 +142,6 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.UserState;
-import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
 
 import java.io.FileDescriptor;
@@ -376,41 +375,6 @@
 
     private boolean mInitialized;
 
-    /**
-     * Description of a request to start a new activity, which has been held
-     * due to app switches being disabled.
-     */
-    static class PendingActivityLaunch {
-        final ActivityRecord r;
-        final ActivityRecord sourceRecord;
-        final int startFlags;
-        final Task rootTask;
-        final WindowProcessController callerApp;
-        final NeededUriGrants intentGrants;
-
-        PendingActivityLaunch(ActivityRecord r, ActivityRecord sourceRecord,
-                int startFlags, Task rootTask, WindowProcessController callerApp,
-                NeededUriGrants intentGrants) {
-            this.r = r;
-            this.sourceRecord = sourceRecord;
-            this.startFlags = startFlags;
-            this.rootTask = rootTask;
-            this.callerApp = callerApp;
-            this.intentGrants = intentGrants;
-        }
-
-        void sendErrorResult(String message) {
-            try {
-                if (callerApp != null && callerApp.hasThread()) {
-                    callerApp.getThread().scheduleCrash(message);
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Exception scheduling crash of failed "
-                        + "activity launcher sourceRecord=" + sourceRecord, e);
-            }
-        }
-    }
-
     public ActivityTaskSupervisor(ActivityTaskManagerService service, Looper looper) {
         mService = service;
         mLooper = looper;
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index e6b7585..7f0adca 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -101,10 +101,6 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mService.mGlobalLock) {
-                if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, -1, -1,
-                        "Move to front")) {
-                    return;
-                }
                 WindowProcessController callerApp = null;
                 if (appThread != null) {
                     callerApp = mService.getProcessController(appThread);
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 91014aa..26871d1 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -28,7 +28,7 @@
  */
 class RefreshRatePolicy {
 
-    private final int mLowRefreshRateId;
+    private final Mode mLowRefreshRateMode;
     private final ArraySet<String> mNonHighRefreshRatePackages = new ArraySet<>();
     private final HighRefreshRateDenylist mHighRefreshRateDenylist;
     private final WindowManagerService mWmService;
@@ -56,7 +56,7 @@
 
     RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
             HighRefreshRateDenylist denylist) {
-        mLowRefreshRateId = findLowRefreshRateModeId(displayInfo);
+        mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
         mHighRefreshRateDenylist = denylist;
         mWmService = wmService;
     }
@@ -65,7 +65,7 @@
      * Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
      * default mode.
      */
-    private int findLowRefreshRateModeId(DisplayInfo displayInfo) {
+    private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
         Mode mode = displayInfo.getDefaultMode();
         float[] refreshRates = displayInfo.getDefaultRefreshRates();
         float bestRefreshRate = mode.getRefreshRate();
@@ -104,13 +104,9 @@
 
         // If app is using Camera, force it to default (lower) refresh rate.
         if (mNonHighRefreshRatePackages.contains(packageName)) {
-            return mLowRefreshRateId;
+            return mLowRefreshRateMode.getModeId();
         }
 
-        // If app is denylisted using higher refresh rate, return default (lower) refresh rate
-        if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
-            return mLowRefreshRateId;
-        }
         return 0;
     }
 
@@ -137,4 +133,18 @@
         }
         return LAYER_PRIORITY_UNSET;
     }
+
+    float getPreferredRefreshRate(WindowState w) {
+        // If app is animating, it's not able to control refresh rate because we want the animation
+        // to run in default refresh rate.
+        if (w.isAnimating(TRANSITION | PARENTS)) {
+            return 0;
+        }
+
+        final String packageName = w.getOwningPackage();
+        if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
+            return mLowRefreshRateMode.getRefreshRate();
+        }
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ce4e5ec..bbf6c76 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -68,7 +68,6 @@
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
 import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
-import static com.android.server.wm.RootWindowContainerProto.PENDING_ACTIVITIES;
 import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
 import static com.android.server.wm.Task.ActivityState.FINISHING;
 import static com.android.server.wm.Task.ActivityState.PAUSED;
@@ -1289,7 +1288,6 @@
         mTaskSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER);
         proto.write(IS_HOME_RECENTS_COMPONENT,
                 mTaskSupervisor.mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
-        mService.getActivityStartController().dumpDebug(proto, PENDING_ACTIVITIES);
 
         proto.end(token);
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 79d1123..ec1588d 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -124,6 +124,7 @@
 import static com.android.server.wm.Task.ActivityState.STARTED;
 import static com.android.server.wm.Task.ActivityState.STOPPING;
 import static com.android.server.wm.TaskProto.ACTIVITY_TYPE;
+import static com.android.server.wm.TaskProto.AFFINITY;
 import static com.android.server.wm.TaskProto.BOUNDS;
 import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
 import static com.android.server.wm.TaskProto.DISPLAY_ID;
@@ -1244,27 +1245,20 @@
         mCallingFeatureId = r.launchedFromFeatureId;
         setIntent(intent != null ? intent : r.intent, info != null ? info : r.info);
         setLockTaskAuth(r);
-
-        final WindowContainer parent = getParent();
-        if (parent != null) {
-            final Task t = parent.asTask();
-            if (t != null) {
-                t.setIntent(r);
-            }
-        }
     }
 
     /** Sets the original intent, _without_ updating the calling uid or package. */
     private void setIntent(Intent _intent, ActivityInfo info) {
-        final boolean isLeaf = isLeafTask();
+        if (!isLeafTask()) return;
+
         if (intent == null) {
             mNeverRelinquishIdentity =
                     (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0;
-        } else if (mNeverRelinquishIdentity && isLeaf) {
+        } else if (mNeverRelinquishIdentity) {
             return;
         }
 
-        affinity = isLeaf ? info.taskAffinity : null;
+        affinity = info.taskAffinity;
         if (intent == null) {
             // If this task already has an intent associated with it, don't set the root
             // affinity -- we don't want it changing after initially set, but the initially
@@ -7809,6 +7803,7 @@
         }
 
         proto.write(CREATED_BY_ORGANIZER, mCreatedByOrganizer);
+        proto.write(AFFINITY, affinity);
 
         proto.end(token);
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3be4e78..d0afa2a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -233,6 +233,7 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.Surface;
 import android.view.Surface.Rotation;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
@@ -730,6 +731,13 @@
      */
     int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET;
 
+    /**
+     * This is the frame rate which is passed to SurfaceFlinger if the window is part of the
+     * high refresh rate deny list. The variable is cached, so we do not send too many updates to
+     * SF.
+     */
+    float mDenyListFrameRate = 0f;
+
     static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */
 
     private final WindowProcessController mWpcForDisplayAreaConfigChanges;
@@ -5233,7 +5241,6 @@
         return (mAttrs.flags & FLAG_BLUR_BEHIND) != 0 && mOwnerCanUseBackgroundBlur;
     }
 
-
     /**
      * Notifies SF about the priority of the window, if it changed. SF then uses this information
      * to decide which window's desired rendering rate should have a priority when deciding about
@@ -5242,13 +5249,21 @@
      */
     @VisibleForTesting
     void updateFrameRateSelectionPriorityIfNeeded() {
-        final int priority = getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
-                .calculatePriority(this);
+        RefreshRatePolicy refreshRatePolicy =
+                getDisplayContent().getDisplayPolicy().getRefreshRatePolicy();
+        final int priority = refreshRatePolicy.calculatePriority(this);
         if (mFrameRateSelectionPriority != priority) {
             mFrameRateSelectionPriority = priority;
             getPendingTransaction().setFrameRateSelectionPriority(mSurfaceControl,
                     mFrameRateSelectionPriority);
         }
+
+        final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this);
+        if (mDenyListFrameRate != refreshRate) {
+            mDenyListFrameRate = refreshRate;
+            getPendingTransaction().setFrameRate(
+                    mSurfaceControl, mDenyListFrameRate, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+        }
     }
 
     private void updateGlobalScaleIfNeeded() {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 9d013c1..345b246 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -70,6 +70,7 @@
         "frameworks/base/libs",
         "frameworks/native/services",
         "system/gatekeeper/include",
+        "system/memory/libmeminfo/include",
     ],
 
     header_libs: [
@@ -97,6 +98,7 @@
         "libhardware_legacy",
         "libhidlbase",
         "libkeystore_binder",
+        "libmeminfo",
         "libmtp",
         "libnativehelper",
         "libnativewindow",
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 678308a..156ef79 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -17,15 +17,25 @@
 #define LOG_TAG "CachedAppOptimizer"
 //#define LOG_NDEBUG 0
 
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <cutils/compiler.h>
 #include <dirent.h>
+#include <jni.h>
+#include <linux/errno.h>
+#include <log/log.h>
+#include <meminfo/procmeminfo.h>
+#include <nativehelper/JNIHelp.h>
 #include <stddef.h>
 #include <stdio.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
+#include <sys/syscall.h>
 #include <sys/types.h>
 #include <unistd.h>
 
-#include <android-base/stringprintf.h>
-#include <android-base/file.h>
+#include <algorithm>
 
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
@@ -35,12 +45,149 @@
 
 using android::base::StringPrintf;
 using android::base::WriteStringToFile;
+using android::meminfo::ProcMemInfo;
+using namespace android::meminfo;
+
+// This is temporarily hard-coded and should be removed once
+// bionic/libc/kernel/uapi/asm-generic/unistd.h are updated with process_madvise syscall header
+#ifndef __NR_process_madvise
+#define __NR_process_madvise 440
+#define MADV_COLD 20 /* deactivate these pages */
+#define MADV_PAGEOUT 21
+#endif
+
+#define COMPACT_ACTION_FILE_FLAG 1
+#define COMPACT_ACTION_ANON_FLAG 2
+
+using VmaToAdviseFunc = std::function<int(const Vma&)>;
 
 #define SYNC_RECEIVED_WHILE_FROZEN (1)
 #define ASYNC_RECEIVED_WHILE_FROZEN (2)
 
 namespace android {
 
+// Legacy method for compacting processes, any new code should
+// use compactProcess instead.
+static inline void compactProcessProcfs(int pid, const std::string& compactionType) {
+    std::string reclaim_path = StringPrintf("/proc/%d/reclaim", pid);
+    WriteStringToFile(compactionType, reclaim_path);
+}
+
+static int compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) {
+    // UIO_MAXIOV is currently a small value and we might have more addresses
+    // we do multiple syscalls if we exceed its maximum
+    static struct iovec vmasToKernel[UIO_MAXIOV];
+
+    int err = 0;
+
+    if (vmas.empty()) {
+        return err;
+    }
+
+    int pidfd = syscall(__NR_pidfd_open, pid, 0);
+    err = -errno;
+    if (err < 0) {
+        // Skip compaction if failed to open pidfd with any error
+        return err;
+    }
+
+    for (int iBase = 0; iBase < vmas.size(); iBase += UIO_MAXIOV) {
+        int totalVmasToKernel = std::min(UIO_MAXIOV, (int)(vmas.size() - iBase));
+        for (int iVec = 0, iVma = iBase; iVec < totalVmasToKernel; ++iVec, ++iVma) {
+            vmasToKernel[iVec].iov_base = (void*)vmas[iVma].start;
+            vmasToKernel[iVec].iov_len = vmas[iVma].end - vmas[iVma].start;
+        }
+
+        process_madvise(pidfd, vmasToKernel, totalVmasToKernel, madviseType, 0);
+        err = -errno;
+        if (CC_UNLIKELY(err == -ENOSYS)) {
+            // Syscall does not exist, skip trying more calls process_madvise
+            break;
+        }
+    }
+
+    close(pidfd);
+
+    return err;
+}
+
+static int getFilePageAdvice(const Vma& vma) {
+    if (vma.inode > 0 && !vma.is_shared) {
+        return MADV_COLD;
+    }
+    return -1;
+}
+static int getAnonPageAdvice(const Vma& vma) {
+    if (vma.inode == 0 && !vma.is_shared) {
+        return MADV_PAGEOUT;
+    }
+    return -1;
+}
+static bool getAnyPageAdvice(const Vma& vma) {
+    if (vma.inode == 0 && !vma.is_shared) {
+        return MADV_PAGEOUT;
+    }
+    return MADV_COLD;
+}
+
+// Perform a full process compaction using process_madvise syscall
+// reading all filtering VMAs and filtering pages as specified by pageFilter
+static int compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) {
+    ProcMemInfo meminfo(pid);
+    std::vector<Vma> pageoutVmas, coldVmas;
+    auto vmaCollectorCb = [&](Vma vma) {
+        int advice = vmaToAdviseFunc(vma);
+        switch (advice) {
+            case MADV_COLD:
+                coldVmas.push_back(vma);
+                break;
+            case MADV_PAGEOUT:
+                pageoutVmas.push_back(vma);
+                break;
+        }
+    };
+    meminfo.ForEachVma(vmaCollectorCb);
+
+    int err = compactMemory(pageoutVmas, pid, MADV_PAGEOUT);
+    if (!err) {
+        err = compactMemory(coldVmas, pid, MADV_COLD);
+    }
+    return err;
+}
+
+// Compact process using process_madvise syscall or fallback to procfs in
+// case syscall does not exist.
+static void compactProcessOrFallback(int pid, int compactionFlags) {
+    if ((compactionFlags & (COMPACT_ACTION_ANON_FLAG | COMPACT_ACTION_FILE_FLAG)) == 0) return;
+
+    bool compactAnon = compactionFlags & COMPACT_ACTION_ANON_FLAG;
+    bool compactFile = compactionFlags & COMPACT_ACTION_FILE_FLAG;
+
+    // Set when the system does not support process_madvise syscall to avoid
+    // gathering VMAs in subsequent calls prior to falling back to procfs
+    static bool shouldForceProcFs = false;
+    std::string compactionType;
+    VmaToAdviseFunc vmaToAdviseFunc;
+
+    if (compactAnon) {
+        if (compactFile) {
+            compactionType = "all";
+            vmaToAdviseFunc = getAnyPageAdvice;
+        } else {
+            compactionType = "anon";
+            vmaToAdviseFunc = getAnonPageAdvice;
+        }
+    } else {
+        compactionType = "file";
+        vmaToAdviseFunc = getFilePageAdvice;
+    }
+
+    if (shouldForceProcFs || compactProcess(pid, vmaToAdviseFunc) == -ENOSYS) {
+        shouldForceProcFs = true;
+        compactProcessProcfs(pid, compactionType);
+    }
+}
+
 // This performs per-process reclaim on all processes belonging to non-app UIDs.
 // For the most part, these are non-zygote processes like Treble HALs, but it
 // also includes zygote-derived processes that run in system UIDs, like bluetooth
@@ -74,11 +221,17 @@
             continue;
         }
 
-        std::string reclaim_path = StringPrintf("/proc/%s/reclaim", current->d_name);
-        WriteStringToFile(std::string("all"), reclaim_path);
+        int pid = atoi(current->d_name);
+
+        compactProcessOrFallback(pid, COMPACT_ACTION_ANON_FLAG | COMPACT_ACTION_FILE_FLAG);
     }
 }
 
+static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, jobject, jint pid,
+                                                                    jint compactionFlags) {
+    compactProcessOrFallback(pid, compactionFlags);
+}
+
 static void com_android_server_am_CachedAppOptimizer_enableFreezerInternal(
         JNIEnv *env, jobject clazz, jboolean enable) {
     bool success = true;
@@ -126,14 +279,14 @@
 }
 
 static const JNINativeMethod sMethods[] = {
-    /* name, signature, funcPtr */
-    {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
-    {"enableFreezerInternal", "(Z)V",
-        (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal},
-    {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
-    {"getBinderFreezeInfo", "(I)I",
-        (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}
-};
+        /* name, signature, funcPtr */
+        {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
+        {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
+        {"enableFreezerInternal", "(Z)V",
+         (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal},
+        {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+        {"getBinderFreezeInfo", "(I)I",
+         (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}};
 
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
 {
diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp
index d1918d8..bdccf45 100644
--- a/services/core/xsd/Android.bp
+++ b/services/core/xsd/Android.bp
@@ -8,11 +8,19 @@
 
 xsd_config {
     name: "platform-compat-config",
-    srcs: ["platform-compat-config.xsd"],
-    api_dir: "platform-compat-schema",
+    srcs: ["platform-compat/config/platform-compat-config.xsd"],
+    api_dir: "platform-compat/config/schema",
     package_name: "com.android.server.compat.config",
 }
 
+xsd_config {
+    name: "platform-compat-overrides",
+    srcs: ["platform-compat/overrides/platform-compat-overrides.xsd"],
+    api_dir: "platform-compat/overrides/schema",
+    package_name: "com.android.server.compat.overrides",
+    gen_writer: true,
+}
+
 
 xsd_config {
     name: "display-device-config",
diff --git a/services/core/xsd/platform-compat-schema/OWNERS b/services/core/xsd/platform-compat/OWNERS
similarity index 100%
rename from services/core/xsd/platform-compat-schema/OWNERS
rename to services/core/xsd/platform-compat/OWNERS
diff --git a/services/core/xsd/platform-compat-config.xsd b/services/core/xsd/platform-compat/config/platform-compat-config.xsd
similarity index 100%
rename from services/core/xsd/platform-compat-config.xsd
rename to services/core/xsd/platform-compat/config/platform-compat-config.xsd
diff --git a/services/core/xsd/platform-compat-schema/current.txt b/services/core/xsd/platform-compat/config/schema/current.txt
similarity index 100%
rename from services/core/xsd/platform-compat-schema/current.txt
rename to services/core/xsd/platform-compat/config/schema/current.txt
diff --git a/services/core/xsd/platform-compat-schema/last_current.txt b/services/core/xsd/platform-compat/config/schema/last_current.txt
similarity index 100%
rename from services/core/xsd/platform-compat-schema/last_current.txt
rename to services/core/xsd/platform-compat/config/schema/last_current.txt
diff --git a/services/core/xsd/platform-compat-schema/last_removed.txt b/services/core/xsd/platform-compat/config/schema/last_removed.txt
similarity index 100%
rename from services/core/xsd/platform-compat-schema/last_removed.txt
rename to services/core/xsd/platform-compat/config/schema/last_removed.txt
diff --git a/services/core/xsd/platform-compat-schema/removed.txt b/services/core/xsd/platform-compat/config/schema/removed.txt
similarity index 100%
rename from services/core/xsd/platform-compat-schema/removed.txt
rename to services/core/xsd/platform-compat/config/schema/removed.txt
diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
new file mode 100644
index 0000000..e27e1b8
--- /dev/null
+++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- This defines the format of the XML file used to store compat config overrides in
+  ~ /data/misc/appcompat/compat_framework_overrides.xml
+-->
+<xs:schema version="2.0" elementFormDefault="qualified"
+    xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+
+    <xs:complexType name="override-value">
+        <xs:attribute type="xs:string" name="packageName" use="required" />
+        <xs:attribute type="xs:boolean" name="enabled" use="required" />
+    </xs:complexType>
+
+    <xs:complexType name="change-overrides">
+        <xs:attribute type="xs:long" name="changeId" use="required"/>
+        <xs:element name="validated">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element name="override-value" type="override-value" maxOccurs="unbounded" minOccurs="0" />
+                </xs:sequence>
+            </xs:complexType>
+        </xs:element>
+        <xs:element name="deferred">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element name="override-value" type="override-value" maxOccurs="unbounded" minOccurs="0" />
+                </xs:sequence>
+            </xs:complexType>
+        </xs:element>
+    </xs:complexType>
+
+    <xs:element name="overrides">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="change-overrides" type="change-overrides" maxOccurs="unbounded" minOccurs="0" />
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+</xs:schema>
diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt
new file mode 100644
index 0000000..08b8207
--- /dev/null
+++ b/services/core/xsd/platform-compat/overrides/schema/current.txt
@@ -0,0 +1,51 @@
+// Signature format: 2.0
+package com.android.server.compat.overrides {
+
+  public class ChangeOverrides {
+    ctor public ChangeOverrides();
+    method public long getChangeId();
+    method public com.android.server.compat.overrides.ChangeOverrides.Deferred getDeferred();
+    method public com.android.server.compat.overrides.ChangeOverrides.Validated getValidated();
+    method public void setChangeId(long);
+    method public void setDeferred(com.android.server.compat.overrides.ChangeOverrides.Deferred);
+    method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated);
+  }
+
+  public static class ChangeOverrides.Deferred {
+    ctor public ChangeOverrides.Deferred();
+    method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
+  }
+
+  public static class ChangeOverrides.Validated {
+    ctor public ChangeOverrides.Validated();
+    method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
+  }
+
+  public class OverrideValue {
+    ctor public OverrideValue();
+    method public boolean getEnabled();
+    method public String getPackageName();
+    method public void setEnabled(boolean);
+    method public void setPackageName(String);
+  }
+
+  public class Overrides {
+    ctor public Overrides();
+    method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides();
+  }
+
+  public class XmlParser {
+    ctor public XmlParser();
+    method public static com.android.server.compat.overrides.Overrides 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;
+  }
+
+  public class XmlWriter implements java.io.Closeable {
+    ctor public XmlWriter(java.io.PrintWriter);
+    method public void close();
+    method public static void write(com.android.server.compat.overrides.XmlWriter, com.android.server.compat.overrides.Overrides) throws java.io.IOException;
+  }
+
+}
+
diff --git a/services/core/xsd/platform-compat-schema/last_current.txt b/services/core/xsd/platform-compat/overrides/schema/last_current.txt
similarity index 100%
copy from services/core/xsd/platform-compat-schema/last_current.txt
copy to services/core/xsd/platform-compat/overrides/schema/last_current.txt
diff --git a/services/core/xsd/platform-compat-schema/last_removed.txt b/services/core/xsd/platform-compat/overrides/schema/last_removed.txt
similarity index 100%
copy from services/core/xsd/platform-compat-schema/last_removed.txt
copy to services/core/xsd/platform-compat/overrides/schema/last_removed.txt
diff --git a/services/core/xsd/platform-compat-schema/removed.txt b/services/core/xsd/platform-compat/overrides/schema/removed.txt
similarity index 100%
copy from services/core/xsd/platform-compat-schema/removed.txt
copy to services/core/xsd/platform-compat/overrides/schema/removed.txt
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index a691a8d..607fb47 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -271,8 +271,7 @@
         verify(mMockIActivityManager).registerUidObserver(
                 uidObserverArgumentCaptor.capture(),
                 eq(ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
-                        | ActivityManager.UID_OBSERVER_ACTIVE
-                        | ActivityManager.UID_OBSERVER_PROCSTATE),
+                        | ActivityManager.UID_OBSERVER_ACTIVE),
                 eq(ActivityManager.PROCESS_STATE_UNKNOWN),
                 isNull());
         verify(mMockIAppOpsService).startWatchingMode(
@@ -650,11 +649,6 @@
         assertFalse(instance.isUidActiveSynced(UID_2));
         assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
 
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertFalse(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
-
         mIUidObserver.onUidStateChanged(UID_2,
                 ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 0,
                 ActivityManager.PROCESS_CAPABILITY_NONE);
@@ -670,11 +664,6 @@
         assertFalse(instance.isUidActiveSynced(UID_2));
         assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
 
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertTrue(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
-
         mIUidObserver.onUidStateChanged(UID_1,
                 ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0,
                 ActivityManager.PROCESS_CAPABILITY_NONE);
@@ -686,10 +675,6 @@
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
-        assertTrue(instance.isUidInForeground(UID_1));
-        assertTrue(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
         mIUidObserver.onUidGone(UID_1, true);
 
         waitUntilMainHandlerDrain();
@@ -699,10 +684,6 @@
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertTrue(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
         mIUidObserver.onUidIdle(UID_2, true);
 
         waitUntilMainHandlerDrain();
@@ -712,10 +693,6 @@
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertFalse(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
         mIUidObserver.onUidStateChanged(UID_1,
                 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0,
                 ActivityManager.PROCESS_CAPABILITY_NONE);
@@ -727,10 +704,6 @@
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
-        assertTrue(instance.isUidInForeground(UID_1));
-        assertFalse(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
         mIUidObserver.onUidStateChanged(UID_1,
                 ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0,
                 ActivityManager.PROCESS_CAPABILITY_NONE);
@@ -746,10 +719,6 @@
         assertFalse(instance.isUidActiveSynced(UID_2));
         assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
 
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertFalse(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
         // The result from AMI.isUidActive() only affects isUidActiveSynced().
         when(mMockIActivityManagerInternal.isUidActive(anyInt())).thenReturn(true);
 
@@ -760,11 +729,6 @@
         assertTrue(instance.isUidActiveSynced(UID_1));
         assertTrue(instance.isUidActiveSynced(UID_2));
         assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
-
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertFalse(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
     }
 
     @Test
@@ -1480,7 +1444,6 @@
         callStart(instance);
 
         instance.mActiveUids.put(UID_1, true);
-        instance.mForegroundUids.put(UID_2, true);
         instance.mRunAnyRestrictedPackages.add(Pair.create(UID_1, PACKAGE_1));
         instance.mExemptedBucketPackages.add(UserHandle.getUserId(UID_2), PACKAGE_2);
 
@@ -1493,7 +1456,6 @@
         mReceiver.onReceive(mMockContext, packageRemoved);
 
         assertEquals(1, instance.mActiveUids.size());
-        assertEquals(1, instance.mForegroundUids.size());
         assertEquals(1, instance.mRunAnyRestrictedPackages.size());
         assertEquals(1, instance.mExemptedBucketPackages.size());
 
@@ -1506,7 +1468,6 @@
         mReceiver.onReceive(mMockContext, packageRemoved);
 
         assertEquals(1, instance.mActiveUids.size());
-        assertEquals(1, instance.mForegroundUids.size());
         assertEquals(1, instance.mRunAnyRestrictedPackages.size());
         assertEquals(1, instance.mExemptedBucketPackages.size());
 
@@ -1518,7 +1479,6 @@
         mReceiver.onReceive(mMockContext, packageRemoved);
 
         assertEquals(0, instance.mActiveUids.size());
-        assertEquals(1, instance.mForegroundUids.size());
         assertEquals(0, instance.mRunAnyRestrictedPackages.size());
         assertEquals(1, instance.mExemptedBucketPackages.size());
 
@@ -1530,7 +1490,6 @@
         mReceiver.onReceive(mMockContext, packageRemoved);
 
         assertEquals(0, instance.mActiveUids.size());
-        assertEquals(0, instance.mForegroundUids.size());
         assertEquals(0, instance.mRunAnyRestrictedPackages.size());
         assertEquals(0, instance.mExemptedBucketPackages.size());
     }
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 7a970a1..1254df9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -44,14 +44,15 @@
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
-import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
-import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
+import static com.android.server.alarm.AlarmManagerService.Constants.ALLOW_WHILE_IDLE_WINDOW;
+import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
+import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
 import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
 import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
 import static com.android.server.alarm.AlarmManagerService.TIME_CHANGED_MASK;
@@ -409,6 +410,12 @@
         return mockPi;
     }
 
+    private void setDeviceConfigInt(String key, int val) {
+        mDeviceConfigKeys.add(key);
+        doReturn(val).when(mDeviceConfigProperties).getInt(eq(key), anyInt());
+        mService.mConstants.onPropertiesChanged(mDeviceConfigProperties);
+    }
+
     private void setDeviceConfigLong(String key, long val) {
         mDeviceConfigKeys.add(key);
         doReturn(val).when(mDeviceConfigProperties).getLong(eq(key), anyLong());
@@ -430,10 +437,12 @@
         setDeviceConfigLong(KEY_MIN_INTERVAL, 0);
         mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
         mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]);
-        doReturn(8).when(mDeviceConfigProperties)
+        doReturn(50).when(mDeviceConfigProperties)
                 .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]), anyInt());
-        doReturn(5).when(mDeviceConfigProperties)
+        doReturn(35).when(mDeviceConfigProperties)
                 .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]), anyInt());
+        doReturn(20).when(mDeviceConfigProperties)
+                .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[FREQUENT_INDEX]), anyInt());
 
         mService.mConstants.onPropertiesChanged(mDeviceConfigProperties);
     }
@@ -496,15 +505,13 @@
         setDeviceConfigLong(KEY_MIN_FUTURITY, 5);
         setDeviceConfigLong(KEY_MIN_INTERVAL, 10);
         setDeviceConfigLong(KEY_MAX_INTERVAL, 15);
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, 20);
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, 25);
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 20);
         setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 30);
         setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 35);
         assertEquals(5, mService.mConstants.MIN_FUTURITY);
         assertEquals(10, mService.mConstants.MIN_INTERVAL);
         assertEquals(15, mService.mConstants.MAX_INTERVAL);
-        assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_SHORT_TIME);
-        assertEquals(25, mService.mConstants.ALLOW_WHILE_IDLE_LONG_TIME);
+        assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
         assertEquals(30, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION);
         assertEquals(35, mService.mConstants.LISTENER_TIMEOUT);
     }
@@ -1301,62 +1308,54 @@
     public void allowWhileIdleAlarmsWhileDeviceIdle() throws Exception {
         doReturn(0).when(mService).fuzzForDuration(anyLong());
 
-        final long awiDelayForTest = 23;
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, awiDelayForTest);
-
-        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1000,
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + ALLOW_WHILE_IDLE_WINDOW + 1000,
                 getNewMockPendingIntent());
         assertNotNull(mService.mPendingIdleUntil);
 
-        final long seedTrigger = mNowElapsedTest + 3;
-        final int numAlarms = 10;
-        final PendingIntent[] pis = new PendingIntent[numAlarms];
-        for (int i = 0; i < numAlarms; i++) {
-            pis[i] = getNewMockPendingIntent();
-            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, seedTrigger + i * i, pis[i], false);
-        }
-
-        long lastAwiDispatch = -1;
-        int i = 0;
-        while (i < numAlarms) {
-            final long nextDispatch = (lastAwiDispatch >= 0) ? (lastAwiDispatch + awiDelayForTest)
-                    : (seedTrigger + i * i);
-            assertEquals("Wrong allow-while-idle dispatch", nextDispatch, mTestTimer.getElapsed());
-
-            mNowElapsedTest = nextDispatch;
+        final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+                    getNewMockPendingIntent(), false);
+            mNowElapsedTest = mTestTimer.getElapsed();
             mTestTimer.expire();
-
-            while (i < numAlarms && (seedTrigger + i * i) <= nextDispatch) {
-                verify(pis[i]).send(eq(mMockContext), eq(0), any(Intent.class), any(),
-                        any(Handler.class), isNull(), any());
-                i++;
-            }
-            Log.d(TAG, "Dispatched alarms upto " + i + " at " + nextDispatch);
-            lastAwiDispatch = nextDispatch;
         }
+        // This one should get deferred on set.
+        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
+                getNewMockPendingIntent(), false);
+        final long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
+        assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
+                mTestTimer.getElapsed());
+
+        // Bring the idle until alarm back.
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger - 50,
+                getNewMockPendingIntent());
+        assertEquals(expectedNextTrigger - 50, mService.mPendingIdleUntil.getWhenElapsed());
+        assertEquals(expectedNextTrigger - 50, mTestTimer.getElapsed());
     }
 
     @Test
-    public void allowWhileIdleUnrestrictedInIdle() throws Exception {
+    public void allowWhileIdleUnrestricted() throws Exception {
         doReturn(0).when(mService).fuzzForDuration(anyLong());
 
-        final long awiDelayForTest = 127;
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, awiDelayForTest);
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, 0);
-
+        // Both battery saver and doze are on.
         setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1000,
                 getNewMockPendingIntent());
         assertNotNull(mService.mPendingIdleUntil);
 
-        final long seedTrigger = mNowElapsedTest + 3;
-        for (int i = 1; i <= 5; i++) {
-            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, seedTrigger + i * i,
+        when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
+                TEST_CALLING_PACKAGE)).thenReturn(true);
+
+        final int numAlarms = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA + 100;
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < numAlarms; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
                     getNewMockPendingIntent(), true);
         }
-        for (int i = 1; i <= 5; i++) {
-            final long nextTrigger = mTestTimer.getElapsed();
-            assertEquals("Wrong trigger for alarm " + i, seedTrigger + i * i, nextTrigger);
-            mNowElapsedTest = nextTrigger;
+        // All of them should fire as expected.
+        for (int i = 0; i < numAlarms; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals("Incorrect trigger at i=" + i, firstTrigger + i, mNowElapsedTest);
             mTestTimer.expire();
         }
     }
@@ -1427,9 +1426,10 @@
         verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture());
         final AppStateTrackerImpl.Listener listener = listenerArgumentCaptor.getValue();
 
-        final PendingIntent alarmPi = getNewMockPendingIntent();
         when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
                 TEST_CALLING_PACKAGE)).thenReturn(true);
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 7, alarmPi);
         assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed());
 
@@ -1446,61 +1446,64 @@
 
     @Test
     public void allowWhileIdleAlarmsInBatterySaver() throws Exception {
-        final ArgumentCaptor<AppStateTrackerImpl.Listener> listenerArgumentCaptor =
-                ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class);
-        verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture());
-        final AppStateTrackerImpl.Listener listener = listenerArgumentCaptor.getValue();
-
-        final long longDelay = 23;
-        final long shortDelay = 7;
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, longDelay);
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, shortDelay);
-
         when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
                 TEST_CALLING_PACKAGE)).thenReturn(true);
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1,
+        when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
+
+        final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
+        long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+                    getNewMockPendingIntent(), false);
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        // This one should get deferred on set.
+        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
                 getNewMockPendingIntent(), false);
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 2,
+        long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
+        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
+                mTestTimer.getElapsed());
+
+        // Refresh the state
+        mService.removeLocked(TEST_CALLING_UID);
+        mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+
+        firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+                    getNewMockPendingIntent(), false);
+        }
+        // This one should get deferred after the latest alarm expires.
+        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
                 getNewMockPendingIntent(), false);
+        for (int i = 0; i < quota; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
+        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
+                mTestTimer.getElapsed());
 
-        assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed());
+        // Refresh the state
+        mService.removeLocked(TEST_CALLING_UID);
+        mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
 
-        mNowElapsedTest += 1;
-        mTestTimer.expire();
-
-        assertEquals(mNowElapsedTest + longDelay, mTestTimer.getElapsed());
-        listener.onUidForeground(TEST_CALLING_UID, true);
-        // The next alarm should be deferred by shortDelay.
-        assertEquals(mNowElapsedTest + shortDelay, mTestTimer.getElapsed());
-
-        mNowElapsedTest = mTestTimer.getElapsed();
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1,
+        firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+                    getNewMockPendingIntent(), false);
+        }
+        // This delivery time maintains the quota invariant. Should not be deferred.
+        expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW + 5;
+        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger,
                 getNewMockPendingIntent(), false);
-
-        when(mAppStateTracker.isUidInForeground(TEST_CALLING_UID)).thenReturn(true);
-        mTestTimer.expire();
-        // The next alarm should be deferred by shortDelay again.
-        assertEquals(mNowElapsedTest + shortDelay, mTestTimer.getElapsed());
-
-        mNowElapsedTest = mTestTimer.getElapsed();
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1,
-                getNewMockPendingIntent(), true);
-        when(mAppStateTracker.isUidInForeground(TEST_CALLING_UID)).thenReturn(false);
-        mTestTimer.expire();
-        final long lastAwiDispatch = mNowElapsedTest;
-        // Unrestricted, so should not be changed.
-        assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed());
-
-        mNowElapsedTest = mTestTimer.getElapsed();
-        // AWI_unrestricted should not affect normal AWI bookkeeping.
-        // The next alarm is after the short delay but before the long delay.
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, lastAwiDispatch + shortDelay + 1,
-                getNewMockPendingIntent(), false);
-        mTestTimer.expire();
-        assertEquals(lastAwiDispatch + longDelay, mTestTimer.getElapsed());
-
-        listener.onUidForeground(TEST_CALLING_UID, true);
-        assertEquals(lastAwiDispatch + shortDelay + 1, mTestTimer.getElapsed());
+        for (int i = 0; i < quota; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
+                mTestTimer.getElapsed());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 90ce6cb..57b0d01 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -33,6 +33,7 @@
 import android.hardware.biometrics.IBiometricService;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.annotation.NonNull;
@@ -267,6 +268,39 @@
         assertEquals(0, bsp.recentOperations.length);
     }
 
+    @Test
+    public void testCancelPendingAuth() throws RemoteException {
+        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+
+        final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon);
+        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+        final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+                mToken, callback);
+
+        // Add a non-cancellable client, then add the auth client
+        mScheduler.scheduleClientMonitor(client1);
+        mScheduler.scheduleClientMonitor(client2);
+        waitForIdle();
+
+        assertEquals(mScheduler.getCurrentClient(), client1);
+        assertEquals(Operation.STATE_WAITING_IN_QUEUE,
+                mScheduler.mPendingOperations.getFirst().mState);
+
+        // Request cancel before the authentication client has started
+        mScheduler.cancelAuthentication(mToken);
+        waitForIdle();
+        assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
+                mScheduler.mPendingOperations.getFirst().mState);
+
+        // Finish the blocking client. The authentication client should send ERROR_CANCELED
+        client1.getCallback().onClientFinished(client1, true /* success */);
+        waitForIdle();
+        verify(callback).onError(anyInt(), anyInt(),
+                eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
+                eq(0) /* vendorCode */);
+        assertNull(mScheduler.getCurrentClient());
+    }
+
     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
         return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
     }
@@ -293,6 +327,29 @@
         }
     }
 
+    private static class TestAuthenticationClient extends AuthenticationClient<Object> {
+
+        public TestAuthenticationClient(@NonNull Context context,
+                @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
+                @NonNull ClientMonitorCallbackConverter listener) {
+            super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
+                    false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
+                    TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */,
+                    0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class),
+                    false /* isKeyguard */);
+        }
+
+        @Override
+        protected void stopHalOperation() {
+
+        }
+
+        @Override
+        protected void startHalOperation() {
+
+        }
+    }
+
     private static class TestClientMonitor2 extends TestClientMonitor {
         private final int mProtoEnum;
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index 61cc8e6..904ade8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -19,17 +19,20 @@
 import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
@@ -80,7 +83,7 @@
                 .thenReturn(5);
 
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
-        mFingerprint21 = new Fingerprint21(mContext, mScheduler,
+        mFingerprint21 = new TestableFingerprint21(mContext, mScheduler,
                 new Handler(Looper.getMainLooper()), SENSOR_ID,
                 BiometricManager.Authenticators.BIOMETRIC_WEAK, mLockoutResetDispatcher,
                 mHalResultController);
@@ -100,4 +103,21 @@
         waitForIdle();
         verify(mScheduler).reset();
     }
+
+    private static class TestableFingerprint21 extends Fingerprint21 {
+
+        TestableFingerprint21(@NonNull Context context,
+                @NonNull BiometricScheduler scheduler,
+                @NonNull Handler handler, int sensorId, int strength,
+                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+                @NonNull HalResultController controller) {
+            super(context, scheduler, handler, sensorId, strength, lockoutResetDispatcher,
+                    controller);
+        }
+
+        @Override
+        synchronized IBiometricsFingerprint getDaemon() {
+            return mock(IBiometricsFingerprint.class);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index ac8dc34..a53ff9b 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -44,6 +44,8 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.UUID;
 
 @RunWith(AndroidJUnit4.class)
@@ -69,6 +71,10 @@
         os.close();
     }
 
+    private String readFile(File file) throws IOException {
+        return new String(Files.readAllBytes(Paths.get(file.toURI())));
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -499,4 +505,86 @@
         assertThat(compatConfig.isChangeEnabled(1236L,
             ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue();
     }
+
+    @Test
+    public void testSaveOverrides() throws Exception {
+        File overridesFile = new File(createTempDir(), "overrides.xml");
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1L)
+                .addEnableSinceSdkChangeWithId(2, 2L)
+                .build();
+        compatConfig.forceNonDebuggableFinalForTest(true);
+        compatConfig.initOverrides(overridesFile);
+        when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                                .withPackageName("foo.bar")
+                                .debuggable()
+                                .build());
+        when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt()))
+                .thenThrow(new NameNotFoundException());
+
+        compatConfig.addOverride(1L, "foo.bar", true);
+        compatConfig.addOverride(2L, "bar.baz", false);
+
+        assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<overrides>\n"
+                + "    <change-overrides changeId=\"1\">\n"
+                + "        <validated>\n"
+                + "            <override-value packageName=\"foo.bar\" enabled=\"true\">\n"
+                + "            </override-value>\n"
+                + "        </validated>\n"
+                + "        <deferred>\n"
+                + "        </deferred>\n"
+                + "    </change-overrides>\n"
+                + "    <change-overrides changeId=\"2\">\n"
+                + "        <validated>\n"
+                + "        </validated>\n"
+                + "        <deferred>\n"
+                + "            <override-value packageName=\"bar.baz\" enabled=\"false\">\n"
+                + "            </override-value>\n"
+                + "        </deferred>\n"
+                + "    </change-overrides>\n"
+                + "</overrides>\n");
+    }
+
+    @Test
+    public void testLoadOverrides() throws Exception {
+        File tempDir = createTempDir();
+        File overridesFile = new File(tempDir, "overrides.xml");
+        // Change 1 is enabled for foo.bar (validated)
+        // Change 2 is disabled for bar.baz (deferred)
+        String xmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+                       + "<overrides>"
+                       +    "<change-overrides changeId=\"1\">"
+                       +        "<deferred/>"
+                       +        "<validated>"
+                       +            "<override-value packageName=\"foo.bar\" enabled=\"true\"/>"
+                       +        "</validated>"
+                       +    "</change-overrides>"
+                       +    "<change-overrides changeId=\"2\">"
+                       +        "<deferred>"
+                       +           "<override-value packageName=\"bar.baz\" enabled=\"false\"/>"
+                       +        "</deferred>"
+                       +        "<validated/>"
+                       +    "</change-overrides>"
+                       + "</overrides>";
+        writeToFile(tempDir, "overrides.xml", xmlData);
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1L)
+                .addEnableSinceSdkChangeWithId(2, 2L)
+                .build();
+        compatConfig.forceNonDebuggableFinalForTest(true);
+        compatConfig.initOverrides(overridesFile);
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("foo.bar")
+                .debuggable()
+                .build();
+        when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+                .thenReturn(applicationInfo);
+        when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt()))
+                .thenThrow(new NameNotFoundException());
+
+        assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue();
+        assertThat(compatConfig.willChangeBeEnabled(2L, "bar.baz")).isFalse();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 26c304f..2e3178b 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -818,10 +818,5 @@
                         PEAK_REFRESH_RATE_URI);
             }
         }
-
-        @Override
-        public boolean isDeviceInteractive(@NonNull Context context) {
-            return true;
-        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
new file mode 100644
index 0000000..c10cee9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.graphics.fonts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class PersistentSystemFontConfigTest {
+
+    @Test
+    public void testWriteRead() throws IOException, XmlPullParserException {
+        long expectedModifiedDate = 1234567890;
+        PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+        config.lastModifiedDate = expectedModifiedDate;
+
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            PersistentSystemFontConfig.writeToXml(baos, config);
+
+            byte[] written = baos.toByteArray();
+            assertThat(written).isNotEmpty();
+
+            try (ByteArrayInputStream bais = new ByteArrayInputStream(written)) {
+                PersistentSystemFontConfig.Config another = new PersistentSystemFontConfig.Config();
+                PersistentSystemFontConfig.loadFromXml(bais, another);
+
+                assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate);
+            }
+        }
+    }
+
+    @Test
+    public void testWrongType() throws IOException, XmlPullParserException {
+        String xml = "<fontConfig>"
+                + "  <lastModifiedDate value=\"string\" />"
+                + "</fontConfig>";
+
+        try (ByteArrayInputStream bais =
+                     new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
+            PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+            PersistentSystemFontConfig.loadFromXml(bais, config);
+            assertThat(config.lastModifiedDate).isEqualTo(0);
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 75bf1e6..0e872d9 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.graphics.fonts.FontManager;
 import android.os.FileUtils;
 import android.platform.test.annotations.Presubmit;
 
@@ -36,6 +37,7 @@
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -107,6 +109,7 @@
 
     private File mCacheDir;
     private File mUpdatableFontFilesDir;
+    private File mConfigFile;
     private List<File> mPreinstalledFontDirs;
 
     @SuppressWarnings("ResultOfMethodCallIgnored")
@@ -124,6 +127,7 @@
         for (File dir : mPreinstalledFontDirs) {
             dir.mkdir();
         }
+        mConfigFile = new File(mCacheDir, "config.xml");
     }
 
     @After
@@ -133,19 +137,30 @@
 
     @Test
     public void construct() throws Exception {
+        long expectedModifiedDate = 1234567890;
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+        config.lastModifiedDate = expectedModifiedDate;
+        writeConfig(config, mConfigFile);
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedDate())
+                .isEqualTo(expectedModifiedDate);
         installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
+        //
+        assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedDate())
+                .isNotEqualTo(expectedModifiedDate);
 
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
         assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
         assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3);
         assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
@@ -159,7 +174,8 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
         assertThat(dir.getFontFileMap()).isEmpty();
     }
 
@@ -168,7 +184,8 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
         installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -179,7 +196,8 @@
         fakeFsverityUtil.remove(
                 dirForPreparation.getFontFileMap().get("foo.ttf").getAbsolutePath());
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
         assertThat(dir.getFontFileMap()).isEmpty();
         // All font dirs (including dir for "bar.ttf") should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -190,7 +208,8 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
         installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -202,7 +221,8 @@
         FileUtils.stringToFile(dirForPreparation.getFontFileMap().get("foo.ttf"), "bar,4");
 
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
         assertThat(dir.getFontFileMap()).isEmpty();
         // All font dirs (including dir for "bar.ttf") should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -213,7 +233,8 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
         installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -226,7 +247,8 @@
         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "bar.ttf"), "bar,1");
         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,2");
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
         // For foo.ttf, preinstalled font (revision 5) should be used.
         assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf");
         // For bar.ttf, updated font (revision 4) should be used.
@@ -239,11 +261,22 @@
     }
 
     @Test
+    public void construct_failedToLoadConfig() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                new File("/dev/null"));
+        assertThat(dir.getFontFileMap()).isEmpty();
+    }
+
+    @Test
     public void installFontFile() throws Exception {
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
 
         installFontFile(dir, "test,1", GOOD_SIGNATURE);
         assertThat(dir.getFontFileMap()).containsKey("test.ttf");
@@ -255,7 +288,8 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
 
         installFontFile(dir, "test,1", GOOD_SIGNATURE);
         Map<String, File> mapBeforeUpgrade = dir.getFontFileMap();
@@ -272,14 +306,15 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
 
         installFontFile(dir, "test,2", GOOD_SIGNATURE);
         try {
             installFontFile(dir, "test,1", GOOD_SIGNATURE);
             fail("Expect IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // Expect
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode()).isEqualTo(FontManager.ERROR_CODE_DOWNGRADING);
         }
         assertThat(dir.getFontFileMap()).containsKey("test.ttf");
         assertWithMessage("Font should not be downgraded to an older revision")
@@ -291,7 +326,8 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
 
         installFontFile(dir, "foo,1", GOOD_SIGNATURE);
         installFontFile(dir, "bar,2", GOOD_SIGNATURE);
@@ -302,17 +338,19 @@
     }
 
     @Test
-    public void installFontFile_invalidSignature() {
+    public void installFontFile_invalidSignature() throws Exception {
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
 
         try {
             installFontFile(dir, "test,1", "Invalid signature");
-            fail("Expect IOException");
-        } catch (IOException e) {
-            // Expect
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.ERROR_CODE_VERIFICATION_FAILURE);
         }
         assertThat(dir.getFontFileMap()).isEmpty();
     }
@@ -323,23 +361,155 @@
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test,1");
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
 
         try {
             installFontFile(dir, "test,1", GOOD_SIGNATURE);
             fail("Expect IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // Expect
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode()).isEqualTo(FontManager.ERROR_CODE_DOWNGRADING);
+        }
+        assertThat(dir.getFontFileMap()).isEmpty();
+    }
+
+    @Test
+    public void installFontFile_failedToWriteConfigXml() throws Exception {
+        long expectedModifiedDate = 1234567890;
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test,1");
+
+        File readonlyDir = new File(mCacheDir, "readonly");
+        assertThat(readonlyDir.mkdir()).isTrue();
+        File readonlyFile = new File(readonlyDir, "readonly_config.xml");
+
+        PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+        config.lastModifiedDate = expectedModifiedDate;
+        writeConfig(config, readonlyFile);
+
+        assertThat(readonlyDir.setWritable(false, false)).isTrue();
+        try {
+            UpdatableFontDir dir = new UpdatableFontDir(
+                    mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                    readonlyFile);
+
+            try {
+                installFontFile(dir, "test,2", GOOD_SIGNATURE);
+            } catch (FontManagerService.SystemFontException e) {
+                assertThat(e.getErrorCode())
+                        .isEqualTo(FontManager.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE);
+            }
+            assertThat(dir.getSystemFontConfig().getLastModifiedDate())
+                    .isEqualTo(expectedModifiedDate);
+            assertThat(dir.getFontFileMap()).isEmpty();
+        } finally {
+            assertThat(readonlyDir.setWritable(true, true)).isTrue();
+        }
+    }
+
+    @Test
+    public void installFontFile_failedToParsePostScript() throws Exception {
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs,
+                new UpdatableFontDir.FontFileParser() {
+                    @Override
+                    public String getPostScriptName(File file) throws IOException {
+                        return null;
+                    }
+
+                    @Override
+                    public long getRevision(File file) throws IOException {
+                        return 0;
+                    }
+                }, fakeFsverityUtil, mConfigFile);
+
+        try {
+            installFontFile(dir, "foo,1", GOOD_SIGNATURE);
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.ERROR_CODE_MISSING_POST_SCRIPT_NAME);
+        }
+        assertThat(dir.getFontFileMap()).isEmpty();
+    }
+
+    @Test
+    public void installFontFile_failedToParsePostScriptName_invalidFont() throws Exception {
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs,
+                new UpdatableFontDir.FontFileParser() {
+                    @Override
+                    public String getPostScriptName(File file) throws IOException {
+                        throw new IOException();
+                    }
+
+                    @Override
+                    public long getRevision(File file) throws IOException {
+                        return 0;
+                    }
+                }, fakeFsverityUtil, mConfigFile);
+
+        try {
+            installFontFile(dir, "foo,1", GOOD_SIGNATURE);
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.ERROR_CODE_INVALID_FONT_FILE);
+        }
+        assertThat(dir.getFontFileMap()).isEmpty();
+    }
+
+    @Test
+    public void installFontFile_renameToPsNameFailure() throws Exception {
+        UpdatableFontDir.FsverityUtil fakeFsverityUtil = new UpdatableFontDir.FsverityUtil() {
+            private final FakeFsverityUtil mFake = new FakeFsverityUtil();
+
+            @Override
+            public boolean hasFsverity(String path) {
+                return mFake.hasFsverity(path);
+            }
+
+            @Override
+            public void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException {
+                mFake.setUpFsverity(path, pkcs7Signature);
+            }
+
+            @Override
+            public boolean rename(File src, File dest) {
+                return false;
+            }
+        };
+        FakeFontFileParser parser = new FakeFontFileParser();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+
+        try {
+            installFontFile(dir, "foo,1", GOOD_SIGNATURE);
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE);
         }
         assertThat(dir.getFontFileMap()).isEmpty();
     }
 
     private void installFontFile(UpdatableFontDir dir, String content, String signature)
-            throws IOException {
+            throws Exception {
         File file = File.createTempFile("font", "ttf", mCacheDir);
         FileUtils.stringToFile(file, content);
         try (FileInputStream in = new FileInputStream(file)) {
             dir.installFontFile(in.getFD(), signature.getBytes());
         }
     }
+
+    private void writeConfig(PersistentSystemFontConfig.Config config,
+            File file) throws IOException {
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            PersistentSystemFontConfig.writeToXml(fos, config);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
index a408d4c..137bd88 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
@@ -113,6 +113,15 @@
                     + "    </allowed-values>"
                     + "    <default-value int-value=\"1\" />"
                     + "  </setting>"
+                    + "  <setting name=\"tv_send_standby_on_sleep\""
+                    + "           value-type=\"int\""
+                    + "           user-configurable=\"true\">"
+                    + "    <allowed-values>"
+                    + "      <value int-value=\"0\" />"
+                    + "      <value int-value=\"1\" />"
+                    + "    </allowed-values>"
+                    + "    <default-value int-value=\"1\" />"
+                    + "  </setting>"
                     + "  <setting name=\"rc_profile_tv\""
                     + "           value-type=\"int\""
                     + "           user-configurable=\"false\">"
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 882d2f5..e6b56ca 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -35,7 +35,6 @@
 import android.os.PowerManager;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
-import android.provider.Settings.Global;
 import android.sysprop.HdmiProperties;
 import android.view.KeyEvent;
 
@@ -133,7 +132,6 @@
                     }
                 };
 
-        mHdmiControlService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true);
         mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
         mHdmiCecLocalDevicePlayback.init();
         mHdmiControlService.setIoLooper(mMyLooper);
@@ -560,7 +558,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_TV);
         mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
                 "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
@@ -580,7 +580,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_BROADCAST);
         mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
                 "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
@@ -600,7 +602,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_NONE);
         mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
                 "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
@@ -620,7 +624,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_TV);
         mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
                 mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
@@ -640,7 +646,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_BROADCAST);
         mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
                 mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
@@ -660,7 +668,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_NONE);
         mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
                 mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index ec806fab..0f527f3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -269,6 +270,30 @@
     }
 
     @Test
+    public void tvSendStandbyOnSleep_Enabled() {
+        mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
+        mTestLooper.dispatchAll();
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage standby = HdmiCecMessageBuilder.buildStandby(ADDR_TV, ADDR_BROADCAST);
+        assertThat(mNativeWrapper.getResultMessages()).contains(standby);
+    }
+
+    @Test
+    public void tvSendStandbyOnSleep_Disabled() {
+        mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_DISABLED);
+        mTestLooper.dispatchAll();
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage standby = HdmiCecMessageBuilder.buildStandby(ADDR_TV, ADDR_BROADCAST);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standby);
+    }
+
+    @Test
     public void getRcFeatures() {
         ArrayList<Integer> features = new ArrayList<>(mHdmiCecLocalDeviceTv.getRcFeatures());
         assertThat(features.contains(Constants.RC_PROFILE_TV_NONE)).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index 1581d9a..691d174 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -82,6 +82,11 @@
     }
 
     @Override
+    String getRebootEscrowServerBlob() {
+        return makeDirs(mStorageDir, super.getRebootEscrowServerBlob()).getAbsolutePath();
+    }
+
+    @Override
     protected File getSyntheticPasswordDirectoryForUser(int userId) {
         return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser(
                 userId).getAbsolutePath());
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index f74e45b..a4ba4c8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
@@ -52,6 +53,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.widget.RebootEscrowListener;
+import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -92,6 +94,7 @@
     private UserManager mUserManager;
     private RebootEscrowManager.Callbacks mCallbacks;
     private IRebootEscrow mRebootEscrow;
+    private ResumeOnRebootServiceConnection mServiceConnection;
     private RebootEscrowKeyStoreManager mKeyStoreManager;
 
     LockSettingsStorageTestable mStorage;
@@ -108,6 +111,7 @@
 
     static class MockInjector extends RebootEscrowManager.Injector {
         private final IRebootEscrow mRebootEscrow;
+        private final ResumeOnRebootServiceConnection mServiceConnection;
         private final RebootEscrowProviderInterface mRebootEscrowProvider;
         private final UserManager mUserManager;
         private final MockableRebootEscrowInjected mInjected;
@@ -116,10 +120,11 @@
         MockInjector(Context context, UserManager userManager,
                 IRebootEscrow rebootEscrow,
                 RebootEscrowKeyStoreManager keyStoreManager,
+                LockSettingsStorageTestable storage,
                 MockableRebootEscrowInjected injected) {
-            super(context);
+            super(context, storage);
             mRebootEscrow = rebootEscrow;
-
+            mServiceConnection = null;
             RebootEscrowProviderHalImpl.Injector halInjector =
                     new RebootEscrowProviderHalImpl.Injector() {
                         @Override
@@ -133,6 +138,22 @@
             mInjected = injected;
         }
 
+        MockInjector(Context context, UserManager userManager,
+                ResumeOnRebootServiceConnection serviceConnection,
+                RebootEscrowKeyStoreManager keyStoreManager,
+                LockSettingsStorageTestable storage,
+                MockableRebootEscrowInjected injected) {
+            super(context, storage);
+            mServiceConnection = serviceConnection;
+            mRebootEscrow = null;
+            RebootEscrowProviderServerBasedImpl.Injector injector =
+                    new RebootEscrowProviderServerBasedImpl.Injector(serviceConnection);
+            mRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(storage, injector);
+            mUserManager = userManager;
+            mKeyStoreManager = keyStoreManager;
+            mInjected = injected;
+        }
+
         @Override
         public UserManager getUserManager() {
             return mUserManager;
@@ -165,6 +186,7 @@
         mUserManager = mock(UserManager.class);
         mCallbacks = mock(RebootEscrowManager.Callbacks.class);
         mRebootEscrow = mock(IRebootEscrow.class);
+        mServiceConnection = mock(ResumeOnRebootServiceConnection.class);
         mKeyStoreManager = mock(RebootEscrowKeyStoreManager.class);
         mAesKey = new SecretKeySpec(TEST_AES_KEY, "AES");
 
@@ -186,7 +208,12 @@
         when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
         mInjected = mock(MockableRebootEscrowInjected.class);
         mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow,
-                mKeyStoreManager, mInjected), mCallbacks, mStorage);
+                mKeyStoreManager, mStorage, mInjected), mCallbacks, mStorage);
+    }
+
+    private void setServerBasedRebootEscrowProvider() throws Exception {
+        mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager,
+                mServiceConnection, mKeyStoreManager, mStorage, mInjected), mCallbacks, mStorage);
     }
 
     @Test
@@ -202,6 +229,19 @@
     }
 
     @Test
+    public void prepareRebootEscrowServerBased_Success() throws Exception {
+        setServerBasedRebootEscrowProvider();
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+        assertFalse(mStorage.hasRebootEscrowServerBlob());
+    }
+
+    @Test
     public void prepareRebootEscrow_ClearCredentials_Success() throws Exception {
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
         mService.setRebootEscrowListener(mockListener);
@@ -246,6 +286,28 @@
     }
 
     @Test
+    public void armServiceServerBased_Success() throws Exception {
+        setServerBasedRebootEscrowProvider();
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertTrue(mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+
+        assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
+        assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+    }
+
+    @Test
     public void armService_HalFailure_NonFatal() throws Exception {
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
         mService.setRebootEscrowListener(mockListener);
@@ -346,6 +408,40 @@
     }
 
     @Test
+    public void loadRebootEscrowDataIfAvailable_ServerBased_Success() throws Exception {
+        setServerBasedRebootEscrowProvider();
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertTrue(mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
+
+        when(mServiceConnection.unwrap(any(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        mService.loadRebootEscrowDataIfAvailable();
+        verify(mServiceConnection).unwrap(any(), anyLong());
+        assertTrue(metricsSuccessCaptor.getValue());
+        verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
+    }
+
+    @Test
     public void loadRebootEscrowDataIfAvailable_TooManyBootsInBetween_NoMetrics() throws Exception {
         when(mInjected.getBootCount()).thenReturn(0);
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
new file mode 100644
index 0000000..bc1e025
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
@@ -0,0 +1,145 @@
+/*
+ * 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.locksettings;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.platform.test.annotations.Presubmit;
+
+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.stubbing.Answer;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RebootEscrowProviderServerBasedImplTests {
+    private SecretKey mKeyStoreEncryptionKey;
+    private RebootEscrowKey mRebootEscrowKey;
+    private ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection mServiceConnection;
+    private LockSettingsStorageTestable mStorage;
+    private RebootEscrowProviderServerBasedImpl mRebootEscrowProvider;
+    private Answer<byte[]> mFakeEncryption;
+
+    private static final byte[] TEST_AES_KEY = new byte[] {
+            0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31,
+            0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61,
+            0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09,
+            0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23,
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES");
+        mRebootEscrowKey = RebootEscrowKey.generate();
+        mServiceConnection = mock(
+                ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection.class);
+
+        Context context = new ContextWrapper(InstrumentationRegistry.getContext());
+        mStorage = new LockSettingsStorageTestable(context,
+                new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
+        mRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mStorage,
+                new RebootEscrowProviderServerBasedImpl.Injector(mServiceConnection));
+
+        mFakeEncryption = invocation -> {
+            byte[] secret = invocation.getArgument(0);
+            for (int i = 0; i < secret.length; i++) {
+                secret[i] = (byte) (secret[i] ^ 0xf);
+            }
+            return secret;
+        };
+    }
+
+    @Test
+    public void getAndClearRebootEscrowKey_loopback_success() throws Exception {
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption);
+        when(mServiceConnection.unwrap(any(), anyLong())).thenAnswer(mFakeEncryption);
+
+        assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport());
+        mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+
+        RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey(
+                mKeyStoreEncryptionKey);
+        assertThat(ks.getKeyBytes(), is(mRebootEscrowKey.getKeyBytes()));
+        assertFalse(mStorage.hasRebootEscrowServerBlob());
+    }
+
+    @Test
+    public void getAndClearRebootEscrowKey_WrongDecryptionMethod_failure() throws Exception {
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption);
+        when(mServiceConnection.unwrap(any(), anyLong())).thenAnswer(
+                invocation -> {
+                    byte[] secret = invocation.getArgument(0);
+                    for (int i = 0; i < secret.length; i++) {
+                        secret[i] = (byte) (secret[i] ^ 0xe);
+                    }
+                    return secret;
+                }
+        );
+
+        assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport());
+        mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // Expect to get wrong key bytes
+        RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey(
+                mKeyStoreEncryptionKey);
+        assertNotEquals(ks.getKeyBytes(), mRebootEscrowKey.getKeyBytes());
+        assertFalse(mStorage.hasRebootEscrowServerBlob());
+    }
+
+    @Test
+    public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception {
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption);
+        doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong());
+
+        assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport());
+        mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // Expect to get null key bytes when the server service fails to unwrap the blob.
+        RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey(
+                mKeyStoreEncryptionKey);
+        assertNull(ks);
+        assertFalse(mStorage.hasRebootEscrowServerBlob());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index eaf62cb..0dcd608 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -23,12 +23,11 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.dex.DexMetadataHelper;
+import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.FileUtils;
@@ -38,6 +37,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.servicestests.R;
+import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.parsing.TestPackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
@@ -207,28 +207,35 @@
     }
 
     @Test
-    public void testPackageSizeWithDmFile()
-            throws IOException, PackageParserException {
+    public void testPackageSizeWithDmFile() throws IOException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
-        File dm = createDexMetadataFile("install_split_base.apk");
-        ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+        final File dm = createDexMetadataFile("install_split_base.apk");
+        final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
                 ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, 0 /* flags */);
         if (result.isError()) {
             throw new IllegalStateException(result.getErrorMessage(), result.getException());
         }
-        PackageParser.PackageLite pkg = result.getResult();
+        final PackageLite pkg = result.getResult();
         Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkg));
     }
 
     // This simulates the 'adb shell pm install' flow.
     @Test
-    public void testPackageSizeWithPartialPackageLite() throws IOException, PackageParserException {
-        File base = copyApkToToTmpDir("install_split_base", R.raw.install_split_base);
-        File dm = createDexMetadataFile("install_split_base.apk");
+    public void testPackageSizeWithPartialPackageLite() throws IOException,
+            PackageManagerException {
+        final File base = copyApkToToTmpDir("install_split_base", R.raw.install_split_base);
+        final File dm = createDexMetadataFile("install_split_base.apk");
         try (FileInputStream is = new FileInputStream(base)) {
-            ApkLite baseApk = PackageParser.parseApkLite(is.getFD(), base.getAbsolutePath(), 0);
-            PackageLite pkgLite = new PackageLite(null, baseApk.codePath, baseApk, null, null, null,
-                    null, null, null);
+            final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(
+                    ParseTypeImpl.forDefaultParsing().reset(), is.getFD(),
+                    base.getAbsolutePath(), /* flags */ 0);
+            if (result.isError()) {
+                throw new PackageManagerException(result.getErrorCode(),
+                        result.getErrorMessage(), result.getException());
+            }
+            final ApkLite baseApk = result.getResult();
+            final PackageLite pkgLite = new PackageLite(null, baseApk.getPath(), baseApk, null,
+                    null, null, null, null, null);
             Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkgLite));
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
index ce96771..db241de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
@@ -16,9 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -26,22 +23,17 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
-import android.app.IApplicationThread;
 import android.content.Intent;
-import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.server.wm.ActivityStarter.Factory;
-import com.android.server.wm.ActivityTaskSupervisor.PendingActivityLaunch;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Random;
-
 /**
  * Tests for the {@link ActivityStartController} class.
  *
@@ -66,36 +58,6 @@
     }
 
     /**
-     * Ensures that pending launches are processed.
-     */
-    @Test
-    public void testPendingActivityLaunches() {
-        final Random random = new Random();
-
-        final ActivityRecord activity = new ActivityBuilder(mAtm).build();
-        final ActivityRecord source = new ActivityBuilder(mAtm)
-                .setCreateTask(true)
-                .build();
-        final int startFlags = random.nextInt();
-        final Task rootTask = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final WindowProcessController wpc = new WindowProcessController(mAtm,
-                mAtm.mContext.getApplicationInfo(), "name", 12345,
-                UserHandle.getUserId(12345), mock(Object.class),
-                mock(WindowProcessListener.class));
-        wpc.setThread(mock(IApplicationThread.class));
-
-        mController.addPendingActivityLaunch(
-                new PendingActivityLaunch(activity, source, startFlags, rootTask, wpc, null));
-        final boolean resume = random.nextBoolean();
-        mController.doPendingActivityLaunches(resume);
-
-        verify(mStarter, times(1)).startResolvedActivity(eq(activity), eq(source), eq(null),
-                eq(null), eq(startFlags), eq(resume), eq(null), eq(null), eq(null));
-    }
-
-
-    /**
      * Ensures instances are recycled after execution.
      */
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 4bfc837..36adf28 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -27,7 +27,6 @@
 import static android.app.ActivityManager.START_PERMISSION_DENIED;
 import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
 import static android.app.ActivityManager.START_SUCCESS;
-import static android.app.ActivityManager.START_SWITCHES_CANCELED;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -119,7 +118,6 @@
     private static final int PRECONDITION_DIFFERENT_UID = 1 << 7;
     private static final int PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION = 1 << 8;
     private static final int PRECONDITION_CANNOT_START_ANY_ACTIVITY = 1 << 9;
-    private static final int PRECONDITION_DISALLOW_APP_SWITCHING = 1 << 10;
 
     private static final int FAKE_CALLING_UID = 666;
     private static final int FAKE_REAL_CALLING_UID = 667;
@@ -153,8 +151,6 @@
                         | PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION,
                 START_NOT_VOICE_COMPATIBLE);
         verifyStartActivityPreconditions(PRECONDITION_CANNOT_START_ANY_ACTIVITY, START_ABORTED);
-        verifyStartActivityPreconditions(PRECONDITION_DISALLOW_APP_SWITCHING,
-                START_SWITCHES_CANCELED);
     }
 
     private static boolean containsConditions(int preconditions, int mask) {
@@ -244,11 +240,6 @@
             intent.setComponent(source.mActivityComponent);
         }
 
-        if (containsConditions(preconditions, PRECONDITION_DISALLOW_APP_SWITCHING)) {
-            doReturn(false).when(service).checkAppSwitchAllowedLocked(
-                    anyInt(), anyInt(), anyInt(), anyInt(), any());
-        }
-
         if (containsConditions(preconditions, PRECONDITION_CANNOT_START_ANY_ACTIVITY)) {
             doReturn(false).when(service.mTaskSupervisor).checkStartAnyActivityPermission(
                     any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), any(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index 032edde..325bca4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -18,15 +18,24 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.platform.test.annotations.Presubmit;
+import android.view.Display.Mode;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import android.view.SurfaceControl;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,16 +49,40 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class FrameRateSelectionPriorityTests extends WindowTestsBase {
+    private static final float FLOAT_TOLERANCE = 0.01f;
+    private static final int LOW_MODE_ID = 3;
+
+    private DisplayPolicy mDisplayPolicy = mock(DisplayPolicy.class);
+    private RefreshRatePolicy mRefreshRatePolicy;
+    private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
+
+    @Before
+    public void setUp() {
+        DisplayInfo di = new DisplayInfo(mDisplayInfo);
+        Mode defaultMode = di.getDefaultMode();
+        di.supportedModes = new Mode[] {
+                new Mode(1, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90),
+                new Mode(2, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70),
+                new Mode(LOW_MODE_ID,
+                        defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60),
+        };
+        di.defaultModeId = 1;
+        mRefreshRatePolicy = new RefreshRatePolicy(mWm, di, mDenylist);
+        when(mDisplayPolicy.getRefreshRatePolicy()).thenReturn(mRefreshRatePolicy);
+    }
 
     @Test
     public void basicTest() {
         final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
         assertNotNull("Window state is created", appWindow);
+
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority doesn't change.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Call the function a few times.
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -57,7 +90,9 @@
 
         // Since nothing changed in the priority state, the transaction should not be updating.
         verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
-                appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+                any(SurfaceControl.class), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
+                any(SurfaceControl.class), anyInt(), anyInt());
     }
 
     @Test
@@ -66,10 +101,16 @@
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
                 .getPreferredModeId(appWindow), 0);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+                .getPreferredRefreshRate(appWindow), 0, FLOAT_TOLERANCE);
+
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority stays MAX_VALUE.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
         verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
 
@@ -78,31 +119,38 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes to 1.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 1);
+        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
+                any(SurfaceControl.class), anyInt(), anyInt());
     }
 
     @Test
     public void testApplicationInFocusWithModeId() {
         final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Application is in focus.
         appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
         // Update the mode ID to a requested number.
         appWindow.mAttrs.preferredDisplayModeId = 1;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 0);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Remove the mode ID request.
         appWindow.mAttrs.preferredDisplayModeId = 0;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Verify we called actions on Transactions correctly.
         verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
@@ -111,12 +159,15 @@
                 appWindow.getSurfaceControl(), 0);
         verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 1);
+        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
+                any(SurfaceControl.class), anyInt(), anyInt());
     }
 
     @Test
     public void testApplicationNotInFocusWithModeId() {
         final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
         appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -124,23 +175,28 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // The window is not in focus.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Update the mode ID to a requested number.
         appWindow.mAttrs.preferredDisplayModeId = 1;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 2);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 2);
+        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
+                any(SurfaceControl.class), anyInt(), anyInt());
     }
 
     @Test
     public void testApplicationNotInFocusWithoutModeId() {
         final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
         appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -148,14 +204,45 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // The window is not in focus.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Make sure that the mode ID is not set.
         appWindow.mAttrs.preferredDisplayModeId = 0;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority doesn't change.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
+                any(SurfaceControl.class), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testPreferredRefreshRate() {
+        final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+        assertNotNull("Window state is created", appWindow);
+        when(appWindow.getDisplayContent().getDisplayPolicy()).thenReturn(mDisplayPolicy);
+
+        appWindow.mAttrs.packageName = "com.android.test";
+        when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
+
+        assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow));
+        assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE);
+
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority);
+        assertEquals(60, appWindow.mDenyListFrameRate, FLOAT_TOLERANCE);
+
+        // Call the function a few times.
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+
+        // Since nothing changed in the priority state, the transaction should not be updating.
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
+                any(SurfaceControl.class), anyInt());
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
+                appWindow.getSurfaceControl(), 60, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 77a4b05..ef3c7ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -44,7 +44,7 @@
 @RunWith(WindowTestRunner.class)
 @FlakyTest
 public class RefreshRatePolicyTest extends WindowTestsBase {
-
+    private static final float FLOAT_TOLERANCE = 0.01f;
     private static final int LOW_MODE_ID = 3;
 
     private RefreshRatePolicy mPolicy;
@@ -70,28 +70,34 @@
                 "cameraUsingWindow");
         cameraUsingWindow.mAttrs.packageName = "com.android.test";
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.addNonHighRefreshRatePackage("com.android.test");
         assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(cameraUsingWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.removeNonHighRefreshRatePackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
 
     @Test
-    public void testBlacklist() {
-        final WindowState blacklistedWindow = createWindow(null, TYPE_BASE_APPLICATION,
-                "blacklistedWindow");
-        blacklistedWindow.mAttrs.packageName = "com.android.test";
+    public void testDenyList() {
+        final WindowState denylistedWindow = createWindow(null, TYPE_BASE_APPLICATION,
+                "denylistedWindow");
+        denylistedWindow.mAttrs.packageName = "com.android.test";
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
-        assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(blacklistedWindow));
+        assertEquals(0, mPolicy.getPreferredModeId(denylistedWindow));
+        assertEquals(60, mPolicy.getPreferredRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
     }
 
     @Test
     public void testAppOverride_blacklist() {
         final WindowState overrideWindow = createWindow(null, TYPE_BASE_APPLICATION,
                 "overrideWindow");
+        overrideWindow.mAttrs.packageName = "com.android.test";
         overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
         assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
+        assertEquals(60, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -102,6 +108,7 @@
         overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
         mPolicy.addNonHighRefreshRatePackage("com.android.test");
         assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -115,6 +122,7 @@
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         mPolicy.addNonHighRefreshRatePackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -125,10 +133,12 @@
 
         mPolicy.addNonHighRefreshRatePackage("com.android.test");
         assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(cameraUsingWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
 
         cameraUsingWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
                 cameraUsingWindow.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index c308fdb..b8d44f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -245,6 +245,12 @@
     }
 
     @Override
+    public SurfaceControl.Transaction setFrameRate(SurfaceControl sc, float frameRate,
+            int compatibility) {
+        return this;
+    }
+
+    @Override
     public SurfaceControl.Transaction unsetColor(SurfaceControl sc) {
         return this;
     }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index c5e6c35..b3092b9 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -201,6 +201,7 @@
 import android.net.UidRange;
 import android.net.UidRangeParcel;
 import android.net.Uri;
+import android.net.VpnInfo;
 import android.net.VpnManager;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.NetworkMonitorUtils;
@@ -245,7 +246,6 @@
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.WakeupMessage;
@@ -8323,8 +8323,7 @@
         assertVpnUidRangesUpdated(true, vpnRange, vpnOwnerUid);
         mMockVpn.setVpnType(vpnType);
 
-        final VpnInfo vpnInfo = new VpnInfo();
-        vpnInfo.ownerUid = vpnOwnerUid;
+        final VpnInfo vpnInfo = new VpnInfo(vpnOwnerUid, null, null);
         mMockVpn.setVpnInfo(vpnInfo);
     }
 
diff --git a/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java
index 3aafe0b..1b33930e 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java
@@ -33,8 +33,7 @@
 import static org.junit.Assert.assertEquals;
 
 import android.net.NetworkStats;
-
-import com.android.internal.net.VpnInfo;
+import android.net.VpnInfo;
 
 /** Superclass with utilities for NetworkStats(Service|Factory)Test */
 abstract class NetworkStatsBaseTest {
@@ -113,10 +112,6 @@
     }
 
     static VpnInfo createVpnInfo(String vpnIface, String[] underlyingIfaces) {
-        VpnInfo info = new VpnInfo();
-        info.ownerUid = UID_VPN;
-        info.vpnIface = vpnIface;
-        info.underlyingIfaces = underlyingIfaces;
-        return info;
+        return new VpnInfo(UID_VPN, vpnIface, underlyingIfaces);
     }
 }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
index e4996d9..76647a6 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -36,13 +36,13 @@
 import android.content.res.Resources;
 import android.net.NetworkStats;
 import android.net.TrafficStats;
+import android.net.VpnInfo;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.tests.net.R;
-import com.android.internal.net.VpnInfo;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 19f9641..b4e37de 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -86,6 +86,7 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.net.VpnInfo;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.ConditionVariable;
 import android.os.Handler;
@@ -104,7 +105,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index dfd0c8a..86a1591 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -28,6 +28,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
@@ -39,6 +40,12 @@
                 NetworkCapabilities.NET_CAPABILITY_INTERNET, NetworkCapabilities.NET_CAPABILITY_MMS
             };
     public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN};
+
+    static {
+        Arrays.sort(EXPOSED_CAPS);
+        Arrays.sort(UNDERLYING_CAPS);
+    }
+
     public static final long[] RETRY_INTERVALS_MS =
             new long[] {
                 TimeUnit.SECONDS.toMillis(5),
@@ -124,12 +131,13 @@
     public void testBuilderAndGetters() {
         final VcnGatewayConnectionConfig config = buildTestConfig();
 
-        for (int cap : EXPOSED_CAPS) {
-            config.hasExposedCapability(cap);
-        }
-        for (int cap : UNDERLYING_CAPS) {
-            config.requiresUnderlyingCapability(cap);
-        }
+        int[] exposedCaps = config.getExposedCapabilities();
+        Arrays.sort(exposedCaps);
+        assertArrayEquals(EXPOSED_CAPS, exposedCaps);
+
+        int[] underlyingCaps = config.getRequiredUnderlyingCapabilities();
+        Arrays.sort(underlyingCaps);
+        assertArrayEquals(UNDERLYING_CAPS, underlyingCaps);
 
         assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMs());
         assertEquals(MAX_MTU, config.getMaxMtu());
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
new file mode 100644
index 0000000..d0fec55
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+import static com.android.server.vcn.VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+/** Tests for VcnGatewayConnection.DisconnectedState */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnectionTestBase {
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession());
+
+        mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState);
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void testIkeSessionClosed() throws Exception {
+        getIkeSessionCallback().onClosed();
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+    }
+
+    @Test
+    public void testTimeoutExpired() throws Exception {
+        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+        mTestLooper.dispatchAll();
+
+        verify(mMockIkeSession).kill();
+    }
+
+    @Test
+    public void testTeardown() throws Exception {
+        mGatewayConnection.teardownAsynchronously();
+        mTestLooper.dispatchAll();
+
+        // Should do nothing; already tearing down.
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 1725dd9..3467859 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -17,11 +17,13 @@
 package com.android.server.vcn;
 
 import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
+import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
 import static com.android.server.vcn.VcnTestUtils.setupIpSecManager;
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -30,6 +32,7 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.ipsec.ike.IkeSessionCallback;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.os.ParcelUuid;
@@ -38,6 +41,7 @@
 import com.android.server.IpSecService;
 
 import org.junit.Before;
+import org.mockito.ArgumentCaptor;
 
 import java.util.UUID;
 
@@ -68,6 +72,7 @@
 
     @NonNull protected final IpSecService mIpSecSvc;
 
+    protected VcnIkeSession mMockIkeSession;
     protected VcnGatewayConnection mGatewayConnection;
 
     public VcnGatewayConnectionTestBase() {
@@ -100,6 +105,16 @@
                         TEST_IPSEC_TUNNEL_IFACE);
         doReturn(resp).when(mIpSecSvc).createTunnelInterface(any(), any(), any(), any(), any());
 
+        mMockIkeSession = mock(VcnIkeSession.class);
+        doReturn(mMockIkeSession).when(mDeps).newIkeSession(any(), any(), any(), any(), any());
+
         mGatewayConnection = new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps);
     }
+
+    protected IkeSessionCallback getIkeSessionCallback() {
+        ArgumentCaptor<IkeSessionCallback> captor =
+                ArgumentCaptor.forClass(IkeSessionCallback.class);
+        verify(mDeps).newIkeSession(any(), any(), any(), captor.capture(), any());
+        return captor.getValue();
+    }
 }