Merge "Fix bugprone-use-after-move in ResourceParser"
diff --git a/Android.bp b/Android.bp
index 0c1095d..e1cb037 100644
--- a/Android.bp
+++ b/Android.bp
@@ -297,6 +297,7 @@
     srcs: [
         ":framework-non-updatable-sources",
         "core/java/**/*.logtags",
+        ":apex-info-list",
     ],
     aidl: {
         generate_get_transaction_name: true,
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 1e5ae7b..82759f7 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -112,7 +112,6 @@
 }
 
 // Defaults module for doc-stubs targets that use module source code as input.
-// This is the default/normal.
 stubs_defaults {
     name: "framework-doc-stubs-sources-default",
     defaults: ["framework-doc-stubs-default"],
@@ -148,12 +147,6 @@
 }
 
 droidstubs {
-    name: "framework-doc-stubs",
-    defaults: ["framework-doc-stubs-sources-default"],
-    args: metalava_framework_docs_args,
-}
-
-droidstubs {
     name: "framework-doc-system-stubs",
     defaults: ["framework-doc-stubs-sources-default"],
     args: metalava_framework_docs_args +
@@ -161,11 +154,8 @@
     api_levels_sdk_type: "system",
 }
 
-// Experimental target building doc stubs with module stub source code as input.
-// This is intended to eventually replace framework-doc-stubs, once all diffs
-// have been eliminated.
 droidstubs {
-    name: "framework-doc-stubs-module-stubs",
+    name: "framework-doc-stubs",
     defaults: ["framework-doc-stubs-default"],
     args: metalava_framework_docs_args,
     srcs: [
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS
new file mode 100644
index 0000000..e4549b4
--- /dev/null
+++ b/PACKAGE_MANAGER_OWNERS
@@ -0,0 +1,3 @@
+chiuwinson@google.com
+patb@google.com
+schfan@google.com
\ No newline at end of file
diff --git a/apct-tests/perftests/textclassifier/AndroidManifest.xml b/apct-tests/perftests/textclassifier/AndroidManifest.xml
index 7cf487f..a79f0f1 100644
--- a/apct-tests/perftests/textclassifier/AndroidManifest.xml
+++ b/apct-tests/perftests/textclassifier/AndroidManifest.xml
@@ -16,6 +16,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.perftests.textclassifier">
 
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 035c9d2..b2e95a5 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -16,8 +16,6 @@
 
 package com.android.server.tare;
 
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
-
 import static com.android.server.tare.EconomicPolicy.REGULATION_BASIC_INCOME;
 import static com.android.server.tare.EconomicPolicy.REGULATION_BIRTHRIGHT;
 import static com.android.server.tare.EconomicPolicy.REGULATION_WEALTH_RECLAMATION;
@@ -69,12 +67,6 @@
     private static final boolean DEBUG = InternalResourceService.DEBUG
             || Log.isLoggable(TAG, Log.DEBUG);
 
-    /**
-     * The minimum amount of time an app must not have been used by the user before we start
-     * regularly reclaiming ARCs from it.
-     */
-    private static final long MIN_UNUSED_TIME_MS = 3 * 24 * HOUR_IN_MILLIS;
-
     private static final String ALARM_TAG_AFFORDABILITY_CHECK = "*tare.affordability_check*";
 
     private final Object mLock;
@@ -550,41 +542,62 @@
 
     /**
      * Reclaim a percentage of unused ARCs from every app that hasn't been used recently. The
-     * reclamation will not reduce an app's balance below its minimum balance as dictated by the
-     * EconomicPolicy.
+     * reclamation will not reduce an app's balance below its minimum balance as dictated by
+     * {@code scaleMinBalance}.
      *
-     * @param percentage A value between 0 and 1 to indicate how much of the unused balance should
-     *                   be reclaimed.
+     * @param percentage      A value between 0 and 1 to indicate how much of the unused balance
+     *                        should be reclaimed.
+     * @param minUnusedTimeMs The minimum amount of time (in milliseconds) that must have
+     *                        transpired since the last user usage event before we will consider
+     *                        reclaiming ARCs from the app.
+     * @param scaleMinBalance Whether or not to used the scaled minimum app balance. If false,
+     *                        this will use the constant min balance floor given by
+     *                        {@link EconomicPolicy#getMinSatiatedBalance(int, String)}. If true,
+     *                        this will use the scaled balance given by
+     *                        {@link InternalResourceService#getMinBalanceLocked(int, String)}.
      */
     @GuardedBy("mLock")
-    void reclaimUnusedAssetsLocked(double percentage) {
+    void reclaimUnusedAssetsLocked(double percentage, long minUnusedTimeMs,
+            boolean scaleMinBalance) {
         final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
-        final List<PackageInfo> pkgs = mIrs.getInstalledPackages();
+        final SparseArrayMap<String, Ledger> ledgers = mScribe.getLedgersLocked();
         final long now = getCurrentTimeMillis();
-        for (int i = 0; i < pkgs.size(); ++i) {
-            final int userId = UserHandle.getUserId(pkgs.get(i).applicationInfo.uid);
-            final String pkgName = pkgs.get(i).packageName;
-            final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
-            // AppStandby only counts elapsed time for things like this
-            // TODO: should we use clock time instead?
-            final long timeSinceLastUsedMs =
-                    mAppStandbyInternal.getTimeSinceLastUsedByUser(pkgName, userId);
-            if (timeSinceLastUsedMs >= MIN_UNUSED_TIME_MS) {
-                // Use a constant floor instead of the scaled floor from the IRS.
-                final long minBalance = economicPolicy.getMinSatiatedBalance(userId, pkgName);
+        for (int u = 0; u < ledgers.numMaps(); ++u) {
+            final int userId = ledgers.keyAt(u);
+            for (int p = 0; p < ledgers.numElementsForKey(userId); ++p) {
+                final Ledger ledger = ledgers.valueAt(u, p);
                 final long curBalance = ledger.getCurrentBalance();
-                long toReclaim = (long) (curBalance * percentage);
-                if (curBalance - toReclaim < minBalance) {
-                    toReclaim = curBalance - minBalance;
+                if (curBalance <= 0) {
+                    continue;
                 }
-                if (toReclaim > 0) {
-                    Slog.i(TAG, "Reclaiming unused wealth! Taking " + toReclaim
-                            + " from " + appToString(userId, pkgName));
+                final String pkgName = ledgers.keyAt(u, p);
+                // AppStandby only counts elapsed time for things like this
+                // TODO: should we use clock time instead?
+                final long timeSinceLastUsedMs =
+                        mAppStandbyInternal.getTimeSinceLastUsedByUser(pkgName, userId);
+                if (timeSinceLastUsedMs >= minUnusedTimeMs) {
+                    final long minBalance;
+                    if (!scaleMinBalance) {
+                        // Use a constant floor instead of the scaled floor from the IRS.
+                        minBalance = economicPolicy.getMinSatiatedBalance(userId, pkgName);
+                    } else {
+                        minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
+                    }
+                    long toReclaim = (long) (curBalance * percentage);
+                    if (curBalance - toReclaim < minBalance) {
+                        toReclaim = curBalance - minBalance;
+                    }
+                    if (toReclaim > 0) {
+                        if (DEBUG) {
+                            Slog.i(TAG, "Reclaiming unused wealth! Taking " + toReclaim
+                                    + " from " + appToString(userId, pkgName));
+                        }
 
-                    recordTransactionLocked(userId, pkgName, ledger,
-                            new Ledger.Transaction(
-                                    now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim),
-                            true);
+                        recordTransactionLocked(userId, pkgName, ledger,
+                                new Ledger.Transaction(
+                                        now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim),
+                                true);
+                    }
                 }
             }
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index a39fd47..1ad7407 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -18,6 +18,7 @@
 
 import static android.provider.Settings.Global.TARE_ALARM_MANAGER_CONSTANTS;
 import static android.provider.Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
@@ -85,6 +86,11 @@
     static final long UNUSED_RECLAMATION_PERIOD_MS = 24 * HOUR_IN_MILLIS;
     /** How much of an app's unused wealth should be reclaimed periodically. */
     private static final float DEFAULT_UNUSED_RECLAMATION_PERCENTAGE = .1f;
+    /**
+     * The minimum amount of time an app must not have been used by the user before we start
+     * periodically reclaiming ARCs from it.
+     */
+    private static final long MIN_UNUSED_TIME_MS = 3 * DAY_IN_MILLIS;
     /** The amount of time to delay reclamation by after boot. */
     private static final long RECLAMATION_STARTUP_DELAY_MS = 30_000L;
     private static final int PACKAGE_QUERY_FLAGS =
@@ -107,6 +113,44 @@
     @GuardedBy("mLock")
     private CompleteEconomicPolicy mCompleteEconomicPolicy;
 
+    private static final class ReclamationConfig {
+        /**
+         * ARC circulation threshold (% circulating vs scaled maximum) above which this config
+         * should come into play.
+         */
+        public final double circulationPercentageThreshold;
+        /** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
+        public final double reclamationPercentage;
+        /** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
+        public final long minUsedTimeMs;
+        /** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
+        public final boolean scaleMinBalance;
+
+        ReclamationConfig(double circulationPercentageThreshold, double reclamationPercentage,
+                long minUsedTimeMs, boolean scaleMinBalance) {
+            this.circulationPercentageThreshold = circulationPercentageThreshold;
+            this.reclamationPercentage = reclamationPercentage;
+            this.minUsedTimeMs = minUsedTimeMs;
+            this.scaleMinBalance = scaleMinBalance;
+        }
+    }
+
+    /**
+     * Sorted list of reclamation configs used to determine how many credits to force reclaim when
+     * the circulation percentage is too high. The list should *always* be sorted in descending
+     * order of {@link ReclamationConfig#circulationPercentageThreshold}.
+     */
+    @GuardedBy("mLock")
+    private final List<ReclamationConfig> mReclamationConfigs = List.of(
+            new ReclamationConfig(2, .75, 12 * HOUR_IN_MILLIS, true),
+            new ReclamationConfig(1.6, .5, DAY_IN_MILLIS, true),
+            new ReclamationConfig(1.4, .25, DAY_IN_MILLIS, true),
+            new ReclamationConfig(1.2, .25, 2 * DAY_IN_MILLIS, true),
+            new ReclamationConfig(1, .25, MIN_UNUSED_TIME_MS, false),
+            new ReclamationConfig(
+                    .9, DEFAULT_UNUSED_RECLAMATION_PERCENTAGE, MIN_UNUSED_TIME_MS, false)
+    );
+
     @NonNull
     @GuardedBy("mLock")
     private final List<PackageInfo> mPkgCache = new ArrayList<>();
@@ -190,7 +234,8 @@
                 @Override
                 public void onAlarm() {
                     synchronized (mLock) {
-                        mAgent.reclaimUnusedAssetsLocked(DEFAULT_UNUSED_RECLAMATION_PERCENTAGE);
+                        mAgent.reclaimUnusedAssetsLocked(
+                                DEFAULT_UNUSED_RECLAMATION_PERCENTAGE, MIN_UNUSED_TIME_MS, false);
                         mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
                         scheduleUnusedWealthReclamationLocked();
                     }
@@ -200,6 +245,7 @@
     private static final int MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER = 0;
     private static final int MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT = 1;
     private static final int MSG_PROCESS_USAGE_EVENT = 2;
+    private static final int MSG_MAYBE_FOCE_RECLAIM = 3;
     private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
     private static final String KEY_PKG = "pkg";
 
@@ -317,6 +363,8 @@
             final int newBatteryLevel = getCurrentBatteryLevel();
             if (newBatteryLevel > mCurrentBatteryLevel) {
                 mAgent.distributeBasicIncomeLocked(newBatteryLevel);
+            } else if (newBatteryLevel < mCurrentBatteryLevel) {
+                mHandler.obtainMessage(MSG_MAYBE_FOCE_RECLAIM).sendToTarget();
             }
             mCurrentBatteryLevel = newBatteryLevel;
         }
@@ -511,6 +559,48 @@
         }
     }
 
+    /**
+     * Reclaim unused ARCs above apps' minimum balances if there are too many credits currently
+     * in circulation.
+     */
+    @GuardedBy("mLock")
+    private void maybeForceReclaimLocked() {
+        final long maxCirculation = getMaxCirculationLocked();
+        if (maxCirculation == 0) {
+            Slog.wtf(TAG, "Max scaled circulation is 0...");
+            mAgent.reclaimUnusedAssetsLocked(1, HOUR_IN_MILLIS, true);
+            mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
+            scheduleUnusedWealthReclamationLocked();
+            return;
+        }
+        final long curCirculation = mScribe.getNarcsInCirculationLocked();
+        final double circulationPerc = 1.0 * curCirculation / maxCirculation;
+        if (DEBUG) {
+            Slog.d(TAG, "Circulation %: " + circulationPerc);
+        }
+        final int numConfigs = mReclamationConfigs.size();
+        if (numConfigs == 0) {
+            return;
+        }
+        // The configs are sorted in descending order of circulationPercentageThreshold, so we can
+        // short-circuit if the current circulation is lower than the lowest threshold.
+        if (circulationPerc
+                < mReclamationConfigs.get(numConfigs - 1).circulationPercentageThreshold) {
+            return;
+        }
+        // TODO: maybe exclude apps we think will be launched in the next few hours
+        for (int i = 0; i < numConfigs; ++i) {
+            final ReclamationConfig config = mReclamationConfigs.get(i);
+            if (circulationPerc >= config.circulationPercentageThreshold) {
+                mAgent.reclaimUnusedAssetsLocked(
+                        config.reclamationPercentage, config.minUsedTimeMs, config.scaleMinBalance);
+                mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
+                scheduleUnusedWealthReclamationLocked();
+                break;
+            }
+        }
+    }
+
     private void registerListeners() {
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED);
@@ -594,6 +684,14 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
+                case MSG_MAYBE_FOCE_RECLAIM: {
+                    removeMessages(MSG_MAYBE_FOCE_RECLAIM);
+                    synchronized (mLock) {
+                        maybeForceReclaimLocked();
+                    }
+                }
+                break;
+
                 case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: {
                     Bundle data = msg.getData();
                     final int userId = msg.arg1;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index d4c6c8c..42f3d9a 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -147,6 +147,12 @@
         return ledger;
     }
 
+    @GuardedBy("mIrs.getLock()")
+    @NonNull
+    SparseArrayMap<String, Ledger> getLedgersLocked() {
+        return mLedgers;
+    }
+
     /** Returns the total amount of narcs currently allocated to apps. */
     @GuardedBy("mIrs.getLock()")
     long getNarcsInCirculationLocked() {
diff --git a/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java b/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java
index fb66609..915f3f6a3 100644
--- a/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java
+++ b/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java
@@ -43,7 +43,7 @@
  */
 abstract class BaseMediaParceledListSlice<T> implements Parcelable {
     private static String TAG = "BaseMediaParceledListSlice";
-    private static boolean DEBUG = false;
+    private static final boolean DEBUG = false;
 
     /*
      * TODO get this number from somewhere else. For now set it to a quarter of
diff --git a/core/api/current.txt b/core/api/current.txt
index f989d06..0e948db 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8684,12 +8684,14 @@
     method public android.bluetooth.BluetoothDevice getRemoteDevice(byte[]);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public int getScanMode();
     method public int getState();
+    method public int isCisCentralSupported();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean isDiscovering();
     method public boolean isEnabled();
     method public boolean isLe2MPhySupported();
     method public boolean isLeCodedPhySupported();
     method public boolean isLeExtendedAdvertisingSupported();
     method public boolean isLePeriodicAdvertisingSupported();
+    method public int isLePeriodicAdvertisingSyncTransferSenderSupported();
     method public boolean isMultipleAdvertisementSupported();
     method public boolean isOffloadedFilteringSupported();
     method public boolean isOffloadedScanBatchingSupported();
@@ -9542,6 +9544,7 @@
     field public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2; // 0x2
     field public static final int ERROR_BLUETOOTH_NOT_ENABLED = 1; // 0x1
     field public static final int ERROR_DEVICE_NOT_BONDED = 3; // 0x3
+    field public static final int ERROR_FEATURE_NOT_SUPPORTED = 8; // 0x8
     field public static final int ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6; // 0x6
     field public static final int ERROR_UNKNOWN = 2147483647; // 0x7fffffff
     field public static final int SUCCESS = 0; // 0x0
@@ -22672,7 +22675,7 @@
     field public static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
     field public static final String KEY_MAX_HEIGHT = "max-height";
     field public static final String KEY_MAX_INPUT_SIZE = "max-input-size";
-    field public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT = "max-output-channel_count";
+    field public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT = "max-output-channel-count";
     field public static final String KEY_MAX_PTS_GAP_TO_ENCODER = "max-pts-gap-to-encoder";
     field public static final String KEY_MAX_WIDTH = "max-width";
     field public static final String KEY_MIME = "mime";
@@ -30975,6 +30978,7 @@
     field public static final int Q = 29; // 0x1d
     field public static final int R = 30; // 0x1e
     field public static final int S = 31; // 0x1f
+    field public static final int S_V2 = 32; // 0x20
     field public static final int TIRAMISU = 10000; // 0x2710
   }
 
@@ -31589,7 +31593,9 @@
     method @NonNull public static android.os.Parcel obtain();
     method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder);
     method @Nullable public Object[] readArray(@Nullable ClassLoader);
+    method @Nullable public <T> T[] readArray(@Nullable ClassLoader, @NonNull Class<T>);
     method @Nullable public java.util.ArrayList readArrayList(@Nullable ClassLoader);
+    method @Nullable public <T> java.util.ArrayList<T> readArrayList(@Nullable ClassLoader, @NonNull Class<? extends T>);
     method public void readBinderArray(@NonNull android.os.IBinder[]);
     method public void readBinderList(@NonNull java.util.List<android.os.IBinder>);
     method public boolean readBoolean();
@@ -31617,6 +31623,7 @@
     method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
     method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>);
     method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
+    method @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader, @NonNull Class<T>);
     method @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
     method @Nullable public <T> android.os.Parcelable.Creator<T> readParcelableCreator(@Nullable ClassLoader, @NonNull Class<T>);
     method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
@@ -31626,6 +31633,7 @@
     method @NonNull public android.util.Size readSize();
     method @NonNull public android.util.SizeF readSizeF();
     method @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader);
+    method @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader, @NonNull Class<? extends T>);
     method @Nullable public android.util.SparseBooleanArray readSparseBooleanArray();
     method @Nullable public String readString();
     method public void readStringArray(@NonNull String[]);
@@ -47696,6 +47704,10 @@
     field public static final int KEYCODE_CUT = 277; // 0x115
     field public static final int KEYCODE_D = 32; // 0x20
     field public static final int KEYCODE_DEL = 67; // 0x43
+    field public static final int KEYCODE_DEMO_APP_1 = 301; // 0x12d
+    field public static final int KEYCODE_DEMO_APP_2 = 302; // 0x12e
+    field public static final int KEYCODE_DEMO_APP_3 = 303; // 0x12f
+    field public static final int KEYCODE_DEMO_APP_4 = 304; // 0x130
     field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17
     field public static final int KEYCODE_DPAD_DOWN = 20; // 0x14
     field public static final int KEYCODE_DPAD_DOWN_LEFT = 269; // 0x10d
@@ -47727,6 +47739,10 @@
     field public static final int KEYCODE_F7 = 137; // 0x89
     field public static final int KEYCODE_F8 = 138; // 0x8a
     field public static final int KEYCODE_F9 = 139; // 0x8b
+    field public static final int KEYCODE_FEATURED_APP_1 = 297; // 0x129
+    field public static final int KEYCODE_FEATURED_APP_2 = 298; // 0x12a
+    field public static final int KEYCODE_FEATURED_APP_3 = 299; // 0x12b
+    field public static final int KEYCODE_FEATURED_APP_4 = 300; // 0x12c
     field public static final int KEYCODE_FOCUS = 80; // 0x50
     field public static final int KEYCODE_FORWARD = 125; // 0x7d
     field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70
@@ -47892,6 +47908,14 @@
     field public static final int KEYCODE_U = 49; // 0x31
     field public static final int KEYCODE_UNKNOWN = 0; // 0x0
     field public static final int KEYCODE_V = 50; // 0x32
+    field public static final int KEYCODE_VIDEO_APP_1 = 289; // 0x121
+    field public static final int KEYCODE_VIDEO_APP_2 = 290; // 0x122
+    field public static final int KEYCODE_VIDEO_APP_3 = 291; // 0x123
+    field public static final int KEYCODE_VIDEO_APP_4 = 292; // 0x124
+    field public static final int KEYCODE_VIDEO_APP_5 = 293; // 0x125
+    field public static final int KEYCODE_VIDEO_APP_6 = 294; // 0x126
+    field public static final int KEYCODE_VIDEO_APP_7 = 295; // 0x127
+    field public static final int KEYCODE_VIDEO_APP_8 = 296; // 0x128
     field public static final int KEYCODE_VOICE_ASSIST = 231; // 0xe7
     field public static final int KEYCODE_VOLUME_DOWN = 25; // 0x19
     field public static final int KEYCODE_VOLUME_MUTE = 164; // 0xa4
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 599f11d..95d06b0 100755
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -95,6 +95,7 @@
     field public static final String CONTROL_OEM_PAID_NETWORK_PREFERENCE = "android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE";
     field public static final String CONTROL_VPN = "android.permission.CONTROL_VPN";
     field public static final String CREATE_USERS = "android.permission.CREATE_USERS";
+    field public static final String CREATE_VIRTUAL_DEVICE = "android.permission.CREATE_VIRTUAL_DEVICE";
     field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
     field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER";
     field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS";
@@ -1948,6 +1949,22 @@
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
   }
 
+  public final class BluetoothActivityEnergyInfo implements android.os.Parcelable {
+    method public int getBluetoothStackState();
+    method public long getControllerEnergyUsed();
+    method public long getControllerIdleTimeMillis();
+    method public long getControllerRxTimeMillis();
+    method public long getControllerTxTimeMillis();
+    method public long getTimestampMillis();
+    method @NonNull public java.util.List<android.bluetooth.UidTraffic> getUidTraffic();
+    method public boolean isValid();
+    field public static final int BT_STACK_STATE_INVALID = 0; // 0x0
+    field public static final int BT_STACK_STATE_STATE_ACTIVE = 1; // 0x1
+    field public static final int BT_STACK_STATE_STATE_IDLE = 3; // 0x3
+    field public static final int BT_STACK_STATE_STATE_SCANNING = 2; // 0x2
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothActivityEnergyInfo> CREATOR;
+  }
+
   public final class BluetoothAdapter {
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean disable(boolean);
@@ -2247,6 +2264,13 @@
     method @NonNull public android.bluetooth.OobData.LeBuilder setRandomizerHash(@NonNull byte[]);
   }
 
+  public final class UidTraffic implements java.lang.Cloneable android.os.Parcelable {
+    method public long getRxBytes();
+    method public long getTxBytes();
+    method public int getUid();
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.UidTraffic> CREATOR;
+  }
+
 }
 
 package android.bluetooth.le {
@@ -2310,6 +2334,17 @@
 
 }
 
+package android.companion.virtual {
+
+  public final class VirtualDeviceManager {
+  }
+
+  public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
+    method public void close();
+  }
+
+}
+
 package android.content {
 
   public class ApexEnvironment {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e707b03..72e93f8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -337,6 +337,7 @@
   }
 
   public class StatusBarManager {
+    method public void cancelRequestAddTile(@NonNull String);
     method public void clickNotification(@Nullable String, int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void collapsePanels();
     method public void expandNotificationsPanel();
@@ -2785,7 +2786,7 @@
     method public static String actionToString(int);
     method public final void setDisplayId(int);
     field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
-    field public static final int LAST_KEYCODE = 288; // 0x120
+    field public static final int LAST_KEYCODE = 304; // 0x130
   }
 
   public final class KeyboardShortcutGroup implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index e3909f9..0881db0 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -518,6 +518,11 @@
             Notification notification, int id, String pkg, @UserIdInt int userId);
 
     /**
+     * Un-foreground all foreground services in the given app.
+     */
+    public abstract void makeServicesNonForeground(String pkg, @UserIdInt int userId);
+
+    /**
      * If the given app has any FGSs whose notifications are in the given channel,
      * stop them.
      */
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index 08cd0b3..53b16d3 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -48,18 +48,19 @@
                                ClassLoader parent, String classLoaderName) {
         return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled,
                               librarySearchPath, libraryPermittedPath, parent, classLoaderName,
-                              null, null);
+                              null, null, null);
     }
 
     ClassLoader getClassLoaderWithSharedLibraries(
             String zip, int targetSdkVersion, boolean isBundled,
             String librarySearchPath, String libraryPermittedPath,
             ClassLoader parent, String classLoaderName,
-            List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries) {
+            List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
+            List<ClassLoader> sharedLibrariesLoadedAfterApp) {
         // For normal usage the cache key used is the same as the zip path.
         return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
                               libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries,
-                              nativeSharedLibraries);
+                              nativeSharedLibraries, sharedLibrariesLoadedAfterApp);
     }
 
     /**
@@ -71,7 +72,8 @@
      */
     ClassLoader getSharedLibraryClassLoaderWithSharedLibraries(String zip, int targetSdkVersion,
             boolean isBundled, String librarySearchPath, String libraryPermittedPath,
-            ClassLoader parent, String classLoaderName, List<ClassLoader> sharedLibraries) {
+            ClassLoader parent, String classLoaderName, List<ClassLoader> sharedLibraries,
+            List<ClassLoader> sharedLibrariesAfter) {
         ClassLoader loader = getCachedNonBootclasspathSystemLib(zip, parent, classLoaderName,
                 sharedLibraries);
         if (loader != null) {
@@ -86,14 +88,15 @@
         nativeSharedLibraries.add("ALL");
         return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled,
               librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries,
-              nativeSharedLibraries);
+              nativeSharedLibraries, sharedLibrariesAfter);
     }
 
     private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                                        String librarySearchPath, String libraryPermittedPath,
                                        ClassLoader parent, String cacheKey,
                                        String classLoaderName, List<ClassLoader> sharedLibraries,
-                                       List<String> nativeSharedLibraries) {
+                                       List<String> nativeSharedLibraries,
+                                       List<ClassLoader> sharedLibrariesLoadedAfterApp) {
         /*
          * This is the parent we use if they pass "null" in.  In theory
          * this should be the "system" class loader; in practice we
@@ -123,7 +126,7 @@
                 ClassLoader classloader = ClassLoaderFactory.createClassLoader(
                         zip,  librarySearchPath, libraryPermittedPath, parent,
                         targetSdkVersion, isBundled, classLoaderName, sharedLibraries,
-                        nativeSharedLibraries);
+                        nativeSharedLibraries, sharedLibrariesLoadedAfterApp);
 
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
@@ -140,7 +143,8 @@
 
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
             ClassLoader loader = ClassLoaderFactory.createClassLoader(
-                    zip, null, parent, classLoaderName, sharedLibraries);
+                    zip, null, parent, classLoaderName, sharedLibraries,
+                    null /*sharedLibrariesLoadedAfterApp*/);
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
             return loader;
         }
@@ -196,7 +200,7 @@
         ClassLoader classLoader = getClassLoader(path, Build.VERSION.SDK_INT, true /*isBundled*/,
                 null /*librarySearchPath*/, null /*libraryPermittedPath*/, null /*parent*/,
                 null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/,
-                null /* nativeSharedLibraries */);
+                null /* nativeSharedLibraries */, null /*sharedLibrariesLoadedAfterApp*/);
 
         if (classLoader == null) {
             // bad configuration or break in classloading code
@@ -267,7 +271,8 @@
         // stub's APK path, when the actual package path is the donor APK.
         return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null,
                               cacheKey, null /* classLoaderName */, null /* sharedLibraries */,
-                              null /* nativeSharedLibraries */);
+                              null /* nativeSharedLibraries */,
+                              null /*sharedLibrariesLoadedAfterApp*/);
     }
 
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index bd43ab5..756833a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2763,7 +2763,7 @@
      */
     private static Resources createWindowContextResources(@NonNull ContextImpl windowContextBase) {
         final LoadedApk packageInfo = windowContextBase.mPackageInfo;
-        final ClassLoader classLoader = windowContextBase.mClassLoader;
+        final ClassLoader classLoader = windowContextBase.getClassLoader();
         final IBinder token = windowContextBase.getWindowContextToken();
 
         final String resDir = packageInfo.getResDir();
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 47e4810..658c130 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -278,6 +278,7 @@
     List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState();
     boolean clearApplicationUserData(in String packageName, boolean keepState,
             in IPackageDataObserver observer, int userId);
+    void makeServicesNonForeground(in String packageName, int userId);
     @UnsupportedAppUsage
     void forceStopPackage(in String packageName, int userId);
     boolean killPids(in int[] pids, in String reason, boolean secure);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 2be7803..b052bc5 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -68,6 +68,7 @@
 import android.os.WorkSource;
 import android.service.voice.IVoiceInteractionSession;
 import android.view.IRecentsAnimationRunner;
+import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationAdapter;
 import android.window.IWindowOrganizerController;
@@ -344,4 +345,9 @@
      * @param caller is the IApplicationThread representing the calling process.
      */
     void setRunningRemoteTransitionDelegate(in IApplicationThread caller);
+
+    /**
+     * Prepare the back preview in the server
+     */
+    void startBackPreview(IRemoteAnimationRunner runner);
 }
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index a2c9795..5750484 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -54,10 +54,12 @@
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.DisplayAdjustments;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 
@@ -76,6 +78,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Objects;
@@ -700,7 +703,7 @@
     ClassLoader createSharedLibraryLoader(SharedLibraryInfo sharedLibrary,
             boolean isBundledApp, String librarySearchPath, String libraryPermittedPath) {
         List<String> paths = sharedLibrary.getAllCodePaths();
-        List<ClassLoader> sharedLibraries = createSharedLibrariesLoaders(
+        Pair<List<ClassLoader>, List<ClassLoader>> sharedLibraries = createSharedLibrariesLoaders(
                 sharedLibrary.getDependencies(), isBundledApp, librarySearchPath,
                 libraryPermittedPath);
         final String jars = (paths.size() == 1) ? paths.get(0) :
@@ -711,15 +714,31 @@
         return ApplicationLoaders.getDefault().getSharedLibraryClassLoaderWithSharedLibraries(jars,
                     mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                     libraryPermittedPath, /* parent */ null,
-                    /* classLoaderName */ null, sharedLibraries);
+                    /* classLoaderName */ null, sharedLibraries.first, sharedLibraries.second);
     }
 
-    private List<ClassLoader> createSharedLibrariesLoaders(List<SharedLibraryInfo> sharedLibraries,
+    /**
+     *
+     * @return a {@link Pair} of List<ClassLoader> where the first is for standard shared libraries
+     *         and the second is list for shared libraries that code should be loaded after the dex
+     */
+    private Pair<List<ClassLoader>, List<ClassLoader>> createSharedLibrariesLoaders(
+            List<SharedLibraryInfo> sharedLibraries,
             boolean isBundledApp, String librarySearchPath, String libraryPermittedPath) {
-        if (sharedLibraries == null) {
-            return null;
+        if (sharedLibraries == null || sharedLibraries.isEmpty()) {
+            return new Pair<>(null, null);
         }
+
+        // if configured to do so, shared libs are split into 2 collections: those that are
+        // on the class path before the applications code, which is standard, and those
+        // specified to be loaded after the applications code.
+        HashSet<String> libsToLoadAfter = new HashSet<>();
+        Resources systemR = Resources.getSystem();
+        Collections.addAll(libsToLoadAfter, systemR.getStringArray(
+                R.array.config_sharedLibrariesLoadedAfterApp));
+
         List<ClassLoader> loaders = new ArrayList<>();
+        List<ClassLoader> after = new ArrayList<>();
         for (SharedLibraryInfo info : sharedLibraries) {
             if (info.isNative()) {
                 // Native shared lib doesn't contribute to the native lib search path. Its name is
@@ -727,10 +746,19 @@
                 // default linker namespace.
                 continue;
             }
-            loaders.add(createSharedLibraryLoader(
-                    info, isBundledApp, librarySearchPath, libraryPermittedPath));
+            if (libsToLoadAfter.contains(info.getName())) {
+                if (DEBUG) {
+                    Slog.v(ActivityThread.TAG,
+                            info.getName() + " will be loaded after application code");
+                }
+                after.add(createSharedLibraryLoader(
+                        info, isBundledApp, librarySearchPath, libraryPermittedPath));
+            } else {
+                loaders.add(createSharedLibraryLoader(
+                        info, isBundledApp, librarySearchPath, libraryPermittedPath));
+            }
         }
-        return loaders;
+        return new Pair<>(loaders, after);
     }
 
     private StrictMode.ThreadPolicy allowThreadDiskReads() {
@@ -955,9 +983,9 @@
             // as this is early and necessary.
             StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
 
-            List<ClassLoader> sharedLibraries = createSharedLibrariesLoaders(
-                    mApplicationInfo.sharedLibraryInfos, isBundledApp, librarySearchPath,
-                    libraryPermittedPath);
+            Pair<List<ClassLoader>, List<ClassLoader>> sharedLibraries =
+                    createSharedLibrariesLoaders(mApplicationInfo.sharedLibraryInfos, isBundledApp,
+                            librarySearchPath, libraryPermittedPath);
 
             List<String> nativeSharedLibraries = new ArrayList<>();
             if (mApplicationInfo.sharedLibraryInfos != null) {
@@ -971,7 +999,8 @@
             mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(
                     zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                     libraryPermittedPath, mBaseClassLoader,
-                    mApplicationInfo.classLoaderName, sharedLibraries, nativeSharedLibraries);
+                    mApplicationInfo.classLoaderName, sharedLibraries.first, nativeSharedLibraries,
+                    sharedLibraries.second);
             mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
 
             setThreadPolicy(oldPolicy);
@@ -2134,4 +2163,38 @@
             final IBinder mService;
         }
     }
+
+    /**
+     * Check if the Apk paths in the cache are correct, and update them if they are not.
+     * @hide
+     */
+    public static void checkAndUpdateApkPaths(ApplicationInfo expectedAppInfo) {
+        // Get the LoadedApk from the cache
+        ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread == null) {
+            Log.e(TAG, "Cannot find activity thread");
+            return;
+        }
+        checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ true);
+        checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ false);
+    }
+
+    private static void checkAndUpdateApkPaths(ActivityThread activityThread,
+            ApplicationInfo expectedAppInfo, boolean cacheWithCode) {
+        String expectedCodePath = expectedAppInfo.getCodePath();
+        LoadedApk loadedApk = activityThread.peekPackageInfo(
+                expectedAppInfo.packageName, /* includeCode= */ cacheWithCode);
+        // If there is load apk cached, or if the cache is valid, don't do anything.
+        if (loadedApk == null || loadedApk.getApplicationInfo() == null
+                || loadedApk.getApplicationInfo().getCodePath().equals(expectedCodePath)) {
+            return;
+        }
+        // Duplicate framework logic
+        List<String> oldPaths = new ArrayList<>();
+        LoadedApk.makePaths(activityThread, expectedAppInfo, oldPaths);
+
+        // Force update the LoadedApk instance, which should update the reference in the cache
+        loadedApk.updateApplicationInfo(expectedAppInfo, oldPaths);
+    }
+
 }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 9be4adc..7dbd814 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -2579,6 +2579,9 @@
      * for more information.
      * </p>
      * <p>
+     * Callers of this method must have notification listener access or permission to read contacts.
+     * </p>
+     * <p>
      * NOTE: This method calls into Contacts, which may take some time, and should not be called
      * on the main thread.
      * </p>
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 64f89bc..48ceef0 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -669,6 +669,21 @@
         }
     }
 
+    /**
+     * @hide
+     * @param packageName
+     */
+    @TestApi
+    public void cancelRequestAddTile(@NonNull String packageName) {
+        Objects.requireNonNull(packageName);
+        IStatusBarService svc = getService();
+        try {
+            svc.cancelRequestAddTile(packageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     public static String windowStateToString(int state) {
         if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING";
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 8ea1f83..890a8d6 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -54,6 +54,8 @@
 import android.bluetooth.BluetoothManager;
 import android.companion.CompanionDeviceManager;
 import android.companion.ICompanionDeviceManager;
+import android.companion.virtual.IVirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceManager;
 import android.content.ClipboardManager;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
@@ -875,6 +877,16 @@
                 return new CompanionDeviceManager(service, ctx.getOuterContext());
             }});
 
+        registerService(Context.VIRTUAL_DEVICE_SERVICE, VirtualDeviceManager.class,
+                new CachedServiceFetcher<VirtualDeviceManager>() {
+            @Override
+            public VirtualDeviceManager createService(ContextImpl ctx)
+                    throws ServiceNotFoundException {
+                IVirtualDeviceManager service = IVirtualDeviceManager.Stub.asInterface(
+                        ServiceManager.getServiceOrThrow(Context.VIRTUAL_DEVICE_SERVICE));
+                return new VirtualDeviceManager(service, ctx.getOuterContext());
+            }});
+
         registerService(Context.CONSUMER_IR_SERVICE, ConsumerIrManager.class,
                 new CachedServiceFetcher<ConsumerIrManager>() {
             @Override
@@ -1925,39 +1937,6 @@
         public abstract T createService() throws ServiceNotFoundException;
     }
 
-    /**
-     * Like StaticServiceFetcher, creates only one instance of the service per application, but when
-     * creating the service for the first time, passes it the application context of the creating
-     * application.
-     *
-     * TODO: Delete this once its only user (ConnectivityManager) is known to work well in the
-     * case where multiple application components each have their own ConnectivityManager object.
-     */
-    static abstract class StaticApplicationContextServiceFetcher<T> implements ServiceFetcher<T> {
-        private T mCachedInstance;
-
-        @Override
-        public final T getService(ContextImpl ctx) {
-            synchronized (StaticApplicationContextServiceFetcher.this) {
-                if (mCachedInstance == null) {
-                    Context appContext = ctx.getApplicationContext();
-                    // If the application context is null, we're either in the system process or
-                    // it's the application context very early in app initialization. In both these
-                    // cases, the passed-in ContextImpl will not be freed, so it's safe to pass it
-                    // to the service. http://b/27532714 .
-                    try {
-                        mCachedInstance = createService(appContext != null ? appContext : ctx);
-                    } catch (ServiceNotFoundException e) {
-                        onServiceNotFound(e);
-                    }
-                }
-                return mCachedInstance;
-            }
-        }
-
-        public abstract T createService(Context applicationContext) throws ServiceNotFoundException;
-    }
-
     /** @hide */
     public static void onServiceNotFound(ServiceNotFoundException e) {
         // We're mostly interested in tracking down long-lived core system
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index a53ef1b..b3fad21 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -108,7 +108,7 @@
 @SystemService(Context.WALLPAPER_SERVICE)
 public class WallpaperManager {
     private static String TAG = "WallpaperManager";
-    private static boolean DEBUG = false;
+    private static final boolean DEBUG = false;
     private float mWallpaperXStep = -1;
     private float mWallpaperYStep = -1;
     private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java
index 4da1acb..5497b78 100644
--- a/core/java/android/app/slice/SliceManager.java
+++ b/core/java/android/app/slice/SliceManager.java
@@ -41,6 +41,7 @@
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -223,10 +224,15 @@
     public @NonNull Collection<Uri> getSliceDescendants(@NonNull Uri uri) {
         ContentResolver resolver = mContext.getContentResolver();
         try (ContentProviderClient provider = resolver.acquireUnstableContentProviderClient(uri)) {
-            Bundle extras = new Bundle();
-            extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
-            final Bundle res = provider.call(SliceProvider.METHOD_GET_DESCENDANTS, null, extras);
-            return res.getParcelableArrayList(SliceProvider.EXTRA_SLICE_DESCENDANTS);
+            if (provider == null) {
+                Log.w(TAG, TextUtils.formatSimple("Unknown URI: %s", uri));
+            } else {
+                Bundle extras = new Bundle();
+                extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
+                final Bundle res = provider.call(
+                        SliceProvider.METHOD_GET_DESCENDANTS, null, extras);
+                return res.getParcelableArrayList(SliceProvider.EXTRA_SLICE_DESCENDANTS);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to get slice descendants", e);
         }
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index dd147cc..eb10f09 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityOptions;
+import android.app.LoadedApk;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -554,7 +555,7 @@
             }
             // Prepare a local reference to the remote Context so we're ready to
             // inflate any requested LayoutParams.
-            mRemoteContext = getRemoteContext();
+            mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath();
 
             int layoutId = rvToApply.getLayoutId();
             if (rvToApply.canRecycleView(mView)) {
@@ -616,7 +617,7 @@
     private void inflateAsync(@NonNull RemoteViews remoteViews) {
         // Prepare a local reference to the remote Context so we're ready to
         // inflate any requested LayoutParams.
-        mRemoteContext = getRemoteContext();
+        mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath();
         int layoutId = remoteViews.getLayoutId();
 
         if (mLastExecutionSignal != null) {
@@ -718,8 +719,10 @@
      * purposes of reading remote resources.
      * @hide
      */
-    protected Context getRemoteContext() {
+    protected Context getRemoteContextEnsuringCorrectCachedApkPath() {
         try {
+            ApplicationInfo expectedAppInfo = mInfo.providerInfo.applicationInfo;
+            LoadedApk.checkAndUpdateApkPaths(expectedAppInfo);
             // Return if cloned successfully, otherwise default
             Context newContext = mContext.createApplicationContext(
                     mInfo.providerInfo.applicationInfo,
@@ -765,7 +768,7 @@
 
         try {
             if (mInfo != null) {
-                Context theirContext = getRemoteContext();
+                Context theirContext = getRemoteContextEnsuringCorrectCachedApkPath();
                 mRemoteContext = theirContext;
                 LayoutInflater inflater = (LayoutInflater)
                         theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
diff --git a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java b/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java
index df065bf..c17a7b4 100644
--- a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java
+++ b/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java
@@ -16,18 +16,26 @@
 
 package android.bluetooth;
 
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.Arrays;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Record of energy and activity information from controller and
  * underlying bt stack state.Timestamp the record with system
- * time
+ * time.
  *
  * @hide
  */
+@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
 public final class BluetoothActivityEnergyInfo implements Parcelable {
     private final long mTimestamp;
     private int mBluetoothStackState;
@@ -35,13 +43,24 @@
     private long mControllerRxTimeMs;
     private long mControllerIdleTimeMs;
     private long mControllerEnergyUsed;
-    private UidTraffic[] mUidTraffic;
+    private List<UidTraffic> mUidTraffic;
+
+    /** @hide */
+    @IntDef(prefix = { "BT_STACK_STATE_" }, value = {
+            BT_STACK_STATE_INVALID,
+            BT_STACK_STATE_STATE_ACTIVE,
+            BT_STACK_STATE_STATE_SCANNING,
+            BT_STACK_STATE_STATE_IDLE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BluetoothStackState {}
 
     public static final int BT_STACK_STATE_INVALID = 0;
     public static final int BT_STACK_STATE_STATE_ACTIVE = 1;
     public static final int BT_STACK_STATE_STATE_SCANNING = 2;
     public static final int BT_STACK_STATE_STATE_IDLE = 3;
 
+    /** @hide */
     public BluetoothActivityEnergyInfo(long timestamp, int stackState,
             long txTime, long rxTime, long idleTime, long energyUsed) {
         mTimestamp = timestamp;
@@ -52,17 +71,18 @@
         mControllerEnergyUsed = energyUsed;
     }
 
-    @SuppressWarnings("unchecked")
-    BluetoothActivityEnergyInfo(Parcel in) {
+    /** @hide */
+    private BluetoothActivityEnergyInfo(Parcel in) {
         mTimestamp = in.readLong();
         mBluetoothStackState = in.readInt();
         mControllerTxTimeMs = in.readLong();
         mControllerRxTimeMs = in.readLong();
         mControllerIdleTimeMs = in.readLong();
         mControllerEnergyUsed = in.readLong();
-        mUidTraffic = in.createTypedArray(UidTraffic.CREATOR);
+        mUidTraffic = in.createTypedArrayList(UidTraffic.CREATOR);
     }
 
+    /** @hide */
     @Override
     public String toString() {
         return "BluetoothActivityEnergyInfo{"
@@ -72,11 +92,11 @@
                 + " mControllerRxTimeMs=" + mControllerRxTimeMs
                 + " mControllerIdleTimeMs=" + mControllerIdleTimeMs
                 + " mControllerEnergyUsed=" + mControllerEnergyUsed
-                + " mUidTraffic=" + Arrays.toString(mUidTraffic)
+                + " mUidTraffic=" + mUidTraffic
                 + " }";
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothActivityEnergyInfo> CREATOR =
+    public static final @NonNull Parcelable.Creator<BluetoothActivityEnergyInfo> CREATOR =
             new Parcelable.Creator<BluetoothActivityEnergyInfo>() {
                 public BluetoothActivityEnergyInfo createFromParcel(Parcel in) {
                     return new BluetoothActivityEnergyInfo(in);
@@ -87,9 +107,8 @@
                 }
             };
 
-
+    /** @hide */
     @Override
-    @SuppressWarnings("unchecked")
     public void writeToParcel(Parcel out, int flags) {
         out.writeLong(mTimestamp);
         out.writeInt(mBluetoothStackState);
@@ -97,17 +116,21 @@
         out.writeLong(mControllerRxTimeMs);
         out.writeLong(mControllerIdleTimeMs);
         out.writeLong(mControllerEnergyUsed);
-        out.writeTypedArray(mUidTraffic, flags);
+        out.writeTypedList(mUidTraffic);
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
     }
 
     /**
-     * @return bt stack reported state
+     * Get the Bluetooth stack state associated with the energy info.
+     *
+     * @return one of {@link #BluetoothStackState} states
      */
+    @BluetoothStackState
     public int getBluetoothStackState() {
         return mBluetoothStackState;
     }
@@ -134,7 +157,7 @@
     }
 
     /**
-     * product of current(mA), voltage(V) and time(ms)
+     * Get the product of current (mA), voltage (V), and time (ms).
      *
      * @return energy used
      */
@@ -143,22 +166,31 @@
     }
 
     /**
-     * @return timestamp(real time elapsed in milliseconds since boot) of record creation.
+     * @return timestamp (real time elapsed in milliseconds since boot) of record creation
      */
-    public long getTimeStamp() {
+    public @ElapsedRealtimeLong long getTimestampMillis() {
         return mTimestamp;
     }
 
-    public UidTraffic[] getUidTraffic() {
+    /**
+     * Get the {@link List} of each application {@link android.bluetooth.UidTraffic}.
+     *
+     * @return current {@link List} of {@link android.bluetooth.UidTraffic}
+     */
+    public @NonNull List<UidTraffic> getUidTraffic() {
+        if (mUidTraffic == null) {
+            return Collections.emptyList();
+        }
         return mUidTraffic;
     }
 
-    public void setUidTraffic(UidTraffic[] traffic) {
+    /** @hide */
+    public void setUidTraffic(List<UidTraffic> traffic) {
         mUidTraffic = traffic;
     }
 
     /**
-     * @return if the record is valid
+     * @return true if the record Tx time, Rx time, and Idle time are more than 0.
      */
     public boolean isValid() {
         return ((mControllerTxTimeMs >= 0) && (mControllerRxTimeMs >= 0)
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 06ce053..dac8ffe 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2272,6 +2272,66 @@
         return false;
     }
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            BluetoothStatusCodes.SUCCESS,
+            BluetoothStatusCodes.ERROR_UNKNOWN,
+            BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
+            BluetoothStatusCodes.ERROR_FEATURE_NOT_SUPPORTED,
+    })
+    public @interface LeFeatureReturnValues {}
+
+    /**
+     * Returns {@link BluetoothStatusCodes#SUCCESS} if LE Connected Isochronous Stream Central
+     * feature is supported, returns {@link BluetoothStatusCodes#ERROR_FEATURE_NOT_SUPPORTED} if
+     * the feature is not supported or an error code.
+     *
+     * @return whether the chipset supports the LE Connected Isochronous Stream Central feature
+     */
+    @RequiresNoPermission
+    public @LeFeatureReturnValues int isCisCentralSupported() {
+        if (!getLeAccess()) {
+            return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.isCisCentralSupported();
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return BluetoothStatusCodes.ERROR_UNKNOWN;
+    }
+
+    /**
+     * Returns {@link BluetoothStatusCodes#SUCCESS} if LE Periodic Advertising Sync Transfer Sender
+     * feature is supported, returns {@link BluetoothStatusCodes#ERROR_FEATURE_NOT_SUPPORTED} if the
+     * feature is not supported or an error code
+     *
+     * @return whether the chipset supports the LE Periodic Advertising Sync Transfer Sender feature
+     */
+    @RequiresNoPermission
+    public @LeFeatureReturnValues int isLePeriodicAdvertisingSyncTransferSenderSupported() {
+        if (!getLeAccess()) {
+            return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.isLePeriodicAdvertisingSyncTransferSenderSupported();
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return BluetoothStatusCodes.ERROR_UNKNOWN;
+    }
+
     /**
      * Return the maximum LE advertising data length in bytes,
      * if LE Extended Advertising feature is supported, 0 otherwise.
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index e968052..6e918bd 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1186,6 +1186,11 @@
         mAttributionSource = attributionSource;
     }
 
+    /** {@hide} */
+    public void prepareToEnterProcess(@NonNull AttributionSource attributionSource) {
+        setAttributionSource(attributionSource);
+    }
+
     @Override
     public boolean equals(@Nullable Object o) {
         if (o instanceof BluetoothDevice) {
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index b5df4db..20152f3 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -62,15 +62,15 @@
     private static final String TAG = "BluetoothManager";
     private static final boolean DBG = false;
 
-    private static AttributionSource sAttributionSource = null;
+    private final AttributionSource mAttributionSource;
     private final BluetoothAdapter mAdapter;
 
     /**
      * @hide
      */
     public BluetoothManager(Context context) {
-        sAttributionSource = resolveAttributionSource(context);
-        mAdapter = BluetoothAdapter.createAdapter(sAttributionSource);
+        mAttributionSource = resolveAttributionSource(context);
+        mAdapter = BluetoothAdapter.createAdapter(mAttributionSource);
     }
 
     /** {@hide} */
@@ -79,9 +79,6 @@
         if (context != null) {
             res = context.getAttributionSource();
         }
-        else if (sAttributionSource != null) {
-            return sAttributionSource;
-        }
         if (res == null) {
             res = ActivityThread.currentAttributionSource();
         }
@@ -201,8 +198,8 @@
             IBluetoothGatt iGatt = managerService.getBluetoothGatt();
             if (iGatt == null) return devices;
             devices = Attributable.setAttributionSource(
-                    iGatt.getDevicesMatchingConnectionStates(states, sAttributionSource),
-                    sAttributionSource);
+                    iGatt.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                    mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
diff --git a/core/java/android/bluetooth/BluetoothStatusCodes.java b/core/java/android/bluetooth/BluetoothStatusCodes.java
index 3e46c49..63e84ed 100644
--- a/core/java/android/bluetooth/BluetoothStatusCodes.java
+++ b/core/java/android/bluetooth/BluetoothStatusCodes.java
@@ -79,6 +79,11 @@
     public static final int ERROR_MISSING_BLUETOOTH_SCAN_PERMISSION = 7;
 
     /**
+     * Error code indicating that the feature is not supported.
+     */
+    public static final int ERROR_FEATURE_NOT_SUPPORTED = 8;
+
+    /**
      * If another application has already requested {@link OobData} then another fetch will be
      * disallowed until the callback is removed.
      *
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 858819e..bb537dd 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -189,7 +189,7 @@
     @NonNull
     @SystemApi
     public static final ParcelUuid CAP =
-        ParcelUuid.fromString("EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE");
+            ParcelUuid.fromString("00008FE0-0000-1000-8000-00805F9B34FB");
     /** @hide */
     @NonNull
     @SystemApi
diff --git a/core/java/android/bluetooth/UidTraffic.java b/core/java/android/bluetooth/UidTraffic.java
index 2ee786a..9982fa6 100644
--- a/core/java/android/bluetooth/UidTraffic.java
+++ b/core/java/android/bluetooth/UidTraffic.java
@@ -15,6 +15,7 @@
  */
 package android.bluetooth;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -23,27 +24,27 @@
  *
  * @hide
  */
-public class UidTraffic implements Cloneable, Parcelable {
+@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+public final class UidTraffic implements Cloneable, Parcelable {
     private final int mAppUid;
     private long mRxBytes;
     private long mTxBytes;
 
-    public UidTraffic(int appUid) {
-        mAppUid = appUid;
-    }
-
+    /** @hide */
     public UidTraffic(int appUid, long rx, long tx) {
         mAppUid = appUid;
         mRxBytes = rx;
         mTxBytes = tx;
     }
 
-    UidTraffic(Parcel in) {
+    /** @hide */
+    private UidTraffic(Parcel in) {
         mAppUid = in.readInt();
         mRxBytes = in.readLong();
         mTxBytes = in.readLong();
     }
 
+    /** @hide */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mAppUid);
@@ -51,44 +52,60 @@
         dest.writeLong(mTxBytes);
     }
 
+    /** @hide */
     public void setRxBytes(long bytes) {
         mRxBytes = bytes;
     }
 
+    /** @hide */
     public void setTxBytes(long bytes) {
         mTxBytes = bytes;
     }
 
+    /** @hide */
     public void addRxBytes(long bytes) {
         mRxBytes += bytes;
     }
 
+    /** @hide */
     public void addTxBytes(long bytes) {
         mTxBytes += bytes;
     }
 
+    /**
+     * @return corresponding app Uid
+     */
     public int getUid() {
         return mAppUid;
     }
 
+    /**
+     * @return rx bytes count
+     */
     public long getRxBytes() {
         return mRxBytes;
     }
 
+    /**
+     * @return tx bytes count
+     */
     public long getTxBytes() {
         return mTxBytes;
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /** @hide */
     @Override
     public UidTraffic clone() {
         return new UidTraffic(mAppUid, mRxBytes, mTxBytes);
     }
 
+    /** @hide */
     @Override
     public String toString() {
         return "UidTraffic{mAppUid=" + mAppUid + ", mRxBytes=" + mRxBytes + ", mTxBytes="
diff --git a/core/java/android/bluetooth/le/BluetoothLeUtils.java b/core/java/android/bluetooth/le/BluetoothLeUtils.java
index 6381f55..ed50b09 100644
--- a/core/java/android/bluetooth/le/BluetoothLeUtils.java
+++ b/core/java/android/bluetooth/le/BluetoothLeUtils.java
@@ -24,6 +24,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * Helper class for Bluetooth LE utils.
@@ -137,4 +138,21 @@
         }
     }
 
+    /**
+     * Compares two UUIDs with a UUID mask.
+     *
+     * @param data first {@link #UUID} to compare.
+     * @param uuid second {@link #UUID} to compare.
+     * @param mask mask {@link #UUID}.
+     * @return true if both UUIDs are equals when masked, false otherwise.
+     */
+    static boolean maskedEquals(UUID data, UUID uuid, UUID mask) {
+        if (mask == null) {
+            return Objects.equals(data, uuid);
+        }
+        return (data.getLeastSignificantBits() & mask.getLeastSignificantBits())
+                == (uuid.getLeastSignificantBits() & mask.getLeastSignificantBits())
+                && (data.getMostSignificantBits() & mask.getMostSignificantBits())
+                == (uuid.getMostSignificantBits() & mask.getMostSignificantBits());
+    }
 }
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index 8ff0181..b059193 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -28,8 +28,6 @@
 import android.os.ParcelUuid;
 import android.os.Parcelable;
 
-import com.android.internal.util.BitUtils;
-
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
@@ -448,7 +446,7 @@
 
     // Check if the uuid pattern matches the particular service uuid.
     private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
-        return BitUtils.maskedEquals(data, uuid, mask);
+        return BluetoothLeUtils.maskedEquals(data, uuid, mask);
     }
 
     /**
@@ -478,7 +476,7 @@
     // Check if the solicitation uuid pattern matches the particular service solicitation uuid.
     private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid,
             UUID solicitationUuidMask, UUID data) {
-        return BitUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask);
+        return BluetoothLeUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask);
     }
 
     // Check whether the data pattern matches the parsed data.
diff --git a/core/java/android/companion/Association.aidl b/core/java/android/companion/AssociationInfo.aidl
similarity index 95%
rename from core/java/android/companion/Association.aidl
rename to core/java/android/companion/AssociationInfo.aidl
index 2a28f1f..abab743 100644
--- a/core/java/android/companion/Association.aidl
+++ b/core/java/android/companion/AssociationInfo.aidl
@@ -15,4 +15,4 @@
  */
 package android.companion;
 
-parcelable Association;
+parcelable AssociationInfo;
diff --git a/core/java/android/companion/Association.java b/core/java/android/companion/AssociationInfo.java
similarity index 91%
rename from core/java/android/companion/Association.java
rename to core/java/android/companion/AssociationInfo.java
index 7cea33d..ab1eb1f 100644
--- a/core/java/android/companion/Association.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -39,7 +39,7 @@
  * TODO(b/1979395): un-hide and rename to AssociationInfo when implementing public APIs that use
  *                  this class.
  */
-public final class Association implements Parcelable {
+public final class AssociationInfo implements Parcelable {
     /**
      * A unique ID of this Association record.
      * Disclosed to the clients (ie. companion applications) for referring to this record (eg. in
@@ -63,7 +63,7 @@
      *
      * @hide
      */
-    public Association(int associationId, @UserIdInt int userId, @NonNull String packageName,
+    public AssociationInfo(int associationId, @UserIdInt int userId, @NonNull String packageName,
             @NonNull List<DeviceId> deviceIds, @Nullable String deviceProfile,
             boolean managedByCompanionApp, boolean notifyOnDeviceNearby, long timeApprovedMs) {
         if (associationId <= 0) {
@@ -178,8 +178,8 @@
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
-        if (!(o instanceof Association)) return false;
-        final Association that = (Association) o;
+        if (!(o instanceof AssociationInfo)) return false;
+        final AssociationInfo that = (AssociationInfo) o;
         return mAssociationId == that.mAssociationId
                 && mUserId == that.mUserId
                 && mManagedByCompanionApp == that.mManagedByCompanionApp
@@ -216,7 +216,7 @@
         dest.writeLong(mTimeApprovedMs);
     }
 
-    private Association(@NonNull Parcel in) {
+    private AssociationInfo(@NonNull Parcel in) {
         mAssociationId = in.readInt();
 
         mUserId = in.readInt();
@@ -230,16 +230,16 @@
         mTimeApprovedMs = in.readLong();
     }
 
-    public static final Parcelable.Creator<Association> CREATOR =
-            new Parcelable.Creator<Association>() {
+    public static final Parcelable.Creator<AssociationInfo> CREATOR =
+            new Parcelable.Creator<AssociationInfo>() {
         @Override
-        public Association[] newArray(int size) {
-            return new Association[size];
+        public AssociationInfo[] newArray(int size) {
+            return new AssociationInfo[size];
         }
 
         @Override
-        public Association createFromParcel(@NonNull Parcel in) {
-            return new Association(in);
+        public AssociationInfo createFromParcel(@NonNull Parcel in) {
+            return new AssociationInfo(in);
         }
     };
 
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index d42d6b4..a8fe602 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -319,13 +319,13 @@
     }
 
     /**
-     * Gets all package-device {@link Association}s for the current user.
+     * Gets all package-device {@link AssociationInfo}s for the current user.
      *
      * @return the associations list
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
-    public @NonNull List<Association> getAllAssociations() {
+    public @NonNull List<AssociationInfo> getAllAssociations() {
         if (!checkFeaturePresent()) {
             return Collections.emptyList();
         }
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 25c5cb3..101f948 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -18,7 +18,7 @@
 
 import android.app.PendingIntent;
 import android.companion.IFindDeviceCallback;
-import android.companion.Association;
+import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.content.ComponentName;
 
@@ -36,7 +36,7 @@
         in String callingPackage);
 
     List<String> getAssociations(String callingPackage, int userId);
-    List<Association> getAssociationsForUser(int userId);
+    List<AssociationInfo> getAssociationsForUser(int userId);
 
     void disassociate(String deviceMacAddress, String callingPackage);
 
diff --git a/core/java/android/companion/Association.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
similarity index 74%
copy from core/java/android/companion/Association.aidl
copy to core/java/android/companion/virtual/IVirtualDevice.aidl
index 2a28f1f..0aa442b 100644
--- a/core/java/android/companion/Association.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -13,6 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion;
 
-parcelable Association;
+package android.companion.virtual;
+
+/**
+ * Interface for a virtual device.
+ *
+ * @hide
+ */
+interface IVirtualDevice {
+    void close();
+}
diff --git a/core/java/android/companion/Association.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
similarity index 62%
copy from core/java/android/companion/Association.aidl
copy to core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 2a28f1f..91e717d 100644
--- a/core/java/android/companion/Association.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -13,6 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion;
 
-parcelable Association;
+package android.companion.virtual;
+
+import android.companion.virtual.IVirtualDevice;
+
+/**
+ * Interface for communication between VirtualDeviceManager and VirtualDeviceManagerService.
+ *
+ * @hide
+ */
+interface IVirtualDeviceManager {
+
+    IVirtualDevice createVirtualDevice();
+}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
new file mode 100644
index 0000000..6187de5
--- /dev/null
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+
+/**
+ * System level service for managing virtual devices.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.VIRTUAL_DEVICE_SERVICE)
+public final class VirtualDeviceManager {
+
+    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "VirtualDeviceManager";
+
+    private final IVirtualDeviceManager mService;
+    private final Context mContext;
+
+    /** @hide */
+    public VirtualDeviceManager(
+            @Nullable IVirtualDeviceManager service, @NonNull Context context) {
+        mService = service;
+        mContext = context;
+    }
+
+    /**
+     * Creates a virtual device.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    @Nullable
+    public VirtualDevice createVirtualDevice() {
+        // TODO(b/194949534): Add CDM association ID here and unhide this API
+        try {
+            IVirtualDevice virtualDevice = mService.createVirtualDevice();
+            return new VirtualDevice(mContext, virtualDevice);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * A virtual device has its own virtual display, audio output, microphone, and camera etc. The
+     * creator of a virtual device can take the output from the virtual display and stream it over
+     * to another device, and inject input events that are received from the remote device.
+     */
+    public static class VirtualDevice implements AutoCloseable {
+
+        private final Context mContext;
+        private final IVirtualDevice mVirtualDevice;
+
+        private VirtualDevice(Context context, IVirtualDevice virtualDevice) {
+            mContext = context.getApplicationContext();
+            mVirtualDevice = virtualDevice;
+        }
+
+        /**
+         * Closes the virtual device, stopping and tearing down any virtual displays,
+         * audio policies, and event injection that's currently in progress.
+         */
+        public void close() {
+            try {
+                mVirtualDevice.close();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cfd3417..bfc4333 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3799,6 +3799,7 @@
             //@hide: INCIDENT_COMPANION_SERVICE,
             //@hide: STATS_COMPANION_SERVICE,
             COMPANION_DEVICE_SERVICE,
+            //@hide: VIRTUAL_DEVICE_SERVICE,
             CROSS_PROFILE_APPS_SERVICE,
             //@hide: SYSTEM_UPDATE_SERVICE,
             //@hide: TIME_DETECTOR_SERVICE,
@@ -5262,6 +5263,16 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.companion.virtual.VirtualDeviceManager} for managing virtual devices.
+     *
+     * @see #getSystemService(String)
+     * @see android.companion.virtual.VirtualDeviceManager
+     * @hide
+     */
+    public static final String VIRTUAL_DEVICE_SERVICE = "virtualdevice";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.hardware.ConsumerIrManager} for transmitting infrared
      * signals from the device.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 6446b44..e838d93 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -32,6 +32,7 @@
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
+import android.bluetooth.BluetoothDevice;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -11688,6 +11689,16 @@
         if (fromProtectedComponent) {
             mLocalFlags |= LOCAL_FLAG_FROM_PROTECTED_COMPONENT;
         }
+
+        // Special attribution fix-up logic for any BluetoothDevice extras
+        // passed via Bluetooth intents
+        if (mAction != null && mAction.startsWith("android.bluetooth.")
+                && hasExtra(BluetoothDevice.EXTRA_DEVICE)) {
+            final BluetoothDevice device = getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            if (device != null) {
+                device.prepareToEnterProcess(source);
+            }
+        }
     }
 
     /** @hide */
diff --git a/core/java/android/content/OWNERS b/core/java/android/content/OWNERS
index c7f92c9..660368a 100644
--- a/core/java/android/content/OWNERS
+++ b/core/java/android/content/OWNERS
@@ -2,11 +2,9 @@
 per-file Context.java = *
 per-file ContextWrapper.java = *
 per-file Content* = file:/services/core/java/com/android/server/am/OWNERS
-per-file IntentFilter.java = toddke@google.com
-per-file IntentFilter.java = patb@google.com
+per-file IntentFilter.java = file:/PACKAGE_MANAGER_OWNERS
 per-file IntentFilter.java = file:/services/core/java/com/android/server/am/OWNERS
-per-file Intent.java = toddke@google.com
-per-file Intent.java = patb@google.com
+per-file Intent.java = file:/PACKAGE_MANAGER_OWNERS
 per-file Intent.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file Intent.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file AutofillOptions* = file:/core/java/android/service/autofill/OWNERS
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 172a51a..cbb5c2b 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -995,9 +995,10 @@
      * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
      * OVERRIDE_MIN_ASPECT_RATIO_LARGE
      *
-     * If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's
-     * manifest will be overridden to the largest enabled aspect ratio treatment unless the app's
-     * manifest value is higher.
+     * If OVERRIDE_MIN_ASPECT_RATIO is applied, and the activity's orientation is fixed to
+     * portrait, the min aspect ratio given in the app's manifest will be overridden to the
+     * largest enabled aspect ratio treatment unless the app's manifest value is higher.
+     * TODO(b/203647190): add OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY instead of portrait by default
      * @hide
      */
     @ChangeId
@@ -1233,8 +1234,8 @@
      * Returns true if the activity has maximum or minimum aspect ratio.
      * @hide
      */
-    public boolean hasFixedAspectRatio() {
-        return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
+    public boolean hasFixedAspectRatio(@ScreenOrientation int orientation) {
+        return getMaxAspectRatio() != 0 || getMinAspectRatio(orientation) != 0;
     }
 
     /**
@@ -1393,10 +1394,14 @@
      * {@code getManifestMinAspectRatio}.
      * @hide
      */
-    public float getMinAspectRatio() {
+    public float getMinAspectRatio(@ScreenOrientation int orientation) {
+        // TODO(b/203647190): check orientation only if OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY
+        // In case the activity's orientation isn't fixed to portrait, OVERRIDE_MIN_ASPECT_RATIO
+        // shouldn't be applied.
         if (applicationInfo == null || !CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO,
                 applicationInfo.packageName,
-                UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+                UserHandle.getUserHandleForUid(applicationInfo.uid))
+                || !isFixedOrientationPortrait(orientation)) {
             return mMinAspectRatio;
         }
 
@@ -1522,9 +1527,10 @@
         if (getMaxAspectRatio() != 0) {
             pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());
         }
-        if (getMinAspectRatio() != 0) {
-            pw.println(prefix + "minAspectRatio=" + getMinAspectRatio());
-            if (getManifestMinAspectRatio() !=  getMinAspectRatio()) {
+        final float minAspectRatio = getMinAspectRatio(screenOrientation);
+        if (minAspectRatio != 0) {
+            pw.println(prefix + "minAspectRatio=" + minAspectRatio);
+            if (getManifestMinAspectRatio() !=  minAspectRatio) {
                 pw.println(prefix + "getManifestMinAspectRatio=" + getManifestMinAspectRatio());
             }
         }
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 0d8fdcc..cc4782a 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -1,6 +1,15 @@
 {
   "imports": [
     {
+      "path": "frameworks/base/services/tests/PackageManagerServiceTests"
+    },
+    {
+      "path": "frameworks/base/services/tests/PackageManager"
+    },
+    {
+      "path": "frameworks/base/services/tests/PackageManagerComponentOverrideTests"
+    },
+    {
       "path": "cts/tests/tests/packageinstaller"
     },
     {
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index e93ec00..dd00c3a 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -21,6 +21,8 @@
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+import android.system.ErrnoException;
+import android.system.Os;
 
 import java.io.Closeable;
 import java.io.FileDescriptor;
@@ -40,7 +42,7 @@
      * the data extends to the end of the file.
      */
     public static final long UNKNOWN_LENGTH = -1;
-    
+
     @UnsupportedAppUsage
     private final ParcelFileDescriptor mFd;
     @UnsupportedAppUsage
@@ -52,11 +54,11 @@
     /**
      * Create a new AssetFileDescriptor from the given values.
      *
-     * @param fd The underlying file descriptor.
+     * @param fd          The underlying file descriptor.
      * @param startOffset The location within the file that the asset starts.
-     *            This must be 0 if length is UNKNOWN_LENGTH.
-     * @param length The number of bytes of the asset, or
-     *            {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
+     *                    This must be 0 if length is UNKNOWN_LENGTH.
+     * @param length      The number of bytes of the asset, or
+     *                    {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
      */
     public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
             long length) {
@@ -66,13 +68,13 @@
     /**
      * Create a new AssetFileDescriptor from the given values.
      *
-     * @param fd The underlying file descriptor.
+     * @param fd          The underlying file descriptor.
      * @param startOffset The location within the file that the asset starts.
-     *            This must be 0 if length is UNKNOWN_LENGTH.
-     * @param length The number of bytes of the asset, or
-     *            {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
-     * @param extras additional details that can be used to interpret the
-     *            underlying file descriptor. May be null.
+     *                    This must be 0 if length is UNKNOWN_LENGTH.
+     * @param length      The number of bytes of the asset, or
+     *                    {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
+     * @param extras      additional details that can be used to interpret the
+     *                    underlying file descriptor. May be null.
      */
     public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
             long length, Bundle extras) {
@@ -97,7 +99,7 @@
     public ParcelFileDescriptor getParcelFileDescriptor() {
         return mFd;
     }
-    
+
     /**
      * Returns the FileDescriptor that can be used to read the data in the
      * file.
@@ -105,7 +107,7 @@
     public FileDescriptor getFileDescriptor() {
         return mFd.getFileDescriptor();
     }
-    
+
     /**
      * Returns the byte offset where this asset entry's data starts.
      */
@@ -129,7 +131,7 @@
      * ParcelFileDescriptor.getStatSize()} to find the total size of the file,
      * returning that number if found or {@link #UNKNOWN_LENGTH} if it could
      * not be determined.
-     * 
+     *
      * @see #getDeclaredLength()
      */
     public long getLength() {
@@ -139,19 +141,19 @@
         long len = mFd.getStatSize();
         return len >= 0 ? len : UNKNOWN_LENGTH;
     }
-    
+
     /**
      * Return the actual number of bytes that were declared when the
      * AssetFileDescriptor was constructed.  Will be
      * {@link #UNKNOWN_LENGTH} if the length was not declared, meaning data
      * should be read to the end of the file.
-     * 
+     *
      * @see #getDeclaredLength()
      */
     public long getDeclaredLength() {
         return mLength;
     }
-    
+
     /**
      * Convenience for calling <code>getParcelFileDescriptor().close()</code>.
      */
@@ -174,7 +176,7 @@
         }
         return new AutoCloseInputStream(this);
     }
-    
+
     /**
      * Create and return a new auto-close output stream for this asset.  This
      * will either return a full asset {@link AutoCloseOutputStream}, or
@@ -189,13 +191,13 @@
         }
         return new AutoCloseOutputStream(this);
     }
-    
+
     @Override
     public String toString() {
         return "{AssetFileDescriptor: " + mFd
                 + " start=" + mStartOffset + " len=" + mLength + "}";
     }
-    
+
     /**
      * An InputStream you can create on a ParcelFileDescriptor, which will
      * take care of calling {@link ParcelFileDescriptor#close
@@ -203,19 +205,24 @@
      */
     public static class AutoCloseInputStream
             extends ParcelFileDescriptor.AutoCloseInputStream {
-        private long mRemaining;
-        
+        private final long mSizeFromStartOffset;
+        private final long mStartOffset;
+        private long mPosFromStartOffset;
+
         public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
             super(fd.getParcelFileDescriptor());
+            // this skip is necessary if getChannel() is called
             super.skip(fd.getStartOffset());
-            mRemaining = (int)fd.getLength();
+            mSizeFromStartOffset = fd.getLength();
+            mStartOffset = fd.getStartOffset();
         }
 
         @Override
         public int available() throws IOException {
-            return mRemaining >= 0
-                    ? (mRemaining < 0x7fffffff ? (int)mRemaining : 0x7fffffff)
-                    : super.available();
+            long available = mSizeFromStartOffset - mPosFromStartOffset;
+            return available >= 0
+                    ? (available < 0x7fffffff ? (int) available : 0x7fffffff)
+                    : 0;
         }
 
         @Override
@@ -227,15 +234,24 @@
 
         @Override
         public int read(byte[] buffer, int offset, int count) throws IOException {
-            if (mRemaining >= 0) {
-                if (mRemaining == 0) return -1;
-                if (count > mRemaining) count = (int)mRemaining;
-                int res = super.read(buffer, offset, count);
-                if (res >= 0) mRemaining -= res;
-                return res;
+            int available = available();
+
+            if (available <= 0) {
+                return -1;
+            } else {
+                if (count > available) count = available;
+                try {
+                    int res = Os.pread(getFD(), buffer, offset, count,
+                            mStartOffset + mPosFromStartOffset);
+                    // pread returns 0 at end of file, while java's InputStream interface
+                    // requires -1
+                    if (res == 0) res = -1;
+                    if (res >= 0) mPosFromStartOffset += res;
+                    return res;
+                } catch (ErrnoException e) {
+                    throw new IOException(e);
+                }
             }
-            
-            return super.read(buffer, offset, count);
         }
 
         @Override
@@ -245,41 +261,31 @@
 
         @Override
         public long skip(long count) throws IOException {
-            if (mRemaining >= 0) {
-                if (mRemaining == 0) return -1;
-                if (count > mRemaining) count = mRemaining;
-                long res = super.skip(count);
-                if (res >= 0) mRemaining -= res;
-                return res;
+            int available = available();
+            if (available <= 0) {
+                return -1;
+            } else {
+                if (count > available) count = available;
+                mPosFromStartOffset += count;
+                return count;
             }
-            
-            return super.skip(count);
         }
 
         @Override
         public void mark(int readlimit) {
-            if (mRemaining >= 0) {
-                // Not supported.
-                return;
-            }
-            super.mark(readlimit);
+            // Not supported.
+            return;
         }
 
         @Override
         public boolean markSupported() {
-            if (mRemaining >= 0) {
-                return false;
-            }
-            return super.markSupported();
+            return false;
         }
 
         @Override
         public synchronized void reset() throws IOException {
-            if (mRemaining >= 0) {
-                // Not supported.
-                return;
-            }
-            super.reset();
+            // Not supported.
+            return;
         }
     }
 
@@ -291,25 +297,25 @@
     public static class AutoCloseOutputStream
             extends ParcelFileDescriptor.AutoCloseOutputStream {
         private long mRemaining;
-        
+
         public AutoCloseOutputStream(AssetFileDescriptor fd) throws IOException {
             super(fd.getParcelFileDescriptor());
             if (fd.getParcelFileDescriptor().seekTo(fd.getStartOffset()) < 0) {
                 throw new IOException("Unable to seek");
             }
-            mRemaining = (int)fd.getLength();
+            mRemaining = (int) fd.getLength();
         }
 
         @Override
         public void write(byte[] buffer, int offset, int count) throws IOException {
             if (mRemaining >= 0) {
                 if (mRemaining == 0) return;
-                if (count > mRemaining) count = (int)mRemaining;
+                if (count > mRemaining) count = (int) mRemaining;
                 super.write(buffer, offset, count);
                 mRemaining -= count;
                 return;
             }
-            
+
             super.write(buffer, offset, count);
         }
 
@@ -318,12 +324,12 @@
             if (mRemaining >= 0) {
                 if (mRemaining == 0) return;
                 int count = buffer.length;
-                if (count > mRemaining) count = (int)mRemaining;
+                if (count > mRemaining) count = (int) mRemaining;
                 super.write(buffer);
                 mRemaining -= count;
                 return;
             }
-            
+
             super.write(buffer);
         }
 
@@ -335,7 +341,7 @@
                 mRemaining--;
                 return;
             }
-            
+
             super.write(oneByte);
         }
     }
@@ -375,6 +381,7 @@
         public AssetFileDescriptor createFromParcel(Parcel in) {
             return new AssetFileDescriptor(in);
         }
+
         public AssetFileDescriptor[] newArray(int size) {
             return new AssetFileDescriptor[size];
         }
diff --git a/core/java/android/hardware/SensorDirectChannel.java b/core/java/android/hardware/SensorDirectChannel.java
index 214d3ec..293d8b0 100644
--- a/core/java/android/hardware/SensorDirectChannel.java
+++ b/core/java/android/hardware/SensorDirectChannel.java
@@ -64,7 +64,7 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, prefix = { "RATE_" }, value = {
+    @IntDef(prefix = { "RATE_" }, value = {
             RATE_STOP,
             RATE_NORMAL,
             RATE_FAST,
diff --git a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java
index 603b06d..065ae64a 100644
--- a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java
@@ -38,13 +38,16 @@
     int REASON_AUTH_KEYGUARD = 4;
     /** Non-specific usage (from FingerprintManager). */
     int REASON_AUTH_OTHER = 5;
+    /** Usage from Settings. */
+    int REASON_AUTH_SETTINGS = 6;
 
     @IntDef({REASON_UNKNOWN,
             REASON_ENROLL_FIND_SENSOR,
             REASON_ENROLL_ENROLLING,
             REASON_AUTH_BP,
             REASON_AUTH_KEYGUARD,
-            REASON_AUTH_OTHER})
+            REASON_AUTH_OTHER,
+            REASON_AUTH_SETTINGS})
     @Retention(RetentionPolicy.SOURCE)
     @interface ShowReason {}
 }
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
index 1d07a03..eb8f43e 100644
--- a/core/java/android/net/NetworkIdentity.java
+++ b/core/java/android/net/NetworkIdentity.java
@@ -220,8 +220,10 @@
         String networkId = null;
         boolean roaming = !snapshot.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
-        boolean metered = !snapshot.getNetworkCapabilities().hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        boolean metered = !(snapshot.getNetworkCapabilities().hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                || snapshot.getNetworkCapabilities().hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED));
 
         final int oemManaged = getOemBitfield(snapshot.getNetworkCapabilities());
 
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 08f75df..74506da 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -160,19 +160,19 @@
     }
 
     /**
-     * Template to match cellular networks with the given IMSI and {@code ratType}.
-     * Use {@link #NETWORK_TYPE_ALL} to include all network types when filtering.
-     * See {@code TelephonyManager.NETWORK_TYPE_*}.
+     * Template to match cellular networks with the given IMSI, {@code ratType} and
+     * {@code metered}. Use {@link #NETWORK_TYPE_ALL} to include all network types when
+     * filtering. See {@code TelephonyManager.NETWORK_TYPE_*}.
      */
     public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId,
-            @NetworkType int ratType) {
+            @NetworkType int ratType, int metered) {
         if (TextUtils.isEmpty(subscriberId)) {
             return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null, null,
-                    METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
+                    metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
                     SUBSCRIBER_ID_MATCH_RULE_EXACT);
         }
         return new NetworkTemplate(MATCH_MOBILE, subscriberId, new String[]{subscriberId}, null,
-                METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
+                metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
                 SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
@@ -305,6 +305,7 @@
         }
     }
 
+    // TODO: Deprecate this constructor, mark it @UnsupportedAppUsage(maxTargetSdk = S)
     @UnsupportedAppUsage
     public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
         this(matchRule, subscriberId, new String[] { subscriberId }, networkId);
@@ -312,9 +313,14 @@
 
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
             String networkId) {
-        this(matchRule, subscriberId, matchSubscriberIds, networkId, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
-                SUBSCRIBER_ID_MATCH_RULE_EXACT);
+        // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates
+        // to metered networks. It is now possible to match mobile with any meteredness, but
+        // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this
+        //constructor passes METERED_YES for these types.
+        this(matchRule, subscriberId, matchSubscriberIds, networkId,
+                (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD) ? METERED_YES
+                : METERED_ALL , ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+                OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
     // TODO: Remove it after updating all of the caller.
@@ -589,11 +595,7 @@
             // TODO: consider matching against WiMAX subscriber identity
             return true;
         } else {
-            // Only metered mobile network would be matched regardless of metered filter.
-            // This is used to exclude non-metered APNs, e.g. IMS. See ag/908650.
-            // TODO: Respect metered filter and remove mMetered condition.
-            return (ident.mType == TYPE_MOBILE && ident.mMetered)
-                    && !ArrayUtils.isEmpty(mMatchSubscriberIds)
+            return ident.mType == TYPE_MOBILE && !ArrayUtils.isEmpty(mMatchSubscriberIds)
                     && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId)
                     && matchesCollapsedRatType(ident);
         }
@@ -707,8 +709,7 @@
         if (ident.mType == TYPE_WIMAX) {
             return true;
         } else {
-            return (ident.mType == TYPE_MOBILE && ident.mMetered)
-                    && matchesCollapsedRatType(ident);
+            return ident.mType == TYPE_MOBILE && matchesCollapsedRatType(ident);
         }
     }
 
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 1c1fc2c..1853c65 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -174,7 +174,7 @@
     public static final int PROCESS_STATE_BACKGROUND = 2;
     public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
 
-    static final int PROCESS_STATE_COUNT = 4;
+    public static final int PROCESS_STATE_COUNT = 4;
 
     private static final String[] sProcessStateNames = new String[PROCESS_STATE_COUNT];
 
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 30613b9..da968b3 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -654,6 +654,24 @@
     }
 
     /**
+     * Maps BatteryStats.Uid process state to the BatteryConsumer process state.
+     */
+    public static @BatteryConsumer.ProcessState int
+            mapUidProcessStateToBatteryConsumerProcessState(int processState) {
+        switch (processState) {
+            case BatteryStats.Uid.PROCESS_STATE_TOP:
+                return BatteryConsumer.PROCESS_STATE_FOREGROUND;
+            case BatteryStats.Uid.PROCESS_STATE_BACKGROUND:
+            case BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING:
+                return BatteryConsumer.PROCESS_STATE_BACKGROUND;
+            case BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE:
+                return BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+            default:
+                return BatteryConsumer.PROCESS_STATE_ANY;
+        }
+    }
+
+    /**
      * Returns true if battery consumption is tracked on a per-process-state basis.
      */
     public abstract boolean isProcessStateDataAvailable();
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 635f581..b677b69 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -313,7 +313,7 @@
      * @hide
      */
     @CriticalNative
-    public static final native boolean isHandlingTransaction();
+    public static final native boolean isDirectlyHandlingTransaction();
 
     /**
      * Return the Linux uid assigned to the process that sent the transaction
@@ -323,7 +323,7 @@
      *        executing an incoming transaction.
      */
     public static final int getCallingUidOrThrow() {
-        if (!isHandlingTransaction()) {
+        if (!isDirectlyHandlingTransaction()) {
             throw new IllegalStateException(
                   "Thread is not in a binder transcation");
         }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 394d270..743468a 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1024,7 +1024,7 @@
          * will also enable {@link StrictMode.ThreadPolicy.Builder#detectUnbufferedIo}.</li>
          * <li>{@link android.provider.DocumentsContract}'s various methods will throw failure
          * exceptions back to the caller instead of returning null.
-         * <li>{@link View#hasFocusable View.hasFocusable} now includes auto-focusable views.</li>
+         * <li>{@link View#hasFocusable() View.hasFocusable} now includes auto-focusable views.</li>
          * <li>{@link android.view.SurfaceView} will no longer always change the underlying
          * Surface object when something about it changes; apps need to look at the current
          * state of the object to determine which things they are interested in have changed.</li>
@@ -1132,6 +1132,13 @@
         public static final int S = 31;
 
         /**
+         * S V2.
+         *
+         * Once more unto the breach, dear friends, once more.
+         */
+        public static final int S_V2 = 32;
+
+        /**
          * Tiramisu.
          */
         public static final int TIRAMISU = CUR_DEVELOPMENT;
diff --git a/core/java/android/os/BytesMatcher.java b/core/java/android/os/BytesMatcher.java
deleted file mode 100644
index 8974c5e..0000000
--- a/core/java/android/os/BytesMatcher.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.bluetooth.BluetoothUuid;
-import android.net.MacAddress;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.util.HexDump;
-
-import java.util.ArrayList;
-import java.util.function.Predicate;
-
-/**
- * Predicate that tests if a given {@code byte[]} value matches a set of
- * configured rules.
- * <p>
- * Rules are tested in the order in which they were originally added, which
- * means a narrow rule can reject a specific value before a later broader rule
- * might accept that same value, or vice versa.
- * <p>
- * Matchers can contain rules of varying lengths, and tested values will only be
- * matched against rules of the exact same length. This is designed to support
- * {@link BluetoothUuid} style values which can be variable length.
- *
- * @hide
- */
-public class BytesMatcher implements Predicate<byte[]> {
-    private static final String TAG = "BytesMatcher";
-
-    private static final char TYPE_EXACT_ACCEPT = '+';
-    private static final char TYPE_EXACT_REJECT = '-';
-    private static final char TYPE_PREFIX_ACCEPT = '⊆';
-    private static final char TYPE_PREFIX_REJECT = '⊈';
-
-    private final ArrayList<Rule> mRules = new ArrayList<>();
-
-    private static class Rule {
-        public final char type;
-        public final @NonNull byte[] value;
-        public final @Nullable byte[] mask;
-
-        public Rule(char type, @NonNull byte[] value, @Nullable byte[] mask) {
-            if (mask != null && value.length != mask.length) {
-                throw new IllegalArgumentException(
-                        "Expected length " + value.length + " but found " + mask.length);
-            }
-            this.type = type;
-            this.value = value;
-            this.mask = mask;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder builder = new StringBuilder();
-            encode(builder);
-            return builder.toString();
-        }
-
-        public void encode(@NonNull StringBuilder builder) {
-            builder.append(type);
-            builder.append(HexDump.toHexString(value));
-            if (mask != null) {
-                builder.append('/');
-                builder.append(HexDump.toHexString(mask));
-            }
-        }
-
-        public boolean test(@NonNull byte[] value) {
-            switch (type) {
-                case TYPE_EXACT_ACCEPT:
-                case TYPE_EXACT_REJECT:
-                    if (value.length != this.value.length) {
-                        return false;
-                    }
-                    break;
-                case TYPE_PREFIX_ACCEPT:
-                case TYPE_PREFIX_REJECT:
-                    if (value.length < this.value.length) {
-                        return false;
-                    }
-                    break;
-            }
-            for (int i = 0; i < this.value.length; i++) {
-                byte local = this.value[i];
-                byte remote = value[i];
-                if (this.mask != null) {
-                    local &= this.mask[i];
-                    remote &= this.mask[i];
-                }
-                if (local != remote) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    /**
-     * Add a rule that will result in {@link #test(byte[])} returning
-     * {@code true} when a value being tested matches it. This rule will only
-     * match values of the exact same length.
-     * <p>
-     * Rules are tested in the order in which they were originally added, which
-     * means a narrow rule can reject a specific value before a later broader
-     * rule might accept that same value, or vice versa.
-     *
-     * @param value to be matched
-     * @param mask to be applied to both values before testing for equality; if
-     *            {@code null} then both values must match exactly
-     */
-    public void addExactAcceptRule(@NonNull byte[] value, @Nullable byte[] mask) {
-        mRules.add(new Rule(TYPE_EXACT_ACCEPT, value, mask));
-    }
-
-    /**
-     * Add a rule that will result in {@link #test(byte[])} returning
-     * {@code false} when a value being tested matches it. This rule will only
-     * match values of the exact same length.
-     * <p>
-     * Rules are tested in the order in which they were originally added, which
-     * means a narrow rule can reject a specific value before a later broader
-     * rule might accept that same value, or vice versa.
-     *
-     * @param value to be matched
-     * @param mask to be applied to both values before testing for equality; if
-     *            {@code null} then both values must match exactly
-     */
-    public void addExactRejectRule(@NonNull byte[] value, @Nullable byte[] mask) {
-        mRules.add(new Rule(TYPE_EXACT_REJECT, value, mask));
-    }
-
-    /**
-     * Add a rule that will result in {@link #test(byte[])} returning
-     * {@code true} when a value being tested matches it. This rule will match
-     * values of the exact same length or longer.
-     * <p>
-     * Rules are tested in the order in which they were originally added, which
-     * means a narrow rule can reject a specific value before a later broader
-     * rule might accept that same value, or vice versa.
-     *
-     * @param value to be matched
-     * @param mask to be applied to both values before testing for equality; if
-     *            {@code null} then both values must match exactly
-     */
-    public void addPrefixAcceptRule(@NonNull byte[] value, @Nullable byte[] mask) {
-        mRules.add(new Rule(TYPE_PREFIX_ACCEPT, value, mask));
-    }
-
-    /**
-     * Add a rule that will result in {@link #test(byte[])} returning
-     * {@code false} when a value being tested matches it. This rule will match
-     * values of the exact same length or longer.
-     * <p>
-     * Rules are tested in the order in which they were originally added, which
-     * means a narrow rule can reject a specific value before a later broader
-     * rule might accept that same value, or vice versa.
-     *
-     * @param value to be matched
-     * @param mask to be applied to both values before testing for equality; if
-     *            {@code null} then both values must match exactly
-     */
-    public void addPrefixRejectRule(@NonNull byte[] value, @Nullable byte[] mask) {
-        mRules.add(new Rule(TYPE_PREFIX_REJECT, value, mask));
-    }
-
-    /**
-     * Test if the given {@code ParcelUuid} value matches the set of rules
-     * configured in this matcher.
-     */
-    public boolean testBluetoothUuid(@NonNull ParcelUuid value) {
-        return test(BluetoothUuid.uuidToBytes(value));
-    }
-
-    /**
-     * Test if the given {@code MacAddress} value matches the set of rules
-     * configured in this matcher.
-     */
-    public boolean testMacAddress(@NonNull MacAddress value) {
-        return test(value.toByteArray());
-    }
-
-    /**
-     * Test if the given {@code byte[]} value matches the set of rules
-     * configured in this matcher.
-     */
-    @Override
-    public boolean test(@NonNull byte[] value) {
-        return test(value, false);
-    }
-
-    /**
-     * Test if the given {@code byte[]} value matches the set of rules
-     * configured in this matcher.
-     */
-    public boolean test(@NonNull byte[] value, boolean defaultValue) {
-        final int size = mRules.size();
-        for (int i = 0; i < size; i++) {
-            final Rule rule = mRules.get(i);
-            if (rule.test(value)) {
-                switch (rule.type) {
-                    case TYPE_EXACT_ACCEPT:
-                    case TYPE_PREFIX_ACCEPT:
-                        return true;
-                    case TYPE_EXACT_REJECT:
-                    case TYPE_PREFIX_REJECT:
-                        return false;
-                }
-            }
-        }
-        return defaultValue;
-    }
-
-    /**
-     * Encode the given matcher into a human-readable {@link String} which can
-     * be used to transport matchers across device boundaries.
-     * <p>
-     * The human-readable format is an ordered list separated by commas, where
-     * each rule is a {@code +} or {@code -} symbol indicating if the match
-     * should be accepted or rejected, then followed by a hex value and an
-     * optional hex mask. For example, {@code -caff,+cafe/ff00} is a valid
-     * encoded matcher.
-     *
-     * @see #decode(String)
-     */
-    public static @NonNull String encode(@NonNull BytesMatcher matcher) {
-        final StringBuilder builder = new StringBuilder();
-        final int size = matcher.mRules.size();
-        for (int i = 0; i < size; i++) {
-            final Rule rule = matcher.mRules.get(i);
-            rule.encode(builder);
-            builder.append(',');
-        }
-        if (builder.length() > 0) {
-            builder.deleteCharAt(builder.length() - 1);
-        }
-        return builder.toString();
-    }
-
-    /**
-     * Decode the given human-readable {@link String} used to transport matchers
-     * across device boundaries.
-     * <p>
-     * The human-readable format is an ordered list separated by commas, where
-     * each rule is a {@code +} or {@code -} symbol indicating if the match
-     * should be accepted or rejected, then followed by a hex value and an
-     * optional hex mask. For example, {@code -caff,+cafe/ff00} is a valid
-     * encoded matcher.
-     *
-     * @see #encode(BytesMatcher)
-     */
-    public static @NonNull BytesMatcher decode(@Nullable String value) {
-        final BytesMatcher matcher = new BytesMatcher();
-        if (TextUtils.isEmpty(value)) return matcher;
-
-        final int length = value.length();
-        for (int i = 0; i < length;) {
-            final char type = value.charAt(i);
-
-            int nextRule = value.indexOf(',', i);
-            int nextMask = value.indexOf('/', i);
-
-            if (nextRule == -1) nextRule = length;
-            if (nextMask > nextRule) nextMask = -1;
-
-            final byte[] ruleValue;
-            final byte[] ruleMask;
-            if (nextMask >= 0) {
-                ruleValue = HexDump.hexStringToByteArray(value.substring(i + 1, nextMask));
-                ruleMask = HexDump.hexStringToByteArray(value.substring(nextMask + 1, nextRule));
-            } else {
-                ruleValue = HexDump.hexStringToByteArray(value.substring(i + 1, nextRule));
-                ruleMask = null;
-            }
-
-            switch (type) {
-                case TYPE_EXACT_ACCEPT:
-                    matcher.addExactAcceptRule(ruleValue, ruleMask);
-                    break;
-                case TYPE_EXACT_REJECT:
-                    matcher.addExactRejectRule(ruleValue, ruleMask);
-                    break;
-                case TYPE_PREFIX_ACCEPT:
-                    matcher.addPrefixAcceptRule(ruleValue, ruleMask);
-                    break;
-                case TYPE_PREFIX_REJECT:
-                    matcher.addPrefixRejectRule(ruleValue, ruleMask);
-                    break;
-                default:
-                    Log.w(TAG, "Ignoring unknown type " + type);
-                    break;
-            }
-
-            i = nextRule + 1;
-        }
-        return matcher;
-    }
-}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index ab2c8c0..1468d9f 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.AppOpsManager;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -3094,14 +3095,24 @@
      * Parcelables.
      */
     @Nullable
-    public final ArrayList readArrayList(@Nullable ClassLoader loader) {
-        int N = readInt();
-        if (N < 0) {
-            return null;
-        }
-        ArrayList l = new ArrayList(N);
-        readListInternal(l, N, loader, /* clazz */ null);
-        return l;
+    public ArrayList readArrayList(@Nullable ClassLoader loader) {
+        return readArrayListInternal(loader, /* clazz */ null);
+    }
+
+    /**
+     * Same as {@link #readArrayList(ClassLoader)} but accepts {@code clazz} parameter as
+     * the type required for each item.
+     *
+     * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+     * is not an instance of that class or any of its children classes or there was an error
+     * trying to instantiate an element.
+     */
+    @SuppressLint({"ConcreteCollection", "NullableCollection"})
+    @Nullable
+    public <T> ArrayList<T> readArrayList(@Nullable ClassLoader loader,
+            @NonNull Class<? extends T> clazz) {
+        Objects.requireNonNull(clazz);
+        return readArrayListInternal(loader, clazz);
     }
 
     /**
@@ -3111,14 +3122,23 @@
      * Parcelables.
      */
     @Nullable
-    public final Object[] readArray(@Nullable ClassLoader loader) {
-        int N = readInt();
-        if (N < 0) {
-            return null;
-        }
-        Object[] l = new Object[N];
-        readArrayInternal(l, N, loader);
-        return l;
+    public Object[] readArray(@Nullable ClassLoader loader) {
+        return readArrayInternal(loader, /* clazz */ null);
+    }
+
+    /**
+     * Same as {@link #readArray(ClassLoader)} but accepts {@code clazz} parameter as
+     * the type required for each item.
+     *
+     * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+     * is not an instance of that class or any of its children classes or there was an error
+     * trying to instantiate an element.
+     */
+    @SuppressLint({"ArrayReturn", "NullableCollection"})
+    @Nullable
+    public <T> T[] readArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        Objects.requireNonNull(clazz);
+        return readArrayInternal(loader, clazz);
     }
 
     /**
@@ -3128,14 +3148,23 @@
      * Parcelables.
      */
     @Nullable
-    public final <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader) {
-        int N = readInt();
-        if (N < 0) {
-            return null;
-        }
-        SparseArray sa = new SparseArray(N);
-        readSparseArrayInternal(sa, N, loader);
-        return sa;
+    public <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader) {
+        return readSparseArrayInternal(loader, /* clazz */ null);
+    }
+
+    /**
+     * Same as {@link #readSparseArray(ClassLoader)} but accepts {@code clazz} parameter as
+     * the type required for each item.
+     *
+     * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+     * is not an instance of that class or any of its children classes or there was an error
+     * trying to instantiate an element.
+     */
+    @Nullable
+    public <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader,
+            @NonNull Class<? extends T> clazz) {
+        Objects.requireNonNull(clazz);
+        return readSparseArrayInternal(loader, clazz);
     }
 
     /**
@@ -3851,7 +3880,7 @@
                     "Parcel " + this + ": Unmarshalling unknown type code " + type
                             + " at offset " + off);
         }
-        if (clazz != null && !clazz.isInstance(object)) {
+        if (object != null && clazz != null && !clazz.isInstance(object)) {
             throw new BadParcelableException("Unparcelled object " + object
                     + " is not an instance of required class " + clazz.getName()
                     + " provided in the parameter");
@@ -3910,7 +3939,6 @@
     }
 
     /**
-     *
      * @param clazz The type of the parcelable expected or {@code null} for performing no checks.
      */
     @SuppressWarnings("unchecked")
@@ -3969,7 +3997,7 @@
      * as the required type.
      *
      * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
-     * is not an instance of that class or any of its children class or there there was an error
+     * is not an instance of that class or any of its children classes or there there was an error
      * trying to read the {@link Parcelable.Creator}.
      */
     @Nullable
@@ -4092,17 +4120,25 @@
         return p;
     }
 
-    /** @hide */
+    /**
+     * Same as {@link #readParcelableArray(ClassLoader)}  but accepts {@code clazz} parameter as
+     * the type required for each item.
+     *
+     * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+     * is not an instance of that class or any of its children classes or there was an error
+     * trying to instantiate an element.
+     */
+    @SuppressLint({"ArrayReturn", "NullableCollection"})
+    @SuppressWarnings("unchecked")
     @Nullable
-    public final <T extends Parcelable> T[] readParcelableArray(@Nullable ClassLoader loader,
-            @NonNull Class<T> clazz) {
-        int N = readInt();
-        if (N < 0) {
+    public <T> T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        int n = readInt();
+        if (n < 0) {
             return null;
         }
-        T[] p = (T[]) Array.newInstance(clazz, N);
-        for (int i = 0; i < N; i++) {
-            p[i] = readParcelable(loader);
+        T[] p = (T[]) Array.newInstance(clazz, n);
+        for (int i = 0; i < n; i++) {
+            p[i] = readParcelableInternal(loader, clazz);
         }
         return p;
     }
@@ -4320,9 +4356,12 @@
         return result;
     }
 
-    private void readListInternal(@NonNull List outVal, int n,
-            @Nullable ClassLoader loader) {
-        readListInternal(outVal, n, loader, null);
+    /**
+     * The method is replaced by {@link #readListInternal(List, int, ClassLoader, Class)}, however
+     * we are keeping this unused method here to allow unsupported app usages.
+     */
+    private void readListInternal(@NonNull List outVal, int n, @Nullable ClassLoader loader) {
+        readListInternal(outVal, n, loader,  /* clazz */ null);
     }
 
     /**
@@ -4338,26 +4377,88 @@
         }
     }
 
+    /**
+     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     */
+    @SuppressLint({"ConcreteCollection", "NullableCollection"})
+    @Nullable
+    private <T> ArrayList<T> readArrayListInternal(@Nullable ClassLoader loader,
+            @Nullable Class<? extends T> clazz) {
+        int n = readInt();
+        if (n < 0) {
+            return null;
+        }
+        ArrayList<T> l = new ArrayList<>(n);
+        readListInternal(l, n, loader, clazz);
+        return l;
+    }
+
+    /**
+     * The method is replaced by {@link #readArrayInternal(ClassLoader, Class)}, however
+     * we are keeping this unused method here to allow unsupported app usages.
+     */
     private void readArrayInternal(@NonNull Object[] outVal, int N,
             @Nullable ClassLoader loader) {
         for (int i = 0; i < N; i++) {
-            Object value = readValue(loader);
-            //Log.d(TAG, "Unmarshalling value=" + value);
+            Object value = readValue(loader, /* clazz */ null);
             outVal[i] = value;
         }
     }
 
+    /**
+     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     */
+    @SuppressWarnings("unchecked")
+    @Nullable
+    private <T> T[] readArrayInternal(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+        int n = readInt();
+        if (n < 0) {
+            return null;
+        }
+        T[] outVal = (T[]) ((clazz == null) ? new Object[n] : Array.newInstance(clazz, n));
+
+        for (int i = 0; i < n; i++) {
+            T value = readValue(loader, clazz);
+            outVal[i] = value;
+        }
+        return outVal;
+    }
+
+    /**
+     * The method is replaced by {@link #readSparseArray(ClassLoader, Class)}, however
+     * we are keeping this unused method here to allow unsupported app usages.
+     */
     private void readSparseArrayInternal(@NonNull SparseArray outVal, int N,
             @Nullable ClassLoader loader) {
         while (N > 0) {
             int key = readInt();
             Object value = readValue(loader);
-            //Log.i(TAG, "Unmarshalling key=" + key + " value=" + value);
             outVal.append(key, value);
             N--;
         }
     }
 
+    /**
+     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     */
+    @Nullable
+    private <T> SparseArray<T> readSparseArrayInternal(@Nullable ClassLoader loader,
+            @Nullable Class<? extends T> clazz) {
+        int n = readInt();
+        if (n < 0) {
+            return null;
+        }
+        SparseArray<T> outVal = new SparseArray<>(n);
+
+        while (n > 0) {
+            int key = readInt();
+            T value = readValue(loader, clazz);
+            outVal.append(key, value);
+            n--;
+        }
+        return outVal;
+    }
+
 
     private void readSparseBooleanArrayInternal(@NonNull SparseBooleanArray outVal, int N) {
         while (N > 0) {
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 387fc39..aa1bbc5 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5366,5 +5366,13 @@
         public static final String COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS =
                 "d2d_sharing_contacts";
 
+        /**
+        * TelephonyProvider column name for NR Advanced calling
+        * Determines if the user has enabled VoNR settings for this subscription.
+        *
+        * @hide
+        */
+        public static final String COLUMN_NR_ADVANCED_CALLING_ENABLED =
+                "nr_advanced_calling_enabled";
     }
 }
diff --git a/core/java/android/security/attestationverification/OWNERS b/core/java/android/security/attestationverification/OWNERS
new file mode 100644
index 0000000..80a1f44
--- /dev/null
+++ b/core/java/android/security/attestationverification/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1111194
+
+dlm@google.com
+dkrahn@google.com
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 9819648..096595f 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -26,7 +26,10 @@
 import android.app.AlarmManager;
 import android.app.Service;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.ServiceConnection;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -58,6 +61,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayDeque;
+import java.util.function.Consumer;
 
 /**
  * Extend this class to implement a custom dream (available to the user as a "Daydream").
@@ -170,6 +175,13 @@
             "android.service.dreams.DreamService";
 
     /**
+     * The name of the extra where the dream overlay component is stored.
+     * @hide
+     */
+    public static final String EXTRA_DREAM_OVERLAY_COMPONENT =
+            "android.service.dream.DreamService.dream_overlay_component";
+
+    /**
      * Name under which a Dream publishes information about itself.
      * This meta-data must reference an XML resource containing
      * a <code>&lt;{@link android.R.styleable#Dream dream}&gt;</code>
@@ -191,6 +203,7 @@
     private boolean mCanDoze;
     private boolean mDozing;
     private boolean mWindowless;
+    private boolean mOverlayServiceBound;
     private int mDozeScreenState = Display.STATE_UNKNOWN;
     private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
 
@@ -199,8 +212,62 @@
     private DreamServiceWrapper mDreamServiceWrapper;
     private Runnable mDispatchAfterOnAttachedToWindow;
 
+    private OverlayConnection mOverlayConnection;
+
+    private static class OverlayConnection implements ServiceConnection {
+        // Overlay set during onBind.
+        private IDreamOverlay mOverlay;
+        // A Queue of pending requests to execute on the overlay.
+        private ArrayDeque<Consumer<IDreamOverlay>> mRequests;
+
+        OverlayConnection() {
+            mRequests = new ArrayDeque<>();
+        }
+
+        public void request(Consumer<IDreamOverlay> request) {
+            mRequests.push(request);
+            evaluate();
+        }
+
+        private void evaluate() {
+            if (mOverlay == null) {
+                return;
+            }
+
+            // Any new requests that arrive during this loop will be processed synchronously after
+            // the loop exits.
+            while (!mRequests.isEmpty()) {
+                final Consumer<IDreamOverlay> request = mRequests.pop();
+                request.accept(mOverlay);
+            }
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            // Store Overlay and execute pending requests.
+            mOverlay = IDreamOverlay.Stub.asInterface(service);
+            evaluate();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            // Clear Overlay binder to prevent further request processing.
+            mOverlay = null;
+        }
+    }
+
+    private IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
+        @Override
+        public void onExitRequested() {
+            // Simply finish dream when exit is requested.
+            finish();
+        }
+    };
+
+
     public DreamService() {
         mDreamManager = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE));
+        mOverlayConnection = new OverlayConnection();
     }
 
     /**
@@ -861,6 +928,18 @@
     public final IBinder onBind(Intent intent) {
         if (mDebug) Slog.v(TAG, "onBind() intent = " + intent);
         mDreamServiceWrapper = new DreamServiceWrapper();
+
+        // Connect to the overlay service if present.
+        final ComponentName overlayComponent =
+                intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT);
+        if (overlayComponent != null && !mWindowless) {
+            final Intent overlayIntent = new Intent();
+            overlayIntent.setComponent(overlayComponent);
+
+            mOverlayServiceBound = getApplicationContext().bindService(overlayIntent,
+                    mOverlayConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
+        }
+
         return mDreamServiceWrapper;
     }
 
@@ -894,6 +973,11 @@
             return;
         }
 
+        if (!mWindowless && mOverlayServiceBound) {
+            unbindService(mOverlayConnection);
+            mOverlayServiceBound = false;
+        }
+
         try {
             // finishSelf will unbind the dream controller from the dream service. This will
             // trigger DreamService.this.onDestroy and DreamService.this will die.
@@ -1101,6 +1185,16 @@
                         }
                     }
                 });
+
+        // Request the DreamOverlay be told to dream with dream's window parameters once the service
+        // has connected.
+        mOverlayConnection.request(overlay -> {
+            try {
+                overlay.startDream(mWindow.getAttributes(), mOverlayCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "could not send window attributes:" + e);
+            }
+        });
     }
 
     private boolean getWindowFlagValue(int flag, boolean defaultValue) {
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 0ce9cfa..3e0deeb 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -41,4 +41,5 @@
     void forceAmbientDisplayEnabled(boolean enabled);
     ComponentName[] getDreamComponentsForUser(int userId);
     void setDreamComponentsForUser(int userId, in ComponentName[] componentNames);
+    void registerDreamOverlayService(in ComponentName componentName);
 }
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
new file mode 100644
index 0000000..2b6633d
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dreams;
+
+import android.service.dreams.IDreamOverlayCallback;
+import android.view.WindowManager.LayoutParams;
+
+/**
+* {@link IDreamOverlay} provides a way for a component to annotate a dream with additional view
+* elements. Registered through the DreamManager, a IDreamOverlay is bound to by the dream and
+* passed the necessary window details to participate in the user interface.
+
+* @hide
+*/
+interface IDreamOverlay {
+    /**
+    * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
+                    token of the Dream Activity.
+    * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
+    *                 dream.
+    */
+    void startDream(in LayoutParams params, in IDreamOverlayCallback callback);
+}
diff --git a/core/java/android/service/dreams/IDreamOverlayCallback.aidl b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
new file mode 100644
index 0000000..ec76a33
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dreams;
+
+/**
+* {@link IDreamOverlayCallback} defines the interactions a dream overlay can have with its
+* associated dream. It is the discretion of the {@link DreamService}) to honor any inbound requests
+* from this callback.
+*
+* @hide
+*/
+interface IDreamOverlayCallback {
+    /**
+    * Invoked to request the dream exit.
+    */
+    void onExitRequested();
+}
\ No newline at end of file
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index d12ed8f..1460cb2 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -832,13 +832,45 @@
      * consuming content. May be consumed by system to set account globally.
      */
     public static final int KEYCODE_PROFILE_SWITCH = 288;
+    /** Key code constant: Video Application key #1. */
+    public static final int KEYCODE_VIDEO_APP_1 = 289;
+    /** Key code constant: Video Application key #2. */
+    public static final int KEYCODE_VIDEO_APP_2 = 290;
+    /** Key code constant: Video Application key #3. */
+    public static final int KEYCODE_VIDEO_APP_3 = 291;
+    /** Key code constant: Video Application key #4. */
+    public static final int KEYCODE_VIDEO_APP_4 = 292;
+    /** Key code constant: Video Application key #5. */
+    public static final int KEYCODE_VIDEO_APP_5 = 293;
+    /** Key code constant: Video Application key #6. */
+    public static final int KEYCODE_VIDEO_APP_6 = 294;
+    /** Key code constant: Video Application key #7. */
+    public static final int KEYCODE_VIDEO_APP_7 = 295;
+    /** Key code constant: Video Application key #8. */
+    public static final int KEYCODE_VIDEO_APP_8 = 296;
+    /** Key code constant: Featured Application key #1. */
+    public static final int KEYCODE_FEATURED_APP_1 = 297;
+    /** Key code constant: Featured Application key #2. */
+    public static final int KEYCODE_FEATURED_APP_2 = 298;
+    /** Key code constant: Featured Application key #3. */
+    public static final int KEYCODE_FEATURED_APP_3 = 299;
+    /** Key code constant: Featured Application key #4. */
+    public static final int KEYCODE_FEATURED_APP_4 = 300;
+    /** Key code constant: Demo Application key #1. */
+    public static final int KEYCODE_DEMO_APP_1 = 301;
+    /** Key code constant: Demo Application key #2. */
+    public static final int KEYCODE_DEMO_APP_2 = 302;
+    /** Key code constant: Demo Application key #3. */
+    public static final int KEYCODE_DEMO_APP_3 = 303;
+    /** Key code constant: Demo Application key #4. */
+    public static final int KEYCODE_DEMO_APP_4 = 304;
 
-    /**
+   /**
      * Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
      * @hide
      */
     @TestApi
-    public static final int LAST_KEYCODE = KEYCODE_PROFILE_SWITCH;
+    public static final int LAST_KEYCODE = KEYCODE_DEMO_APP_4;
 
     // NOTE: If you add a new keycode here you must also add it to:
     //  isSystem()
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index e1c4305..2b79bbf 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -19,7 +19,7 @@
 import android.annotation.IntDef;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
-import android.os.SystemProperties;
+import android.sysprop.InputProperties;
 import android.util.ArrayMap;
 import android.util.Pools.SynchronizedPool;
 
@@ -279,8 +279,7 @@
         // If user has not selected a specific strategy
         if (strategy == VELOCITY_TRACKER_STRATEGY_DEFAULT) {
             // Check if user specified strategy by overriding system property.
-            String strategyProperty =
-                                    SystemProperties.get("persist.input.velocitytracker.strategy");
+            String strategyProperty = InputProperties.velocitytracker_strategy().orElse(null);
             if (strategyProperty == null || strategyProperty.isEmpty()) {
                 mStrategy = strategy;
             } else {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 89e1e08..4879206 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -57,12 +57,15 @@
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED;
@@ -72,6 +75,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -90,6 +94,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ResourcesManager;
+import android.app.WindowConfiguration;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -515,6 +520,8 @@
 
     private final WindowLayout mWindowLayout = new WindowLayout();
 
+    private ViewRootImpl mParentViewRoot;
+
     // This is used to reduce the race between window focus changes being dispatched from
     // the window manager and input events coming through the input system.
     @GuardedBy("this")
@@ -796,11 +803,7 @@
                 context);
         mCompatibleVisibilityInfo = new SystemUiVisibilityInfo();
         mAccessibilityManager = AccessibilityManager.getInstance(context);
-        mAccessibilityManager.addAccessibilityStateChangeListener(
-                mAccessibilityInteractionConnectionManager, mHandler);
         mHighContrastTextManager = new HighContrastTextManager();
-        mAccessibilityManager.addHighTextContrastStateChangeListener(
-                mHighContrastTextManager, mHandler);
         mViewConfiguration = ViewConfiguration.get(context);
         mDensity = context.getResources().getDisplayMetrics().densityDpi;
         mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
@@ -977,8 +980,6 @@
                 mView = view;
 
                 mAttachInfo.mDisplayState = mDisplay.getState();
-                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
-
                 mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
                 mFallbackEventHandler.setView(view);
                 mWindowAttributes.copyFrom(attrs);
@@ -1059,6 +1060,7 @@
                 if (panelParentView != null) {
                     mAttachInfo.mPanelParentWindowToken
                             = panelParentView.getApplicationWindowToken();
+                    mParentViewRoot = panelParentView.getViewRootImpl();
                 }
                 mAdded = true;
                 int res; /* = WindowManagerImpl.ADD_OKAY; */
@@ -1119,10 +1121,13 @@
                 final InsetsState state = mInsetsController.getState();
                 final Rect displayCutoutSafe = mTempRect;
                 state.getDisplayCutoutSafe(displayCutoutSafe);
+                final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
                 mWindowLayout.computeWindowFrames(mWindowAttributes, state,
-                        displayCutoutSafe, getConfiguration().windowConfiguration.getBounds(),
+                        displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
+                        UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
                         mInsetsController.getRequestedVisibilities(),
-                        null /* attachedWindowFrame */, mTmpFrames.frame, mTempRect2);
+                        getAttachedWindowFrame(), 1f /* compactScale */,
+                        mTmpFrames.displayFrame, mTempRect2, mTmpFrames.frame);
                 setFrame(mTmpFrames.frame);
                 if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
                 if (res < WindowManagerGlobal.ADD_OKAY) {
@@ -1176,6 +1181,7 @@
                             "Unable to add window -- unknown error code " + res);
                 }
 
+                registerListeners();
                 if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
                     mUseBLASTAdapter = true;
                 }
@@ -1232,6 +1238,36 @@
         }
     }
 
+    private Rect getAttachedWindowFrame() {
+        final int type = mWindowAttributes.type;
+        final boolean layoutAttached = (mParentViewRoot != null
+                && type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW
+                && type != TYPE_APPLICATION_ATTACHED_DIALOG);
+        return layoutAttached ? mParentViewRoot.mWinFrame : null;
+    }
+
+    /**
+     * Register any kind of listeners if setView was success.
+     */
+    private void registerListeners() {
+        mAccessibilityManager.addAccessibilityStateChangeListener(
+                mAccessibilityInteractionConnectionManager, mHandler);
+        mAccessibilityManager.addHighTextContrastStateChangeListener(
+                mHighContrastTextManager, mHandler);
+        mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+    }
+
+    /**
+     * Unregister all listeners while detachedFromWindow.
+     */
+    private void unregisterListeners() {
+        mAccessibilityManager.removeAccessibilityStateChangeListener(
+                mAccessibilityInteractionConnectionManager);
+        mAccessibilityManager.removeHighTextContrastStateChangeListener(
+                mHighContrastTextManager);
+        mDisplayManager.unregisterDisplayListener(mDisplayListener);
+    }
+
     private void setTag() {
         final String[] split = mWindowAttributes.getTitle().toString().split("\\.");
         if (split.length > 0) {
@@ -4912,10 +4948,6 @@
         }
 
         mAccessibilityInteractionConnectionManager.ensureNoConnection();
-        mAccessibilityManager.removeAccessibilityStateChangeListener(
-                mAccessibilityInteractionConnectionManager);
-        mAccessibilityManager.removeHighTextContrastStateChangeListener(
-                mHighContrastTextManager);
         removeSendWindowContentChangedCallback();
 
         destroyHardwareRenderer();
@@ -4948,8 +4980,7 @@
             mInputEventReceiver = null;
         }
 
-        mDisplayManager.unregisterDisplayListener(mDisplayListener);
-
+        unregisterListeners();
         unscheduleTraversals();
     }
 
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index cdc1977..7dfc95e 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -19,29 +19,44 @@
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
 
+import android.app.WindowConfiguration;
+import android.app.WindowConfiguration.WindowingMode;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.util.Log;
 
 /**
  * Computes window frames.
  * @hide
  */
 public class WindowLayout {
+    private static final String TAG = WindowLayout.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    public static final int UNSPECIFIED_LENGTH = -1;
+
     private final Rect mTempDisplayCutoutSafeExceptMaybeBarsRect = new Rect();
     private final Rect mTempRect = new Rect();
 
     public boolean computeWindowFrames(WindowManager.LayoutParams attrs, InsetsState state,
-            Rect displayCutoutSafe, Rect windowBounds, InsetsVisibilities requestedVisibilities,
-            Rect attachedWindowFrame, Rect outDisplayFrame, Rect outParentFrame) {
+            Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
+            int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
+            Rect attachedWindowFrame, float compatScale, Rect outDisplayFrame, Rect outParentFrame,
+            Rect outFrame) {
         final int type = attrs.type;
         final int fl = attrs.flags;
         final int pfl = attrs.privateFlags;
@@ -72,18 +87,14 @@
         }
 
         // Compute bounds restricted by display cutout
+        final int cutoutMode = attrs.layoutInDisplayCutoutMode;
         final DisplayCutout cutout = state.getDisplayCutout();
-        if (cutout.isEmpty()) {
-            return false;
-        }
-        boolean clippedByDisplayCutout = false;
         final Rect displayCutoutSafeExceptMaybeBars = mTempDisplayCutoutSafeExceptMaybeBarsRect;
         displayCutoutSafeExceptMaybeBars.set(displayCutoutSafe);
-
-        // Ensure that windows with a non-ALWAYS display cutout mode are laid out in
-        // the cutout safe zone.
-        final int cutoutMode = attrs.layoutInDisplayCutoutMode;
-        if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
+        boolean clippedByDisplayCutout = false;
+        if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS && !cutout.isEmpty()) {
+            // Ensure that windows with a non-ALWAYS display cutout mode are laid out in
+            // the cutout safe zone.
             final Rect displayFrame = state.getDisplayFrame();
             final InsetsSource statusBarSource = state.peekSource(ITYPE_STATUS_BAR);
             if (statusBarSource != null && displayCutoutSafe.top > displayFrame.top) {
@@ -147,6 +158,118 @@
             }
             outDisplayFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
         }
+
+        final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
+        final boolean inMultiWindowMode = WindowConfiguration.inMultiWindowMode(windowingMode);
+
+        // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
+        // Also, we don't allow windows in multi-window mode to extend out of the screen.
+        if (noLimits && type != TYPE_SYSTEM_ERROR && !inMultiWindowMode) {
+            outDisplayFrame.left = outDisplayFrame.top = -10000;
+            outDisplayFrame.right = outDisplayFrame.bottom = 10000;
+        }
+
+        final boolean hasCompatScale = compatScale != 1f;
+        final int pw = outParentFrame.width();
+        final int ph = outParentFrame.height();
+        int rw = requestedWidth;
+        int rh = requestedHeight;
+        float x, y;
+        int w, h;
+
+        // If the view hierarchy hasn't been measured, the requested width and height would be
+        // UNSPECIFIED_LENGTH. This can happen in the first layout of a window or in the simulated
+        // layout.
+        if (rw == UNSPECIFIED_LENGTH) {
+            rw = attrs.width >= 0 ? attrs.width : pw;
+        }
+        if (rh == UNSPECIFIED_LENGTH) {
+            rh = attrs.height >= 0 ? attrs.height : ph;
+        }
+
+        if ((attrs.flags & FLAG_SCALED) != 0) {
+            if (attrs.width < 0) {
+                w = pw;
+            } else if (hasCompatScale) {
+                w = (int) (attrs.width * compatScale + .5f);
+            } else {
+                w = attrs.width;
+            }
+            if (attrs.height < 0) {
+                h = ph;
+            } else if (hasCompatScale) {
+                h = (int) (attrs.height * compatScale + .5f);
+            } else {
+                h = attrs.height;
+            }
+        } else {
+            if (attrs.width == MATCH_PARENT) {
+                w = pw;
+            } else if (hasCompatScale) {
+                w = (int) (rw * compatScale + .5f);
+            } else {
+                w = rw;
+            }
+            if (attrs.height == MATCH_PARENT) {
+                h = ph;
+            } else if (hasCompatScale) {
+                h = (int) (rh * compatScale + .5f);
+            } else {
+                h = rh;
+            }
+        }
+
+        if (hasCompatScale) {
+            x = attrs.x * compatScale;
+            y = attrs.y * compatScale;
+        } else {
+            x = attrs.x;
+            y = attrs.y;
+        }
+
+        if (inMultiWindowMode
+                && (attrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) == 0) {
+            // Make sure window fits in parent frame since it is in a non-fullscreen task as
+            // required by {@link Gravity#apply} call.
+            w = Math.min(w, pw);
+            h = Math.min(h, ph);
+        }
+
+        // We need to fit it to the display if either
+        // a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
+        // for the taskless windows)
+        // b) If it's a secondary app window, we also need to fit it to the display unless
+        // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
+        // screen, but SurfaceViews want to be always at a specific location so we don't fit it to
+        // the display.
+        final boolean fitToDisplay = !inMultiWindowMode
+                || ((attrs.type != TYPE_BASE_APPLICATION) && !noLimits);
+
+        // Set mFrame
+        Gravity.apply(attrs.gravity, w, h, outParentFrame,
+                (int) (x + attrs.horizontalMargin * pw),
+                (int) (y + attrs.verticalMargin * ph), outFrame);
+        // Now make sure the window fits in the overall display frame.
+        if (fitToDisplay) {
+            Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
+        }
+
+        if (DEBUG) Log.d(TAG, "computeWindowFrames " + attrs.getTitle()
+                + " outFrame=" + outFrame.toShortString()
+                + " outParentFrame=" + outParentFrame.toShortString()
+                + " outDisplayFrame=" + outDisplayFrame.toShortString()
+                + " attachedWindowFrame=" + (attachedWindowFrame != null
+                        ? attachedWindowFrame.toShortString()
+                        : "null")
+                + " requestedWidth=" + requestedWidth
+                + " requestedHeight=" + requestedHeight
+                + " compatScale=" + compatScale
+                + " windowingMode=" + WindowConfiguration.windowingModeToString(windowingMode)
+                + " displayCutoutSafe=" + displayCutoutSafe
+                + " attrs=" + attrs
+                + " state=" + state
+                + " requestedVisibilities=" + requestedVisibilities);
+
         return clippedByDisplayCutout;
     }
 }
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 3db7136..69b4187 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -371,6 +371,10 @@
                     Log.v(TAG, "onVirtualViewTranslationCompleted: received response for "
                             + "AutofillId " + autofillId);
                 }
+                view.onVirtualViewTranslationResponses(virtualChildResponse);
+                if (mCurrentState == STATE_UI_TRANSLATION_PAUSED) {
+                    return;
+                }
                 mActivity.runOnUiThread(() -> {
                     if (view.getViewTranslationCallback() == null) {
                         if (DEBUG) {
@@ -379,7 +383,6 @@
                         }
                         return;
                     }
-                    view.onVirtualViewTranslationResponses(virtualChildResponse);
                     if (view.getViewTranslationCallback() != null) {
                         view.getViewTranslationCallback().onShowTranslation(view);
                     }
@@ -427,6 +430,8 @@
                             + " may be gone.");
                     continue;
                 }
+                int currentState;
+                currentState = mCurrentState;
                 mActivity.runOnUiThread(() -> {
                     ViewTranslationCallback callback = view.getViewTranslationCallback();
                     if (view.getViewTranslationResponse() != null
@@ -460,6 +465,9 @@
                         callback.enableContentPadding();
                     }
                     view.onViewTranslationResponse(response);
+                    if (currentState == STATE_UI_TRANSLATION_PAUSED) {
+                        return;
+                    }
                     callback.onShowTranslation(view);
                 });
             }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index fe5eb08..0f309f1 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -34,6 +34,7 @@
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
 import android.app.Application;
+import android.app.LoadedApk;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
 import android.appwidget.AppWidgetHostView;
@@ -5475,7 +5476,8 @@
         // user. So build a context that loads resources from that user but
         // still returns the current users userId so settings like data / time formats
         // are loaded without requiring cross user persmissions.
-        final Context contextForResources = getContextForResources(context);
+        final Context contextForResources =
+                getContextForResourcesEnsuringCorrectCachedApkPaths(context);
         if (colorResources != null) {
             colorResources.apply(contextForResources);
         }
@@ -5853,13 +5855,14 @@
         }
     }
 
-    private Context getContextForResources(Context context) {
+    private Context getContextForResourcesEnsuringCorrectCachedApkPaths(Context context) {
         if (mApplication != null) {
             if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
                     && context.getPackageName().equals(mApplication.packageName)) {
                 return context;
             }
             try {
+                LoadedApk.checkAndUpdateApkPaths(mApplication);
                 return context.createApplicationContext(mApplication,
                         Context.CONTEXT_RESTRICTED);
             } catch (NameNotFoundException e) {
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 6b33428..8e293f4 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -408,7 +408,7 @@
         }
 
         @Override
-        protected Context getRemoteContext() {
+        protected Context getRemoteContextEnsuringCorrectCachedApkPath() {
             return null;
         }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3eaf852..8fba583 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8751,6 +8751,11 @@
         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
     }
 
+    private boolean hasEditorInFocusSearchDirection(@FocusRealDirection int direction) {
+        final View nextView = focusSearch(direction);
+        return nextView != null && nextView.onCheckIsTextEditor();
+    }
+
     @Override
     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
         if (onCheckIsTextEditor() && isEnabled()) {
@@ -8767,10 +8772,10 @@
                 outAttrs.imeOptions = EditorInfo.IME_NULL;
                 outAttrs.hintLocales = null;
             }
-            if (focusSearch(FOCUS_DOWN) != null) {
+            if (hasEditorInFocusSearchDirection(FOCUS_DOWN)) {
                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
             }
-            if (focusSearch(FOCUS_UP) != null) {
+            if (hasEditorInFocusSearchDirection(FOCUS_UP)) {
                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
             }
             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index d259b7f..87e8ac1 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -473,6 +473,7 @@
                 }
             }
             mCurCombinedState = state;
+            mStats.mUidStates.get(mUid).updateCombinedState(state, now);
         }
     }
 
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index fd16662..b7abe73 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -179,17 +179,19 @@
     public static final int REPORT_PKG_ASC_STATS = 0x08;
     // Should report package stats.
     public static final int REPORT_PKG_STATS = 0x0E;
+    // Should report uid stats.
+    public static final int REPORT_UID_STATS = 0x10;
     // Should report all stats.
-    public static final int REPORT_ALL = 0x0F;
+    public static final int REPORT_ALL = 0x1F;
 
     public static final int[] OPTIONS =
             {REPORT_PROC_STATS, REPORT_PKG_PROC_STATS, REPORT_PKG_SVC_STATS, REPORT_PKG_ASC_STATS,
-                    REPORT_PKG_STATS, REPORT_ALL};
+                    REPORT_PKG_STATS, REPORT_UID_STATS, REPORT_ALL};
     public static final String[] OPTIONS_STR =
-            {"proc", "pkg-proc", "pkg-svc", "pkg-asc", "pkg-all", "all"};
+            {"proc", "pkg-proc", "pkg-svc", "pkg-asc", "pkg-all", "uid", "all"};
 
     // Current version of the parcel format.
-    private static final int PARCEL_VERSION = 40;
+    private static final int PARCEL_VERSION = 41;
     // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
     private static final int MAGIC = 0x50535454;
 
@@ -200,6 +202,8 @@
     public final ProcessMap<LongSparseArray<PackageState>> mPackages = new ProcessMap<>();
     public final ProcessMap<ProcessState> mProcesses = new ProcessMap<>();
 
+    public final SparseArray<UidState> mUidStates = new SparseArray<>();
+
     public final ArrayList<AssociationState.SourceState> mTrackingAssociations = new ArrayList<>();
 
     public final long[] mMemFactorDurations = new long[ADJ_COUNT];
@@ -337,6 +341,18 @@
             }
         }
 
+        SparseArray<UidState> uidStates = other.mUidStates;
+        for (int ip = 0, size = uidStates.size(); ip < size; ip++) {
+            final int uid = uidStates.keyAt(ip);
+            UidState uidState = mUidStates.get(uid);
+            if (uidState == null) {
+                uidState = uidStates.valueAt(ip).clone();
+                mUidStates.put(uid, uidState);
+            } else {
+                uidState.add(uidStates.valueAt(ip));
+            }
+        }
+
         ArrayMap<String, SparseArray<ProcessState>> procMap = other.mProcesses.getMap();
         for (int ip=0; ip<procMap.size(); ip++) {
             SparseArray<ProcessState> uids = procMap.valueAt(ip);
@@ -358,9 +374,21 @@
                     }
                 }
                 thisProc.add(otherProc);
+                if (thisProc.isActive()) {
+                    UidState uidState = mUidStates.get(uid);
+                    if (uidState == null) {
+                        uidState = new UidState(this, uid);
+                        mUidStates.put(uid, uidState);
+                    }
+                    uidState.addProcess(thisProc);
+                }
             }
         }
 
+        for (int ip = 0, size = mUidStates.size(); ip < size; ip++) {
+            mUidStates.valueAt(ip).updateCombinedState(-1);
+        }
+
         for (int i=0; i<ADJ_COUNT; i++) {
             if (DEBUG) Slog.d(TAG, "Total duration #" + i + " inc by "
                     + other.mMemFactorDurations[i] + " from "
@@ -488,6 +516,7 @@
         resetCommon();
         mPackages.getMap().clear();
         mProcesses.getMap().clear();
+        mUidStates.clear();
         mMemFactor = STATE_NOTHING;
         mStartTime = 0;
         if (DEBUG) Slog.d(TAG, "State reset; now " + mTimePeriodStartClockStr);
@@ -580,6 +609,7 @@
                     }
                 } else {
                     ps.makeDead();
+                    mUidStates.get(uids.keyAt(iu)).removeProcess(ps, now);
                     uids.removeAt(iu);
                 }
             }
@@ -588,6 +618,15 @@
             }
         }
 
+        for (int ip = mUidStates.size() - 1; ip >= 0; ip--) {
+            final UidState uidState = mUidStates.valueAt(ip);
+            if (uidState.isInUse()) {
+                mUidStates.valueAt(ip).resetSafely(now);
+            } else {
+                mUidStates.removeAt(ip);
+            }
+        }
+
         mStartTime = now;
         if (DEBUG) Slog.d(TAG, "State reset; now " + mTimePeriodStartClockStr);
     }
@@ -900,6 +939,13 @@
 
         mSysMemUsage.writeToParcel(out);
 
+        final int numOfUids = mUidStates.size();
+        out.writeInt(numOfUids);
+        for (int ip = 0; ip < numOfUids; ip++) {
+            out.writeInt(mUidStates.keyAt(ip));
+            mUidStates.valueAt(ip).writeToParcel(out, now);
+        }
+
         out.writeInt(NPROC);
         for (int ip=0; ip<NPROC; ip++) {
             writeCommonString(out, procMap.keyAt(ip));
@@ -1026,7 +1072,8 @@
 
     public void readFromParcel(Parcel in) {
         final boolean hadData = mPackages.getMap().size() > 0
-                || mProcesses.getMap().size() > 0;
+                || mProcesses.getMap().size() > 0
+                || mUidStates.size() > 0;
         if (hadData) {
             resetSafely();
         }
@@ -1083,6 +1130,16 @@
             return;
         }
 
+        final int numOfUids = in.readInt();
+        for (int ip = 0; ip < numOfUids; ip++) {
+            final int uid = in.readInt();
+            final UidState uidState = new UidState(this, uid);
+            if (!uidState.readFromParcel(in)) {
+                return;
+            }
+            mUidStates.put(uid, uidState);
+        }
+
         int NPROC = in.readInt();
         if (NPROC < 0) {
             mReadError = "bad process count: " + NPROC;
@@ -1127,9 +1184,17 @@
                 if (DEBUG_PARCEL) Slog.d(TAG, "Adding process: " + procName + " " + uid
                         + " " + proc);
                 mProcesses.put(procName, uid, proc);
+
+                if (proc.isActive()) {
+                    mUidStates.get(uid).addProcess(proc);
+                }
             }
         }
 
+        for (int ip = 0; ip < numOfUids; ip++) {
+            mUidStates.valueAt(ip).updateCombinedState(-1);
+        }
+
         if (DEBUG_PARCEL) Slog.d(TAG, "Read " + mProcesses.getMap().size() + " processes");
 
         int NPKG = in.readInt();
@@ -1327,6 +1392,12 @@
             commonProc = new ProcessState(this, pkgState.mPackageName, pkgState.mUid,
                     pkgState.mVersionCode, processName);
             mProcesses.put(processName, pkgState.mUid, commonProc);
+            UidState uidState = mUidStates.get(pkgState.mUid);
+            if (uidState == null) {
+                uidState = new UidState(this, pkgState.mUid);
+                mUidStates.put(pkgState.mUid, uidState);
+            }
+            uidState.addProcess(commonProc);
             if (DEBUG) Slog.d(TAG, "GETPROC created new common " + commonProc);
         }
         if (!commonProc.isMultiPackage()) {
@@ -1787,6 +1858,42 @@
             pw.print(" shown of "); pw.print(numTotalProcs); pw.println(" total");
         }
 
+        if ((section & REPORT_UID_STATS) != 0) {
+            SparseArray<UidState> uidStates = mUidStates;
+            int numShownUids = 0, numTotalUids = 0;
+            printedHeader = false;
+            for (int iu = 0, size = uidStates.size(); iu < size; iu++) {
+                final int uid = uidStates.keyAt(iu);
+                final UidState uidState = uidStates.valueAt(iu);
+                numTotalUids++;
+                if (reqPackage != null && !uidState.hasPackage(reqPackage)) {
+                    continue;
+                }
+                numShownUids++;
+                pw.println();
+                if (!printedHeader) {
+                    pw.println("Per-UID Stats:");
+                    printedHeader = true;
+                }
+                if (activeOnly && !uidState.isInUse()) {
+                    pw.print("      (Not active: ");
+                    pw.print(UserHandle.formatUid(uid));
+                    pw.println(")");
+                    continue;
+                }
+                pw.print("  * ");
+                UserHandle.formatUid(pw, uid);
+                pw.print(" (");
+                pw.print(uidState.getDurationsBucketCount());
+                pw.print(" entries)");
+                pw.println(":");
+                uidState.dumpState(pw, "        ", ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+                        ALL_PROC_STATES, now);
+            }
+            pw.print("  Total UIDs: "); pw.print(numShownUids);
+            pw.print(" shown of "); pw.print(numTotalUids); pw.println(" total");
+        }
+
         if (dumpAll) {
             pw.println();
             if (mTrackingAssociations.size() > 0) {
diff --git a/core/java/com/android/internal/app/procstats/ProcessStatsInternal.java b/core/java/com/android/internal/app/procstats/ProcessStatsInternal.java
new file mode 100644
index 0000000..2f23008
--- /dev/null
+++ b/core/java/com/android/internal/app/procstats/ProcessStatsInternal.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app.procstats;
+
+import android.util.SparseArray;
+
+/**
+ * The internal interface to access the process stats service, to be used within
+ * system_server only.
+ */
+public abstract class ProcessStatsInternal {
+    /**
+     * Return the duration over the given time, that an UID spent in each processs state
+     * which is defined in the {@link ProcessStats}, the key of the array is the uid.
+     */
+    public abstract SparseArray<long[]> getUidProcStateStatsOverTime(long minTime);
+}
diff --git a/core/java/com/android/internal/app/procstats/UidState.java b/core/java/com/android/internal/app/procstats/UidState.java
new file mode 100644
index 0000000..8761b74
--- /dev/null
+++ b/core/java/com/android/internal/app/procstats/UidState.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app.procstats;
+
+import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
+import static com.android.internal.app.procstats.ProcessStats.STATE_NOTHING;
+
+import android.os.Parcel;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * The class to track the individual time-in-state of a UID.
+ */
+public final class UidState {
+    private static final String TAG = "ProcessStats";
+
+    private final ProcessStats mStats;
+    private final int mUid;
+    private final DurationsTable mDurations;
+
+    private ArraySet<ProcessState> mProcesses = new ArraySet<>();
+    private int mCurCombinedState = STATE_NOTHING;
+    private long mStartTime;
+
+    private long mTotalRunningStartTime;
+    private long mTotalRunningDuration;
+
+    /**
+     * Create a new UID state. The initial state is not running.
+     */
+    public UidState(ProcessStats processStats, int uid) {
+        mStats = processStats;
+        mUid = uid;
+        mDurations = new DurationsTable(processStats.mTableData);
+    }
+
+    /**
+     * Create a copy of this instance.
+     */
+    public UidState clone() {
+        UidState unew = new UidState(mStats, mUid);
+        unew.mDurations.addDurations(mDurations);
+        unew.mCurCombinedState = mCurCombinedState;
+        unew.mStartTime = mStartTime;
+        unew.mTotalRunningStartTime = mTotalRunningStartTime;
+        unew.mTotalRunningDuration = mTotalRunningDuration;
+        return unew;
+    }
+
+    /**
+     * Update the current state of the UID, it should be a combination
+     * of all running processes in this UID.
+     */
+    public void updateCombinedState(int state, long now) {
+        if (mCurCombinedState != state) {
+            updateCombinedState(now);
+        }
+    }
+
+    /**
+     * Update the current state of the UID, it should be a combination
+     * of all running processes in this UID.
+     */
+    public void updateCombinedState(long now) {
+        setCombinedStateInner(calcCombinedState(), now);
+    }
+
+    private int calcCombinedState() {
+        int minCombined = STATE_NOTHING;
+        int min = STATE_NOTHING;
+        for (int i = 0, size = mProcesses.size(); i < size; i++) {
+            final int combinedState = mProcesses.valueAt(i).getCombinedState();
+            final int state = combinedState % STATE_COUNT;
+            if (combinedState != STATE_NOTHING) {
+                if (min == STATE_NOTHING || state < min) {
+                    minCombined = combinedState;
+                    min = state;
+                }
+            }
+        }
+        return minCombined;
+    }
+
+    /**
+     * Set the combined state and commit the state.
+     *
+     * @param now When it's negative, the previous state won't be committed.
+     */
+    private void setCombinedStateInner(int state, long now) {
+        if (mCurCombinedState != state) {
+            if (now >= 0) {
+                commitStateTime(now);
+                if (state == STATE_NOTHING) {
+                    // We are transitioning to a no longer running state... stop counting run time.
+                    mTotalRunningDuration += now - mTotalRunningStartTime;
+                } else if (mCurCombinedState == STATE_NOTHING) {
+                    // We previously weren't running...  now starting again, clear out total
+                    // running info.
+                    mTotalRunningDuration = 0;
+                }
+            }
+            mCurCombinedState = state;
+        }
+    }
+
+    /**
+     * @return The current combine state of the UID.
+     */
+    public int getCombinedState() {
+        return mCurCombinedState;
+    }
+
+    /**
+     * Commit the current state's duration into stats.
+     */
+    public void commitStateTime(long now) {
+        if (mCurCombinedState != STATE_NOTHING) {
+            long dur = now - mStartTime;
+            if (dur > 0) {
+                mDurations.addDuration(mCurCombinedState, dur);
+            }
+            mTotalRunningDuration += now - mTotalRunningStartTime;
+            mTotalRunningStartTime = now;
+        }
+        mStartTime = now;
+    }
+
+    /**
+     * Reset the UID stats safely.
+     */
+    public void resetSafely(long now) {
+        mDurations.resetTable();
+        mStartTime = now;
+    }
+
+    /**
+     * @return Whether this UID stats is still being used or not.
+     */
+    public boolean isInUse() {
+        for (int i = 0, size = mProcesses.size(); i < size; i++) {
+            if (mProcesses.valueAt(i).isInUse()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return Whether the given package belongs to this UID or not.
+     */
+    public boolean hasPackage(String packageName) {
+        for (int i = 0, size = mProcesses.size(); i < size; i++) {
+            final ProcessState proc = mProcesses.valueAt(i);
+            if (TextUtils.equals(packageName, proc.getName())
+                    && TextUtils.equals(packageName, proc.getPackage())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Add stats data from another instance to this one.
+     */
+    public void add(UidState other) {
+        mDurations.addDurations(other.mDurations);
+        mTotalRunningDuration += other.mTotalRunningDuration;
+    }
+
+    void addProcess(ProcessState proc) {
+        mProcesses.add(proc);
+    }
+
+    void addProcess(ProcessState proc, long now) {
+        mProcesses.add(proc);
+        setCombinedStateInner(proc.getCombinedState(), now);
+    }
+
+    void removeProcess(ProcessState proc, long now) {
+        mProcesses.remove(proc);
+        setCombinedStateInner(proc.getCombinedState(), now);
+    }
+
+    /**
+     * @return The total amount of stats it's currently tracking.
+     */
+    public int getDurationsBucketCount() {
+        return mDurations.getKeyCount();
+    }
+
+    /**
+     * @return The total running duration of this UID.
+     */
+    public long getTotalRunningDuration(long now) {
+        return mTotalRunningDuration
+                + (mTotalRunningStartTime != 0 ? (now - mTotalRunningStartTime) : 0);
+    }
+
+    /**
+     * @return The duration in the given state.
+     */
+    public long getDuration(int state, long now) {
+        long time = mDurations.getValueForId((byte) state);
+        if (mCurCombinedState == state) {
+            time += now - mStartTime;
+        }
+        return time;
+    }
+
+    /**
+     * @return The durations in each process state, the mem/screen factors
+     *         are consolidated into the bucket with the same process state.
+     */
+    public long[] getAggregatedDurationsInStates() {
+        final long[] states = new long[STATE_COUNT];
+        final int numOfBuckets = getDurationsBucketCount();
+        for (int i = 0; i < numOfBuckets; i++) {
+            final int key = mDurations.getKeyAt(i);
+            final int combinedState = SparseMappingTable.getIdFromKey(key);
+            states[combinedState % STATE_COUNT] += mDurations.getValue(key);
+        }
+        return states;
+    }
+
+    void writeToParcel(Parcel out, long now) {
+        mDurations.writeToParcel(out);
+        out.writeLong(getTotalRunningDuration(now));
+    }
+
+    boolean readFromParcel(Parcel in) {
+        if (!mDurations.readFromParcel(in)) {
+            return false;
+        }
+        mTotalRunningDuration = in.readLong();
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("UidState{").append(Integer.toHexString(System.identityHashCode(this)))
+                .append(" ").append(UserHandle.formatUid(mUid)).append("}");
+        return sb.toString();
+    }
+
+    void dumpState(PrintWriter pw, String prefix,
+            int[] screenStates, int[] memStates, int[] procStates, long now) {
+        long totalTime = 0;
+        int printedScreen = -1;
+        for (int is = 0; is < screenStates.length; is++) {
+            int printedMem = -1;
+            for (int im = 0; im < memStates.length; im++) {
+                for (int ip = 0; ip < procStates.length; ip++) {
+                    final int iscreen = screenStates[is];
+                    final int imem = memStates[im];
+                    final int bucket = ((iscreen + imem) * STATE_COUNT) + procStates[ip];
+                    long time = mDurations.getValueForId((byte) bucket);
+                    String running = "";
+                    if (mCurCombinedState == bucket) {
+                        running = " (running)";
+                        time += now - mStartTime;
+                    }
+                    if (time != 0) {
+                        pw.print(prefix);
+                        if (screenStates.length > 1) {
+                            DumpUtils.printScreenLabel(pw, printedScreen != iscreen
+                                    ? iscreen : STATE_NOTHING);
+                            printedScreen = iscreen;
+                        }
+                        if (memStates.length > 1) {
+                            DumpUtils.printMemLabel(pw,
+                                    printedMem != imem ? imem : STATE_NOTHING, '/');
+                            printedMem = imem;
+                        }
+                        pw.print(DumpUtils.STATE_LABELS[procStates[ip]]); pw.print(": ");
+                        TimeUtils.formatDuration(time, pw); pw.println(running);
+                        totalTime += time;
+                    }
+                }
+            }
+        }
+        if (totalTime != 0) {
+            pw.print(prefix);
+            if (screenStates.length > 1) {
+                DumpUtils.printScreenLabel(pw, STATE_NOTHING);
+            }
+            if (memStates.length > 1) {
+                DumpUtils.printMemLabel(pw, STATE_NOTHING, '/');
+            }
+            pw.print(DumpUtils.STATE_LABEL_TOTAL);
+            pw.print(": ");
+            TimeUtils.formatDuration(totalTime, pw);
+            pw.println();
+        }
+    }
+}
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
index 6c73e70..29b31d3 100644
--- a/core/java/com/android/internal/content/om/OverlayConfig.java
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -26,20 +26,25 @@
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 
+import com.android.apex.ApexInfo;
+import com.android.apex.XmlParser;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.om.OverlayConfigParser.OverlayPartition;
 import com.android.internal.content.om.OverlayConfigParser.ParsedConfiguration;
 import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.TriConsumer;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
-import java.util.function.BiConsumer;
 import java.util.function.Supplier;
 
 /**
@@ -77,7 +82,7 @@
     public interface PackageProvider {
 
         /** Performs the given action for each package. */
-        void forEachPackage(BiConsumer<ParsingPackageRead, Boolean> p);
+        void forEachPackage(TriConsumer<ParsingPackageRead, Boolean, File> p);
     }
 
     private static final Comparator<ParsedConfiguration> sStaticOverlayComparator = (c1, c2) -> {
@@ -119,6 +124,8 @@
                             p)));
         }
 
+        ArrayMap<Integer, List<String>> activeApexesPerPartition = getActiveApexes(partitions);
+
         boolean foundConfigFile = false;
         final Map<String, ParsedOverlayInfo> packageManagerOverlayInfos =
                 packageProvider == null ? null : getOverlayPackageInfos(packageProvider);
@@ -129,7 +136,9 @@
             final OverlayScanner scanner = (scannerFactory == null) ? null : scannerFactory.get();
             final ArrayList<ParsedConfiguration> partitionOverlays =
                     OverlayConfigParser.getConfigurations(partition, scanner,
-                            packageManagerOverlayInfos);
+                            packageManagerOverlayInfos,
+                            activeApexesPerPartition.getOrDefault(partition.type,
+                                    Collections.emptyList()));
             if (partitionOverlays != null) {
                 foundConfigFile = true;
                 overlays.addAll(partitionOverlays);
@@ -147,7 +156,8 @@
                 // Filter out overlays not present in the partition.
                 partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos.values());
                 for (int j = partitionOverlayInfos.size() - 1; j >= 0; j--) {
-                    if (!partition.containsFile(partitionOverlayInfos.get(j).path)) {
+                    if (!partition.containsFile(partitionOverlayInfos.get(j)
+                            .getOriginalPartitionPath())) {
                         partitionOverlayInfos.remove(j);
                     }
                 }
@@ -294,16 +304,50 @@
     private static Map<String, ParsedOverlayInfo> getOverlayPackageInfos(
             @NonNull PackageProvider packageManager) {
         final HashMap<String, ParsedOverlayInfo> overlays = new HashMap<>();
-        packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem) -> {
+        packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem,
+                @Nullable File preInstalledApexPath) -> {
             if (p.getOverlayTarget() != null && isSystem) {
                 overlays.put(p.getPackageName(), new ParsedOverlayInfo(p.getPackageName(),
                         p.getOverlayTarget(), p.getTargetSdkVersion(), p.isOverlayIsStatic(),
-                        p.getOverlayPriority(), new File(p.getBaseApkPath())));
+                        p.getOverlayPriority(), new File(p.getBaseApkPath()),
+                        preInstalledApexPath));
             }
         });
         return overlays;
     }
 
+    /** Returns a map of PartitionType to List of active APEX module names. */
+    @NonNull
+    private static ArrayMap<Integer, List<String>> getActiveApexes(
+            @NonNull List<OverlayPartition> partitions) {
+        // An Overlay in an APEX, which is an update of an APEX in a given partition,
+        // is considered as belonging to that partition.
+        ArrayMap<Integer, List<String>> result = new ArrayMap<>();
+        for (OverlayPartition partition : partitions) {
+            result.put(partition.type, new ArrayList<String>());
+        }
+        // Read from apex-info-list because ApexManager is not accessible to zygote.
+        File apexInfoList = new File("/apex/apex-info-list.xml");
+        if (apexInfoList.exists() && apexInfoList.canRead()) {
+            try (FileInputStream stream = new FileInputStream(apexInfoList)) {
+                List<ApexInfo> apexInfos = XmlParser.readApexInfoList(stream).getApexInfo();
+                for (ApexInfo info : apexInfos) {
+                    if (info.getIsActive()) {
+                        for (OverlayPartition partition : partitions) {
+                            if (partition.containsPath(info.getPreinstalledModulePath())) {
+                                result.get(partition.type).add(info.getModuleName());
+                                break;
+                            }
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                Log.w(TAG, "Error reading apex-info-list: " + e);
+            }
+        }
+        return result;
+    }
+
     /** Represents a single call to idmap create-multiple. */
     @VisibleForTesting
     public static class IdmapInvocation {
diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java
index 053a341..0ab7b3d 100644
--- a/core/java/com/android/internal/content/om/OverlayConfigParser.java
+++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java
@@ -41,6 +41,7 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -195,13 +196,19 @@
     @Nullable
     static ArrayList<ParsedConfiguration> getConfigurations(
             @NonNull OverlayPartition partition, @Nullable OverlayScanner scanner,
-            @Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos) {
-        if (partition.getOverlayFolder() == null) {
-            return null;
+            @Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
+            @NonNull List<String> activeApexes) {
+        if (scanner != null) {
+            if (partition.getOverlayFolder() != null) {
+                scanner.scanDir(partition.getOverlayFolder());
+            }
+            for (String apex : activeApexes) {
+                scanner.scanDir(new File("/apex/" + apex + "/overlay/"));
+            }
         }
 
-        if (scanner != null) {
-            scanner.scanDir(partition.getOverlayFolder());
+        if (partition.getOverlayFolder() == null) {
+            return null;
         }
 
         final File configFile = new File(partition.getOverlayFolder(), CONFIG_DEFAULT_FILENAME);
diff --git a/core/java/com/android/internal/content/om/OverlayScanner.java b/core/java/com/android/internal/content/om/OverlayScanner.java
index 4387d1b..e4e0228 100644
--- a/core/java/com/android/internal/content/om/OverlayScanner.java
+++ b/core/java/com/android/internal/content/om/OverlayScanner.java
@@ -54,23 +54,38 @@
         public final boolean isStatic;
         public final int priority;
         public final File path;
+        @Nullable public final File preInstalledApexPath;
 
         public ParsedOverlayInfo(String packageName, String targetPackageName,
-                int targetSdkVersion, boolean isStatic, int priority, File path) {
+                int targetSdkVersion, boolean isStatic, int priority, File path,
+                @Nullable File preInstalledApexPath) {
             this.packageName = packageName;
             this.targetPackageName = targetPackageName;
             this.targetSdkVersion = targetSdkVersion;
             this.isStatic = isStatic;
             this.priority = priority;
             this.path = path;
+            this.preInstalledApexPath = preInstalledApexPath;
         }
 
         @Override
         public String toString() {
             return getClass().getSimpleName() + String.format("{packageName=%s"
                             + ", targetPackageName=%s, targetSdkVersion=%s, isStatic=%s"
-                            + ", priority=%s, path=%s}",
-                    packageName, targetPackageName, targetSdkVersion, isStatic, priority, path);
+                            + ", priority=%s, path=%s, preInstalledApexPath=%s}",
+                    packageName, targetPackageName, targetSdkVersion, isStatic,
+                    priority, path, preInstalledApexPath);
+        }
+
+        /**
+         * Retrieves the path of the overlay in its original installation partition.
+         *
+         * An Overlay in an APEX, which is an update of an APEX in a given partition,
+         * is considered as belonging to that partition.
+         */
+        @NonNull
+        public File getOriginalPartitionPath() {
+            return preInstalledApexPath != null ? preInstalledApexPath : path;
         }
     }
 
@@ -189,6 +204,6 @@
         }
         return new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(),
                 apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(),
-                apkLite.getOverlayPriority(), new File(apkLite.getPath()));
+                apkLite.getOverlayPriority(), new File(apkLite.getPath()), null);
     }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 985331c..1e20cfa 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -160,7 +160,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    static final int VERSION = 202;
+    static final int VERSION = 203;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -237,6 +237,10 @@
         return mKernelMemoryStats;
     }
 
+    private static final int[] SUPPORTED_PER_PROCESS_STATE_STANDARD_ENERGY_BUCKETS = {
+            MeasuredEnergyStats.POWER_BUCKET_CPU,
+    };
+
     @GuardedBy("this")
     public boolean mPerProcStateCpuTimesAvailable = true;
 
@@ -526,6 +530,7 @@
      * mPendingRemovedUids queue.
      */
     @GuardedBy("this")
+    @SuppressWarnings("GuardedBy")    // errorprone false positive on removeLocked
     public void clearPendingRemovedUidsLocked() {
         long cutOffTimeMs = mClock.elapsedRealtime() - mConstants.UID_REMOVE_DELAY_MS;
         while (!mPendingRemovedUids.isEmpty()
@@ -1061,6 +1066,10 @@
 
     int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
 
+    @GuardedBy("this")
+    @VisibleForTesting
+    protected @Nullable MeasuredEnergyStats.Config mMeasuredEnergyStatsConfig;
+
     /**
      * Accumulated global (generally, device-wide total) charge consumption of various consumers
      * while on battery.
@@ -3564,6 +3573,7 @@
     // from a battery level change.
     static final int BATTERY_DELTA_LEVEL_FLAG   = 0x00000001;
 
+    @GuardedBy("this")
     public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
         if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
             dest.writeInt(DELTA_TIME_ABS);
@@ -3836,11 +3846,13 @@
         mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
     }
 
+    @GuardedBy("this")
     @Override
     public void commitCurrentHistoryBatchLocked() {
         mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
     }
 
+    @GuardedBy("this")
     public void createFakeHistoryEvents(long numEvents) {
         final long elapsedRealtimeMs = mClock.elapsedRealtime();
         final long uptimeMs = mClock.uptimeMillis();
@@ -3852,6 +3864,7 @@
         }
     }
 
+    @GuardedBy("this")
     void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
         if (!mHaveBatteryLevel || !mRecordingHistory) {
             return;
@@ -3955,6 +3968,7 @@
         addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur);
     }
 
+    @GuardedBy("this")
     private void addHistoryBufferLocked(long elapsedRealtimeMs, byte cmd, HistoryItem cur) {
         if (mBatteryStatsHistoryIterator != null) {
             throw new IllegalStateException("Can't do this while iterating history!");
@@ -3981,6 +3995,7 @@
     int mChangedStates = 0;
     int mChangedStates2 = 0;
 
+    @GuardedBy("this")
     void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
             final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
@@ -4001,10 +4016,12 @@
         addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur);
     }
 
+    @GuardedBy("this")
     void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
         addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur);
     }
 
+    @GuardedBy("this")
     public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
             String name, int uid) {
         mHistoryCur.eventCode = code;
@@ -4014,6 +4031,7 @@
         addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
     }
 
+    @GuardedBy("this")
     void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs, byte cmd, HistoryItem cur) {
         HistoryItem rec = mHistoryCache;
         if (rec != null) {
@@ -4026,6 +4044,7 @@
         addHistoryRecordLocked(rec);
     }
 
+    @GuardedBy("this")
     void addHistoryRecordLocked(HistoryItem rec) {
         mNumHistoryItems++;
         rec.next = null;
@@ -4038,6 +4057,7 @@
         }
     }
 
+    @GuardedBy("this")
     void clearHistoryLocked() {
         if (DEBUG_HISTORY) Slog.i(TAG, "********** CLEARING HISTORY!");
         mHistoryBaseTimeMs = 0;
@@ -4100,6 +4120,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void updateBatteryPropertiesLocked() {
         try {
             IBatteryPropertiesRegistrar registrar = IBatteryPropertiesRegistrar.Stub.asInterface(
@@ -4112,11 +4133,14 @@
         }
     }
 
+    @GuardedBy("this")
     public void addIsolatedUidLocked(int isolatedUid, int appUid) {
         addIsolatedUidLocked(isolatedUid, appUid,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
+    @SuppressWarnings("GuardedBy")   // errorprone false positive on u.addIsolatedUid
     public void addIsolatedUidLocked(int isolatedUid, int appUid,
             long elapsedRealtimeMs, long uptimeMs) {
         mIsolatedUids.put(isolatedUid, appUid);
@@ -4191,10 +4215,12 @@
         return isolated > 0 ? isolated : uid;
     }
 
+    @GuardedBy("this")
     public void noteEventLocked(int code, String name, int uid) {
         noteEventLocked(code, name, uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteEventLocked(int code, String name, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
@@ -4204,6 +4230,7 @@
         addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, code, name, uid);
     }
 
+    @GuardedBy("this")
     public void noteCurrentTimeChangedLocked() {
         final long currentTime = mClock.currentTimeMillis();
         final long elapsedRealtime = mClock.elapsedRealtime();
@@ -4211,15 +4238,18 @@
         noteCurrentTimeChangedLocked(currentTime, elapsedRealtime, uptime);
     }
 
+    @GuardedBy("this")
     public void noteCurrentTimeChangedLocked(long currentTimeMs,
             long elapsedRealtimeMs, long uptimeMs) {
         recordCurrentTimeChangeLocked(currentTimeMs, elapsedRealtimeMs, uptimeMs);
     }
 
+    @GuardedBy("this")
     public void noteProcessStartLocked(String name, int uid) {
         noteProcessStartLocked(name, uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteProcessStartLocked(String name, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
@@ -4236,10 +4266,12 @@
         addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_START, name, uid);
     }
 
+    @GuardedBy("this")
     public void noteProcessCrashLocked(String name, int uid) {
         noteProcessCrashLocked(name, uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteProcessCrashLocked(String name, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
@@ -4249,10 +4281,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteProcessAnrLocked(String name, int uid) {
         noteProcessAnrLocked(name, uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteProcessAnrLocked(String name, int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (isOnBattery()) {
@@ -4261,10 +4295,13 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteUidProcessStateLocked(int uid, int state) {
         noteUidProcessStateLocked(uid, state, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
+    @SuppressWarnings("GuardedBy")   // errorprone false positive on u.updateUidProcessStateLocked
     public void noteUidProcessStateLocked(int uid, int state,
             long elapsedRealtimeMs, long uptimeMs) {
         int parentUid = mapUid(uid);
@@ -4282,10 +4319,12 @@
                 .updateUidProcessStateLocked(state, elapsedRealtimeMs, uptimeMs);
     }
 
+    @GuardedBy("this")
     public void noteProcessFinishLocked(String name, int uid) {
         noteProcessFinishLocked(name, uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteProcessFinishLocked(String name, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
@@ -4299,10 +4338,12 @@
                 name, uid);
     }
 
+    @GuardedBy("this")
     public void noteSyncStartLocked(String name, int uid) {
         noteSyncStartLocked(name, uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteSyncStartLocked(String name, int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -4313,10 +4354,12 @@
         addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_START, name, uid);
     }
 
+    @GuardedBy("this")
     public void noteSyncFinishLocked(String name, int uid) {
         noteSyncFinishLocked(name, uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteSyncFinishLocked(String name, int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -4328,10 +4371,12 @@
                 name, uid);
     }
 
+    @GuardedBy("this")
     public void noteJobStartLocked(String name, int uid) {
         noteJobStartLocked(name, uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteJobStartLocked(String name, int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -4342,11 +4387,13 @@
         addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_START, name, uid);
     }
 
+    @GuardedBy("this")
     public void noteJobFinishLocked(String name, int uid, int stopReason) {
         noteJobFinishLocked(name, uid, stopReason,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteJobFinishLocked(String name, int uid, int stopReason,
             long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
@@ -4358,11 +4405,13 @@
         addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_FINISH, name, uid);
     }
 
+    @GuardedBy("this")
     public void noteJobsDeferredLocked(int uid, int numDeferred, long sinceLast) {
         noteJobsDeferredLocked(uid, numDeferred, sinceLast,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteJobsDeferredLocked(int uid, int numDeferred, long sinceLast,
             long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
@@ -4370,34 +4419,33 @@
                 .noteJobsDeferredLocked(numDeferred, sinceLast);
     }
 
+    @GuardedBy("this")
     public void noteAlarmStartLocked(String name, WorkSource workSource, int uid) {
         noteAlarmStartLocked(name, workSource, uid,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteAlarmStartLocked(String name, WorkSource workSource, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_START, name, workSource, uid,
                 elapsedRealtimeMs, uptimeMs);
     }
 
+    @GuardedBy("this")
     public void noteAlarmFinishLocked(String name, WorkSource workSource, int uid) {
         noteAlarmFinishLocked(name, workSource, uid,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteAlarmFinishLocked(String name, WorkSource workSource, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_FINISH, name, workSource, uid,
                 elapsedRealtimeMs, uptimeMs);
     }
 
-    private void noteAlarmStartOrFinishLocked(int historyItem, String name, WorkSource workSource,
-            int uid) {
-        noteAlarmStartOrFinishLocked(historyItem, name, workSource, uid,
-                mClock.elapsedRealtime(), mClock.uptimeMillis());
-    }
-
+    @GuardedBy("this")
     private void noteAlarmStartOrFinishLocked(int historyItem, String name, WorkSource workSource,
             int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (!mRecordAllHistory) {
@@ -4430,12 +4478,14 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWakupAlarmLocked(String packageName, int uid, WorkSource workSource,
             String tag) {
         noteWakupAlarmLocked(packageName, uid, workSource, tag,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWakupAlarmLocked(String packageName, int uid, WorkSource workSource,
             String tag, long elapsedRealtimeMs, long uptimeMs) {
         if (workSource != null) {
@@ -4481,6 +4531,7 @@
         mExternalSync.scheduleCpuSyncDueToWakelockChange(0 /* delayMillis */);
     }
 
+    @GuardedBy("this")
     public void setRecordAllHistoryLocked(boolean enabled) {
         mRecordAllHistory = enabled;
         if (!enabled) {
@@ -4524,6 +4575,7 @@
         mNoAutoReset = enabled;
     }
 
+    @GuardedBy("this")
     public void setPretendScreenOff(boolean pretendScreenOff) {
         if (mPretendScreenOff != pretendScreenOff) {
             mPretendScreenOff = pretendScreenOff;
@@ -4537,12 +4589,14 @@
     private String mInitialAcquireWakeName;
     private int mInitialAcquireWakeUid = -1;
 
+    @GuardedBy("this")
     public void noteStartWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
             int type, boolean unimportantForLogging) {
         noteStartWakeLocked(uid, pid, wc, name, historyName, type, unimportantForLogging,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteStartWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
             int type, boolean unimportantForLogging, long elapsedRealtimeMs, long uptimeMs) {
         final int mappedUid = mapUid(uid);
@@ -4613,12 +4667,14 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteStopWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
             int type) {
         noteStopWakeLocked(uid, pid, wc, name, historyName, type,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteStopWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
             int type, long elapsedRealtimeMs, long uptimeMs) {
         final int mappedUid = mapUid(uid);
@@ -4701,12 +4757,14 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name,
             String historyName, int type, boolean unimportantForLogging) {
         noteStartWakeFromSourceLocked(ws, pid, name, historyName, type, unimportantForLogging,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name,
             String historyName, int type, boolean unimportantForLogging,
             long elapsedRealtimeMs, long uptimeMs) {
@@ -4726,6 +4784,7 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteChangeWakelockFromSourceLocked(WorkSource ws, int pid, String name,
             String historyName, int type, WorkSource newWs, int newPid, String newName,
             String newHistoryName, int newType, boolean newUnimportantForLogging) {
@@ -4734,6 +4793,7 @@
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteChangeWakelockFromSourceLocked(WorkSource ws, int pid, String name,
             String historyName, int type, WorkSource newWs, int newPid, String newName,
             String newHistoryName, int newType, boolean newUnimportantForLogging,
@@ -4780,12 +4840,14 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name,
             String historyName, int type) {
         noteStopWakeFromSourceLocked(ws, pid, name, historyName, type,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name,
             String historyName, int type, long elapsedRealtimeMs, long uptimeMs) {
         final int N = ws.size();
@@ -4804,23 +4866,27 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteLongPartialWakelockStart(String name, String historyName, int uid) {
         noteLongPartialWakelockStart(name, historyName, uid,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteLongPartialWakelockStart(String name, String historyName, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         noteLongPartialWakeLockStartInternal(name, historyName, uid, elapsedRealtimeMs, uptimeMs);
     }
 
+    @GuardedBy("this")
     public void noteLongPartialWakelockStartFromSource(String name, String historyName,
             WorkSource workSource) {
         noteLongPartialWakelockStartFromSource(name, historyName, workSource,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteLongPartialWakelockStartFromSource(String name, String historyName,
             WorkSource workSource, long elapsedRealtimeMs, long uptimeMs) {
         final int N = workSource.size();
@@ -4841,6 +4907,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void noteLongPartialWakeLockStartInternal(String name, String historyName, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         if (historyName == null) {
@@ -4854,23 +4921,27 @@
                 historyName, uid);
     }
 
+    @GuardedBy("this")
     public void noteLongPartialWakelockFinish(String name, String historyName, int uid) {
         noteLongPartialWakelockFinish(name, historyName, uid,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteLongPartialWakelockFinish(String name, String historyName, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         noteLongPartialWakeLockFinishInternal(name, historyName, uid, elapsedRealtimeMs, uptimeMs);
     }
 
+    @GuardedBy("this")
     public void noteLongPartialWakelockFinishFromSource(String name, String historyName,
             WorkSource workSource) {
         noteLongPartialWakelockFinishFromSource(name, historyName, workSource,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteLongPartialWakelockFinishFromSource(String name, String historyName,
             WorkSource workSource, long elapsedRealtimeMs, long uptimeMs) {
         final int N = workSource.size();
@@ -4891,6 +4962,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void noteLongPartialWakeLockFinishInternal(String name, String historyName, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         if (historyName == null) {
@@ -4904,6 +4976,7 @@
                 historyName, uid);
     }
 
+    @GuardedBy("this")
     void aggregateLastWakeupUptimeLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mLastWakeupReason != null) {
             long deltaUptimeMs = uptimeMs - mLastWakeupUptimeMs;
@@ -4915,10 +4988,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWakeupReasonLocked(String reason) {
         noteWakeupReasonLocked(reason, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) {
         if (DEBUG_HISTORY) Slog.v(TAG, "Wakeup reason \"" + reason +"\": "
                 + Integer.toHexString(mHistoryCur.states));
@@ -4931,11 +5006,13 @@
         addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
     }
 
+    @GuardedBy("this")
     public boolean startAddingCpuLocked() {
         mExternalSync.cancelCpuSyncDueToWakelockChange();
         return mOnBatteryInternal;
     }
 
+    @GuardedBy("this")
     public void finishAddingCpuLocked(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs,
                                       int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
                                       int statSoftIrqTimeMs, int statIdleTimeMs) {
@@ -4985,10 +5062,12 @@
 
     int mSensorNesting;
 
+    @GuardedBy("this")
     public void noteStartSensorLocked(int uid, int sensor) {
         noteStartSensorLocked(uid, sensor, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteStartSensorLocked(int uid, int sensor, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mSensorNesting == 0) {
@@ -5002,10 +5081,12 @@
                 .noteStartSensor(sensor, elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteStopSensorLocked(int uid, int sensor) {
         noteStopSensorLocked(uid, sensor, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteStopSensorLocked(int uid, int sensor, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         mSensorNesting--;
@@ -5021,10 +5102,12 @@
 
     int mGpsNesting;
 
+    @GuardedBy("this")
     public void noteGpsChangedLocked(WorkSource oldWs, WorkSource newWs) {
         noteGpsChangedLocked(oldWs, newWs, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteGpsChangedLocked(WorkSource oldWs, WorkSource newWs,
             long elapsedRealtimeMs, long uptimeMs) {
         for (int i = 0; i < newWs.size(); ++i) {
@@ -5053,6 +5136,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void noteStartGpsLocked(int uid, WorkChain workChain,
             long elapsedRealtimeMs, long uptimeMs) {
         uid = getAttributionUid(uid, workChain);
@@ -5076,6 +5160,7 @@
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs).noteStartGps(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     private void noteStopGpsLocked(int uid, WorkChain workChain,
             long elapsedRealtimeMs, long uptimeMs) {
         uid = getAttributionUid(uid, workChain);
@@ -5100,10 +5185,12 @@
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs).noteStopGps(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteGpsSignalQualityLocked(int signalLevel) {
         noteGpsSignalQualityLocked(signalLevel, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteGpsSignalQualityLocked(int signalLevel, long elapsedRealtimeMs, long uptimeMs) {
         if (mGpsNesting == 0) {
             return;
@@ -5372,6 +5459,7 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void noteScreenBrightnessLocked(int brightness) {
         noteScreenBrightnessLocked(0, brightness);
     }
@@ -5379,6 +5467,7 @@
     /**
      * Note screen brightness change for a display.
      */
+    @GuardedBy("this")
     public void noteScreenBrightnessLocked(int display, int brightness) {
         noteScreenBrightnessLocked(display, brightness, mClock.elapsedRealtime(),
                 mClock.uptimeMillis());
@@ -5388,6 +5477,7 @@
     /**
      * Note screen brightness change for a display.
      */
+    @GuardedBy("this")
     public void noteScreenBrightnessLocked(int display, int brightness, long elapsedRealtimeMs,
             long uptimeMs) {
         // Bin the brightness.
@@ -5425,6 +5515,7 @@
         maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs);
     }
 
+    @GuardedBy("this")
     private int evaluateOverallScreenBrightnessBinLocked() {
         int overallBin = -1;
         final int numDisplays = getDisplayCount();
@@ -5442,6 +5533,7 @@
         return overallBin;
     }
 
+    @GuardedBy("this")
     private void maybeUpdateOverallScreenBrightness(int overallBin, long elapsedRealtimeMs,
             long uptimeMs) {
         if (mScreenBrightnessBin != overallBin) {
@@ -5469,10 +5561,12 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void noteUserActivityLocked(int uid, int event) {
         noteUserActivityLocked(uid, event, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteUserActivityLocked(int uid, int event, long elapsedRealtimeMs, long uptimeMs) {
         if (mOnBatteryInternal) {
             uid = mapUid(uid);
@@ -5480,20 +5574,24 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWakeUpLocked(String reason, int reasonUid) {
         noteWakeUpLocked(reason, reasonUid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWakeUpLocked(String reason, int reasonUid,
             long elapsedRealtimeMs, long uptimeMs) {
         addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SCREEN_WAKE_UP,
                 reason, reasonUid);
     }
 
+    @GuardedBy("this")
     public void noteInteractiveLocked(boolean interactive) {
         noteInteractiveLocked(interactive, mClock.elapsedRealtime());
     }
 
+    @GuardedBy("this")
     public void noteInteractiveLocked(boolean interactive, long elapsedRealtimeMs) {
         if (mInteractive != interactive) {
             mInteractive = interactive;
@@ -5506,11 +5604,13 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteConnectivityChangedLocked(int type, String extra) {
         noteConnectivityChangedLocked(type, extra,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteConnectivityChangedLocked(int type, String extra,
             long elapsedRealtimeMs, long uptimeMs) {
         addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_CONNECTIVITY_CHANGED,
@@ -5518,6 +5618,7 @@
         mNumConnectivityChange++;
     }
 
+    @GuardedBy("this")
     private void noteMobileRadioApWakeupLocked(final long elapsedRealtimeMillis,
             final long uptimeMillis, int uid) {
         uid = mapUid(uid);
@@ -5529,11 +5630,13 @@
     /**
      * Updates the radio power state and returns true if an external stats collection should occur.
      */
+    @GuardedBy("this")
     public boolean noteMobileRadioPowerStateLocked(int powerState, long timestampNs, int uid) {
         return noteMobileRadioPowerStateLocked(powerState, timestampNs, uid,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public boolean noteMobileRadioPowerStateLocked(int powerState, long timestampNs, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         if (mMobileRadioPowerState != powerState) {
@@ -5578,6 +5681,7 @@
         return false;
     }
 
+    @GuardedBy("this")
     public void notePowerSaveModeLocked(boolean enabled) {
         notePowerSaveModeLocked(enabled, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
@@ -5585,6 +5689,7 @@
     /**
      * Toggles the power save mode state.
      */
+    @GuardedBy("this")
     public void notePowerSaveModeLockedInit(boolean enabled, long elapsedRealtimeMs,
             long uptimeMs) {
         if (mPowerSaveModeEnabled != enabled) {
@@ -5599,6 +5704,7 @@
         }
     }
 
+    @GuardedBy("this")
     public void notePowerSaveModeLocked(boolean enabled, long elapsedRealtimeMs, long uptimeMs) {
         if (mPowerSaveModeEnabled != enabled) {
             int stepState = enabled ? STEP_LEVEL_MODE_POWER_SAVE : 0;
@@ -5624,11 +5730,13 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteDeviceIdleModeLocked(final int mode, String activeReason, int activeUid) {
         noteDeviceIdleModeLocked(mode, activeReason, activeUid,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteDeviceIdleModeLocked(final int mode, String activeReason, int activeUid,
             long elapsedRealtimeMs, long uptimeMs) {
         boolean nowIdling = mode == DEVICE_IDLE_MODE_DEEP;
@@ -5702,11 +5810,13 @@
         }
     }
 
+    @GuardedBy("this")
     public void notePackageInstalledLocked(String pkgName, long versionCode) {
         notePackageInstalledLocked(pkgName, versionCode,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void notePackageInstalledLocked(String pkgName, long versionCode,
             long elapsedRealtimeMs, long uptimeMs) {
         // XXX need to figure out what to do with long version codes.
@@ -5719,10 +5829,12 @@
         addPackageChange(pc);
     }
 
+    @GuardedBy("this")
     public void notePackageUninstalledLocked(String pkgName) {
         notePackageUninstalledLocked(pkgName, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void notePackageUninstalledLocked(String pkgName,
             long elapsedRealtimeMs, long uptimeMs) {
         addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
@@ -5740,10 +5852,12 @@
         mDailyPackageChanges.add(pc);
     }
 
+    @GuardedBy("this")
     void stopAllGpsSignalQualityTimersLocked(int except) {
         stopAllGpsSignalQualityTimersLocked(except, mClock.elapsedRealtime());
     }
 
+    @GuardedBy("this")
     void stopAllGpsSignalQualityTimersLocked(int except, long elapsedRealtimeMs) {
         for (int i = 0; i < mGpsSignalQualityTimer.length; i++) {
             if (i == except) {
@@ -5756,10 +5870,12 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void notePhoneOnLocked() {
         notePhoneOnLocked(mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void notePhoneOnLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (!mPhoneOn) {
             mHistoryCur.states2 |= HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
@@ -5772,10 +5888,12 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void notePhoneOffLocked() {
         notePhoneOffLocked(mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void notePhoneOffLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mPhoneOn) {
             mHistoryCur.states2 &= ~HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
@@ -5787,6 +5905,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void registerUsbStateReceiver(Context context) {
         final IntentFilter usbStateFilter = new IntentFilter();
         usbStateFilter.addAction(UsbManager.ACTION_USB_STATE);
@@ -5811,6 +5930,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void noteUsbConnectionStateLocked(boolean connected, long elapsedRealtimeMs,
             long uptimeMs) {
         int newState = connected ? USB_DATA_CONNECTED : USB_DATA_DISCONNECTED;
@@ -5825,6 +5945,7 @@
         }
     }
 
+    @GuardedBy("this")
     void stopAllPhoneSignalStrengthTimersLocked(int except, long elapsedRealtimeMs) {
         for (int i = 0; i < CellSignalStrength.getNumSignalStrengthLevels(); i++) {
             if (i == except) {
@@ -5849,6 +5970,7 @@
         return state;
     }
 
+    @GuardedBy("this")
     private void updateAllPhoneStateLocked(int state, int simState, int strengthBin,
             long elapsedRealtimeMs, long uptimeMs) {
         boolean scanning = false;
@@ -5945,10 +6067,12 @@
      * Telephony stack updates the phone state.
      * @param state phone state from ServiceState.getState()
      */
+    @GuardedBy("this")
     public void notePhoneStateLocked(int state, int simState) {
         notePhoneStateLocked(state, simState, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void notePhoneStateLocked(int state, int simState,
             long elapsedRealtimeMs, long uptimeMs) {
         updateAllPhoneStateLocked(state, simState, mPhoneSignalStrengthBinRaw,
@@ -5956,11 +6080,13 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void notePhoneSignalStrengthLocked(SignalStrength signalStrength) {
         notePhoneSignalStrengthLocked(signalStrength,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void notePhoneSignalStrengthLocked(SignalStrength signalStrength,
             long elapsedRealtimeMs, long uptimeMs) {
         // Bin the strength.
@@ -5970,11 +6096,13 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType) {
         notePhoneDataConnectionStateLocked(dataType, hasData, serviceType,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType,
             long elapsedRealtimeMs, long uptimeMs) {
         // BatteryStats uses 0 to represent no network type.
@@ -6014,10 +6142,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiOnLocked() {
         noteWifiOnLocked(mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiOnLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (!mWifiOn) {
             mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_ON_FLAG;
@@ -6030,10 +6160,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiOffLocked() {
         noteWifiOffLocked(mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiOffLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mWifiOn) {
             mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_ON_FLAG;
@@ -6047,10 +6179,12 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void noteAudioOnLocked(int uid) {
         noteAudioOnLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteAudioOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mAudioOnNesting == 0) {
@@ -6066,10 +6200,12 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void noteAudioOffLocked(int uid) {
         noteAudioOffLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteAudioOffLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (mAudioOnNesting == 0) {
             return;
@@ -6087,10 +6223,12 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void noteVideoOnLocked(int uid) {
         noteVideoOnLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteVideoOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mVideoOnNesting == 0) {
@@ -6106,10 +6244,12 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void noteVideoOffLocked(int uid) {
         noteVideoOffLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteVideoOffLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (mVideoOnNesting == 0) {
             return;
@@ -6126,10 +6266,12 @@
                 .noteVideoTurnedOffLocked(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteResetAudioLocked() {
         noteResetAudioLocked(mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteResetAudioLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mAudioOnNesting > 0) {
             mAudioOnNesting = 0;
@@ -6145,10 +6287,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteResetVideoLocked() {
         noteResetVideoLocked(mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteResetVideoLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mVideoOnNesting > 0) {
             mVideoOnNesting = 0;
@@ -6164,31 +6308,37 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteActivityResumedLocked(int uid) {
         noteActivityResumedLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteActivityResumedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteActivityResumedLocked(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteActivityPausedLocked(int uid) {
         noteActivityPausedLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteActivityPausedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteActivityPausedLocked(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteVibratorOnLocked(int uid, long durationMillis) {
         noteVibratorOnLocked(uid, durationMillis,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteVibratorOnLocked(int uid, long durationMillis,
             long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
@@ -6196,20 +6346,24 @@
                 .noteVibratorOnLocked(durationMillis, elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteVibratorOffLocked(int uid) {
         noteVibratorOffLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteVibratorOffLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteVibratorOffLocked(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteFlashlightOnLocked(int uid) {
         noteFlashlightOnLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteFlashlightOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mFlashlightOnNesting++ == 0) {
@@ -6223,10 +6377,12 @@
                 .noteFlashlightTurnedOnLocked(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteFlashlightOffLocked(int uid) {
         noteFlashlightOffLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteFlashlightOffLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (mFlashlightOnNesting == 0) {
             return;
@@ -6243,10 +6399,12 @@
                 .noteFlashlightTurnedOffLocked(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteCameraOnLocked(int uid) {
         noteCameraOnLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteCameraOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mCameraOnNesting++ == 0) {
@@ -6260,10 +6418,12 @@
                 .noteCameraTurnedOnLocked(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteCameraOffLocked(int uid) {
         noteCameraOffLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteCameraOffLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (mCameraOnNesting == 0) {
             return;
@@ -6280,10 +6440,12 @@
                 .noteCameraTurnedOffLocked(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteResetCameraLocked() {
         noteResetCameraLocked(mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteResetCameraLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mCameraOnNesting > 0) {
             mCameraOnNesting = 0;
@@ -6299,10 +6461,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteResetFlashlightLocked() {
         noteResetFlashlightLocked(mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteResetFlashlightLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mFlashlightOnNesting > 0) {
             mFlashlightOnNesting = 0;
@@ -6318,6 +6482,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void noteBluetoothScanStartedLocked(WorkChain workChain, int uid,
             boolean isUnoptimized, long elapsedRealtimeMs, long uptimeMs) {
         uid = getAttributionUid(uid, workChain);
@@ -6333,11 +6498,13 @@
                 .noteBluetoothScanStartedLocked(elapsedRealtimeMs, isUnoptimized);
     }
 
+    @GuardedBy("this")
     public void noteBluetoothScanStartedFromSourceLocked(WorkSource ws, boolean isUnoptimized) {
         noteBluetoothScanStartedFromSourceLocked(ws, isUnoptimized,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteBluetoothScanStartedFromSourceLocked(WorkSource ws, boolean isUnoptimized,
             long elapsedRealtimeMs, long uptimeMs) {
         final int N = ws.size();
@@ -6355,6 +6522,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void noteBluetoothScanStoppedLocked(WorkChain workChain, int uid,
             boolean isUnoptimized, long elapsedRealtimeMs, long uptimeMs) {
         uid = getAttributionUid(uid, workChain);
@@ -6378,11 +6546,13 @@
         return mapUid(uid);
     }
 
+    @GuardedBy("this")
     public void noteBluetoothScanStoppedFromSourceLocked(WorkSource ws, boolean isUnoptimized) {
         noteBluetoothScanStoppedFromSourceLocked(ws, isUnoptimized,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteBluetoothScanStoppedFromSourceLocked(WorkSource ws, boolean isUnoptimized,
             long elapsedRealtimeMs, long uptimeMs) {
         final int N = ws.size();
@@ -6400,10 +6570,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteResetBluetoothScanLocked() {
         noteResetBluetoothScanLocked(mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteResetBluetoothScanLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mBluetoothScanNesting > 0) {
             mBluetoothScanNesting = 0;
@@ -6419,11 +6591,13 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteBluetoothScanResultsFromSourceLocked(WorkSource ws, int numNewResults) {
         noteBluetoothScanResultsFromSourceLocked(ws, numNewResults,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteBluetoothScanResultsFromSourceLocked(WorkSource ws, int numNewResults,
             long elapsedRealtimeMs, long uptimeMs) {
         final int N = ws.size();
@@ -6444,6 +6618,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void noteWifiRadioApWakeupLocked(final long elapsedRealtimeMillis,
             final long uptimeMillis, int uid) {
         uid = mapUid(uid);
@@ -6452,11 +6627,13 @@
         getUidStatsLocked(uid, elapsedRealtimeMillis, uptimeMillis).noteWifiRadioApWakeupLocked();
     }
 
+    @GuardedBy("this")
     public void noteWifiRadioPowerState(int powerState, long timestampNs, int uid) {
         noteWifiRadioPowerState(powerState, timestampNs, uid,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiRadioPowerState(int powerState, long timestampNs, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
         if (mWifiRadioPowerState != powerState) {
@@ -6480,10 +6657,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiRunningLocked(WorkSource ws) {
         noteWifiRunningLocked(ws, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiRunningLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) {
         if (!mGlobalWifiRunning) {
             mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_RUNNING_FLAG;
@@ -6514,11 +6693,13 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiRunningChangedLocked(WorkSource oldWs, WorkSource newWs) {
         noteWifiRunningChangedLocked(oldWs, newWs,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiRunningChangedLocked(WorkSource oldWs, WorkSource newWs,
             long elapsedRealtimeMs, long uptimeMs) {
         if (mGlobalWifiRunning) {
@@ -6558,10 +6739,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiStoppedLocked(WorkSource ws) {
         noteWifiStoppedLocked(ws, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiStoppedLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) {
         if (mGlobalWifiRunning) {
             mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_RUNNING_FLAG;
@@ -6592,10 +6775,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiStateLocked(int wifiState, String accessPoint) {
         noteWifiStateLocked(wifiState, accessPoint, mClock.elapsedRealtime());
     }
 
+    @GuardedBy("this")
     public void noteWifiStateLocked(int wifiState, String accessPoint, long elapsedRealtimeMs) {
         if (DEBUG) Log.i(TAG, "WiFi state -> " + wifiState);
         if (mWifiState != wifiState) {
@@ -6608,11 +6793,13 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiSupplicantStateChangedLocked(int supplState, boolean failedAuth) {
         noteWifiSupplicantStateChangedLocked(supplState, failedAuth,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiSupplicantStateChangedLocked(int supplState, boolean failedAuth,
             long elapsedRealtimeMs, long uptimeMs) {
         if (DEBUG) Log.i(TAG, "WiFi suppl state -> " + supplState);
@@ -6631,6 +6818,7 @@
         }
     }
 
+    @GuardedBy("this")
     void stopAllWifiSignalStrengthTimersLocked(int except, long elapsedRealtimeMs) {
         for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
             if (i == except) {
@@ -6642,10 +6830,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiRssiChangedLocked(int newRssi) {
         noteWifiRssiChangedLocked(newRssi, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiRssiChangedLocked(int newRssi, long elapsedRealtimeMs, long uptimeMs) {
         int strengthBin = WifiManager.calculateSignalLevel(newRssi, NUM_WIFI_SIGNAL_STRENGTH_BINS);
         if (DEBUG) Log.i(TAG, "WiFi rssi -> " + newRssi + " bin=" + strengthBin);
@@ -6674,10 +6864,12 @@
     int mWifiFullLockNesting = 0;
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void noteFullWifiLockAcquiredLocked(int uid) {
         noteFullWifiLockAcquiredLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteFullWifiLockAcquiredLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (mWifiFullLockNesting == 0) {
             mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
@@ -6691,10 +6883,12 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void noteFullWifiLockReleasedLocked(int uid) {
         noteFullWifiLockReleasedLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteFullWifiLockReleasedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         mWifiFullLockNesting--;
         if (mWifiFullLockNesting == 0) {
@@ -6709,10 +6903,12 @@
 
     int mWifiScanNesting = 0;
 
+    @GuardedBy("this")
     public void noteWifiScanStartedLocked(int uid) {
         noteWifiScanStartedLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiScanStartedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (mWifiScanNesting == 0) {
             mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG;
@@ -6725,10 +6921,12 @@
                 .noteWifiScanStartedLocked(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteWifiScanStoppedLocked(int uid) {
         noteWifiScanStoppedLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiScanStoppedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         mWifiScanNesting--;
         if (mWifiScanNesting == 0) {
@@ -6765,11 +6963,13 @@
 
     int mWifiMulticastNesting = 0;
 
+    @GuardedBy("this")
     @UnsupportedAppUsage
     public void noteWifiMulticastEnabledLocked(int uid) {
         noteWifiMulticastEnabledLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiMulticastEnabledLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mWifiMulticastNesting == 0) {
@@ -6790,10 +6990,12 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void noteWifiMulticastDisabledLocked(int uid) {
         noteWifiMulticastDisabledLocked(uid, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiMulticastDisabledLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         mWifiMulticastNesting--;
@@ -6813,11 +7015,13 @@
                 .noteWifiMulticastDisabledLocked(elapsedRealtimeMs);
     }
 
+    @GuardedBy("this")
     public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) {
         noteFullWifiLockAcquiredFromSourceLocked(ws,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws,
             long elapsedRealtimeMs, long uptimeMs) {
         int N = ws.size();
@@ -6836,11 +7040,13 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) {
         noteFullWifiLockReleasedFromSourceLocked(ws,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws,
             long elapsedRealtimeMs, long uptimeMs) {
         int N = ws.size();
@@ -6859,10 +7065,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiScanStartedFromSourceLocked(WorkSource ws) {
         noteWifiScanStartedFromSourceLocked(ws, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiScanStartedFromSourceLocked(WorkSource ws,
             long elapsedRealtimeMs, long uptimeMs) {
         int N = ws.size();
@@ -6881,10 +7089,12 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiScanStoppedFromSourceLocked(WorkSource ws) {
         noteWifiScanStoppedFromSourceLocked(ws, mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiScanStoppedFromSourceLocked(WorkSource ws,
             long elapsedRealtimeMs, long uptimeMs) {
         int N = ws.size();
@@ -6903,11 +7113,13 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiBatchedScanStartedFromSourceLocked(WorkSource ws, int csph) {
         noteWifiBatchedScanStartedFromSourceLocked(ws, csph,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
 
+    @GuardedBy("this")
     public void noteWifiBatchedScanStartedFromSourceLocked(WorkSource ws, int csph,
             long elapsedRealtimeMs, long uptimeMs) {
         int N = ws.size();
@@ -6924,10 +7136,13 @@
         }
     }
 
+    @GuardedBy("this")
     public void noteWifiBatchedScanStoppedFromSourceLocked(WorkSource ws) {
         noteWifiBatchedScanStoppedFromSourceLocked(ws,
                 mClock.elapsedRealtime(), mClock.uptimeMillis());
     }
+
+    @GuardedBy("this")
     public void noteWifiBatchedScanStoppedFromSourceLocked(WorkSource ws,
             long elapsedRealtimeMs, long uptimeMs) {
         int N = ws.size();
@@ -7454,36 +7669,43 @@
         }
     }
 
+    @GuardedBy("this")
     @Override
     public long getBluetoothMeasuredBatteryConsumptionUC() {
         return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH);
     }
 
+    @GuardedBy("this")
     @Override
     public long getCpuMeasuredBatteryConsumptionUC() {
         return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU);
     }
 
+    @GuardedBy("this")
     @Override
     public long getGnssMeasuredBatteryConsumptionUC() {
         return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS);
     }
 
+    @GuardedBy("this")
     @Override
     public long getMobileRadioMeasuredBatteryConsumptionUC() {
         return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO);
     }
 
+    @GuardedBy("this")
     @Override
     public long getScreenOnMeasuredBatteryConsumptionUC() {
         return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
     }
 
+    @GuardedBy("this")
     @Override
     public long getScreenDozeMeasuredBatteryConsumptionUC() {
         return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE);
     }
 
+    @GuardedBy("this")
     @Override
     public long getWifiMeasuredBatteryConsumptionUC() {
         return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI);
@@ -7496,6 +7718,7 @@
      * @param bucket standard power bucket of interest
      * @return charge (in microcoulombs) used for this power bucket
      */
+    @GuardedBy("this")
     private long getPowerBucketConsumptionUC(@StandardPowerBucket int bucket) {
         if (mGlobalMeasuredEnergyStats == null) {
             return POWER_DATA_UNAVAILABLE;
@@ -7503,6 +7726,7 @@
         return mGlobalMeasuredEnergyStats.getAccumulatedStandardBucketCharge(bucket);
     }
 
+    @GuardedBy("this")
     @Override
     public @Nullable long[] getCustomConsumerMeasuredBatteryConsumptionUC() {
         if (mGlobalMeasuredEnergyStats == null) {
@@ -7514,12 +7738,13 @@
     /**
      * Returns the names of custom power components.
      */
+    @GuardedBy("this")
     @Override
     public @NonNull String[] getCustomEnergyConsumerNames() {
-        if (mGlobalMeasuredEnergyStats == null) {
+        if (mMeasuredEnergyStatsConfig == null) {
             return new String[0];
         }
-        final String[] names = mGlobalMeasuredEnergyStats.getCustomBucketNames();
+        final String[] names = mMeasuredEnergyStatsConfig.getCustomBucketNames();
         for (int i = 0; i < names.length; i++) {
             if (TextUtils.isEmpty(names[i])) {
                 names[i] = "CUSTOM_" + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i;
@@ -7528,6 +7753,7 @@
         return names;
     }
 
+    @GuardedBy("this")
     @Override public long getStartClockTime() {
         final long currentTimeMs = mClock.currentTimeMillis();
         if ((currentTimeMs > MILLISECONDS_IN_YEAR
@@ -7954,11 +8180,18 @@
             mJobsFreshnessBuckets = new Counter[JOB_FRESHNESS_BUCKETS.length];
         }
 
+        @GuardedBy("mBsi")
         @VisibleForTesting
         public void setProcessStateForTest(int procState, long elapsedTimeMs) {
             mProcessState = procState;
             getProcStateTimeCounter().setState(procState, elapsedTimeMs);
             getProcStateScreenOffTimeCounter().setState(procState, elapsedTimeMs);
+            final MeasuredEnergyStats energyStats =
+                    getOrCreateMeasuredEnergyStatsIfSupportedLocked();
+            if (energyStats != null) {
+                energyStats.setState(mapUidProcessStateToBatteryConsumerProcessState(procState),
+                        elapsedTimeMs);
+            }
         }
 
         @Override
@@ -7981,6 +8214,7 @@
             return nullIfAllZeros(mCpuClusterTimesMs, STATS_SINCE_CHARGED);
         }
 
+        @GuardedBy("mBsi")
         @Override
         public long[] getCpuFreqTimes(int which, int procState) {
             if (procState < 0 || procState >= NUM_PROCESS_STATE) {
@@ -7997,6 +8231,7 @@
             return mProcStateTimeMs.getCountsLocked(which, procState);
         }
 
+        @GuardedBy("mBsi")
         @Override
         public long[] getScreenOffCpuFreqTimes(int which, int procState) {
             if (procState < 0 || procState >= NUM_PROCESS_STATE) {
@@ -8026,6 +8261,7 @@
             return mProportionalSystemServiceUsage;
         }
 
+        @GuardedBy("mBsi")
         public void addIsolatedUid(int isolatedUid) {
             if (mChildUids == null) {
                 mChildUids = new SparseArray<>();
@@ -8312,23 +8548,37 @@
             return mModemControllerActivity;
         }
 
+        @GuardedBy("mBsi")
         private MeasuredEnergyStats getOrCreateMeasuredEnergyStatsLocked() {
             if (mUidMeasuredEnergyStats == null) {
-                mUidMeasuredEnergyStats =
-                        MeasuredEnergyStats.createFromTemplate(mBsi.mGlobalMeasuredEnergyStats);
+                mUidMeasuredEnergyStats = new MeasuredEnergyStats(mBsi.mMeasuredEnergyStatsConfig);
+            }
+            return mUidMeasuredEnergyStats;
+        }
+
+        @GuardedBy("mBsi")
+        private MeasuredEnergyStats getOrCreateMeasuredEnergyStatsIfSupportedLocked() {
+            if (mUidMeasuredEnergyStats == null && mBsi.mMeasuredEnergyStatsConfig != null) {
+                mUidMeasuredEnergyStats = new MeasuredEnergyStats(mBsi.mMeasuredEnergyStatsConfig);
             }
             return mUidMeasuredEnergyStats;
         }
 
         /** Adds the given charge to the given standard power bucket for this uid. */
+        @GuardedBy("mBsi")
         private void addChargeToStandardBucketLocked(long chargeDeltaUC,
                 @StandardPowerBucket int powerBucket) {
-            getOrCreateMeasuredEnergyStatsLocked().updateStandardBucket(powerBucket, chargeDeltaUC);
+            final MeasuredEnergyStats measuredEnergyStats =
+                    getOrCreateMeasuredEnergyStatsLocked();
+            measuredEnergyStats.updateStandardBucket(powerBucket, chargeDeltaUC,
+                    mBsi.mClock.elapsedRealtime());
         }
 
         /** Adds the given charge to the given custom power bucket for this uid. */
+        @GuardedBy("mBsi")
         private void addChargeToCustomBucketLocked(long chargeDeltaUC, int powerBucket) {
-            getOrCreateMeasuredEnergyStatsLocked().updateCustomBucket(powerBucket, chargeDeltaUC);
+            getOrCreateMeasuredEnergyStatsLocked().updateCustomBucket(powerBucket, chargeDeltaUC,
+                    mBsi.mClock.elapsedRealtime());
         }
 
         /**
@@ -8337,6 +8587,7 @@
          * @param bucket standard power bucket of interest
          * @return consumption (in microcolombs) used by this uid for this power bucket
          */
+        @GuardedBy("mBsi")
         public long getMeasuredBatteryConsumptionUC(@StandardPowerBucket int bucket) {
             if (mBsi.mGlobalMeasuredEnergyStats == null
                     || !mBsi.mGlobalMeasuredEnergyStats.isStandardBucketSupported(bucket)) {
@@ -8348,6 +8599,7 @@
             return mUidMeasuredEnergyStats.getAccumulatedStandardBucketCharge(bucket);
         }
 
+        @GuardedBy("mBsi")
         @Override
         public long[] getCustomConsumerMeasuredBatteryConsumptionUC() {
             if (mBsi.mGlobalMeasuredEnergyStats == null) {
@@ -8360,31 +8612,37 @@
             return mUidMeasuredEnergyStats.getAccumulatedCustomBucketCharges();
         }
 
+        @GuardedBy("mBsi")
         @Override
         public long getBluetoothMeasuredBatteryConsumptionUC() {
             return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH);
         }
 
+        @GuardedBy("mBsi")
         @Override
         public long getCpuMeasuredBatteryConsumptionUC() {
             return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU);
         }
 
+        @GuardedBy("mBsi")
         @Override
         public long getGnssMeasuredBatteryConsumptionUC() {
             return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS);
         }
 
+        @GuardedBy("mBsi")
         @Override
         public long getMobileRadioMeasuredBatteryConsumptionUC() {
             return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO);
         }
 
+        @GuardedBy("mBsi")
         @Override
         public long getScreenOnMeasuredBatteryConsumptionUC() {
             return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
         }
 
+        @GuardedBy("mBsi")
         @Override
         public long getWifiMeasuredBatteryConsumptionUC() {
             return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI);
@@ -9678,6 +9936,7 @@
             }
         }
 
+        @GuardedBy("mBsi")
         void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
             mOnBatteryBackgroundTimeBase.readFromParcel(in);
             mOnBatteryScreenOffBackgroundTimeBase.readFromParcel(in);
@@ -9917,7 +10176,8 @@
             }
 
             if (in.readInt() != 0) {
-                mUidMeasuredEnergyStats = new MeasuredEnergyStats(in);
+                mUidMeasuredEnergyStats = new MeasuredEnergyStats(mBsi.mMeasuredEnergyStatsConfig,
+                        in);
             }
 
             mUserCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
@@ -10801,6 +11061,7 @@
                     mBsi.mClock.elapsedRealtime(), mBsi.mClock.uptimeMillis());
         }
 
+        @GuardedBy("mBsi")
         public void updateUidProcessStateLocked(int procState,
                 long elapsedRealtimeMs, long uptimeMs) {
             int uidRunningState;
@@ -10845,6 +11106,14 @@
 
                 updateOnBatteryBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
                 updateOnBatteryScreenOffBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
+
+                final MeasuredEnergyStats energyStats =
+                        getOrCreateMeasuredEnergyStatsIfSupportedLocked();
+                if (energyStats != null) {
+                    energyStats.setState(
+                            mapUidProcessStateToBatteryConsumerProcessState(mProcessState),
+                            elapsedRealtimeMs);
+                }
             }
 
             if (userAwareService != mInForegroundService) {
@@ -11485,6 +11754,7 @@
         }
     }
 
+    @GuardedBy("this")
     public void readDailyStatsLocked() {
         Slog.d(TAG, "Reading daily items from " + mDailyFile.getBaseFile());
         mDailyItems.clear();
@@ -11659,6 +11929,7 @@
         return mNextMaxDailyDeadlineMs;
     }
 
+    @GuardedBy("this")
     public int getHistoryTotalSize() {
         return mConstants.MAX_HISTORY_BUFFER * mConstants.MAX_HISTORY_FILES;
     }
@@ -11779,6 +12050,7 @@
         mBatteryResetListener = batteryResetListener;
     }
 
+    @GuardedBy("this")
     public void resetAllStatsCmdLocked() {
         final long mSecUptime = mClock.uptimeMillis();
         long uptimeUs = mSecUptime * 1000;
@@ -11813,6 +12085,7 @@
         initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
     }
 
+    @GuardedBy("this")
     private void resetAllStatsLocked(long uptimeMillis, long elapsedRealtimeMillis,
             int resetReason) {
         if (mBatteryResetListener != null) {
@@ -11971,6 +12244,7 @@
         mHandler.sendEmptyMessage(MSG_REPORT_RESET_STATS);
     }
 
+    @GuardedBy("this")
     private void initActiveHistoryEventsLocked(long elapsedRealtimeMs, long uptimeMs) {
         for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
             if (!mRecordAllHistory && i == HistoryItem.EVENT_PROC) {
@@ -11991,11 +12265,13 @@
         }
     }
 
+    @GuardedBy("this")
     void updateDischargeScreenLevelsLocked(int oldState, int newState) {
         updateOldDischargeScreenLevelLocked(oldState);
         updateNewDischargeScreenLevelLocked(newState);
     }
 
+    @GuardedBy("this")
     private void updateOldDischargeScreenLevelLocked(int state) {
         if (Display.isOnState(state)) {
             int diff = mDischargeScreenOnUnplugLevel - mDischargeCurrentLevel;
@@ -12018,6 +12294,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void updateNewDischargeScreenLevelLocked(int state) {
         if (Display.isOnState(state)) {
             mDischargeScreenOnUnplugLevel = mDischargeCurrentLevel;
@@ -12034,6 +12311,7 @@
         }
     }
 
+    @GuardedBy("this")
     public void pullPendingStateUpdatesLocked() {
         if (mOnBatteryInternal) {
             updateDischargeScreenLevelsLocked(mScreenState, mScreenState);
@@ -12080,10 +12358,13 @@
      * Distribute WiFi energy info and network traffic to apps.
      * @param info The energy information from the WiFi controller.
      */
+    @GuardedBy("this")
     public void updateWifiState(@Nullable final WifiActivityEnergyInfo info,
             final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) {
         if (DEBUG_ENERGY) {
-            Slog.d(TAG, "Updating wifi stats: " + Arrays.toString(mWifiIfaces));
+            synchronized (mWifiNetworkLock) {
+                Slog.d(TAG, "Updating wifi stats: " + Arrays.toString(mWifiIfaces));
+            }
         }
 
         // Grab a separate lock to acquire the network stats, which may do I/O.
@@ -12714,7 +12995,7 @@
             rxTimeMs = info.getControllerRxTimeMillis();
             txTimeMs = info.getControllerTxTimeMillis();
             energy = info.getControllerEnergyUsed();
-            if (info.getUidTraffic() != null) {
+            if (!info.getUidTraffic().isEmpty()) {
                 for (UidTraffic traffic : info.getUidTraffic()) {
                     uidRxBytes.put(traffic.getUid(), traffic.getRxBytes());
                     uidTxBytes.put(traffic.getUid(), traffic.getTxBytes());
@@ -12740,6 +13021,7 @@
      *
      * @param info The accumulated energy information from the bluetooth controller.
      */
+    @GuardedBy("this")
     public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info,
             final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) {
         if (DEBUG_ENERGY) {
@@ -12865,10 +13147,10 @@
         long totalTxBytes = 0;
         long totalRxBytes = 0;
 
-        final UidTraffic[] uidTraffic = info.getUidTraffic();
-        final int numUids = uidTraffic != null ? uidTraffic.length : 0;
+        final List<UidTraffic> uidTraffic = info.getUidTraffic();
+        final int numUids = uidTraffic.size();
         for (int i = 0; i < numUids; i++) {
-            final UidTraffic traffic = uidTraffic[i];
+            final UidTraffic traffic = uidTraffic.get(i);
             final long rxBytes = traffic.getRxBytes() - mLastBluetoothActivityInfo.uidRxBytes.get(
                     traffic.getUid());
             final long txBytes = traffic.getTxBytes() - mLastBluetoothActivityInfo.uidTxBytes.get(
@@ -12891,7 +13173,7 @@
         if ((totalTxBytes != 0 || totalRxBytes != 0) && (leftOverRxTimeMs != 0
                 || leftOverTxTimeMs != 0)) {
             for (int i = 0; i < numUids; i++) {
-                final UidTraffic traffic = uidTraffic[i];
+                final UidTraffic traffic = uidTraffic.get(i);
                 final int uid = traffic.getUid();
                 final long rxBytes =
                         traffic.getRxBytes() - mLastBluetoothActivityInfo.uidRxBytes.get(uid);
@@ -13039,6 +13321,7 @@
      *                    clusterChargeUC against.
      */
     @GuardedBy("this")
+    @SuppressWarnings("GuardedBy") // errorprone false positive on u.addChargeToStandardBucketLocked
     private void updateCpuMeasuredEnergyStatsLocked(@NonNull long[] clusterChargeUC,
             @NonNull CpuDeltaPowerAccumulator accumulator) {
         if (DEBUG_ENERGY) {
@@ -13057,7 +13340,7 @@
         if (totalCpuChargeUC <= 0) return;
 
         mGlobalMeasuredEnergyStats.updateStandardBucket(MeasuredEnergyStats.POWER_BUCKET_CPU,
-                totalCpuChargeUC);
+                totalCpuChargeUC, mClock.elapsedRealtime());
 
         // Calculate the measured microcoulombs/calculated milliamp-hour charge ratio for each
         // cluster to normalize  each uid's estimated power usage against actual power usage for
@@ -13261,6 +13544,8 @@
      *                    Data inside uidCharges will not be modified (treated immutable).
      *                    Uids not already known to BatteryStats will be ignored.
      */
+    @GuardedBy("this")
+    @SuppressWarnings("GuardedBy") // errorprone false positive on u.addChargeToCustomBucketLocked
     public void updateCustomMeasuredEnergyStatsLocked(int customPowerBucket,
             long totalChargeUC, @Nullable SparseLongArray uidCharges) {
         if (DEBUG_ENERGY) {
@@ -13272,7 +13557,8 @@
         if (mGlobalMeasuredEnergyStats == null) return;
         if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalChargeUC <= 0) return;
 
-        mGlobalMeasuredEnergyStats.updateCustomBucket(customPowerBucket, totalChargeUC);
+        mGlobalMeasuredEnergyStats.updateCustomBucket(customPowerBucket, totalChargeUC,
+                mClock.elapsedRealtime());
 
         if (uidCharges == null) return;
         final int numUids = uidCharges.size();
@@ -13280,6 +13566,7 @@
             final int uidInt = mapUid(uidCharges.keyAt(i));
             final long uidChargeUC = uidCharges.valueAt(i);
             if (uidChargeUC == 0) continue;
+
             final Uid uidObj = getAvailableUidStatsLocked(uidInt);
             if (uidObj != null) {
                 uidObj.addChargeToCustomBucketLocked(uidChargeUC, customPowerBucket);
@@ -13314,6 +13601,8 @@
      *
      * <p>All uids in ratioNumerators must exist in mUidStats already.
      */
+    @GuardedBy("this")
+    @SuppressWarnings("GuardedBy") // errorprone false positive on u.addChargeToStandardBucketLocked
     private void distributeEnergyToUidsLocked(@StandardPowerBucket int bucket,
             long totalConsumedChargeUC, SparseDoubleArray ratioNumerators,
             double minRatioDenominator) {
@@ -14256,6 +14545,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
             boolean reset) {
         mRecordingHistory = true;
@@ -14269,6 +14559,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void recordCurrentTimeChangeLocked(final long currentTimeMs,
             final long elapsedRealtimeMs, final long uptimeMs) {
         if (mRecordingHistory) {
@@ -14278,6 +14569,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void recordShutdownLocked(final long currentTimeMs, final long elapsedRealtimeMs) {
         if (mRecordingHistory) {
             mHistoryCur.currentTime = currentTimeMs;
@@ -14304,6 +14596,7 @@
                 mClock.elapsedRealtime(), mClock.uptimeMillis(), mClock.currentTimeMillis());
     }
 
+    @GuardedBy("this")
     public void setBatteryStateLocked(final int status, final int health, final int plugType,
             final int level, /* not final */ int temp, final int voltageMv, final int chargeUah,
             final int chargeFullUah, final long chargeTimeToFullSeconds,
@@ -14987,6 +15280,7 @@
         return u;
     }
 
+    @GuardedBy("this")
     public void onCleanupUserLocked(int userId, long elapsedRealtimeMs) {
         final int firstUidForUser = UserHandle.getUid(userId, 0);
         final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
@@ -14994,6 +15288,7 @@
                 new UidToRemove(firstUidForUser, lastUidForUser, elapsedRealtimeMs));
     }
 
+    @GuardedBy("this")
     public void onUserRemovedLocked(int userId) {
         if (mExternalSync != null) {
             // Clear out the removed user's UIDs after a short delay. The delay is needed
@@ -15006,6 +15301,7 @@
     /**
      * Removes battery stats for UIDs corresponding to a removed user.
      */
+    @GuardedBy("this")
     public void clearRemovedUserUidsLocked(int userId) {
         final int firstUidForUser = UserHandle.getUid(userId, 0);
         final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
@@ -15027,6 +15323,7 @@
      * Remove the statistics object for a particular uid.
      */
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void removeUidStatsLocked(int uid) {
         removeUidStatsLocked(uid, mClock.elapsedRealtime());
     }
@@ -15034,6 +15331,7 @@
     /**
      * @see #removeUidStatsLocked(int)
      */
+    @GuardedBy("this")
     public void removeUidStatsLocked(int uid, long elapsedRealtimeMs) {
         final Uid u = mUidStats.get(uid);
         if (u != null) {
@@ -15131,21 +15429,25 @@
         return u.getServiceStatsLocked(pkg, name);
     }
 
+    @GuardedBy("this")
     public void shutdownLocked() {
         recordShutdownLocked(mClock.currentTimeMillis(), mClock.elapsedRealtime());
         writeSyncLocked();
         mShuttingDown = true;
     }
 
+    @GuardedBy("this")
     @Override
     public boolean isProcessStateDataAvailable() {
         return trackPerProcStateCpuTimes();
     }
 
+    @GuardedBy("this")
     public boolean trackPerProcStateCpuTimes() {
         return mConstants.TRACK_CPU_TIMES_BY_PROC_STATE && mPerProcStateCpuTimesAvailable;
     }
 
+    @GuardedBy("this")
     public void systemServicesReady(Context context) {
         mConstants.startObserving(context.getContentResolver());
         registerUsbStateReceiver(context);
@@ -15162,28 +15464,28 @@
     @GuardedBy("this")
     public void initMeasuredEnergyStatsLocked(@Nullable boolean[] supportedStandardBuckets,
             String[] customBucketNames) {
-        boolean supportedBucketMismatch = false;
-
         final int numDisplays = mPerDisplayBatteryStats.length;
         for (int i = 0; i < numDisplays; i++) {
             final int screenState = mPerDisplayBatteryStats[i].screenState;
             mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement = screenState;
         }
 
-        if (supportedStandardBuckets == null) {
-            if (mGlobalMeasuredEnergyStats != null) {
-                // Measured energy no longer supported, wipe out the existing data.
-                supportedBucketMismatch = true;
-            }
-        } else {
-            if (mGlobalMeasuredEnergyStats == null) {
-                mGlobalMeasuredEnergyStats =
-                        new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
+        final boolean compatibleConfig;
+        if (supportedStandardBuckets != null) {
+            final MeasuredEnergyStats.Config config = new MeasuredEnergyStats.Config(
+                    supportedStandardBuckets, customBucketNames,
+                    SUPPORTED_PER_PROCESS_STATE_STANDARD_ENERGY_BUCKETS,
+                    getBatteryConsumerProcessStateNames());
+
+            if (mMeasuredEnergyStatsConfig == null) {
+                compatibleConfig = true;
             } else {
-                supportedBucketMismatch = !mGlobalMeasuredEnergyStats.isSupportEqualTo(
-                        supportedStandardBuckets, customBucketNames);
+                compatibleConfig = mMeasuredEnergyStatsConfig.isCompatible(config);
             }
 
+            mMeasuredEnergyStatsConfig = config;
+            mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(config);
+
             if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH]) {
                 mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
             }
@@ -15196,11 +15498,14 @@
             if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_WIFI]) {
                 mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile);
             }
+        } else {
+            compatibleConfig = (mMeasuredEnergyStatsConfig == null);
+            // Measured energy no longer supported, wipe out the existing data.
+            mMeasuredEnergyStatsConfig = null;
+            mGlobalMeasuredEnergyStats = null;
         }
 
-        if (supportedBucketMismatch) {
-            mGlobalMeasuredEnergyStats = supportedStandardBuckets == null
-                    ? null : new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
+        if (!compatibleConfig) {
             // Supported power buckets changed since last boot.
             // Existing data is no longer reliable.
             resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
@@ -15208,6 +15513,16 @@
         }
     }
 
+    @NonNull
+    private static String[] getBatteryConsumerProcessStateNames() {
+        String[] procStateNames = new String[BatteryConsumer.PROCESS_STATE_COUNT];
+        for (int procState = 0; procState < BatteryConsumer.PROCESS_STATE_COUNT; procState++) {
+            procStateNames[procState] = BatteryConsumer.processStateToString(procState);
+        }
+        procStateNames[BatteryConsumer.PROCESS_STATE_ANY] = "untracked";
+        return procStateNames;
+    }
+
     /** Get the last known Battery voltage (in millivolts), returns -1 if unknown */
     @GuardedBy("this")
     public int getBatteryVoltageMvLocked() {
@@ -15373,6 +15688,7 @@
             }
         }
 
+        @GuardedBy("BatteryStatsImpl.this")
         private void updateProcStateCpuTimesReadDelayMs(long oldDelayMillis, long newDelayMillis) {
             PROC_STATE_CPU_TIMES_READ_DELAY_MS = newDelayMillis;
             if (oldDelayMillis != newDelayMillis) {
@@ -15393,6 +15709,7 @@
             }
         }
 
+        @GuardedBy("BatteryStatsImpl.this")
         private void updateUidRemoveDelay(long newTimeMs) {
             UID_REMOVE_DELAY_MS = newTimeMs;
             clearPendingRemovedUidsLocked();
@@ -15530,16 +15847,19 @@
 
     final ReentrantLock mWriteLock = new ReentrantLock();
 
+    @GuardedBy("this")
     public void writeAsyncLocked() {
         writeStatsLocked(false);
         writeHistoryLocked(false);
     }
 
+    @GuardedBy("this")
     public void writeSyncLocked() {
         writeStatsLocked(true);
         writeHistoryLocked(true);
     }
 
+    @GuardedBy("this")
     void writeStatsLocked(boolean sync) {
         if (mStatsFile == null) {
             Slog.w(TAG,
@@ -15621,6 +15941,7 @@
     }
 
     @UnsupportedAppUsage
+    @GuardedBy("this")
     public void readLocked() {
         if (mDailyFile != null) {
             readDailyStatsLocked();
@@ -15705,6 +16026,7 @@
         return 0;
     }
 
+    @GuardedBy("this")
     void  readHistoryBuffer(Parcel in) throws ParcelFormatException {
         final int version = in.readInt();
         if (version != VERSION) {
@@ -15784,6 +16106,7 @@
         out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
     }
 
+    @GuardedBy("this")
     public void readSummaryFromParcel(Parcel in) throws ParcelFormatException {
         final int version = in.readInt();
         if (version != VERSION) {
@@ -15870,11 +16193,14 @@
         mNextMaxDailyDeadlineMs = in.readLong();
         mBatteryTimeToFullSeconds = in.readLong();
 
+        mMeasuredEnergyStatsConfig = MeasuredEnergyStats.Config.createFromParcel(in);
+
         /**
          * WARNING: Supported buckets may have changed across boots. Bucket mismatch is handled
          *          later when {@link #initMeasuredEnergyStatsLocked} is called.
          */
-        mGlobalMeasuredEnergyStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(in);
+        mGlobalMeasuredEnergyStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(
+                mMeasuredEnergyStatsConfig, in);
 
         mStartCount++;
 
@@ -16191,8 +16517,8 @@
                 u.mWifiRadioApWakeupCount = null;
             }
 
-            u.mUidMeasuredEnergyStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(in,
-                    /* template */ mGlobalMeasuredEnergyStats);
+            u.mUidMeasuredEnergyStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(
+                    mMeasuredEnergyStatsConfig, in);
 
             int NW = in.readInt();
             if (NW > (MAX_WAKELOCKS_PER_UID+1)) {
@@ -16304,6 +16630,7 @@
      *
      * @param out the Parcel to be written to.
      */
+    @GuardedBy("this")
     public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
         pullPendingStateUpdatesLocked();
 
@@ -16377,7 +16704,8 @@
         out.writeLong(mNextMaxDailyDeadlineMs);
         out.writeLong(mBatteryTimeToFullSeconds);
 
-        MeasuredEnergyStats.writeSummaryToParcel(mGlobalMeasuredEnergyStats, out, false, false);
+        MeasuredEnergyStats.Config.writeToParcel(mMeasuredEnergyStatsConfig, out);
+        MeasuredEnergyStats.writeSummaryToParcel(mGlobalMeasuredEnergyStats, out);
 
         mScreenOnTimer.writeSummaryFromParcelLocked(out, nowRealtime);
         mScreenDozeTimer.writeSummaryFromParcelLocked(out, nowRealtime);
@@ -16700,7 +17028,7 @@
                 out.writeInt(0);
             }
 
-            MeasuredEnergyStats.writeSummaryToParcel(u.mUidMeasuredEnergyStats, out, true, true);
+            MeasuredEnergyStats.writeSummaryToParcel(u.mUidMeasuredEnergyStats, out);
 
             final ArrayMap<String, Uid.Wakelock> wakeStats = u.mWakelockStats.getMap();
             int NW = wakeStats.size();
@@ -16822,10 +17150,13 @@
         LongSamplingCounterArray.writeSummaryToParcelLocked(out, mBinderThreadCpuTimesUs);
     }
 
+    @GuardedBy("this")
     public void readFromParcel(Parcel in) {
         readFromParcelLocked(in);
     }
 
+    @GuardedBy("this")
+    @SuppressWarnings("GuardedBy")  // errorprone false positive on u.readFromParcelLocked
     void readFromParcelLocked(Parcel in) {
         int magic = in.readInt();
         if (magic != MAGIC) {
@@ -16966,9 +17297,9 @@
         mLastWriteTimeMs = in.readLong();
         mBatteryTimeToFullSeconds = in.readLong();
 
-        if (in.readInt() != 0) {
-            mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(in);
-        }
+        mMeasuredEnergyStatsConfig = MeasuredEnergyStats.Config.createFromParcel(in);
+        mGlobalMeasuredEnergyStats =
+                MeasuredEnergyStats.createFromParcel(mMeasuredEnergyStatsConfig, in);
 
         mRpmStats.clear();
         int NRPMS = in.readInt();
@@ -17039,22 +17370,26 @@
         for (int i = 0; i < numUids; i++) {
             int uid = in.readInt();
             Uid u = new Uid(this, uid, elapsedRealtimeMs, uptimeMs);
-            u.readFromParcelLocked(mOnBatteryTimeBase, mOnBatteryScreenOffTimeBase, in);
+            u.readFromParcelLocked(mOnBatteryTimeBase, mOnBatteryScreenOffTimeBase,
+                    in);
             mUidStats.append(uid, u);
         }
 
         mBinderThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
     }
 
+    @GuardedBy("this")
     public void writeToParcel(Parcel out, int flags) {
         writeToParcelLocked(out, true, flags);
     }
 
+    @GuardedBy("this")
     public void writeToParcelWithoutUids(Parcel out, int flags) {
         writeToParcelLocked(out, false, flags);
     }
 
     @SuppressWarnings("unused")
+    @GuardedBy("this")
     void writeToParcelLocked(Parcel out, boolean inclUids, int flags) {
         // Need to update with current kernel wake lock counts.
         pullPendingStateUpdatesLocked();
@@ -17170,6 +17505,8 @@
         out.writeLong(mLastWriteTimeMs);
         out.writeLong(mBatteryTimeToFullSeconds);
 
+        MeasuredEnergyStats.Config.writeToParcel(mMeasuredEnergyStatsConfig, out);
+
         if (mGlobalMeasuredEnergyStats != null) {
             out.writeInt(1);
             mGlobalMeasuredEnergyStats.writeToParcel(out);
@@ -17332,6 +17669,7 @@
         }
     };
 
+    @GuardedBy("this")
     public void prepareForDumpLocked() {
         // Need to retrieve current kernel wake lock stats before printing.
         pullPendingStateUpdatesLocked();
@@ -17343,6 +17681,7 @@
         updateSystemServiceCallStats();
     }
 
+    @GuardedBy("this")
     public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
         if (DEBUG) {
             pw.println("mOnBatteryTimeBase:");
diff --git a/core/java/com/android/internal/os/ClassLoaderFactory.java b/core/java/com/android/internal/os/ClassLoaderFactory.java
index d347f2e..8b0411d 100644
--- a/core/java/com/android/internal/os/ClassLoaderFactory.java
+++ b/core/java/com/android/internal/os/ClassLoaderFactory.java
@@ -80,15 +80,20 @@
      */
     public static ClassLoader createClassLoader(String dexPath,
             String librarySearchPath, ClassLoader parent, String classloaderName,
-            List<ClassLoader> sharedLibraries) {
+            List<ClassLoader> sharedLibraries, List<ClassLoader> sharedLibrariesLoadedAfter) {
         ClassLoader[] arrayOfSharedLibraries = (sharedLibraries == null)
                 ? null
                 : sharedLibraries.toArray(new ClassLoader[sharedLibraries.size()]);
+        ClassLoader[] arrayOfSharedLibrariesLoadedAfterApp = (sharedLibrariesLoadedAfter == null)
+                ? null
+                : sharedLibrariesLoadedAfter.toArray(
+                        new ClassLoader[sharedLibrariesLoadedAfter.size()]);
         if (isPathClassLoaderName(classloaderName)) {
-            return new PathClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries);
+            return new PathClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries,
+                    arrayOfSharedLibrariesLoadedAfterApp);
         } else if (isDelegateLastClassLoaderName(classloaderName)) {
             return new DelegateLastClassLoader(dexPath, librarySearchPath, parent,
-                    arrayOfSharedLibraries);
+                    arrayOfSharedLibraries, arrayOfSharedLibrariesLoadedAfterApp);
         }
 
         throw new AssertionError("Invalid classLoaderName: " + classloaderName);
@@ -102,20 +107,20 @@
             String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
             int targetSdkVersion, boolean isNamespaceShared, String classLoaderName) {
         return createClassLoader(dexPath, librarySearchPath, libraryPermittedPath,
-            parent, targetSdkVersion, isNamespaceShared, classLoaderName, null, null);
+            parent, targetSdkVersion, isNamespaceShared, classLoaderName, null, null, null);
     }
 
-
     /**
      * Create a ClassLoader and initialize a linker-namespace for it.
      */
     public static ClassLoader createClassLoader(String dexPath,
             String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
             int targetSdkVersion, boolean isNamespaceShared, String classLoaderName,
-            List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries) {
+            List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
+            List<ClassLoader> sharedLibrariesAfter) {
 
         final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
-                classLoaderName, sharedLibraries);
+                classLoaderName, sharedLibraries, sharedLibrariesAfter);
 
         String sonameList = "";
         if (nativeSharedLibraries != null) {
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index 72b57ab..0723766 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -29,6 +29,7 @@
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.LongMultiStateCounter;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -75,6 +76,147 @@
     public @interface StandardPowerBucket {
     }
 
+    private static final int INVALID_STATE = -1;
+
+    /**
+     * Configuration of measured energy stats: which power rails (buckets)  are supported on
+     * this device, what custom power drains are supported etc.
+     */
+    public static class Config {
+        private final boolean[] mSupportedStandardBuckets;
+        @NonNull
+        private final String[] mCustomBucketNames;
+        private final boolean[] mSupportedMultiStateBuckets;
+        @NonNull
+        private final String[] mStateNames;
+
+        public Config(@NonNull boolean[] supportedStandardBuckets,
+                @Nullable String[] customBucketNames,
+                @NonNull int[] supportedMultiStateBuckets,
+                @Nullable String[] stateNames) {
+            mSupportedStandardBuckets = supportedStandardBuckets;
+            mCustomBucketNames = customBucketNames != null ? customBucketNames : new String[0];
+            mSupportedMultiStateBuckets =
+                    new boolean[supportedStandardBuckets.length + mCustomBucketNames.length];
+            for (int bucket : supportedMultiStateBuckets) {
+                if (mSupportedStandardBuckets[bucket]) {
+                    mSupportedMultiStateBuckets[bucket] = true;
+                }
+            }
+            mStateNames = stateNames != null ? stateNames : new String[] {""};
+        }
+
+        /**
+         * Returns true if the supplied Config is compatible with this one and therefore
+         * data collected with one of them will work with the other.
+         */
+        public boolean isCompatible(Config other) {
+            return Arrays.equals(mSupportedStandardBuckets, other.mSupportedStandardBuckets)
+                    && Arrays.equals(mCustomBucketNames, other.mCustomBucketNames)
+                    && Arrays.equals(mSupportedMultiStateBuckets,
+                    other.mSupportedMultiStateBuckets)
+                    && Arrays.equals(mStateNames, other.mStateNames);
+        }
+
+        /**
+         * Writes the Config object into the supplied Parcel.
+         */
+        public static void writeToParcel(@Nullable Config config, Parcel out) {
+            if (config == null) {
+                out.writeBoolean(false);
+                return;
+            }
+
+            out.writeBoolean(true);
+            out.writeInt(config.mSupportedStandardBuckets.length);
+            out.writeBooleanArray(config.mSupportedStandardBuckets);
+            out.writeStringArray(config.mCustomBucketNames);
+            int multiStateBucketCount = 0;
+            for (boolean supported : config.mSupportedMultiStateBuckets) {
+                if (supported) {
+                    multiStateBucketCount++;
+                }
+            }
+            final int[] supportedMultiStateBuckets = new int[multiStateBucketCount];
+            int index = 0;
+            for (int bucket = 0; bucket < config.mSupportedMultiStateBuckets.length; bucket++) {
+                if (config.mSupportedMultiStateBuckets[bucket]) {
+                    supportedMultiStateBuckets[index++] = bucket;
+                }
+            }
+            out.writeInt(multiStateBucketCount);
+            out.writeIntArray(supportedMultiStateBuckets);
+            out.writeStringArray(config.mStateNames);
+        }
+
+        /**
+         * Reads a Config object from the supplied Parcel.
+         */
+        @Nullable
+        public static Config createFromParcel(Parcel in) {
+            if (!in.readBoolean()) {
+                return null;
+            }
+
+            final int supportedStandardBucketCount = in.readInt();
+            final boolean[] supportedStandardBuckets = new boolean[supportedStandardBucketCount];
+            in.readBooleanArray(supportedStandardBuckets);
+            final String[] customBucketNames = in.readStringArray();
+            final int supportedMultiStateBucketCount = in.readInt();
+            final int[] supportedMultiStateBuckets = new int[supportedMultiStateBucketCount];
+            in.readIntArray(supportedMultiStateBuckets);
+            final String[] stateNames = in.readStringArray();
+            return new Config(supportedStandardBuckets, customBucketNames,
+                    supportedMultiStateBuckets, stateNames);
+        }
+
+        /** Get number of possible buckets, including both standard and custom ones. */
+        private int getNumberOfBuckets() {
+            return mSupportedStandardBuckets.length + mCustomBucketNames.length;
+        }
+
+        /**
+         * Returns true if the specified charge bucket is tracked.
+         */
+        public boolean isSupportedBucket(int index) {
+            return mSupportedStandardBuckets[index];
+        }
+
+        @NonNull
+        public String[] getCustomBucketNames() {
+            return mCustomBucketNames;
+        }
+
+        /**
+         * Returns true if the specified charge bucket is tracked on a per-state basis.
+         */
+        public boolean isSupportedMultiStateBucket(int index) {
+            return mSupportedMultiStateBuckets[index];
+        }
+
+        public String[] getStateNames() {
+            return mStateNames;
+        }
+
+        /**
+         * If the index is a standard bucket, returns its name; otherwise returns its prefixed
+         * custom bucket number.
+         */
+        private String getBucketName(int index) {
+            if (isValidStandardBucket(index)) {
+                return DebugUtils.valueToString(MeasuredEnergyStats.class, "POWER_BUCKET_", index);
+            }
+            final int customBucket = indexToCustomBucket(index);
+            StringBuilder name = new StringBuilder().append("CUSTOM_").append(customBucket);
+            if (!TextUtils.isEmpty(mCustomBucketNames[customBucket])) {
+                name.append('(').append(mCustomBucketNames[customBucket]).append(')');
+            }
+            return name.toString();
+        }
+    }
+
+    private final Config mConfig;
+
     /**
      * Total charge (in microcoulombs) that a power bucket (including both
      * {@link StandardPowerBucket} and custom buckets) has accumulated since the last reset.
@@ -90,74 +232,76 @@
      */
     private final long[] mAccumulatedChargeMicroCoulomb;
 
-    private final String[] mCustomBucketNames;
+    private LongMultiStateCounter[] mAccumulatedMultiStateChargeMicroCoulomb;
+    private int mState = INVALID_STATE;
+    private long mStateChangeTimestampMs;
 
     /**
      * Creates a MeasuredEnergyStats set to support the provided power buckets.
      * supportedStandardBuckets must be of size {@link #NUMBER_STANDARD_POWER_BUCKETS}.
      * numCustomBuckets >= 0 is the number of (non-standard) custom power buckets on the device.
      */
-    public MeasuredEnergyStats(@NonNull boolean[] supportedStandardBuckets,
-            @Nullable String[] customBucketNames) {
-        mCustomBucketNames = customBucketNames == null ? new String[0] : customBucketNames;
-        final int numTotalBuckets = NUMBER_STANDARD_POWER_BUCKETS + mCustomBucketNames.length;
+    public MeasuredEnergyStats(MeasuredEnergyStats.Config config) {
+        mConfig = config;
+        final int numTotalBuckets = config.getNumberOfBuckets();
         mAccumulatedChargeMicroCoulomb = new long[numTotalBuckets];
         // Initialize to all zeros where supported, otherwise POWER_DATA_UNAVAILABLE.
         // All custom buckets are, by definition, supported, so their values stay at 0.
         for (int stdBucket = 0; stdBucket < NUMBER_STANDARD_POWER_BUCKETS; stdBucket++) {
-            if (!supportedStandardBuckets[stdBucket]) {
+            if (!mConfig.mSupportedStandardBuckets[stdBucket]) {
                 mAccumulatedChargeMicroCoulomb[stdBucket] = POWER_DATA_UNAVAILABLE;
             }
         }
     }
 
     /**
-     * Creates a new zero'd MeasuredEnergyStats, using the template to determine which buckets are
-     * supported. This certainly does NOT produce an exact clone of the template.
+     * Reads a MeasuredEnergyStats from the supplied Parcel.
      */
-    private MeasuredEnergyStats(MeasuredEnergyStats template) {
-        final int numIndices = template.getNumberOfIndices();
-        mAccumulatedChargeMicroCoulomb = new long[numIndices];
-        // Initialize to all zeros where supported, otherwise POWER_DATA_UNAVAILABLE.
-        // All custom buckets are, by definition, supported, so their values stay at 0.
-        for (int stdBucket = 0; stdBucket < NUMBER_STANDARD_POWER_BUCKETS; stdBucket++) {
-            if (!template.isIndexSupported(stdBucket)) {
-                mAccumulatedChargeMicroCoulomb[stdBucket] = POWER_DATA_UNAVAILABLE;
-            }
+    @Nullable
+    public static MeasuredEnergyStats createFromParcel(Config config, Parcel in) {
+        if (!in.readBoolean()) {
+            return null;
         }
-        mCustomBucketNames = template.getCustomBucketNames();
-    }
-
-    /**
-     * Creates a new zero'd MeasuredEnergyStats, using the template to determine which buckets are
-     * supported.
-     */
-    public static MeasuredEnergyStats createFromTemplate(MeasuredEnergyStats template) {
-        return new MeasuredEnergyStats(template);
-    }
-
-    /**
-     * Constructor for creating a temp MeasuredEnergyStats.
-     * See {@link #createAndReadSummaryFromParcel(Parcel, MeasuredEnergyStats)}.
-     */
-    private MeasuredEnergyStats(int numIndices) {
-        mAccumulatedChargeMicroCoulomb = new long[numIndices];
-        mCustomBucketNames = new String[numIndices - NUMBER_STANDARD_POWER_BUCKETS];
+        return new MeasuredEnergyStats(config, in);
     }
 
     /** Construct from parcel. */
-    public MeasuredEnergyStats(Parcel in) {
+    public MeasuredEnergyStats(MeasuredEnergyStats.Config config, Parcel in) {
+        mConfig = config;
+
         final int size = in.readInt();
         mAccumulatedChargeMicroCoulomb = new long[size];
         in.readLongArray(mAccumulatedChargeMicroCoulomb);
-        mCustomBucketNames = in.readStringArray();
+        if (in.readBoolean()) {
+            mAccumulatedMultiStateChargeMicroCoulomb = new LongMultiStateCounter[size];
+            for (int i = 0; i < size; i++) {
+                if (in.readBoolean()) {
+                    mAccumulatedMultiStateChargeMicroCoulomb[i] =
+                            LongMultiStateCounter.CREATOR.createFromParcel(in);
+                }
+            }
+        } else {
+            mAccumulatedMultiStateChargeMicroCoulomb = null;
+        }
     }
 
     /** Write to parcel */
     public void writeToParcel(Parcel out) {
         out.writeInt(mAccumulatedChargeMicroCoulomb.length);
         out.writeLongArray(mAccumulatedChargeMicroCoulomb);
-        out.writeStringArray(mCustomBucketNames);
+        if (mAccumulatedMultiStateChargeMicroCoulomb != null) {
+            out.writeBoolean(true);
+            for (LongMultiStateCounter counter : mAccumulatedMultiStateChargeMicroCoulomb) {
+                if (counter != null) {
+                    out.writeBoolean(true);
+                    counter.writeToParcel(out, 0);
+                } else {
+                    out.writeBoolean(false);
+                }
+            }
+        } else {
+            out.writeBoolean(false);
+        }
     }
 
     /**
@@ -167,17 +311,27 @@
      * Note: {@link com.android.internal.os.BatteryStatsImpl#VERSION} must be updated if summary
      *       parceling changes.
      *
-     * Corresponding write performed by {@link #writeSummaryToParcel(Parcel, boolean)}.
+     * Corresponding write performed by {@link #writeSummaryToParcel(Parcel)}.
      */
-    private void readSummaryFromParcel(Parcel in, boolean overwriteAvailability) {
+    private void readSummaryFromParcel(Parcel in) {
         final int numWrittenEntries = in.readInt();
         for (int entry = 0; entry < numWrittenEntries; entry++) {
             final int index = in.readInt();
             final long chargeUC = in.readLong();
-            if (overwriteAvailability) {
-                mAccumulatedChargeMicroCoulomb[index] = chargeUC;
-            } else {
+            LongMultiStateCounter multiStateCounter = null;
+            if (in.readBoolean()) {
+                multiStateCounter = LongMultiStateCounter.CREATOR.createFromParcel(in);
+            }
+
+            if (index < mAccumulatedChargeMicroCoulomb.length) {
                 setValueIfSupported(index, chargeUC);
+                if (multiStateCounter != null) {
+                    if (mAccumulatedMultiStateChargeMicroCoulomb == null) {
+                        mAccumulatedMultiStateChargeMicroCoulomb =
+                                new LongMultiStateCounter[numWrittenEntries];
+                    }
+                    mAccumulatedMultiStateChargeMicroCoulomb[index] = multiStateCounter;
+                }
             }
         }
     }
@@ -186,20 +340,26 @@
      * Write to summary parcel.
      * Note: Measured subsystem availability may be different when the summary parcel is read.
      *
-     * Corresponding read performed by {@link #readSummaryFromParcel(Parcel, boolean)}.
+     * Corresponding read performed by {@link #readSummaryFromParcel(Parcel)}.
      */
-    private void writeSummaryToParcel(Parcel out, boolean skipZero) {
+    private void writeSummaryToParcel(Parcel out) {
         final int posOfNumWrittenEntries = out.dataPosition();
         out.writeInt(0);
         int numWrittenEntries = 0;
         // Write only the supported buckets (with non-zero charge, if applicable).
         for (int index = 0; index < mAccumulatedChargeMicroCoulomb.length; index++) {
             final long charge = mAccumulatedChargeMicroCoulomb[index];
-            if (charge < 0) continue;
-            if (charge == 0 && skipZero) continue;
+            if (charge <= 0) continue;
 
             out.writeInt(index);
             out.writeLong(charge);
+            if (mAccumulatedMultiStateChargeMicroCoulomb != null
+                    && mAccumulatedMultiStateChargeMicroCoulomb[index] != null) {
+                out.writeBoolean(true);
+                mAccumulatedMultiStateChargeMicroCoulomb[index].writeToParcel(out, 0);
+            } else {
+                out.writeBoolean(false);
+            }
             numWrittenEntries++;
         }
         final int currPos = out.dataPosition();
@@ -208,40 +368,87 @@
         out.setDataPosition(currPos);
     }
 
-    /** Get number of possible buckets, including both standard and custom ones. */
-    private int getNumberOfIndices() {
-        return mAccumulatedChargeMicroCoulomb.length;
-    }
-
-
     /** Updates the given standard power bucket with the given charge if accumulate is true. */
     public void updateStandardBucket(@StandardPowerBucket int bucket, long chargeDeltaUC) {
+        updateStandardBucket(bucket, chargeDeltaUC, 0);
+    }
+
+    /**
+     * Updates the given standard power bucket with the given charge if supported.
+     * @param timestampMs elapsed realtime in milliseconds
+     */
+    public void updateStandardBucket(@StandardPowerBucket int bucket, long chargeDeltaUC,
+            long timestampMs) {
         checkValidStandardBucket(bucket);
-        updateEntry(bucket, chargeDeltaUC);
+        updateEntry(bucket, chargeDeltaUC, timestampMs);
     }
 
     /** Updates the given custom power bucket with the given charge if accumulate is true. */
     public void updateCustomBucket(int customBucket, long chargeDeltaUC) {
+        updateCustomBucket(customBucket, chargeDeltaUC, 0);
+    }
+
+    /**
+     * Updates the given custom power bucket with the given charge if supported.
+     * @param timestampMs elapsed realtime in milliseconds
+     */
+    public void updateCustomBucket(int customBucket, long chargeDeltaUC, long timestampMs) {
         if (!isValidCustomBucket(customBucket)) {
             Slog.e(TAG, "Attempted to update invalid custom bucket " + customBucket);
             return;
         }
         final int index = customBucketToIndex(customBucket);
-        updateEntry(index, chargeDeltaUC);
+        updateEntry(index, chargeDeltaUC, timestampMs);
     }
 
-    /** Updates the given index with the given charge if accumulate is true. */
-    private void updateEntry(int index, long chargeDeltaUC) {
+    /** Updates the given bucket with the given charge delta. */
+    private void updateEntry(int index, long chargeDeltaUC, long timestampMs) {
         if (mAccumulatedChargeMicroCoulomb[index] >= 0L) {
             mAccumulatedChargeMicroCoulomb[index] += chargeDeltaUC;
+            if (mState != INVALID_STATE && mConfig.isSupportedMultiStateBucket(index)) {
+                if (mAccumulatedMultiStateChargeMicroCoulomb == null) {
+                    mAccumulatedMultiStateChargeMicroCoulomb =
+                            new LongMultiStateCounter[mAccumulatedChargeMicroCoulomb.length];
+                }
+                LongMultiStateCounter counter =
+                        mAccumulatedMultiStateChargeMicroCoulomb[index];
+                if (counter == null) {
+                    counter = new LongMultiStateCounter(mConfig.mStateNames.length);
+                    mAccumulatedMultiStateChargeMicroCoulomb[index] = counter;
+                    counter.setState(mState, mStateChangeTimestampMs);
+                    counter.updateValue(0, mStateChangeTimestampMs);
+                }
+                counter.updateValue(mAccumulatedChargeMicroCoulomb[index], timestampMs);
+            }
         } else {
             Slog.wtf(TAG, "Attempting to add " + chargeDeltaUC + " to unavailable bucket "
-                    + getBucketName(index) + " whose value was "
+                    + mConfig.getBucketName(index) + " whose value was "
                     + mAccumulatedChargeMicroCoulomb[index]);
         }
     }
 
     /**
+     * Updates the "state" on all multi-state counters used by this MeasuredEnergyStats. Further
+     * accumulated charge updates will assign the deltas to this state, until the state changes.
+     *
+     * If setState is never called on a MeasuredEnergyStats object, then it does not track
+     * per-state usage.
+     */
+    public void setState(int state, long timestampMs) {
+        mState = state;
+        mStateChangeTimestampMs = timestampMs;
+        if (mAccumulatedMultiStateChargeMicroCoulomb == null) {
+            return;
+        }
+        for (int i = 0; i < mAccumulatedMultiStateChargeMicroCoulomb.length; i++) {
+            LongMultiStateCounter counter = mAccumulatedMultiStateChargeMicroCoulomb[i];
+            if (counter != null) {
+                counter.setState(state, timestampMs);
+            }
+        }
+    }
+
+    /**
      * Return accumulated charge (in microcouloumb) for a standard power bucket since last reset.
      * Returns {@link android.os.BatteryStats#POWER_DATA_UNAVAILABLE} if this data is unavailable.
      * @throws IllegalArgumentException if no such {@link StandardPowerBucket}.
@@ -252,6 +459,26 @@
     }
 
     /**
+     * Returns the accumulated charge (in microcouloumb) for the standard power bucket and
+     * the specified state since last reset.
+     *
+     * Returns {@link android.os.BatteryStats#POWER_DATA_UNAVAILABLE} if this data is unavailable.
+     */
+    public long getAccumulatedStandardBucketCharge(@StandardPowerBucket int bucket, int state) {
+        if (!mConfig.isSupportedMultiStateBucket(bucket)) {
+            return POWER_DATA_UNAVAILABLE;
+        }
+        if (mAccumulatedMultiStateChargeMicroCoulomb == null) {
+            return 0;
+        }
+        final LongMultiStateCounter counter = mAccumulatedMultiStateChargeMicroCoulomb[bucket];
+        if (counter == null) {
+            return 0;
+        }
+        return counter.getCount(state);
+    }
+
+    /**
      * Return accumulated charge (in microcoulomb) for the a custom power bucket since last
      * reset.
      * Returns {@link android.os.BatteryStats#POWER_DATA_UNAVAILABLE} if this data is unavailable.
@@ -289,32 +516,6 @@
     }
 
     /**
-     * Create a MeasuredEnergyStats object from a summary parcel.
-     *
-     * Corresponding write performed by
-     * {@link #writeSummaryToParcel(MeasuredEnergyStats, Parcel, boolean, boolean)}.
-     *
-     * @return a new MeasuredEnergyStats object as described.
-     *         Returns null if the parcel indicates there is no data to populate.
-     */
-    public static @Nullable MeasuredEnergyStats createAndReadSummaryFromParcel(Parcel in) {
-        final int arraySize = in.readInt();
-        // Check if any MeasuredEnergyStats exists on the parcel
-        if (arraySize == 0) return null;
-
-        final String[] customBucketNames;
-        if (in.readBoolean()) {
-            customBucketNames = in.readStringArray();
-        } else {
-            customBucketNames = new String[0];
-        }
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(
-                new boolean[NUMBER_STANDARD_POWER_BUCKETS], customBucketNames);
-        stats.readSummaryFromParcel(in, true);
-        return stats;
-    }
-
-    /**
      * Create a MeasuredEnergyStats using the template to determine which buckets are supported,
      * and populate this new object from the given parcel.
      *
@@ -322,44 +523,37 @@
      * possible (not necessarily supported) standard and custom buckets.
      *
      * Corresponding write performed by
-     * {@link #writeSummaryToParcel(MeasuredEnergyStats, Parcel, boolean, boolean)}.
+     * {@link #writeSummaryToParcel(MeasuredEnergyStats, Parcel)}.
      *
      * @return a new MeasuredEnergyStats object as described.
      *         Returns null if the stats contain no non-0 information (such as if template is null
      *         or if the parcel indicates there is no data to populate).
-     *
-     * @see #createFromTemplate
      */
-    public static @Nullable MeasuredEnergyStats createAndReadSummaryFromParcel(Parcel in,
-            @Nullable MeasuredEnergyStats template) {
+    public static @Nullable MeasuredEnergyStats createAndReadSummaryFromParcel(
+            @Nullable Config config, Parcel in) {
         final int arraySize = in.readInt();
         // Check if any MeasuredEnergyStats exists on the parcel
         if (arraySize == 0) return null;
 
-        boolean includesCustomBucketNames = in.readBoolean();
-        if (includesCustomBucketNames) {
-            // Consume the array of custom bucket names. They are already included in the
-            // template.
-            in.readStringArray();
-        }
-        if (template == null) {
+        if (config == null) {
             // Nothing supported anymore. Create placeholder object just to consume the parcel data.
-            final MeasuredEnergyStats mes = new MeasuredEnergyStats(arraySize);
-            mes.readSummaryFromParcel(in, false);
+            final MeasuredEnergyStats mes = new MeasuredEnergyStats(
+                    new Config(new boolean[arraySize], null, new int[0], new String[]{""}));
+            mes.readSummaryFromParcel(in);
             return null;
         }
 
-        if (arraySize != template.getNumberOfIndices()) {
+        if (arraySize != config.getNumberOfBuckets()) {
             Slog.wtf(TAG, "Size of MeasuredEnergyStats parcel (" + arraySize
-                    + ") does not match template (" + template.getNumberOfIndices() + ").");
+                    + ") does not match config (" + config.getNumberOfBuckets() + ").");
             // Something is horribly wrong. Just consume the parcel and return null.
-            final MeasuredEnergyStats mes = new MeasuredEnergyStats(arraySize);
-            mes.readSummaryFromParcel(in, false);
+            final MeasuredEnergyStats mes = new MeasuredEnergyStats(config);
+            mes.readSummaryFromParcel(in);
             return null;
         }
 
-        final MeasuredEnergyStats stats = createFromTemplate(template);
-        stats.readSummaryFromParcel(in, false);
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        stats.readSummaryFromParcel(in);
         if (stats.containsInterestingData()) {
             return stats;
         } else {
@@ -379,28 +573,20 @@
     /**
      * Write a MeasuredEnergyStats to a parcel. If the stats is null, just write a 0.
      *
-     * Corresponding read performed by {@link #createAndReadSummaryFromParcel(Parcel)}
-     * and {@link #createAndReadSummaryFromParcel(Parcel, MeasuredEnergyStats)}.
+     * Corresponding read performed by {@link #createAndReadSummaryFromParcel}.
      */
-    public static void writeSummaryToParcel(@Nullable MeasuredEnergyStats stats,
-            Parcel dest, boolean skipZero, boolean skipCustomBucketNames) {
+    public static void writeSummaryToParcel(@Nullable MeasuredEnergyStats stats, Parcel dest) {
         if (stats == null) {
             dest.writeInt(0);
             return;
         }
-        dest.writeInt(stats.getNumberOfIndices());
-        if (!skipCustomBucketNames) {
-            dest.writeBoolean(true);
-            dest.writeStringArray(stats.getCustomBucketNames());
-        } else {
-            dest.writeBoolean(false);
-        }
-        stats.writeSummaryToParcel(dest, skipZero);
+        dest.writeInt(stats.mConfig.getNumberOfBuckets());
+        stats.writeSummaryToParcel(dest);
     }
 
     /** Reset accumulated charges. */
     private void reset() {
-        final int numIndices = getNumberOfIndices();
+        final int numIndices = mConfig.getNumberOfBuckets();
         for (int index = 0; index < numIndices; index++) {
             setValueIfSupported(index, 0L);
         }
@@ -431,46 +617,32 @@
         return mAccumulatedChargeMicroCoulomb[index] != POWER_DATA_UNAVAILABLE;
     }
 
-    /** Check if the supported power buckets are precisely those given. */
-    public boolean isSupportEqualTo(
-            @NonNull boolean[] queriedStandardBuckets, @Nullable String[] customBucketNames) {
-        if (customBucketNames == null) {
-            //In practice customBucketNames should never be null, but sanitize it just to be sure.
-            customBucketNames = new String[0];
-        }
-
-        final int numBuckets = getNumberOfIndices();
-        final int numCustomBuckets = customBucketNames == null ? 0 : customBucketNames.length;
-        if (numBuckets != NUMBER_STANDARD_POWER_BUCKETS + numCustomBuckets) {
-            return false;
-        }
-
-        if (!Arrays.equals(mCustomBucketNames, customBucketNames)) {
-            return false;
-        }
-
-        for (int stdBucket = 0; stdBucket < NUMBER_STANDARD_POWER_BUCKETS; stdBucket++) {
-            if (isStandardBucketSupported(stdBucket) != queriedStandardBuckets[stdBucket]) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public String[] getCustomBucketNames() {
-        return mCustomBucketNames;
-    }
-
     /** Dump debug data. */
     public void dump(PrintWriter pw) {
         pw.print("   ");
         for (int index = 0; index < mAccumulatedChargeMicroCoulomb.length; index++) {
-            pw.print(getBucketName(index));
+            pw.print(mConfig.getBucketName(index));
             pw.print(" : ");
             pw.print(mAccumulatedChargeMicroCoulomb[index]);
             if (!isIndexSupported(index)) {
                 pw.print(" (unsupported)");
             }
+            if (mAccumulatedMultiStateChargeMicroCoulomb != null) {
+                final LongMultiStateCounter counter =
+                        mAccumulatedMultiStateChargeMicroCoulomb[index];
+                if (counter != null) {
+                    pw.print(" [");
+                    for (int i = 0; i < mConfig.mStateNames.length; i++) {
+                        if (i != 0) {
+                            pw.print(" ");
+                        }
+                        pw.print(mConfig.mStateNames[i]);
+                        pw.print(": ");
+                        pw.print(counter.getCount(i));
+                    }
+                    pw.print("]");
+                }
+            }
             if (index != mAccumulatedChargeMicroCoulomb.length - 1) {
                 pw.print(", ");
             }
@@ -478,22 +650,6 @@
         pw.println();
     }
 
-    /**
-     * If the index is a standard bucket, returns its name; otherwise returns its prefixed custom
-     * bucket number.
-     */
-    private String getBucketName(int index) {
-        if (isValidStandardBucket(index)) {
-            return DebugUtils.valueToString(MeasuredEnergyStats.class, "POWER_BUCKET_", index);
-        }
-        final int customBucket = indexToCustomBucket(index);
-        StringBuilder name = new StringBuilder().append("CUSTOM_").append(customBucket);
-        if (mCustomBucketNames != null && !TextUtils.isEmpty(mCustomBucketNames[customBucket])) {
-            name.append('(').append(mCustomBucketNames[customBucket]).append(')');
-        }
-        return name.toString();
-    }
-
     /** Get the number of custom power buckets on this device. */
     public int getNumberCustomPowerBuckets() {
         return mAccumulatedChargeMicroCoulomb.length - NUMBER_STANDARD_POWER_BUCKETS;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 0f245e6..4e758e6 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -290,4 +290,5 @@
     void runGcForTest();
 
     void requestAddTile(in ComponentName componentName, in CharSequence appName, in CharSequence label, in Icon icon, in IAddTileResultCallback callback);
+    void cancelRequestAddTile(in String packageName);
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 49c32be..40a3951 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -161,4 +161,5 @@
     void suppressAmbientDisplay(boolean suppress);
 
     void requestAddTile(in ComponentName componentName, in CharSequence label, in Icon icon, int userId, in IAddTileResultCallback callback);
+    void cancelRequestAddTile(in String packageName);
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 1a1a8ba..2bd385c 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -404,6 +404,7 @@
     name: "libandroid_runtime_vm_headers",
     host_supported: true,
     vendor_available: true,
+    recovery_available: true,
     // TODO(b/153609531): remove when libbinder is not native_bridge_supported
     native_bridge_supported: true,
     // Allow only modules from the following list to create threads that can be
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 793b4eb..61b91dd 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -959,8 +959,7 @@
     return IPCThreadState::self()->getCallingUid();
 }
 
-static jboolean android_os_Binder_isHandlingTransaction()
-{
+static jboolean android_os_Binder_isDirectlyHandlingTransaction() {
     return getCurrentServingCall() == BinderCallType::BINDER;
 }
 
@@ -1056,6 +1055,7 @@
 
 // ----------------------------------------------------------------------------
 
+// clang-format off
 static const JNINativeMethod gBinderMethods[] = {
      /* name, signature, funcPtr */
     // @CriticalNative
@@ -1063,7 +1063,7 @@
     // @CriticalNative
     { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
     // @CriticalNative
-    { "isHandlingTransaction", "()Z", (void*)android_os_Binder_isHandlingTransaction },
+    { "isDirectlyHandlingTransaction", "()Z", (void*)android_os_Binder_isDirectlyHandlingTransaction },
     // @CriticalNative
     { "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity },
     // @CriticalNative
@@ -1088,6 +1088,7 @@
     { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension },
     { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
 };
+// clang-format on
 
 const char* const kBinderPathName = "android/os/Binder";
 
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 6f5cc53..40f6e4f 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -138,6 +138,14 @@
         return true;
     }
 
+    // Allow Runtime Resource Overlays inside APEXes.
+    static const char* kOverlayPathSuffix = "/overlay";
+    if (android::base::StartsWith(path, kApexPrefix) &&
+        android::base::EndsWith(android::base::Dirname(path), kOverlayPathSuffix) &&
+        android::base::EndsWith(path, kApkSuffix) && path.find("/../") == std::string::npos) {
+        return true;
+    }
+
     static const char* kOverlayIdmapPrefix = "/data/resource-cache/";
     static const char* kOverlayIdmapSuffix = ".apk@idmap";
     if (android::base::StartsWith(path, kOverlayIdmapPrefix) &&
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 40e3f60..3248cf5 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -202,7 +202,6 @@
     optional .com.android.server.wm.IdentifierProto resumed_activity = 24;
     repeated TaskProto tasks = 25 [deprecated=true];
     optional bool display_ready = 26;
-
     optional WindowStateProto input_method_target = 27;
     optional WindowStateProto input_method_input_target = 28;
     optional WindowStateProto input_method_control_target = 29;
@@ -214,6 +213,9 @@
     optional int32 ime_policy = 34;
 
     repeated InsetsSourceProviderProto insets_source_providers = 35;
+    optional bool is_sleeping = 36;
+    repeated string sleep_tokens = 37;
+
 }
 
 /* represents DisplayArea object */
@@ -527,7 +529,7 @@
 message WindowFramesProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
-    optional .android.graphics.RectProto containing_frame = 1;
+    optional .android.graphics.RectProto containing_frame = 1 [deprecated=true];
     optional .android.graphics.RectProto content_frame = 2 [deprecated=true];
     optional .android.graphics.RectProto decor_frame = 3 [deprecated=true];
     optional .android.graphics.RectProto display_frame = 4;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 329f02a..b3a16d9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5911,6 +5911,11 @@
     <permission android:name="android.permission.READ_GLOBAL_APP_SEARCH_DATA"
                 android:protectionLevel="internal|role" />
 
+    <!-- @SystemApi Allows an application to create virtual devices in VirtualDeviceManager.
+         @hide -->
+    <permission android:name="android.permission.CREATE_VIRTUAL_DEVICE"
+                android:protectionLevel="internal|role" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 765495a..06333e1 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1984,6 +1984,22 @@
         <enum name="KEYCODE_THUMBS_UP" value="286" />
         <enum name="KEYCODE_THUMBS_DOWN" value="287" />
         <enum name="KEYCODE_PROFILE_SWITCH" value="288" />
+        <enum name="KEYCODE_VIDEO_APP_1" value="289" />
+        <enum name="KEYCODE_VIDEO_APP_2" value="290" />
+        <enum name="KEYCODE_VIDEO_APP_3" value="291" />
+        <enum name="KEYCODE_VIDEO_APP_4" value="292" />
+        <enum name="KEYCODE_VIDEO_APP_5" value="293" />
+        <enum name="KEYCODE_VIDEO_APP_6" value="294" />
+        <enum name="KEYCODE_VIDEO_APP_7" value="295" />
+        <enum name="KEYCODE_VIDEO_APP_8" value="296" />
+        <enum name="KEYCODE_FEATURED_APP_1" value="297" />
+        <enum name="KEYCODE_FEATURED_APP_2" value="298" />
+        <enum name="KEYCODE_FEATURED_APP_3" value="299" />
+        <enum name="KEYCODE_FEATURED_APP_4" value="300" />
+        <enum name="KEYCODE_DEMO_APP_1" value="301" />
+        <enum name="KEYCODE_DEMO_APP_2" value="302" />
+        <enum name="KEYCODE_DEMO_APP_3" value="303" />
+        <enum name="KEYCODE_DEMO_APP_4" value="304" />
     </attr>
 
     <!-- ***************************************************************** -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 74ce8f2..e4b3991 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5280,6 +5280,11 @@
          to 0, the seconds hand will be disabled. -->
     <integer name="config_defaultAnalogClockSecondsHandFps">1</integer>
 
+    <!-- List of shared library packages that should be loaded by the classloader after the
+         code and resources provided by applications. This value will be set by the manufacturer  -->
+    <string-array name="config_sharedLibrariesLoadedAfterApp" translatable="false">
+    </string-array>
+
     <!-- the number of the max cached processes in the system. -->
     <integer name="config_customizedMaxCachedProcesses">32</integer>
 
diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml
index 36f1edb..c5dddb8 100644
--- a/core/res/res/values/dimens_car.xml
+++ b/core/res/res/values/dimens_car.xml
@@ -17,7 +17,7 @@
 -->
 <resources>
     <dimen name="car_large_avatar_size">96dp</dimen>
-    <dimen name="car_large_avatar_badge_size">32dp</dimen>
+    <dimen name="car_large_avatar_badge_size">24dp</dimen>
     <!-- Application Bar -->
     <dimen name="car_app_bar_height">80dp</dimen>
     <!-- Margin -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 846ec60..f166b73 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4559,6 +4559,10 @@
 
   <java-symbol type="bool" name="config_volumeShowRemoteSessions" />
 
+  <!-- List of shared library packages that should be loaded by the classloader after the
+       code and resources provided by applications. -->
+  <java-symbol type="array" name="config_sharedLibrariesLoadedAfterApp" />
+
   <java-symbol type="integer" name="config_customizedMaxCachedProcesses" />
 
   <java-symbol type="color" name="overview_background"/>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index a65de91..205c517 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -105,6 +105,9 @@
          http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht -->
     <shortcode country="ee" pattern="1\\d{2,4}" premium="90\\d{5}|15330|1701[0-3]" free="116\\d{3}|95034" />
 
+    <!-- Egypt: 4 digits, known codes listed -->
+    <shortcode country="eg" pattern="\\d{4}" free="1499" />
+
     <!-- Spain: 5-6 digits: 25xxx, 27xxx, 280xx, 35xxx, 37xxx, 795xxx, 797xxx, 995xxx, 997xxx, plus EU.
          http://www.legallink.es/?q=en/content/which-current-regulatory-status-premium-rate-services-spain -->
     <shortcode country="es" premium="[23][57]\\d{3}|280\\d{2}|[79]9[57]\\d{3}" free="116\\d{3}|22791|222145|22189" />
@@ -196,7 +199,7 @@
     <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" />
 
     <!-- Nigeria -->
-    <shortcode country="ng" pattern="\\d{1,5}" free="2441|55019" />
+    <shortcode country="ng" pattern="\\d{1,5}" free="2441|55020" />
 
     <!-- Norway: 4-5 digits (not confirmed), known premium codes listed -->
     <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" free="2171" />
diff --git a/core/tests/bluetoothtests/Android.bp b/core/tests/bluetoothtests/Android.bp
index a2e4dff..68416dd 100644
--- a/core/tests/bluetoothtests/Android.bp
+++ b/core/tests/bluetoothtests/Android.bp
@@ -15,7 +15,10 @@
         "android.test.runner",
         "android.test.base",
     ],
-    static_libs: ["junit"],
+    static_libs: [
+        "junit",
+        "modules-utils-bytesmatcher",
+    ],
     platform_apis: true,
     certificate: "platform",
 }
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
index c287ea9..4e817d4 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
@@ -16,11 +16,11 @@
 
 package android.bluetooth.le;
 
-import android.os.BytesMatcher;
 import android.os.ParcelUuid;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.util.HexDump;
+import com.android.modules.utils.BytesMatcher;
 
 import junit.framework.TestCase;
 
diff --git a/core/tests/coretests/src/android/net/SntpClientTest.java b/core/tests/coretests/src/android/net/SntpClientTest.java
index b400b9b..ba7df1e 100644
--- a/core/tests/coretests/src/android/net/SntpClientTest.java
+++ b/core/tests/coretests/src/android/net/SntpClientTest.java
@@ -26,6 +26,7 @@
 
 import android.net.sntp.Duration64;
 import android.net.sntp.Timestamp64;
+import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -49,6 +50,7 @@
 import java.util.Random;
 import java.util.function.Supplier;
 
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class SntpClientTest {
     private static final String TAG = "SntpClientTest";
diff --git a/core/tests/coretests/src/android/net/sntp/Duration64Test.java b/core/tests/coretests/src/android/net/sntp/Duration64Test.java
index 60b69f6..b228596 100644
--- a/core/tests/coretests/src/android/net/sntp/Duration64Test.java
+++ b/core/tests/coretests/src/android/net/sntp/Duration64Test.java
@@ -21,6 +21,8 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.Presubmit;
+
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
@@ -31,6 +33,7 @@
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class Duration64Test {
 
diff --git a/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java b/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java
index 1b1c500..200c80e 100644
--- a/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java
+++ b/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java
@@ -21,6 +21,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.Presubmit;
+
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
@@ -31,6 +33,7 @@
 import java.util.Random;
 import java.util.Set;
 
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class Timestamp64Test {
 
diff --git a/core/tests/coretests/src/android/os/BytesMatcherTest.java b/core/tests/coretests/src/android/os/BytesMatcherTest.java
deleted file mode 100644
index b28e309..0000000
--- a/core/tests/coretests/src/android/os/BytesMatcherTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import static com.android.internal.util.HexDump.hexStringToByteArray;
-
-import android.bluetooth.BluetoothUuid;
-import android.net.MacAddress;
-
-import androidx.test.filters.SmallTest;
-
-import junit.framework.TestCase;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-@SmallTest
-public class BytesMatcherTest extends TestCase {
-    @Test
-    public void testEmpty() throws Exception {
-        BytesMatcher matcher = BytesMatcher.decode("");
-        assertFalse(matcher.test(hexStringToByteArray("cafe")));
-        assertFalse(matcher.test(hexStringToByteArray("")));
-    }
-
-    @Test
-    public void testExact() throws Exception {
-        BytesMatcher matcher = BytesMatcher.decode("+cafe");
-        assertTrue(matcher.test(hexStringToByteArray("cafe")));
-        assertFalse(matcher.test(hexStringToByteArray("beef")));
-        assertFalse(matcher.test(hexStringToByteArray("ca")));
-        assertFalse(matcher.test(hexStringToByteArray("cafe00")));
-    }
-
-    @Test
-    public void testMask() throws Exception {
-        BytesMatcher matcher = BytesMatcher.decode("+cafe/ff00");
-        assertTrue(matcher.test(hexStringToByteArray("cafe")));
-        assertTrue(matcher.test(hexStringToByteArray("ca88")));
-        assertFalse(matcher.test(hexStringToByteArray("beef")));
-        assertFalse(matcher.test(hexStringToByteArray("ca")));
-        assertFalse(matcher.test(hexStringToByteArray("cafe00")));
-    }
-
-    @Test
-    public void testPrefix() throws Exception {
-        BytesMatcher matcher = BytesMatcher.decode("⊆cafe,⊆beef/ff00");
-        assertTrue(matcher.test(hexStringToByteArray("cafe")));
-        assertFalse(matcher.test(hexStringToByteArray("caff")));
-        assertTrue(matcher.test(hexStringToByteArray("cafecafe")));
-        assertFalse(matcher.test(hexStringToByteArray("ca")));
-        assertTrue(matcher.test(hexStringToByteArray("beef")));
-        assertTrue(matcher.test(hexStringToByteArray("beff")));
-        assertTrue(matcher.test(hexStringToByteArray("beffbeff")));
-        assertFalse(matcher.test(hexStringToByteArray("be")));
-    }
-
-    @Test
-    public void testMacAddress() throws Exception {
-        BytesMatcher matcher = BytesMatcher.decode("+cafe00112233/ffffff000000");
-        assertTrue(matcher.testMacAddress(
-                MacAddress.fromString("ca:fe:00:00:00:00")));
-        assertFalse(matcher.testMacAddress(
-                MacAddress.fromString("f0:0d:00:00:00:00")));
-    }
-
-    @Test
-    public void testBluetoothUuid() throws Exception {
-        BytesMatcher matcher = BytesMatcher.decode("+cafe/ff00");
-        assertTrue(matcher.testBluetoothUuid(
-                BluetoothUuid.parseUuidFrom(hexStringToByteArray("cafe"))));
-        assertFalse(matcher.testBluetoothUuid(
-                BluetoothUuid.parseUuidFrom(hexStringToByteArray("beef"))));
-    }
-
-    /**
-     * Verify that single matcher can be configured to match Bluetooth UUIDs of
-     * varying lengths.
-     */
-    @Test
-    public void testBluetoothUuid_Mixed() throws Exception {
-        BytesMatcher matcher = BytesMatcher.decode("+aaaa/ff00,+bbbbbbbb/ffff0000");
-        assertTrue(matcher.testBluetoothUuid(
-                BluetoothUuid.parseUuidFrom(hexStringToByteArray("aaaa"))));
-        assertFalse(matcher.testBluetoothUuid(
-                BluetoothUuid.parseUuidFrom(hexStringToByteArray("bbbb"))));
-        assertTrue(matcher.testBluetoothUuid(
-                BluetoothUuid.parseUuidFrom(hexStringToByteArray("bbbbbbbb"))));
-        assertFalse(matcher.testBluetoothUuid(
-                BluetoothUuid.parseUuidFrom(hexStringToByteArray("aaaaaaaa"))));
-    }
-
-    @Test
-    public void testSerialize_Empty() throws Exception {
-        BytesMatcher matcher = new BytesMatcher();
-        matcher = BytesMatcher.decode(BytesMatcher.encode(matcher));
-
-        // Also very empty and null values
-        BytesMatcher.decode("");
-        BytesMatcher.decode(null);
-    }
-
-    @Test
-    public void testSerialize_Exact() throws Exception {
-        BytesMatcher matcher = new BytesMatcher();
-        matcher.addExactRejectRule(hexStringToByteArray("cafe00112233"),
-                hexStringToByteArray("ffffff000000"));
-        matcher.addExactRejectRule(hexStringToByteArray("beef00112233"),
-                null);
-        matcher.addExactAcceptRule(hexStringToByteArray("000000000000"),
-                hexStringToByteArray("000000000000"));
-
-        assertFalse(matcher.test(hexStringToByteArray("cafe00ffffff")));
-        assertFalse(matcher.test(hexStringToByteArray("beef00112233")));
-        assertTrue(matcher.test(hexStringToByteArray("beef00ffffff")));
-
-        // Bounce through serialization pass and confirm it still works
-        matcher = BytesMatcher.decode(BytesMatcher.encode(matcher));
-
-        assertFalse(matcher.test(hexStringToByteArray("cafe00ffffff")));
-        assertFalse(matcher.test(hexStringToByteArray("beef00112233")));
-        assertTrue(matcher.test(hexStringToByteArray("beef00ffffff")));
-    }
-
-    @Test
-    public void testSerialize_Prefix() throws Exception {
-        BytesMatcher matcher = new BytesMatcher();
-        matcher.addExactRejectRule(hexStringToByteArray("aa"), null);
-        matcher.addExactAcceptRule(hexStringToByteArray("bb"), null);
-        matcher.addPrefixAcceptRule(hexStringToByteArray("aa"), null);
-        matcher.addPrefixRejectRule(hexStringToByteArray("bb"), null);
-
-        assertFalse(matcher.test(hexStringToByteArray("aa")));
-        assertTrue(matcher.test(hexStringToByteArray("bb")));
-        assertTrue(matcher.test(hexStringToByteArray("aaaa")));
-        assertFalse(matcher.test(hexStringToByteArray("bbbb")));
-
-        // Bounce through serialization pass and confirm it still works
-        matcher = BytesMatcher.decode(BytesMatcher.encode(matcher));
-
-        assertFalse(matcher.test(hexStringToByteArray("aa")));
-        assertTrue(matcher.test(hexStringToByteArray("bb")));
-        assertTrue(matcher.test(hexStringToByteArray("aaaa")));
-        assertFalse(matcher.test(hexStringToByteArray("bbbb")));
-    }
-
-    @Test
-    public void testOrdering_RejectFirst() throws Exception {
-        BytesMatcher matcher = BytesMatcher.decode("-ff/0f,+ff/f0");
-        assertFalse(matcher.test(hexStringToByteArray("ff")));
-        assertTrue(matcher.test(hexStringToByteArray("f0")));
-        assertFalse(matcher.test(hexStringToByteArray("0f")));
-    }
-
-    @Test
-    public void testOrdering_AcceptFirst() throws Exception {
-        BytesMatcher matcher = BytesMatcher.decode("+ff/f0,-ff/0f");
-        assertTrue(matcher.test(hexStringToByteArray("ff")));
-        assertTrue(matcher.test(hexStringToByteArray("f0")));
-        assertFalse(matcher.test(hexStringToByteArray("0f")));
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java
index 3c093d8..2c31b08 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java
@@ -30,6 +30,7 @@
 import com.android.internal.content.om.OverlayConfig.PackageProvider;
 import com.android.internal.content.om.OverlayScanner;
 import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
+import com.android.internal.util.function.TriConsumer;
 
 import org.junit.Assert;
 import org.junit.rules.TestRule;
@@ -42,7 +43,6 @@
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
-import java.util.function.BiConsumer;
 import java.util.function.Supplier;
 
 /**
@@ -74,7 +74,7 @@
         TestOverlayInfo(String packageName, String targetPackageName,
                 int targetSdkVersion, boolean isStatic, int priority, File path,
                 String requiredSystemPropertyName, String requiredSystemPropertyValue) {
-            super(packageName, targetPackageName, targetSdkVersion, isStatic, priority, path);
+            super(packageName, targetPackageName, targetSdkVersion, isStatic, priority, path, null);
             this.requiredSystemPropertyName = requiredSystemPropertyName;
             this.requiredSystemPropertyValue = requiredSystemPropertyValue;
         }
@@ -174,8 +174,8 @@
                 mIteration = Iteration.SYSTEM_SERVER;
                 doAnswer((InvocationOnMock invocation) -> {
                     final Object[] args = invocation.getArguments();
-                    final BiConsumer<ParsingPackageRead, Boolean> f =
-                            (BiConsumer<ParsingPackageRead, Boolean>) args[0];
+                    final TriConsumer<ParsingPackageRead, Boolean, File> f =
+                            (TriConsumer<ParsingPackageRead, Boolean, File>) args[0];
                     for (Map.Entry<File, TestOverlayInfo> overlay :
                             mTestOverlayInfos.entrySet()) {
                         final ParsingPackageRead a = Mockito.mock(ParsingPackageRead.class);
@@ -191,7 +191,8 @@
                         when(a.isOverlayIsStatic()).thenReturn(info.isStatic);
                         when(a.getOverlayPriority()).thenReturn(info.priority);
                         when(a.getBaseApkPath()).thenReturn(info.path.getPath());
-                        f.accept(a, !info.path.getPath().contains("data/overlay"));
+                        f.accept(a, !info.path.getPath().contains("data/overlay"),
+                                /*preInstalledApexPath=*/null);
                     }
                     return null;
                 }).when(mPkgProvider).forEachPackage(any());
diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
index 5c84794..d361da9 100644
--- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
@@ -33,6 +33,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class BluetoothPowerCalculatorTest {
@@ -105,10 +107,10 @@
         final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000,
                 BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 7000, 5000, 0,
                 reportedEnergyUc);
-        info.setUidTraffic(new UidTraffic[]{
-                new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
-                new UidTraffic(APP_UID, 3000, 4000)
-        });
+        info.setUidTraffic(new ArrayList<UidTraffic>(){{
+                add(new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000));
+                add(new UidTraffic(APP_UID, 3000, 4000));
+            }});
         mStatsRule.getBatteryStats().updateBluetoothStateLocked(info,
                 consumedEnergyUc, 1000, 1000);
     }
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index c24dc67..d16689c 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -65,8 +65,9 @@
         final boolean[] supportedStandardBuckets =
                 new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
         Arrays.fill(supportedStandardBuckets, true);
-        mGlobalMeasuredEnergyStats =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
+        mMeasuredEnergyStatsConfig = new MeasuredEnergyStats.Config(supportedStandardBuckets,
+                customBucketNames, new int[0], new String[]{""});
+        mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(mMeasuredEnergyStatsConfig);
     }
 
     public TimeBase getOnBatteryTimeBase() {
diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
index a70033b..dc5bc97 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
@@ -19,9 +19,12 @@
 import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
 
 import static com.android.internal.power.MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS;
+import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH;
+import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_CPU;
 import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE;
 import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON;
 import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_SCREEN_OTHER;
+import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_WIFI;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -56,55 +59,32 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+                        new int[]{POWER_BUCKET_SCREEN_ON, POWER_BUCKET_WIFI},
+                        new String[]{"state0", "state1", "state3"});
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
 
-        for (int i = 0; i < NUMBER_STANDARD_POWER_BUCKETS; i++) {
-            if (supportedStandardBuckets[i]) {
-                assertTrue(stats.isStandardBucketSupported(i));
-                assertEquals(0L, stats.getAccumulatedStandardBucketCharge(i));
+        for (int bucket = 0; bucket < NUMBER_STANDARD_POWER_BUCKETS; bucket++) {
+            if (supportedStandardBuckets[bucket]) {
+                assertTrue(stats.isStandardBucketSupported(bucket));
+                assertEquals(0L, stats.getAccumulatedStandardBucketCharge(bucket));
             } else {
-                assertFalse(stats.isStandardBucketSupported(i));
-                assertEquals(POWER_DATA_UNAVAILABLE, stats.getAccumulatedStandardBucketCharge(i));
+                assertFalse(stats.isStandardBucketSupported(bucket));
+                assertEquals(POWER_DATA_UNAVAILABLE,
+                        stats.getAccumulatedStandardBucketCharge(bucket));
+            }
+            if (bucket == POWER_BUCKET_SCREEN_ON) {
+                assertThat(config.isSupportedMultiStateBucket(bucket)).isTrue();
+            } else {
+                assertThat(config.isSupportedMultiStateBucket(bucket)).isFalse();
             }
         }
         for (int i = 0; i < customBucketNames.length; i++) {
             assertEquals(0L, stats.getAccumulatedCustomBucketCharge(i));
         }
-        assertThat(stats.getCustomBucketNames()).asList().containsExactly("A", "B");
-    }
-
-    @Test
-    public void testCreateFromTemplate() {
-        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
-        final String[] customBucketNames = {"A", "B"};
-        supportedStandardBuckets[POWER_BUCKET_SCREEN_ON] = true;
-        supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
-        supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
-
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
-        stats.updateCustomBucket(0, 50);
-        stats.updateCustomBucket(1, 60);
-
-        final MeasuredEnergyStats newStats = MeasuredEnergyStats.createFromTemplate(stats);
-
-        for (int i = 0; i < NUMBER_STANDARD_POWER_BUCKETS; i++) {
-            if (supportedStandardBuckets[i]) {
-                assertTrue(newStats.isStandardBucketSupported(i));
-                assertEquals(0L, newStats.getAccumulatedStandardBucketCharge(i));
-            } else {
-                assertFalse(newStats.isStandardBucketSupported(i));
-                assertEquals(POWER_DATA_UNAVAILABLE,
-                        newStats.getAccumulatedStandardBucketCharge(i));
-            }
-        }
-        for (int i = 0; i < customBucketNames.length; i++) {
-            assertEquals(0L, newStats.getAccumulatedCustomBucketCharge(i));
-        }
+        assertThat(config.getCustomBucketNames()).asList().containsExactly("A", "B");
+        assertThat(config.getStateNames()).asList().containsExactly("state0", "state1", "state3");
     }
 
     @Test
@@ -115,23 +95,32 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
-        stats.updateCustomBucket(0, 50);
-        stats.updateCustomBucket(1, 60);
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+                        new int[]{POWER_BUCKET_SCREEN_ON}, new String[]{"s0", "s1"});
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+
+        stats.setState(0, 1000);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10, 2000);
+        stats.setState(1, 3000);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5, 4000);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40, 5000);
+        stats.updateCustomBucket(0, 50, 6000);
+        stats.updateCustomBucket(1, 60, 7000);
 
         final Parcel parcel = Parcel.obtain();
         stats.writeToParcel(parcel);
 
         parcel.setDataPosition(0);
-        MeasuredEnergyStats newStats = new MeasuredEnergyStats(parcel);
+        MeasuredEnergyStats newStats = new MeasuredEnergyStats(config, parcel);
 
-        for (int i = 0; i < NUMBER_STANDARD_POWER_BUCKETS; i++) {
-            assertEquals(stats.getAccumulatedStandardBucketCharge(i),
-                    newStats.getAccumulatedStandardBucketCharge(i));
+        for (int bucket = 0; bucket < NUMBER_STANDARD_POWER_BUCKETS; bucket++) {
+            assertEquals(stats.getAccumulatedStandardBucketCharge(bucket),
+                    newStats.getAccumulatedStandardBucketCharge(bucket));
+            for (int state = 0; state < 2; state++) {
+                assertEquals(stats.getAccumulatedStandardBucketCharge(bucket, state),
+                        newStats.getAccumulatedStandardBucketCharge(bucket, state));
+            }
         }
         for (int i = 0; i < customBucketNames.length; i++) {
             assertEquals(stats.getAccumulatedCustomBucketCharge(i),
@@ -143,6 +132,57 @@
     }
 
     @Test
+    public void testCreateAndReadConfigFromParcel() {
+        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
+        final String[] customBucketNames = {"A", "B"};
+        supportedStandardBuckets[POWER_BUCKET_SCREEN_ON] = true;
+        supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
+        supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
+
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+                        new int[]{POWER_BUCKET_SCREEN_ON, POWER_BUCKET_WIFI},
+                        new String[] {"state0", "state1", "state2"});
+
+        final Parcel parcel = Parcel.obtain();
+        MeasuredEnergyStats.Config.writeToParcel(config, parcel);
+
+        parcel.setDataPosition(0);
+
+        final MeasuredEnergyStats.Config newConfig = MeasuredEnergyStats.Config.createFromParcel(
+                parcel);
+
+        assertThat(newConfig).isNotNull();
+        for (int bucket = 0; bucket < NUMBER_STANDARD_POWER_BUCKETS; bucket++) {
+            if (bucket == POWER_BUCKET_SCREEN_ON || bucket == POWER_BUCKET_SCREEN_OTHER) {
+                assertThat(newConfig.isSupportedBucket(bucket)).isTrue();
+            } else {
+                assertThat(newConfig.isSupportedBucket(bucket)).isFalse();
+            }
+            if (bucket == POWER_BUCKET_SCREEN_ON) {
+                assertThat(newConfig.isSupportedMultiStateBucket(bucket)).isTrue();
+            } else {
+                assertThat(newConfig.isSupportedMultiStateBucket(bucket)).isFalse();
+            }
+        }
+        assertThat(newConfig.getCustomBucketNames()).isEqualTo(new String[]{"A", "B"});
+        assertThat(newConfig.getStateNames()).isEqualTo(new String[]{"state0", "state1", "state2"});
+    }
+
+    @Test
+    public void testCreateAndReadConfigFromParcel_nullConfig() {
+        final Parcel parcel = Parcel.obtain();
+        MeasuredEnergyStats.Config.writeToParcel(null, parcel);
+
+        parcel.setDataPosition(0);
+
+        final MeasuredEnergyStats.Config newConfig = MeasuredEnergyStats.Config.createFromParcel(
+                parcel);
+
+        assertThat(newConfig).isNull();
+    }
+
+    @Test
     public void testCreateAndReadSummaryFromParcel() {
         final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
         final String[] customBucketNames = {"A", "B"};
@@ -150,8 +190,11 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+                        new int[0], new String[]{"s"});
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
@@ -159,9 +202,12 @@
         stats.updateCustomBucket(1, 60);
 
         final Parcel parcel = Parcel.obtain();
-        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false, false);
+        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel);
+
         parcel.setDataPosition(0);
-        MeasuredEnergyStats newStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel);
+
+        MeasuredEnergyStats newStats =
+                MeasuredEnergyStats.createAndReadSummaryFromParcel(config, parcel);
 
         for (int i = 0; i < NUMBER_STANDARD_POWER_BUCKETS; i++) {
             assertEquals(stats.isStandardBucketSupported(i),
@@ -175,48 +221,44 @@
         }
         assertEquals(POWER_DATA_UNAVAILABLE,
                 newStats.getAccumulatedCustomBucketCharge(customBucketNames.length + 1));
-        assertThat(newStats.getCustomBucketNames()).asList().containsExactly("A", "B");
         parcel.recycle();
     }
 
     @Test
-    public void testCreateAndReadSummaryFromParcel_existingTemplate() {
+    public void testCreateAndReadSummaryFromParcel_configChange() {
         final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
         final String[] customBucketNames = {"A", "B"};
         supportedStandardBuckets[POWER_BUCKET_SCREEN_ON] = true;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats template =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
-        template.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
-        template.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
-        template.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
-        template.updateCustomBucket(0, 50);
-
-        final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 200);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 7);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 63);
-        stats.updateCustomBucket(0, 315);
-        stats.updateCustomBucket(1, 316);
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+                        new int[0], new String[]{"s"});
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
+        stats.updateCustomBucket(0, 50);
 
         final Parcel parcel = Parcel.obtain();
-        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false, true);
-
-        final boolean[] newsupportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
-        newsupportedStandardBuckets[POWER_BUCKET_SCREEN_ON] = true;
-        newsupportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = true; // switched false > true
-        newsupportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = false; // switched true > false
-        final MeasuredEnergyStats newTemplate =
-                new MeasuredEnergyStats(newsupportedStandardBuckets, customBucketNames);
+        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel);
         parcel.setDataPosition(0);
 
+        final boolean[] newSupportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
+        newSupportedStandardBuckets[POWER_BUCKET_SCREEN_ON] = true;
+        newSupportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = true; // switched false > true
+        newSupportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = false; // switched true > false
+
+        final MeasuredEnergyStats.Config newConfig =
+                new MeasuredEnergyStats.Config(newSupportedStandardBuckets, customBucketNames,
+                        new int[0], new String[]{"s"});
+
         final MeasuredEnergyStats newStats =
-                MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel, newTemplate);
+                MeasuredEnergyStats.createAndReadSummaryFromParcel(newConfig, parcel);
 
         for (int i = 0; i < NUMBER_STANDARD_POWER_BUCKETS; i++) {
-            if (!newsupportedStandardBuckets[i]) {
+            if (!newSupportedStandardBuckets[i]) {
                 assertFalse(newStats.isStandardBucketSupported(i));
                 assertEquals(POWER_DATA_UNAVAILABLE,
                         newStats.getAccumulatedStandardBucketCharge(i));
@@ -235,81 +277,22 @@
         }
         assertEquals(POWER_DATA_UNAVAILABLE,
                 newStats.getAccumulatedCustomBucketCharge(customBucketNames.length + 1));
-        assertThat(newStats.getCustomBucketNames()).asList().containsExactly("A", "B");
         parcel.recycle();
     }
 
     @Test
-    public void testCreateAndReadSummaryFromParcel_skipZero() {
-        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
-        final String[] customBucketNames = {"A", "B"};
-        Arrays.fill(supportedStandardBuckets, true);
-
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
-        // Accumulate charge in one bucket and one custom bucket, the rest should be zero
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 200);
-        stats.updateCustomBucket(1, 60);
-
-        // Let's try parcelling with including zeros
-        final Parcel includeZerosParcel = Parcel.obtain();
-        MeasuredEnergyStats.writeSummaryToParcel(stats, includeZerosParcel, false, false);
-        includeZerosParcel.setDataPosition(0);
-
-        MeasuredEnergyStats newStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(
-                includeZerosParcel);
-
-        for (int i = 0; i < NUMBER_STANDARD_POWER_BUCKETS; i++) {
-            if (i == POWER_BUCKET_SCREEN_ON) {
-                assertEquals(stats.isStandardBucketSupported(i),
-                        newStats.isStandardBucketSupported(i));
-                assertEquals(stats.getAccumulatedStandardBucketCharge(i),
-                        newStats.getAccumulatedStandardBucketCharge(i));
-            } else {
-                assertTrue(newStats.isStandardBucketSupported(i));
-                assertEquals(0L, newStats.getAccumulatedStandardBucketCharge(i));
-            }
-        }
-        assertEquals(0L, newStats.getAccumulatedCustomBucketCharge(0));
-        assertEquals(stats.getAccumulatedCustomBucketCharge(1),
-                newStats.getAccumulatedCustomBucketCharge(1));
-        includeZerosParcel.recycle();
-
-        // Now let's try parcelling with skipping zeros
-        final Parcel skipZerosParcel = Parcel.obtain();
-        MeasuredEnergyStats.writeSummaryToParcel(stats, skipZerosParcel, true, false);
-        skipZerosParcel.setDataPosition(0);
-
-        newStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(skipZerosParcel);
-
-        for (int i = 0; i < NUMBER_STANDARD_POWER_BUCKETS; i++) {
-            if (i == POWER_BUCKET_SCREEN_ON) {
-                assertEquals(stats.isStandardBucketSupported(i),
-                        newStats.isStandardBucketSupported(i));
-                assertEquals(stats.getAccumulatedStandardBucketCharge(i),
-                        newStats.getAccumulatedStandardBucketCharge(i));
-            } else {
-                assertFalse(newStats.isStandardBucketSupported(i));
-                assertEquals(POWER_DATA_UNAVAILABLE,
-                        newStats.getAccumulatedStandardBucketCharge(i));
-            }
-        }
-        assertEquals(0L, newStats.getAccumulatedCustomBucketCharge(0));
-        assertEquals(stats.getAccumulatedCustomBucketCharge(1),
-                newStats.getAccumulatedCustomBucketCharge(1));
-        skipZerosParcel.recycle();
-    }
-
-    @Test
-    public void testCreateAndReadSummaryFromParcel_nullTemplate() {
+    public void testCreateAndReadSummaryFromParcel_nullConfig() {
         final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
         final String[] customBucketNames = {"A", "B"};
         supportedStandardBuckets[POWER_BUCKET_SCREEN_ON] = true;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+                        new int[0], new String[]{"s"});
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
@@ -317,11 +300,11 @@
         stats.updateCustomBucket(1, 60);
 
         final Parcel parcel = Parcel.obtain();
-        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false, true);
+        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel);
         parcel.setDataPosition(0);
 
         MeasuredEnergyStats newStats =
-                MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel, null);
+                MeasuredEnergyStats.createAndReadSummaryFromParcel(null, parcel);
         assertNull(newStats);
         parcel.recycle();
     }
@@ -334,30 +317,30 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats template =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
-        template.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
-        template.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
-        template.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
-        template.updateCustomBucket(0, 50);
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+                        new int[0], new String[]{"s"});
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
 
-        final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 0L);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 7L);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 0);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
 
         final Parcel parcel = Parcel.obtain();
-        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false, true);
+        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel);
 
         final boolean[] newSupportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
         newSupportedStandardBuckets[POWER_BUCKET_SCREEN_ON] = true;
         newSupportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = true; // switched false > true
         newSupportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = false; // switched true > false
-        final MeasuredEnergyStats newTemplate =
-                new MeasuredEnergyStats(newSupportedStandardBuckets, customBucketNames);
+        final MeasuredEnergyStats.Config newConfig =
+                new MeasuredEnergyStats.Config(newSupportedStandardBuckets, customBucketNames,
+                        new int[0], new String[]{"s"});
+
         parcel.setDataPosition(0);
 
         final MeasuredEnergyStats newStats =
-                MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel, newTemplate);
+                MeasuredEnergyStats.createAndReadSummaryFromParcel(newConfig, parcel);
+
         // The only non-0 entry in stats is no longer supported, so now there's no interesting data.
         assertNull(newStats);
         assertEquals("Parcel was not properly consumed", 0, parcel.dataAvail());
@@ -372,30 +355,47 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_DOZE, 30);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+                        new int[]{POWER_BUCKET_SCREEN_ON, POWER_BUCKET_SCREEN_OTHER},
+                        new String[]{"s0", "s1"});
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
 
-        stats.updateCustomBucket(0, 50);
-        stats.updateCustomBucket(1, 60);
-        stats.updateCustomBucket(0, 3);
+        stats.setState(0, 1000);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10, 2000);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_DOZE, 30, 3000);
+        stats.setState(1,  4000);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40, 5000);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 6, 6000);
 
-        assertEquals(15, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_ON));
+        stats.updateCustomBucket(0, 50, 7000);
+        stats.updateCustomBucket(1, 60, 8000);
+        stats.updateCustomBucket(0, 3, 9000);
+
+        assertEquals(16, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_ON));
         assertEquals(POWER_DATA_UNAVAILABLE,
                 stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_DOZE));
         assertEquals(40, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_OTHER));
         assertEquals(50 + 3, stats.getAccumulatedCustomBucketCharge(0));
         assertEquals(60, stats.getAccumulatedCustomBucketCharge(1));
+
+        // 10 + 6 * (4000-2000)/(6000-2000)
+        assertEquals(13, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_ON, 0));
+        // 6 * (6000-4000)/(6000-2000)
+        assertEquals(3, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_ON, 1));
+
+        // POWER_BUCKET_SCREEN_OTHER was only present along with state=1
+        assertEquals(0, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_OTHER, 0));
+        assertEquals(40, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_OTHER, 1));
     }
 
     @Test
     public void testIsValidCustomBucket() {
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
-                        new String[]{"A", "B", "C"});
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+                        new String[]{"A", "B", "C"},
+                        new int[0], new String[]{"s"});
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
         assertFalse(stats.isValidCustomBucket(-1));
         assertTrue(stats.isValidCustomBucket(0));
         assertTrue(stats.isValidCustomBucket(1));
@@ -403,8 +403,10 @@
         assertFalse(stats.isValidCustomBucket(3));
         assertFalse(stats.isValidCustomBucket(4));
 
-        final MeasuredEnergyStats boringStats =
-                new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_POWER_BUCKETS], new String[0]);
+        final MeasuredEnergyStats.Config boringConfig =
+                new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+                        new String[0], new int[0], new String[]{"s"});
+        final MeasuredEnergyStats boringStats = new MeasuredEnergyStats(boringConfig);
         assertFalse(boringStats.isValidCustomBucket(-1));
         assertFalse(boringStats.isValidCustomBucket(0));
         assertFalse(boringStats.isValidCustomBucket(1));
@@ -412,9 +414,11 @@
 
     @Test
     public void testGetAccumulatedCustomBucketCharges() {
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
-                        new String[]{"A", "B", "C"});
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+                        new String[]{"A", "B", "C"},
+                        new int[0], new String[]{"s"});
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
         stats.updateCustomBucket(0, 50);
         stats.updateCustomBucket(1, 60);
         stats.updateCustomBucket(2, 13);
@@ -430,8 +434,10 @@
 
     @Test
     public void testGetAccumulatedCustomBucketCharges_empty() {
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_POWER_BUCKETS], new String[0]);
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+                        new String[0], new int[0], new String[]{"s"});
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
 
         final long[] output = stats.getAccumulatedCustomBucketCharges();
         assertEquals(0, output.length);
@@ -440,10 +446,15 @@
     @Test
     public void testGetNumberCustomChargeBuckets() {
         assertEquals(0,
-                new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_POWER_BUCKETS], new String[0])
+                new MeasuredEnergyStats(
+                        new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+                                new String[0], new int[0], new String[]{"s"}))
                         .getNumberCustomPowerBuckets());
-        assertEquals(3, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
-                new String[]{"A", "B", "C"}).getNumberCustomPowerBuckets());
+        assertEquals(3,
+                new MeasuredEnergyStats(
+                        new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+                                new String[]{"A", "B", "C"}, new int[0], new String[]{"s"}))
+                        .getNumberCustomPowerBuckets());
     }
 
     @Test
@@ -454,8 +465,10 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
+        final MeasuredEnergyStats.Config config =
+                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+                        new int[0], new String[]{"s"});
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
@@ -500,83 +513,62 @@
         assertEquals(exp, MeasuredEnergyStats.getDisplayPowerBucket(Display.STATE_DOZE_SUSPEND));
     }
 
-    /** Test MeasuredEnergyStats#isSupportEqualTo */
     @Test
-    public void testIsSupportEqualTo() {
+    public void testConfig_isCompatible() {
         final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
         Arrays.fill(supportedStandardBuckets, true);
         final String[] customBucketNames = {"A", "B"};
+        final int[] supportedMultiStateBuckets = {POWER_BUCKET_CPU, POWER_BUCKET_WIFI};
+        final String[] stateNames = {"s"};
 
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(supportedStandardBuckets.clone(),
-                        customBucketNames.clone());
-
+        final MeasuredEnergyStats.Config config = new MeasuredEnergyStats.Config(
+                supportedStandardBuckets,
+                customBucketNames,
+                supportedMultiStateBuckets,
+                stateNames);
         assertTrue(
                 "All standard and custom bucket supports match",
-                stats.isSupportEqualTo(supportedStandardBuckets, customBucketNames));
+                config.isCompatible(
+                        new MeasuredEnergyStats.Config(
+                                supportedStandardBuckets,
+                                customBucketNames,
+                                supportedMultiStateBuckets,
+                                stateNames)));
 
         boolean[] differentSupportedStandardBuckets = supportedStandardBuckets.clone();
         differentSupportedStandardBuckets[0] = !differentSupportedStandardBuckets[0];
+
         assertFalse(
                 "Standard bucket support mismatch",
-                stats.isSupportEqualTo(differentSupportedStandardBuckets, customBucketNames));
-
+                config.isCompatible(
+                        new MeasuredEnergyStats.Config(
+                                differentSupportedStandardBuckets,
+                                customBucketNames,
+                                supportedMultiStateBuckets,
+                                stateNames)));
         assertFalse(
                 "Custom bucket support mismatch",
-                stats.isSupportEqualTo(supportedStandardBuckets, new String[]{"C", "B"}));
-
+                config.isCompatible(
+                        new MeasuredEnergyStats.Config(
+                                supportedStandardBuckets,
+                                new String[]{"C", "B"},
+                                supportedMultiStateBuckets,
+                                stateNames)));
         assertFalse(
-                "Fewer custom buckets supported",
-                stats.isSupportEqualTo(supportedStandardBuckets, new String[]{"A"}));
-
+                "Multi-state bucket mismatch",
+                config.isCompatible(
+                        new MeasuredEnergyStats.Config(
+                                supportedStandardBuckets,
+                                new String[]{"A"},
+                                new int[] {POWER_BUCKET_CPU, POWER_BUCKET_BLUETOOTH},
+                                stateNames)));
         assertFalse(
-                "More custom bucket supported",
-                stats.isSupportEqualTo(supportedStandardBuckets, new String[]{"A", "B", "C"}));
-
-        assertFalse(
-                "Custom bucket support order changed",
-                stats.isSupportEqualTo(supportedStandardBuckets, new String[]{"B", "A"}));
-    }
-
-    /** Test MeasuredEnergyStats#isSupportEqualTo when holding a null array of custom buckets */
-    @Test
-    public void testIsSupportEqualTo_nullCustomBuckets() {
-        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
-
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(supportedStandardBuckets.clone(), null);
-
-        assertTrue(
-                "Null custom bucket name lists should match",
-                stats.isSupportEqualTo(supportedStandardBuckets, null));
-
-        assertTrue(
-                "Null and empty custom buckets should match",
-                stats.isSupportEqualTo(supportedStandardBuckets, new String[0]));
-
-        assertFalse(
-                "Null custom buckets should not match populated list",
-                stats.isSupportEqualTo(supportedStandardBuckets, new String[]{"A", "B"}));
-    }
-
-    /** Test MeasuredEnergyStats#isSupportEqualTo when holding an empty array of custom buckets */
-    @Test
-    public void testIsSupportEqualTo_emptyCustomBuckets() {
-        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
-
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(supportedStandardBuckets.clone(), new String[0]);
-
-        assertTrue(
-                "Empty custom buckets should match",
-                stats.isSupportEqualTo(supportedStandardBuckets, new String[0]));
-
-        assertTrue(
-                "Empty and null custom buckets should match",
-                stats.isSupportEqualTo(supportedStandardBuckets, null));
-
-        assertFalse(
-                "Empty custom buckets should not match populated list",
-                stats.isSupportEqualTo(supportedStandardBuckets, new String[]{"A", "B"}));
+                "Multi-state bucket state list mismatch",
+                config.isCompatible(
+                        new MeasuredEnergyStats.Config(
+                                supportedStandardBuckets,
+                                new String[]{"A"},
+                                supportedMultiStateBuckets,
+                                new String[]{"s1", "s2"})));
     }
 }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 6e92755..8b37805 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1561,6 +1561,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "-436553282": {
+      "message": "Remove sleep token: tag=%s, displayId=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
     "-415865166": {
       "message": "findFocusedWindow: Found new focus @ %s",
       "level": "VERBOSE",
@@ -1669,6 +1675,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-317761482": {
+      "message": "Create sleep token: tag=%s, displayId=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
     "-317194205": {
       "message": "clearLockedTasks: %s",
       "level": "INFO",
diff --git a/libs/WindowManager/Shell/res/layout/split_decor.xml b/libs/WindowManager/Shell/res/layout/split_decor.xml
new file mode 100644
index 0000000..9ffa5e8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/split_decor.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent">
+
+    <ImageView android:id="@+id/split_resizing_icon"
+               android:layout_height="wrap_content"
+               android:layout_width="wrap_content"
+               android:layout_gravity="center"
+               android:padding="0dp"
+               android:visibility="gone"
+               android:background="@null"/>
+
+</FrameLayout>
diff --git a/libs/WindowManager/Shell/res/layout/split_outline.xml b/libs/WindowManager/Shell/res/layout/split_outline.xml
index 13a30f5..6cb9ebb 100644
--- a/libs/WindowManager/Shell/res/layout/split_outline.xml
+++ b/libs/WindowManager/Shell/res/layout/split_outline.xml
@@ -1,18 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 9113c79..e87b150 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -103,8 +103,6 @@
                 return runMoveToSideStage(args, pw);
             case "removeFromSideStage":
                 return runRemoveFromSideStage(args, pw);
-            case "setSideStageOutline":
-                return runSetSideStageOutline(args, pw);
             case "setSideStagePosition":
                 return runSetSideStagePosition(args, pw);
             case "setSideStageVisibility":
@@ -163,18 +161,6 @@
         return true;
     }
 
-    private boolean runSetSideStageOutline(String[] args, PrintWriter pw) {
-        if (args.length < 3) {
-            // First arguments are "WMShell" and command name.
-            pw.println("Error: whether to enable or disable side stage outline border should be"
-                    + " provided as arguments");
-            return false;
-        }
-        final boolean enable = new Boolean(args[2]);
-        mSplitScreenOptional.ifPresent(split -> split.setSideStageOutline(enable));
-        return true;
-    }
-
     private boolean runSetSideStagePosition(String[] args, PrintWriter pw) {
         if (args.length < 3) {
             // First arguments are "WMShell" and command name.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index 6a252e0..c8449a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -131,7 +131,8 @@
         mSplitLayout = new SplitLayout(TAG + "SplitDivider",
                 mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
                 mRootTaskInfo.configuration, this /* layoutChangeListener */,
-                mParentContainerCallbacks, mDisplayImeController, mController.getTaskOrganizer());
+                mParentContainerCallbacks, mDisplayImeController, mController.getTaskOrganizer(),
+                true /* applyDismissingParallax */);
         mDisplayInsetsController.addInsetsChangedListener(mRootTaskInfo.displayId, mSplitLayout);
 
         final WindowContainerToken token1 = task1.token;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java
new file mode 100644
index 0000000..dc20f7b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import android.os.SystemProperties;
+import android.view.IWindowManager;
+
+import javax.inject.Inject;
+
+/**
+ * Handle the preview of what a back gesture will lead to.
+ */
+public class BackPreviewHandler {
+
+    private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
+
+    public static boolean isEnabled() {
+        return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
+    }
+
+    private final IWindowManager mWmService;
+
+    @Inject
+    public BackPreviewHandler(IWindowManager windowManagerService) {
+        mWmService = windowManagerService;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SurfaceUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SurfaceUtils.java
index 55c5125..4b138e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SurfaceUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SurfaceUtils.java
@@ -23,16 +23,22 @@
  * Helpers for handling surface.
  */
 public class SurfaceUtils {
-    /** Creates a dim layer above indicated host surface. */
+    /** Creates a dim layer above host surface. */
     public static SurfaceControl makeDimLayer(SurfaceControl.Transaction t, SurfaceControl host,
             String name, SurfaceSession surfaceSession) {
-        SurfaceControl dimLayer = new SurfaceControl.Builder(surfaceSession)
+        final SurfaceControl dimLayer = makeColorLayer(host, name, surfaceSession);
+        t.setLayer(dimLayer, Integer.MAX_VALUE).setColor(dimLayer, new float[]{0f, 0f, 0f});
+        return dimLayer;
+    }
+
+    /** Creates a color layer for host surface. */
+    public static SurfaceControl makeColorLayer(SurfaceControl host, String name,
+            SurfaceSession surfaceSession) {
+        return new SurfaceControl.Builder(surfaceSession)
                 .setParent(host)
                 .setColorLayer()
                 .setName(name)
-                .setCallsite("SurfaceUtils.makeDimLayer")
+                .setCallsite("SurfaceUtils.makeColorLayer")
                 .build();
-        t.setLayer(dimLayer, Integer.MAX_VALUE).setColor(dimLayer, new float[]{0f, 0f, 0f});
-        return dimLayer;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
new file mode 100644
index 0000000..ad9ebb2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.split;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Binder;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.SurfaceUtils;
+
+/**
+ * Handles split decor like showing resizing hint for a specific split.
+ */
+public class SplitDecorManager extends WindowlessWindowManager {
+    private static final String TAG = SplitDecorManager.class.getSimpleName();
+    private static final String RESIZING_BACKGROUND_SURFACE_NAME = "ResizingBackground";
+
+    private final IconProvider mIconProvider;
+    private final SurfaceSession mSurfaceSession;
+
+    private Drawable mIcon;
+    private ImageView mResizingIconView;
+    private SurfaceControlViewHost mViewHost;
+    private SurfaceControl mHostLeash;
+    private SurfaceControl mIconLeash;
+    private SurfaceControl mBackgroundLeash;
+
+    public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
+            SurfaceSession surfaceSession) {
+        super(configuration, null /* rootSurface */, null /* hostInputToken */);
+        mIconProvider = iconProvider;
+        mSurfaceSession = surfaceSession;
+    }
+
+    @Override
+    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+        // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
+        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+                .setContainerLayer()
+                .setName(TAG)
+                .setHidden(true)
+                .setParent(mHostLeash)
+                .setCallsite("SplitDecorManager#attachToParentSurface");
+        mIconLeash = builder.build();
+        b.setParent(mIconLeash);
+    }
+
+    /** Inflates split decor surface on the root surface. */
+    public void inflate(Context context, SurfaceControl rootLeash, Rect rootBounds) {
+        if (mIconLeash != null && mViewHost != null) {
+            return;
+        }
+
+        context = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
+                null /* options */);
+        mHostLeash = rootLeash;
+        mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), this);
+
+        final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context)
+                .inflate(R.layout.split_decor, null);
+        mResizingIconView = rootLayout.findViewById(R.id.split_resizing_icon);
+
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
+                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
+        lp.width = rootBounds.width();
+        lp.height = rootBounds.height();
+        lp.token = new Binder();
+        lp.setTitle(TAG);
+        lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+        // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
+        //  TRUSTED_OVERLAY for windowless window without input channel.
+        mViewHost.setView(rootLayout, lp);
+    }
+
+    /** Releases the surfaces for split decor. */
+    public void release(SurfaceControl.Transaction t) {
+        if (mViewHost != null) {
+            mViewHost.release();
+            mViewHost = null;
+        }
+        if (mIconLeash != null) {
+            t.remove(mIconLeash);
+            mIconLeash = null;
+        }
+        if (mBackgroundLeash != null) {
+            t.remove(mBackgroundLeash);
+            mBackgroundLeash = null;
+        }
+        mHostLeash = null;
+        mIcon = null;
+        mResizingIconView = null;
+    }
+
+    /** Showing resizing hint. */
+    public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
+            SurfaceControl.Transaction t) {
+        if (mResizingIconView == null) {
+            return;
+        }
+
+        if (mIcon == null) {
+            // TODO: add fade-in animation.
+            mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
+                    RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
+            t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
+                    .setLayer(mBackgroundLeash, SPLIT_DIVIDER_LAYER - 1)
+                    .show(mBackgroundLeash);
+
+            mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
+            mResizingIconView.setImageDrawable(mIcon);
+            mResizingIconView.setVisibility(View.VISIBLE);
+
+            WindowManager.LayoutParams lp =
+                    (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+            lp.width = mIcon.getIntrinsicWidth();
+            lp.height = mIcon.getIntrinsicHeight();
+            mViewHost.relayout(lp);
+            t.show(mIconLeash).setLayer(mIconLeash, SPLIT_DIVIDER_LAYER);
+        }
+
+        t.setPosition(mIconLeash,
+                newBounds.width() / 2 - mIcon.getIntrinsicWidth() / 2,
+                newBounds.height() / 2 - mIcon.getIntrinsicWidth() / 2);
+    }
+
+    /** Stops showing resizing hint. */
+    public void onResized(Rect newBounds, SurfaceControl.Transaction t) {
+        if (mResizingIconView == null) {
+            return;
+        }
+
+        if (mIcon != null) {
+            mResizingIconView.setVisibility(View.GONE);
+            mResizingIconView.setImageDrawable(null);
+            t.remove(mBackgroundLeash).hide(mIconLeash);
+            mIcon = null;
+            mBackgroundLeash = null;
+        }
+    }
+
+    private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
+        final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
+        return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents();
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 5b3ce2d..a9ed64c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -103,7 +103,7 @@
     private final SplitWindowManager mSplitWindowManager;
     private final DisplayImeController mDisplayImeController;
     private final ImePositionProcessor mImePositionProcessor;
-    private final DismissingParallaxPolicy mDismissingParallaxPolicy;
+    private final DismissingEffectPolicy mDismissingEffectPolicy;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final InsetsState mInsetsState = new InsetsState();
 
@@ -119,7 +119,8 @@
     public SplitLayout(String windowName, Context context, Configuration configuration,
             SplitLayoutHandler splitLayoutHandler,
             SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
-            DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer) {
+            DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer,
+            boolean applyDismissingParallax) {
         mContext = context.createConfigurationContext(configuration);
         mOrientation = configuration.orientation;
         mRotation = configuration.windowConfiguration.getRotation();
@@ -129,7 +130,7 @@
                 parentContainerCallbacks);
         mTaskOrganizer = taskOrganizer;
         mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
-        mDismissingParallaxPolicy = new DismissingParallaxPolicy();
+        mDismissingEffectPolicy = new DismissingEffectPolicy(applyDismissingParallax);
 
         final Resources resources = context.getResources();
         mDividerWindowWidth = resources.getDimensionPixelSize(
@@ -247,7 +248,7 @@
         }
         DockedDividerUtils.sanitizeStackBounds(mBounds1, true /** topLeft */);
         DockedDividerUtils.sanitizeStackBounds(mBounds2, false /** topLeft */);
-        mDismissingParallaxPolicy.applyDividerPosition(position, isLandscape);
+        mDismissingEffectPolicy.applyDividerPosition(position, isLandscape);
     }
 
     /** Inflates {@link DividerView} on the root surface. */
@@ -290,7 +291,6 @@
      */
     void updateDivideBounds(int position) {
         updateBounds(position);
-        mSplitWindowManager.setResizingSplits(true);
         mSplitLayoutHandler.onLayoutSizeChanging(this);
     }
 
@@ -298,13 +298,11 @@
         mDividePosition = position;
         updateBounds(mDividePosition);
         mSplitLayoutHandler.onLayoutSizeChanged(this);
-        mSplitWindowManager.setResizingSplits(false);
     }
 
     /** Resets divider position. */
     public void resetDividerPosition() {
         mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
-        mSplitWindowManager.setResizingSplits(false);
         updateBounds(mDividePosition);
         mWinToken1 = null;
         mWinToken2 = null;
@@ -360,8 +358,8 @@
     @VisibleForTesting
     void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) {
         if (from == to) {
-            // No animation run, it should stop resizing here.
-            mSplitWindowManager.setResizingSplits(false);
+            // No animation run, still callback to stop resizing.
+            mSplitLayoutHandler.onLayoutSizeChanged(this);
             return;
         }
         ValueAnimator animator = ValueAnimator
@@ -425,7 +423,7 @@
             return;
         }
 
-        mDismissingParallaxPolicy.adjustDismissingSurface(t, leash1, leash2, dimLayer1, dimLayer2);
+        mDismissingEffectPolicy.adjustDismissingSurface(t, leash1, leash2, dimLayer1, dimLayer2);
     }
 
     /** Apply recorded task layout to the {@link WindowContainerTransaction}. */
@@ -543,7 +541,10 @@
      * Calculates and applies proper dismissing parallax offset and dimming value to hint users
      * dismissing gesture.
      */
-    private class DismissingParallaxPolicy {
+    private class DismissingEffectPolicy {
+        /** Indicates whether to offset splitting bounds to hint dismissing progress or not. */
+        private final boolean mApplyParallax;
+
         // The current dismissing side.
         int mDismissingSide = DOCKED_INVALID;
 
@@ -553,6 +554,10 @@
         // The dimming value to hint the dismissing side and progress.
         float mDismissingDimValue = 0.0f;
 
+        DismissingEffectPolicy(boolean applyDismissingParallax) {
+            mApplyParallax = applyDismissingParallax;
+        }
+
         /**
          * Applies a parallax to the task to hint dismissing progress.
          *
@@ -565,11 +570,11 @@
             mDismissingDimValue = 0;
 
             int totalDismissingDistance = 0;
-            if (position <= mDividerSnapAlgorithm.getFirstSplitTarget().position) {
+            if (position < mDividerSnapAlgorithm.getFirstSplitTarget().position) {
                 mDismissingSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
                 totalDismissingDistance = mDividerSnapAlgorithm.getDismissStartTarget().position
                         - mDividerSnapAlgorithm.getFirstSplitTarget().position;
-            } else if (position >= mDividerSnapAlgorithm.getLastSplitTarget().position) {
+            } else if (position > mDividerSnapAlgorithm.getLastSplitTarget().position) {
                 mDismissingSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
                 totalDismissingDistance = mDividerSnapAlgorithm.getLastSplitTarget().position
                         - mDividerSnapAlgorithm.getDismissEndTarget().position;
@@ -627,12 +632,14 @@
                     return false;
             }
 
-            t.setPosition(targetLeash,
-                    mTempRect.left + mDismissingParallaxOffset.x,
-                    mTempRect.top + mDismissingParallaxOffset.y);
-            // Transform the screen-based split bounds to surface-based crop bounds.
-            mTempRect.offsetTo(-mDismissingParallaxOffset.x, -mDismissingParallaxOffset.y);
-            t.setWindowCrop(targetLeash, mTempRect);
+            if (mApplyParallax) {
+                t.setPosition(targetLeash,
+                        mTempRect.left + mDismissingParallaxOffset.x,
+                        mTempRect.top + mDismissingParallaxOffset.y);
+                // Transform the screen-based split bounds to surface-based crop bounds.
+                mTempRect.offsetTo(-mDismissingParallaxOffset.x, -mDismissingParallaxOffset.y);
+                t.setWindowCrop(targetLeash, mTempRect);
+            }
             t.setAlpha(targetDimLayer, mDismissingDimValue)
                     .setVisibility(targetDimLayer, mDismissingDimValue > 0.001f);
             return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 47dceb3..08754d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -25,7 +25,6 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 
-import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
@@ -33,8 +32,6 @@
 import android.graphics.Region;
 import android.os.Binder;
 import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
 import android.view.IWindow;
 import android.view.InsetsState;
 import android.view.LayoutInflater;
@@ -59,7 +56,6 @@
     private Context mContext;
     private SurfaceControlViewHost mViewHost;
     private SurfaceControl mLeash;
-    private boolean mResizingSplits;
     private DividerView mDividerView;
 
     public interface ParentContainerCallbacks {
@@ -154,16 +150,6 @@
         mDividerView.setInteractive(interactive);
     }
 
-    void setResizingSplits(boolean resizing) {
-        if (resizing == mResizingSplits) return;
-        try {
-            ActivityTaskManager.getService().setSplitScreenResizing(resizing);
-            mResizingSplits = resizing;
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Error calling setSplitScreenResizing", e);
-        }
-    }
-
     /**
      * Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not
      * feasible.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 38079af..90074371 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -689,6 +689,8 @@
         pw.println(mUserId);
         pw.print(innerPrefix + "isShortcutEnabled=");
         pw.println(isShortcutEnabled());
+        pw.print(innerPrefix + "mIsSwipeToNotificationEnabled=");
+        pw.println(mIsSwipeToNotificationEnabled);
 
         if (mBackgroundPanelOrganizer != null) {
             mBackgroundPanelOrganizer.dump(pw);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index ff333c8c..2cb7d1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -244,6 +244,8 @@
         pw.println(TAG);
         pw.print(innerPrefix + "isOneHandedModeEnable=");
         pw.println(getSettingsOneHandedModeEnabled(resolver, userId));
+        pw.print(innerPrefix + "isSwipeToNotificationEnabled=");
+        pw.println(getSettingsSwipeToNotificationEnabled(resolver, userId));
         pw.print(innerPrefix + "oneHandedTimeOut=");
         pw.println(getSettingsOneHandedModeTimeout(resolver, userId));
         pw.print(innerPrefix + "tapsAppToExit=");
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 291cbb3..b6e5804 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
@@ -1292,13 +1292,17 @@
         }
         Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
                 ? mPipBoundsState.getBounds() : currentBounds;
+        final boolean existingAnimatorRunning = mPipAnimationController.getCurrentAnimator() != null
+                && mPipAnimationController.getCurrentAnimator().isRunning();
         final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
                 .getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds,
                         sourceHintRect, direction, startingAngle, rotationDelta);
         animator.setTransitionDirection(direction)
-                .setPipAnimationCallback(mPipAnimationCallback)
                 .setPipTransactionHandler(mPipTransactionHandler)
                 .setDuration(durationMs);
+        if (!existingAnimatorRunning) {
+            animator.setPipAnimationCallback(mPipAnimationCallback);
+        }
         if (isInPipDirection(direction)) {
             // Similar to auto-enter-pip transition, we use content overlay when there is no
             // source rect hint to enter PiP use bounds animation.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 328f3ed..b31e6e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -116,6 +116,12 @@
         if (mExitTransition == transition || info.getType() == TRANSIT_EXIT_PIP) {
             mExitTransition = null;
             if (info.getChanges().size() == 1) {
+                if (mFinishCallback != null) {
+                    mFinishCallback.onTransitionFinished(null, null);
+                    mFinishCallback = null;
+                    throw new RuntimeException("Previous callback not called, aborting exit PIP.");
+                }
+
                 final TransitionInfo.Change change = info.getChanges().get(0);
                 mFinishCallback = finishCallback;
                 startTransaction.apply();
@@ -129,6 +135,12 @@
         }
 
         if (info.getType() == TRANSIT_REMOVE_PIP) {
+            if (mFinishCallback != null) {
+                mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
+                mFinishCallback = null;
+                throw new RuntimeException("Previous callback not called, aborting remove PIP.");
+            }
+
             startTransaction.apply();
             finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
                     mPipBoundsState.getDisplayBounds());
@@ -159,6 +171,12 @@
             return false;
         }
 
+        if (mFinishCallback != null) {
+            mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
+            mFinishCallback = null;
+            throw new RuntimeException("Previous callback not called, aborting entering PIP.");
+        }
+
         // Show the wallpaper if there is a wallpaper change.
         if (wallpaper != null) {
             startTransaction.show(wallpaper.getLeash());
@@ -231,7 +249,7 @@
             if (tx != null) {
                 wct.setBoundsChangeTransaction(taskInfo.token, tx);
             }
-            mFinishCallback.onTransitionFinished(wct, null /* wctCallback */);
+            mFinishCallback.onTransitionFinished(wct, null /* callback */);
             mFinishCallback = null;
         }
         finishResizeForMenu(destinationBounds);
@@ -240,7 +258,7 @@
     @Override
     public void forceFinishTransition() {
         if (mFinishCallback == null) return;
-        mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCallback */);
+        mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
         mFinishCallback = null;
     }
 
@@ -286,7 +304,6 @@
             mPipBoundsState.setBounds(destinationBounds);
             onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */);
             sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
-            mFinishCallback = null;
             mPipTransitionState.setInSwipePipToHomeTransition(false);
             return true;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index a47a152..6440ef0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -19,11 +19,13 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.graphics.Rect;
 import android.view.SurfaceSession;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
@@ -37,11 +39,11 @@
 
     private boolean mIsActive = false;
 
-    MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
+    MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession,
+            SurfaceSession surfaceSession, IconProvider iconProvider,
             @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
-        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+        super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
                 stageTaskUnfoldController);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineManager.java
deleted file mode 100644
index a459c8d..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineManager.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.splitscreen;
-
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Binder;
-import android.view.IWindow;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.LayoutInflater;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.WindowlessWindowManager;
-import android.widget.FrameLayout;
-
-import com.android.wm.shell.R;
-
-/**
- * Handles drawing outline of the bounds of provided root surface. The outline will be drown with
- * the consideration of display insets like status bar, navigation bar and display cutout.
- */
-class OutlineManager extends WindowlessWindowManager {
-    private static final String WINDOW_NAME = "SplitOutlineLayer";
-    private final Context mContext;
-    private final Rect mRootBounds = new Rect();
-    private final Rect mTempRect = new Rect();
-    private final Rect mLastOutlineBounds = new Rect();
-    private final InsetsState mInsetsState = new InsetsState();
-    private final int mExpandedTaskBarHeight;
-    private OutlineView mOutlineView;
-    private SurfaceControlViewHost mViewHost;
-    private SurfaceControl mHostLeash;
-    private SurfaceControl mLeash;
-
-    OutlineManager(Context context, Configuration configuration) {
-        super(configuration, null /* rootSurface */, null /* hostInputToken */);
-        mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
-                null /* options */);
-        mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.taskbar_frame_height);
-    }
-
-    @Override
-    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
-        b.setParent(mHostLeash);
-    }
-
-    void inflate(SurfaceControl rootLeash, Rect rootBounds) {
-        if (mLeash != null || mViewHost != null) return;
-
-        mHostLeash = rootLeash;
-        mRootBounds.set(rootBounds);
-        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
-
-        final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
-                .inflate(R.layout.split_outline, null);
-        mOutlineView = rootLayout.findViewById(R.id.split_outline);
-
-        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
-                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
-        lp.width = mRootBounds.width();
-        lp.height = mRootBounds.height();
-        lp.token = new Binder();
-        lp.setTitle(WINDOW_NAME);
-        lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
-        // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
-        //  TRUSTED_OVERLAY for windowless window without input channel.
-        mViewHost.setView(rootLayout, lp);
-        mLeash = getSurfaceControl(mViewHost.getWindowToken());
-
-        drawOutline();
-    }
-
-    void release() {
-        if (mViewHost != null) {
-            mViewHost.release();
-            mViewHost = null;
-        }
-        mRootBounds.setEmpty();
-        mLastOutlineBounds.setEmpty();
-        mOutlineView = null;
-        mHostLeash = null;
-        mLeash = null;
-    }
-
-    @Nullable
-    SurfaceControl getOutlineLeash() {
-        return mLeash;
-    }
-
-    void setVisibility(boolean visible) {
-        if (mOutlineView != null) {
-            mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-        }
-    }
-
-    void setRootBounds(Rect rootBounds) {
-        if (mViewHost == null || mViewHost.getView() == null) {
-            return;
-        }
-
-        if (!mRootBounds.equals(rootBounds)) {
-            WindowManager.LayoutParams lp =
-                    (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
-            lp.width = rootBounds.width();
-            lp.height = rootBounds.height();
-            mViewHost.relayout(lp);
-            mRootBounds.set(rootBounds);
-            drawOutline();
-        }
-    }
-
-    void onInsetsChanged(InsetsState insetsState) {
-        if (!mInsetsState.equals(insetsState)) {
-            mInsetsState.set(insetsState);
-            drawOutline();
-        }
-    }
-
-    private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) {
-        outBounds.set(rootBounds);
-        final InsetsSource taskBarInsetsSource =
-                insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
-        // Only insets the divider bar with task bar when it's expanded so that the rounded corners
-        // will be drawn against task bar.
-        if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
-            outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds));
-        }
-
-        // Offset the coordinate from screen based to surface based.
-        outBounds.offset(-rootBounds.left, -rootBounds.top);
-    }
-
-    void drawOutline() {
-        if (mOutlineView == null) {
-            return;
-        }
-
-        computeOutlineBounds(mRootBounds, mInsetsState, mTempRect);
-        if (mTempRect.equals(mLastOutlineBounds)) {
-            return;
-        }
-
-        ViewGroup.MarginLayoutParams lp =
-                (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams();
-        lp.leftMargin = mTempRect.left;
-        lp.topMargin = mTempRect.top;
-        lp.width = mTempRect.width();
-        lp.height = mTempRect.height();
-        mOutlineView.setLayoutParams(lp);
-        mLastOutlineBounds.set(mTempRect);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineView.java
deleted file mode 100644
index 94dd9b2..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineView.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.splitscreen;
-
-import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
-import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
-import static android.view.RoundedCorner.POSITION_TOP_LEFT;
-import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.util.AttributeSet;
-import android.view.RoundedCorner;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.R;
-
-/** View for drawing split outline. */
-public class OutlineView extends View {
-    private final Paint mPaint = new Paint();
-    private final Path mPath = new Path();
-    private final float[] mRadii = new float[8];
-
-    public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-        mPaint.setStyle(Paint.Style.STROKE);
-        mPaint.setStrokeWidth(
-                getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
-        mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        // TODO(b/200850654): match the screen corners with the actual display decor.
-        mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
-        mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
-        mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
-        mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
-    }
-
-    private int getCornerRadius(@RoundedCorner.Position int position) {
-        final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
-        return roundedCorner == null ? 0 : roundedCorner.getRadius();
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        if (changed) {
-            mPath.reset();
-            mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW);
-        }
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        canvas.drawPath(mPath, mPaint);
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index dc8fb9f..51104e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -16,20 +16,16 @@
 
 package com.android.wm.shell.splitscreen;
 
-import android.annotation.CallSuper;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.graphics.Rect;
-import android.view.InsetsSourceControl;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
 /**
@@ -38,19 +34,15 @@
  *
  * @see StageCoordinator
  */
-class SideStage extends StageTaskListener implements
-        DisplayInsetsController.OnInsetsChangedListener {
+class SideStage extends StageTaskListener {
     private static final String TAG = SideStage.class.getSimpleName();
-    private final Context mContext;
-    private OutlineManager mOutlineManager;
 
     SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession,
+            SurfaceSession surfaceSession, IconProvider iconProvider,
             @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
-        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+        super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
                 stageTaskUnfoldController);
-        mContext = context;
     }
 
     void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
@@ -83,62 +75,4 @@
         wct.reparent(task.token, newParent, false /* onTop */);
         return true;
     }
-
-    @Nullable
-    public SurfaceControl getOutlineLeash() {
-        return mOutlineManager.getOutlineLeash();
-    }
-
-    @Override
-    @CallSuper
-    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
-        super.onTaskAppeared(taskInfo, leash);
-        if (isRootTask(taskInfo)) {
-            mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
-            enableOutline(true);
-        }
-    }
-
-    @Override
-    @CallSuper
-    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
-        super.onTaskInfoChanged(taskInfo);
-        if (isRootTask(taskInfo)) {
-            mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
-        }
-    }
-
-    private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
-        return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
-    }
-
-    void enableOutline(boolean enable) {
-        if (mOutlineManager == null) {
-            return;
-        }
-
-        if (enable) {
-            if (mRootTaskInfo != null) {
-                mOutlineManager.inflate(mRootLeash,
-                        mRootTaskInfo.configuration.windowConfiguration.getBounds());
-            }
-        } else {
-            mOutlineManager.release();
-        }
-    }
-
-    void setOutlineVisibility(boolean visible) {
-        mOutlineManager.setVisibility(visible);
-    }
-
-    @Override
-    public void insetsChanged(InsetsState insetsState) {
-        mOutlineManager.onInsetsChanged(insetsState);
-    }
-
-    @Override
-    public void insetsControlChanged(InsetsState insetsState,
-            InsetsSourceControl[] activeControls) {
-        insetsChanged(insetsState);
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDecorManager.java
new file mode 100644
index 0000000..8e5cc6d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDecorManager.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Binder;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.SurfaceUtils;
+
+/**
+ * Handles split decor like showing resizing hint for a specific split.
+ */
+class SplitDecorManager extends WindowlessWindowManager {
+    private static final String TAG = SplitDecorManager.class.getSimpleName();
+    private static final String RESIZING_BACKGROUND_SURFACE_NAME = "ResizingBackground";
+
+    private final IconProvider mIconProvider;
+    private final SurfaceSession mSurfaceSession;
+
+    private Drawable mIcon;
+    private ImageView mResizingIconView;
+    private SurfaceControlViewHost mViewHost;
+    private SurfaceControl mHostLeash;
+    private SurfaceControl mIconLeash;
+    private SurfaceControl mBackgroundLeash;
+
+    SplitDecorManager(Configuration configuration, IconProvider iconProvider,
+            SurfaceSession surfaceSession) {
+        super(configuration, null /* rootSurface */, null /* hostInputToken */);
+        mIconProvider = iconProvider;
+        mSurfaceSession = surfaceSession;
+    }
+
+    @Override
+    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+        // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
+        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+                .setContainerLayer()
+                .setName(TAG)
+                .setHidden(true)
+                .setParent(mHostLeash)
+                .setCallsite("SplitDecorManager#attachToParentSurface");
+        mIconLeash = builder.build();
+        b.setParent(mIconLeash);
+    }
+
+    void inflate(Context context, SurfaceControl rootLeash, Rect rootBounds) {
+        if (mIconLeash != null && mViewHost != null) {
+            return;
+        }
+
+        context = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
+                null /* options */);
+        mHostLeash = rootLeash;
+        mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), this);
+
+        final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context)
+                .inflate(R.layout.split_decor, null);
+        mResizingIconView = rootLayout.findViewById(R.id.split_resizing_icon);
+
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
+                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
+        lp.width = rootBounds.width();
+        lp.height = rootBounds.height();
+        lp.token = new Binder();
+        lp.setTitle(TAG);
+        lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+        // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
+        //  TRUSTED_OVERLAY for windowless window without input channel.
+        mViewHost.setView(rootLayout, lp);
+    }
+
+    void release(SurfaceControl.Transaction t) {
+        if (mViewHost != null) {
+            mViewHost.release();
+            mViewHost = null;
+        }
+        if (mIconLeash != null) {
+            t.remove(mIconLeash);
+            mIconLeash = null;
+        }
+        if (mBackgroundLeash != null) {
+            t.remove(mBackgroundLeash);
+            mBackgroundLeash = null;
+        }
+        mHostLeash = null;
+        mIcon = null;
+        mResizingIconView = null;
+    }
+
+    /** Showing resizing hint. */
+    void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
+            SurfaceControl.Transaction t) {
+        if (mResizingIconView == null) {
+            return;
+        }
+
+        if (mIcon == null) {
+            // TODO: add fade-in animation.
+            mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
+                    RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
+            t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
+                    .setLayer(mBackgroundLeash, SPLIT_DIVIDER_LAYER - 1)
+                    .show(mBackgroundLeash);
+
+            mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
+            mResizingIconView.setImageDrawable(mIcon);
+            mResizingIconView.setVisibility(View.VISIBLE);
+
+            WindowManager.LayoutParams lp =
+                    (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+            lp.width = mIcon.getIntrinsicWidth();
+            lp.height = mIcon.getIntrinsicHeight();
+            mViewHost.relayout(lp);
+            t.show(mIconLeash).setLayer(mIconLeash, SPLIT_DIVIDER_LAYER);
+        }
+
+        t.setPosition(mIconLeash,
+                newBounds.width() / 2 - mIcon.getIntrinsicWidth() / 2,
+                newBounds.height() / 2 - mIcon.getIntrinsicWidth() / 2);
+    }
+
+    /** Stops showing resizing hint. */
+    void onResized(Rect newBounds, SurfaceControl.Transaction t) {
+        if (mResizingIconView == null) {
+            return;
+        }
+
+        if (mIcon != null) {
+            mResizingIconView.setVisibility(View.GONE);
+            mResizingIconView.setImageDrawable(null);
+            t.remove(mBackgroundLeash).hide(mIconLeash);
+            mIcon = null;
+            mBackgroundLeash = null;
+        }
+    }
+
+    private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
+        final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
+        return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents();
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 3b75bfb..36f1406 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -54,6 +54,7 @@
 
 import com.android.internal.logging.InstanceId;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayImeController;
@@ -78,6 +79,7 @@
 /**
  * Class manages split-screen multitasking mode and implements the main interface
  * {@link SplitScreen}.
+ *
  * @see StageCoordinator
  */
 // TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
@@ -96,6 +98,7 @@
     private final Transitions mTransitions;
     private final TransactionPool mTransactionPool;
     private final SplitscreenEventLogger mLogger;
+    private final IconProvider mIconProvider;
     private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
 
     private StageCoordinator mStageCoordinator;
@@ -105,7 +108,7 @@
             RootTaskDisplayAreaOrganizer rootTDAOrganizer,
             ShellExecutor mainExecutor, DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
-            Transitions transitions, TransactionPool transactionPool,
+            Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
             Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
@@ -118,6 +121,7 @@
         mTransactionPool = transactionPool;
         mUnfoldControllerProvider = unfoldControllerProvider;
         mLogger = new SplitscreenEventLogger();
+        mIconProvider = iconProvider;
     }
 
     public SplitScreen asSplitScreen() {
@@ -140,7 +144,7 @@
             mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                     mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
                     mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
-                    mUnfoldControllerProvider);
+                    mIconProvider, mUnfoldControllerProvider);
         }
     }
 
@@ -165,10 +169,6 @@
         return mStageCoordinator.removeFromSideStage(taskId);
     }
 
-    public void setSideStageOutline(boolean enable) {
-        mStageCoordinator.setSideStageOutline(enable);
-    }
-
     public void setSideStagePosition(@SplitPosition int sideStagePosition) {
         mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
     }
@@ -319,9 +319,7 @@
         }
         transaction.apply();
         transaction.close();
-        return new RemoteAnimationTarget[]{
-                mStageCoordinator.getDividerBarLegacyTarget(),
-                mStageCoordinator.getOutlineLegacyTarget()};
+        return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 72d9880..3b62afc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -80,6 +80,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayImeController;
@@ -151,10 +152,12 @@
     /** Whether the device is supporting legacy split or not. */
     private boolean mUseLegacySplit;
 
-    @SplitScreen.StageType private int mDismissTop = NO_DISMISS;
+    @SplitScreen.StageType
+    private int mDismissTop = NO_DISMISS;
 
     /** The target stage to dismiss to when unlock after folded. */
-    @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+    @SplitScreen.StageType
+    private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
 
     private final Runnable mOnTransitionAnimationComplete = () -> {
         // If still playing, let it finish.
@@ -169,22 +172,23 @@
 
     private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
             new SplitWindowManager.ParentContainerCallbacks() {
-        @Override
-        public void attachToParentSurface(SurfaceControl.Builder b) {
-            mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
-        }
+                @Override
+                public void attachToParentSurface(SurfaceControl.Builder b) {
+                    mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
+                }
 
-        @Override
-        public void onLeashReady(SurfaceControl leash) {
-            mSyncQueue.runInSync(t -> applyDividerVisibility(t));
-        }
-    };
+                @Override
+                public void onLeashReady(SurfaceControl leash) {
+                    mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+                }
+            };
 
     StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, Transitions transitions,
             TransactionPool transactionPool, SplitscreenEventLogger logger,
+            IconProvider iconProvider,
             Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
         mContext = context;
         mDisplayId = displayId;
@@ -196,11 +200,13 @@
         mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
 
         mMainStage = new MainStage(
+                mContext,
                 mTaskOrganizer,
                 mDisplayId,
                 mMainStageListener,
                 mSyncQueue,
                 mSurfaceSession,
+                iconProvider,
                 mMainUnfoldController);
         mSideStage = new SideStage(
                 mContext,
@@ -209,10 +215,10 @@
                 mSideStageListener,
                 mSyncQueue,
                 mSurfaceSession,
+                iconProvider,
                 mSideUnfoldController);
         mDisplayImeController = displayImeController;
         mDisplayInsetsController = displayInsetsController;
-        mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
         mRootTDAOrganizer.registerListener(displayId, this);
         final DeviceStateManager deviceStateManager =
                 mContext.getSystemService(DeviceStateManager.class);
@@ -284,10 +290,6 @@
         return result;
     }
 
-    void setSideStageOutline(boolean enable) {
-        mSideStage.enableOutline(enable);
-    }
-
     /** Starts 2 tasks in one transition. */
     void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
             @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
@@ -697,9 +699,10 @@
 
         if (bothStageInvisible) {
             if (mExitSplitScreenOnHide
-            // Don't dismiss staged split when both stages are not visible due to sleeping display,
-            // like the cases keyguard showing or screen off.
-            || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
+                    // Don't dismiss staged split when both stages are not visible due to sleeping
+                    // display, like the cases keyguard showing or screen off.
+                    || (!mMainStage.mRootTaskInfo.isSleeping
+                    && !mSideStage.mRootTaskInfo.isSleeping)) {
                 exitSplitScreen(null /* childrenToTop */,
                         SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
             }
@@ -719,7 +722,6 @@
                 t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
                         .setVisibility(mMainStage.mRootLeash, bothStageVisible);
                 applyDividerVisibility(t);
-                applyOutlineVisibility(t);
             }
         });
     }
@@ -741,19 +743,6 @@
         }
     }
 
-    private void applyOutlineVisibility(SurfaceControl.Transaction t) {
-        final SurfaceControl outlineLeash = mSideStage.getOutlineLeash();
-        if (outlineLeash == null) {
-            return;
-        }
-
-        if (mDividerVisible) {
-            t.show(outlineLeash).setLayer(outlineLeash, SPLIT_DIVIDER_LAYER);
-        } else {
-            t.hide(outlineLeash);
-        }
-    }
-
     private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
         final boolean hasChildren = stageListener.mHasChildren;
         final boolean isSideStage = stageListener == mSideStageListener;
@@ -817,8 +806,11 @@
 
     @Override
     public void onLayoutSizeChanging(SplitLayout layout) {
-        mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
-        mSideStage.setOutlineVisibility(false);
+        mSyncQueue.runInSync(t -> {
+            updateSurfaceBounds(layout, t);
+            mMainStage.onResizing(getMainStageBounds(), t);
+            mSideStage.onResizing(getSideStageBounds(), t);
+        });
     }
 
     @Override
@@ -827,8 +819,11 @@
         updateWindowBounds(layout, wct);
         updateUnfoldBounds();
         mSyncQueue.queue(wct);
-        mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
-        mSideStage.setOutlineVisibility(true);
+        mSyncQueue.runInSync(t -> {
+            updateSurfaceBounds(layout, t);
+            mMainStage.onResized(getMainStageBounds(), t);
+            mSideStage.onResized(getSideStageBounds(), t);
+        });
         mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
     }
 
@@ -893,7 +888,7 @@
         if (mSplitLayout == null) {
             mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
                     mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
-                    mDisplayImeController, mTaskOrganizer);
+                    mDisplayImeController, mTaskOrganizer, false /* applyDismissingParallax */);
             mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
 
             if (mMainUnfoldController != null && mSideUnfoldController != null) {
@@ -1216,18 +1211,6 @@
                 null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
     }
 
-    RemoteAnimationTarget getOutlineLegacyTarget() {
-        final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds();
-        // Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to
-        // distinguish as a split auxiliary target in Launcher.
-        return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
-                mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */,
-                null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
-                new android.graphics.Point(0, 0) /* position */, bounds, bounds,
-                new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
-                null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
-    }
-
     @Override
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 6f1a09d..5100c56 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -26,6 +26,7 @@
 import android.annotation.CallSuper;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.SparseArray;
@@ -35,9 +36,11 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SurfaceUtils;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.split.SplitDecorManager;
 
 import java.io.PrintWriter;
 
@@ -72,25 +75,31 @@
         void onNoLongerSupportMultiWindow();
     }
 
+    private final Context mContext;
     private final StageListenerCallbacks mCallbacks;
     private final SurfaceSession mSurfaceSession;
-    protected final SyncTransactionQueue mSyncQueue;
+    private final SyncTransactionQueue mSyncQueue;
+    private final IconProvider mIconProvider;
 
     protected ActivityManager.RunningTaskInfo mRootTaskInfo;
     protected SurfaceControl mRootLeash;
     protected SurfaceControl mDimLayer;
     protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
     private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
+    // TODO(b/204308910): Extracts SplitDecorManager related code to common package.
+    private SplitDecorManager mSplitDecorManager;
 
     private final StageTaskUnfoldController mStageTaskUnfoldController;
 
-    StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
+    StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession,
+            SurfaceSession surfaceSession, IconProvider iconProvider,
             @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+        mContext = context;
         mCallbacks = callbacks;
         mSyncQueue = syncQueue;
         mSurfaceSession = surfaceSession;
+        mIconProvider = iconProvider;
         mStageTaskUnfoldController = stageTaskUnfoldController;
         taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
     }
@@ -142,13 +151,14 @@
         if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
             mRootLeash = leash;
             mRootTaskInfo = taskInfo;
+            mSplitDecorManager = new SplitDecorManager(
+                    mRootTaskInfo.configuration,
+                    mIconProvider,
+                    mSurfaceSession);
             mCallbacks.onRootTaskAppeared();
             sendStatusChanged();
-            mSyncQueue.runInSync(t -> {
-                t.hide(mRootLeash);
-                mDimLayer =
-                        SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
-            });
+            mSyncQueue.runInSync(t -> mDimLayer =
+                    SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession));
         } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
             final int taskId = taskInfo.taskId;
             mChildrenLeashes.put(taskId, leash);
@@ -179,6 +189,17 @@
             return;
         }
         if (mRootTaskInfo.taskId == taskInfo.taskId) {
+            // Inflates split decor view only when the root task is visible.
+            if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
+                mSyncQueue.runInSync(t -> {
+                    if (taskInfo.isVisible) {
+                        mSplitDecorManager.inflate(mContext, mRootLeash,
+                                taskInfo.configuration.windowConfiguration.getBounds());
+                    } else {
+                        mSplitDecorManager.release(t);
+                    }
+                });
+            }
             mRootTaskInfo = taskInfo;
         } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
             mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
@@ -205,8 +226,11 @@
         final int taskId = taskInfo.taskId;
         if (mRootTaskInfo.taskId == taskId) {
             mCallbacks.onRootTaskVanished();
-            mSyncQueue.runInSync(t -> t.remove(mDimLayer));
             mRootTaskInfo = null;
+            mSyncQueue.runInSync(t -> {
+                t.remove(mDimLayer);
+                mSplitDecorManager.release(t);
+            });
         } else if (mChildrenTaskInfo.contains(taskId)) {
             mChildrenTaskInfo.remove(taskId);
             mChildrenLeashes.remove(taskId);
@@ -237,6 +261,18 @@
         }
     }
 
+    void onResizing(Rect newBounds, SurfaceControl.Transaction t) {
+        if (mSplitDecorManager != null && mRootTaskInfo != null) {
+            mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, t);
+        }
+    }
+
+    void onResized(Rect newBounds, SurfaceControl.Transaction t) {
+        if (mSplitDecorManager != null) {
+            mSplitDecorManager.onResized(newBounds, t);
+        }
+    }
+
     void setBounds(Rect bounds, WindowContainerTransaction wct) {
         wct.setBounds(mRootTaskInfo.token, bounds);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
index 574e379..60a6cd7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
@@ -882,7 +882,7 @@
         if (mSplitLayout == null) {
             mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
                     mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
-                    mDisplayImeController, mTaskOrganizer);
+                    mDisplayImeController, mTaskOrganizer, true /* applyDismissingParallax */);
             mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
 
             if (mMainUnfoldController != null && mSideUnfoldController != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 38122ff..e2a72bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -314,7 +314,7 @@
 
         @Override
         public void stopAnimation() {
-            if (mIconAnimator != null) {
+            if (mIconAnimator != null && mIconAnimator.isRunning()) {
                 mIconAnimator.end();
             }
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index b4caeb5..73eebad 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -68,7 +68,8 @@
                 mSplitLayoutHandler,
                 mCallbacks,
                 mDisplayImeController,
-                mTaskOrganizer));
+                mTaskOrganizer,
+                false /* applyDismissingParallax */));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index 2bcc45e..c972067 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -25,10 +25,13 @@
 import android.view.SurfaceSession;
 import android.window.WindowContainerTransaction;
 
+import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
@@ -41,22 +44,24 @@
 /** Tests for {@link MainStage} */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class MainStageTests {
+public class MainStageTests extends ShellTestCase {
     @Mock private ShellTaskOrganizer mTaskOrganizer;
     @Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
     @Mock private SyncTransactionQueue mSyncQueue;
     @Mock private ActivityManager.RunningTaskInfo mRootTaskInfo;
     @Mock private SurfaceControl mRootLeash;
+    @Mock private IconProvider mIconProvider;
     private WindowContainerTransaction mWct = new WindowContainerTransaction();
     private SurfaceSession mSurfaceSession = new SurfaceSession();
     private MainStage mMainStage;
 
     @Before
+    @UiThreadTest
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
-        mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue,
-                mSurfaceSession, null);
+        mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
+                mSyncQueue, mSurfaceSession, mIconProvider, null);
         mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
index 838aa81..1857faa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
@@ -33,6 +33,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -54,6 +55,7 @@
     @Mock private SyncTransactionQueue mSyncQueue;
     @Mock private ActivityManager.RunningTaskInfo mRootTask;
     @Mock private SurfaceControl mRootLeash;
+    @Mock private IconProvider mIconProvider;
     @Spy private WindowContainerTransaction mWct;
     private SurfaceSession mSurfaceSession = new SurfaceSession();
     private SideStage mSideStage;
@@ -64,7 +66,7 @@
         MockitoAnnotations.initMocks(this);
         mRootTask = new TestRunningTaskInfoBuilder().build();
         mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
-                mSyncQueue, mSurfaceSession, null);
+                mSyncQueue, mSurfaceSession, mIconProvider, null);
         mSideStage.onTaskAppeared(mRootTask, mRootLeash);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 05496b0..d5dee82 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -55,6 +55,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
@@ -89,6 +90,7 @@
     @Mock private Transitions mTransitions;
     @Mock private SurfaceSession mSurfaceSession;
     @Mock private SplitscreenEventLogger mLogger;
+    @Mock private IconProvider mIconProvider;
     private SplitLayout mSplitLayout;
     private MainStage mMainStage;
     private SideStage mSideStage;
@@ -107,11 +109,13 @@
         doReturn(mockExecutor).when(mTransitions).getAnimExecutor();
         doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire();
         mSplitLayout = SplitTestUtils.createMockSplitLayout();
-        mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
-                StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, null);
+        mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
+                StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
+                mIconProvider, null);
         mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
         mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
-                StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, null);
+                StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
+                mIconProvider, null);
         mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
         mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
                 mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 3ed72e2..53d5076 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -35,10 +35,13 @@
 import android.view.SurfaceSession;
 import android.window.WindowContainerTransaction;
 
+import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
@@ -57,7 +60,7 @@
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public final class StageTaskListenerTests {
+public final class StageTaskListenerTests extends ShellTestCase {
     private static final boolean ENABLE_SHELL_TRANSITIONS =
             SystemProperties.getBoolean("persist.debug.shell_transit", false);
 
@@ -68,6 +71,8 @@
     @Mock
     private SyncTransactionQueue mSyncQueue;
     @Mock
+    private IconProvider mIconProvider;
+    @Mock
     private StageTaskUnfoldController mStageTaskUnfoldController;
     @Captor
     private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
@@ -77,14 +82,17 @@
     private StageTaskListener mStageTaskListener;
 
     @Before
+    @UiThreadTest
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mStageTaskListener = new StageTaskListener(
+                mContext,
                 mTaskOrganizer,
                 DEFAULT_DISPLAY,
                 mCallbacks,
                 mSyncQueue,
                 mSurfaceSession,
+                mIconProvider,
                 mStageTaskUnfoldController);
         mRootTask = new TestRunningTaskInfoBuilder().build();
         mRootTask.parentTaskId = INVALID_TASK_ID;
diff --git a/media/Android.bp b/media/Android.bp
index aa07c13..15b24b2 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -68,13 +68,17 @@
         "aidl/android/media/audio/common/AudioGain.aidl",
         "aidl/android/media/audio/common/AudioGainConfig.aidl",
         "aidl/android/media/audio/common/AudioGainMode.aidl",
+        "aidl/android/media/audio/common/AudioInputFlags.aidl",
+        "aidl/android/media/audio/common/AudioIoFlags.aidl",
         "aidl/android/media/audio/common/AudioMMapPolicy.aidl",
         "aidl/android/media/audio/common/AudioMMapPolicyInfo.aidl",
         "aidl/android/media/audio/common/AudioMMapPolicyType.aidl",
         "aidl/android/media/audio/common/AudioMode.aidl",
         "aidl/android/media/audio/common/AudioOffloadInfo.aidl",
+        "aidl/android/media/audio/common/AudioOutputFlags.aidl",
         "aidl/android/media/audio/common/AudioPort.aidl",
         "aidl/android/media/audio/common/AudioPortConfig.aidl",
+        "aidl/android/media/audio/common/AudioPortDeviceExt.aidl",
         "aidl/android/media/audio/common/AudioPortExt.aidl",
         "aidl/android/media/audio/common/AudioPortMixExt.aidl",
         "aidl/android/media/audio/common/AudioPortMixExtUseCase.aidl",
diff --git a/media/aidl/android/media/audio/common/AudioGain.aidl b/media/aidl/android/media/audio/common/AudioGain.aidl
index 90fdeb6..dbfcd8c 100644
--- a/media/aidl/android/media/audio/common/AudioGain.aidl
+++ b/media/aidl/android/media/audio/common/AudioGain.aidl
@@ -43,4 +43,6 @@
     int minRampMs;
     /** Maximum ramp duration in milliseconds. */
     int maxRampMs;
+    /** Indicates whether it is allowed to use this stage for volume control. */
+    boolean useForVolume;
 }
diff --git a/media/aidl/android/media/audio/common/AudioInputFlags.aidl b/media/aidl/android/media/audio/common/AudioInputFlags.aidl
new file mode 100644
index 0000000..e4b6ec2
--- /dev/null
+++ b/media/aidl/android/media/audio/common/AudioInputFlags.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * Specifies options applicable to audio input. These can be functional
+ * requests or performance requests. These flags apply both to audio ports and
+ * audio streams. Flags specified for an audio stream are usually used to find
+ * the best matching audio port for it.
+ *
+ * {@hide}
+ */
+@VintfStability
+@Backing(type="int")
+enum AudioInputFlags {
+    /**
+     * Input is optimized for decreasing audio latency.
+     */
+    FAST = 0,
+    /**
+     * Input is for capturing "hotword" audio commands.
+     */
+    HW_HOTWORD = 1,
+    /**
+     * Input stream should only have minimal signal processing applied.
+     */
+    RAW = 2,
+    /**
+     * Input stream needs to be synchronized with an output stream.
+     */
+    SYNC = 3,
+    /**
+     * Input uses MMAP no IRQ mode--direct memory mapping with the hardware.
+     */
+    MMAP_NOIRQ = 4,
+    /**
+     * Input is used for receiving VoIP audio.
+     */
+    VOIP_TX = 5,
+    /**
+     * Input stream contains AV synchronization markers embedded.
+     */
+    HW_AV_SYNC = 6,
+    /**
+     * Input contains an encoded audio stream.
+     */
+    DIRECT = 7,
+}
diff --git a/core/java/android/companion/Association.aidl b/media/aidl/android/media/audio/common/AudioIoFlags.aidl
similarity index 60%
copy from core/java/android/companion/Association.aidl
copy to media/aidl/android/media/audio/common/AudioIoFlags.aidl
index 2a28f1f..f978fb6 100644
--- a/core/java/android/companion/Association.aidl
+++ b/media/aidl/android/media/audio/common/AudioIoFlags.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -13,6 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion;
 
-parcelable Association;
+package android.media.audio.common;
+
+/**
+ * Stores a bitmask of input or output flags.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+union AudioIoFlags {
+    /** Bitmask indexed by AudioInputFlags. */
+    int input;
+    /** Bitmask indexed by AudioOutputFlags. */
+    int output;
+}
diff --git a/media/aidl/android/media/audio/common/AudioOutputFlags.aidl b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl
new file mode 100644
index 0000000..0505036
--- /dev/null
+++ b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * Specifies options applicable to audio output. These can be functional
+ * requests or performance requests. These flags apply both to audio ports and
+ * audio streams. Flags specified for an audio stream are usually used to find
+ * the best matching audio port for it.
+ *
+ * {@hide}
+ */
+@VintfStability
+@Backing(type="int")
+enum AudioOutputFlags {
+    /**
+     * Output must not be altered by the framework, it bypasses software mixers.
+     */
+    DIRECT = 0,
+    /**
+     * When used with audio ports, indicates the "main" (primary) port. This
+     * port is opened by default and receives routing, audio mode and volume
+     * controls related to voice calls.
+     */
+    PRIMARY = 1,
+    /**
+     * Output is optimized for decreasing audio latency.
+     */
+    FAST = 2,
+    /**
+     * Output is optimized for having the low power consumption.
+     */
+    DEEP_BUFFER = 3,
+    /**
+     * Output is compressed audio format, intended for hardware decoding.
+     */
+    COMPRESS_OFFLOAD = 4,
+    /**
+     * Write operations must return as fast as possible instead of
+     * being blocked until all provided data has been consumed.
+     */
+    NON_BLOCKING = 5,
+    /**
+     * Output stream contains AV synchronization markers embedded.
+     */
+    HW_AV_SYNC = 6,
+    /**
+     * Used to support ultrasonic communication with beacons.
+     * Note: "TTS" here means "Transmitted Through Speaker",
+     * not "Text-to-Speech".
+     */
+    TTS = 7,
+    /**
+     * Output stream should only have minimal signal processing applied.
+     */
+    RAW = 8,
+    /**
+     * Output stream needs to be synchronized with an input stream.
+     */
+    SYNC = 9,
+    /**
+     * Output stream is encoded according to IEC958.
+     */
+    IEC958_NONAUDIO = 10,
+    /**
+     * Output must not be altered by the framework and hardware.
+     */
+    DIRECT_PCM = 11,
+    /**
+     * Output uses MMAP no IRQ mode--direct memory mapping with the hardware.
+     */
+    MMAP_NOIRQ = 12,
+    /**
+     * Output is used for transmitting VoIP audio.
+     */
+    VOIP_RX = 13,
+    /**
+     * Output is used for music playback during telephony calls.
+     */
+    INCALL_MUSIC = 14,
+    /**
+     * The rendered must ignore any empty blocks between compressed audio
+     * tracks.
+     */
+    GAPLESS_OFFLOAD = 15,
+}
diff --git a/media/aidl/android/media/audio/common/AudioPort.aidl b/media/aidl/android/media/audio/common/AudioPort.aidl
index 19652bc..8e1c5af 100644
--- a/media/aidl/android/media/audio/common/AudioPort.aidl
+++ b/media/aidl/android/media/audio/common/AudioPort.aidl
@@ -17,6 +17,7 @@
 package android.media.audio.common;
 
 import android.media.audio.common.AudioGain;
+import android.media.audio.common.AudioIoFlags;
 import android.media.audio.common.AudioPortConfig;
 import android.media.audio.common.AudioPortExt;
 import android.media.audio.common.AudioProfile;
@@ -45,6 +46,10 @@
      */
     AudioProfile[] profiles;
     /**
+     * I/O feature flags.
+     */
+    AudioIoFlags flags;
+    /**
      * ExtraAudioDescriptors supported by this port. Used for formats not
      * recognized by the platform. The audio capability is described by a
      * hardware descriptor.
diff --git a/media/aidl/android/media/audio/common/AudioPortConfig.aidl b/media/aidl/android/media/audio/common/AudioPortConfig.aidl
index 44850f4..e3a9374 100644
--- a/media/aidl/android/media/audio/common/AudioPortConfig.aidl
+++ b/media/aidl/android/media/audio/common/AudioPortConfig.aidl
@@ -19,6 +19,7 @@
 import android.media.audio.common.AudioChannelLayout;
 import android.media.audio.common.AudioFormatDescription;
 import android.media.audio.common.AudioGainConfig;
+import android.media.audio.common.AudioIoFlags;
 import android.media.audio.common.AudioPortExt;
 import android.media.audio.common.Int;
 
@@ -44,6 +45,8 @@
     @nullable AudioFormatDescription format;
     /** Gain to apply. Can be left unspecified. */
     @nullable AudioGainConfig gain;
+    /** I/O feature flags. Can be left unspecified. */
+    @nullable AudioIoFlags flags;
     /** Extra parameters depending on the port role. */
     AudioPortExt ext;
 }
diff --git a/media/aidl/android/media/audio/common/AudioPortDeviceExt.aidl b/media/aidl/android/media/audio/common/AudioPortDeviceExt.aidl
new file mode 100644
index 0000000..940566c
--- /dev/null
+++ b/media/aidl/android/media/audio/common/AudioPortDeviceExt.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioDevice;
+import android.media.audio.common.AudioFormatDescription;
+
+/**
+ * Extra parameters which are specified when the audio port is in the device role.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable AudioPortDeviceExt {
+    /** Audio device specification. */
+    AudioDevice device;
+    /**
+     * List of supported encoded formats. Specified for ports that perform
+     * hardware-accelerated decoding or transcoding, or connected to external
+     * hardware.
+     */
+    AudioFormatDescription[] encodedFormats;
+}
diff --git a/media/aidl/android/media/audio/common/AudioPortExt.aidl b/media/aidl/android/media/audio/common/AudioPortExt.aidl
index 9b60c57..c4681cb 100644
--- a/media/aidl/android/media/audio/common/AudioPortExt.aidl
+++ b/media/aidl/android/media/audio/common/AudioPortExt.aidl
@@ -16,7 +16,7 @@
 
 package android.media.audio.common;
 
-import android.media.audio.common.AudioDevice;
+import android.media.audio.common.AudioPortDeviceExt;
 import android.media.audio.common.AudioPortMixExt;
 
 /**
@@ -30,9 +30,9 @@
 union AudioPortExt {
     /** Represents an empty union. Value is ignored. */
     boolean unspecified;
-    /** Audio device specification. */
-    AudioDevice device;
-    /** Mix specific info. */
+    /** Information specific to device ports. */
+    AudioPortDeviceExt device;
+    /** Information specific to mix ports. */
     AudioPortMixExt mix;
     /** Audio session identifier. */
     int session;
diff --git a/media/aidl/android/media/audio/common/AudioPortMixExt.aidl b/media/aidl/android/media/audio/common/AudioPortMixExt.aidl
index 54c5d8b..f3613a4 100644
--- a/media/aidl/android/media/audio/common/AudioPortMixExt.aidl
+++ b/media/aidl/android/media/audio/common/AudioPortMixExt.aidl
@@ -30,4 +30,16 @@
     int handle;
     /** Parameters specific to the mix use case. */
     AudioPortMixExtUseCase usecase;
+    /**
+     * Maximum number of input or output streams that can be simultaneously
+     * opened for this port.
+     */
+    int maxOpenStreamCount;
+    /**
+     * Maximum number of input or output streams that can be simultaneously
+     * active for this port.
+     */
+    int maxActiveStreamCount;
+    /** Mute duration while changing device, when used for output. */
+    int recommendedMuteDurationMs;
 }
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioGain.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioGain.aidl
index 71891eb..adc5b672 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioGain.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioGain.aidl
@@ -43,4 +43,5 @@
   int stepValue;
   int minRampMs;
   int maxRampMs;
+  boolean useForVolume;
 }
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl
new file mode 100644
index 0000000..8a5dae0
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioInputFlags {
+  FAST = 0,
+  HW_HOTWORD = 1,
+  RAW = 2,
+  SYNC = 3,
+  MMAP_NOIRQ = 4,
+  VOIP_TX = 5,
+  HW_AV_SYNC = 6,
+  DIRECT = 7,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioIoFlags.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioIoFlags.aidl
new file mode 100644
index 0000000..4a46725
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioIoFlags.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+union AudioIoFlags {
+  int input;
+  int output;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl
new file mode 100644
index 0000000..ed16d17
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioOutputFlags {
+  DIRECT = 0,
+  PRIMARY = 1,
+  FAST = 2,
+  DEEP_BUFFER = 3,
+  COMPRESS_OFFLOAD = 4,
+  NON_BLOCKING = 5,
+  HW_AV_SYNC = 6,
+  TTS = 7,
+  RAW = 8,
+  SYNC = 9,
+  IEC958_NONAUDIO = 10,
+  DIRECT_PCM = 11,
+  MMAP_NOIRQ = 12,
+  VOIP_RX = 13,
+  INCALL_MUSIC = 14,
+  GAPLESS_OFFLOAD = 15,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl
index ceb3774..8f563b3 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl
@@ -38,6 +38,7 @@
   int id;
   @utf8InCpp String name;
   android.media.audio.common.AudioProfile[] profiles;
+  android.media.audio.common.AudioIoFlags flags;
   android.media.audio.common.ExtraAudioDescriptor[] extraAudioDescriptors;
   android.media.audio.common.AudioGain[] gains;
   android.media.audio.common.AudioPortConfig activeConfig;
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl
index fcc5458..78967b4 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl
@@ -40,5 +40,6 @@
   @nullable android.media.audio.common.AudioChannelLayout channelMask;
   @nullable android.media.audio.common.AudioFormatDescription format;
   @nullable android.media.audio.common.AudioGainConfig gain;
+  @nullable android.media.audio.common.AudioIoFlags flags;
   android.media.audio.common.AudioPortExt ext;
 }
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortDeviceExt.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortDeviceExt.aidl
new file mode 100644
index 0000000..2e8124a
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortDeviceExt.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioPortDeviceExt {
+  android.media.audio.common.AudioDevice device;
+  android.media.audio.common.AudioFormatDescription[] encodedFormats;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortExt.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortExt.aidl
index 241e4e6..af9d9c4 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortExt.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortExt.aidl
@@ -36,7 +36,7 @@
 @JavaDerive(equals=true, toString=true) @VintfStability
 union AudioPortExt {
   boolean unspecified;
-  android.media.audio.common.AudioDevice device;
+  android.media.audio.common.AudioPortDeviceExt device;
   android.media.audio.common.AudioPortMixExt mix;
   int session;
 }
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortMixExt.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortMixExt.aidl
index 547396c..5b74c0d 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortMixExt.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortMixExt.aidl
@@ -37,4 +37,7 @@
 parcelable AudioPortMixExt {
   int handle;
   android.media.audio.common.AudioPortMixExtUseCase usecase;
+  int maxOpenStreamCount;
+  int maxActiveStreamCount;
+  int recommendedMuteDurationMs;
 }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d061bd3..a6c6fe6 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -96,8 +96,6 @@
     private Context mOriginalContext;
     private Context mApplicationContext;
     private long mVolumeKeyUpTime;
-    private boolean mUseFixedVolumeInitialized;
-    private boolean mUseFixedVolume;
     private static final String TAG = "AudioManager";
     private static final boolean DEBUG = false;
     private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler();
@@ -893,19 +891,13 @@
      * </ul>
      */
     public boolean isVolumeFixed() {
-        synchronized (this) {
-            try {
-                if (!mUseFixedVolumeInitialized) {
-                    mUseFixedVolume = getContext().getResources().getBoolean(
-                            com.android.internal.R.bool.config_useFixedVolume);
-                }
-            } catch (Exception e) {
-            } finally {
-                // only ever try once, so always consider initialized even if query failed
-                mUseFixedVolumeInitialized = true;
-            }
+        boolean res = false;
+        try {
+            res = getService().isVolumeFixed();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error querying isVolumeFixed", e);
         }
-        return mUseFixedVolume;
+        return res;
     }
 
     /**
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5b05cd3..d736c25 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -454,4 +454,6 @@
     void registerSpatializerOutputCallback(in ISpatializerOutputCallback cb);
 
     void unregisterSpatializerOutputCallback(in ISpatializerOutputCallback cb);
+
+    boolean isVolumeFixed();
 }
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 49477b9..374cc75 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -182,10 +182,15 @@
         public String mName;
         public int mValue;
         public boolean mDefault;
+        public boolean mInternal;
         public Feature(String name, int value, boolean def) {
+            this(name, value, def, false /* internal */);
+        }
+        public Feature(String name, int value, boolean def, boolean internal) {
             mName = name;
             mValue = value;
             mDefault = def;
+            mInternal = internal;
         }
     }
 
@@ -579,6 +584,11 @@
         public static final String FEATURE_LowLatency = "low-latency";
 
         /**
+         * Do not include in REGULAR_CODECS list in MediaCodecList.
+         */
+        private static final String FEATURE_SpecialCodec = "special-codec";
+
+        /**
          * <b>video encoder only</b>: codec supports quantization parameter bounds.
          * @see MediaFormat#KEY_VIDEO_QP_MAX
          * @see MediaFormat#KEY_VIDEO_QP_MIN
@@ -616,6 +626,8 @@
             new Feature(FEATURE_MultipleFrames,   (1 << 5), false),
             new Feature(FEATURE_DynamicTimestamp, (1 << 6), false),
             new Feature(FEATURE_LowLatency,       (1 << 7), true),
+            // feature to exclude codec from REGULAR codec list
+            new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true),
         };
 
         private static final Feature[] encoderFeatures = {
@@ -623,6 +635,8 @@
             new Feature(FEATURE_MultipleFrames, (1 << 1), false),
             new Feature(FEATURE_DynamicTimestamp, (1 << 2), false),
             new Feature(FEATURE_QpBounds, (1 << 3), false),
+            // feature to exclude codec from REGULAR codec list
+            new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true),
         };
 
         /** @hide */
@@ -630,7 +644,9 @@
             Feature[] features = getValidFeatures();
             String[] res = new String[features.length];
             for (int i = 0; i < res.length; i++) {
-                res[i] = features[i].mName;
+                if (!features[i].mInternal) {
+                    res[i] = features[i].mName;
+                }
             }
             return res;
         }
@@ -778,6 +794,10 @@
 
             // check feature support
             for (Feature feat: getValidFeatures()) {
+                if (feat.mInternal) {
+                    continue;
+                }
+
                 Integer yesNo = (Integer)map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
                 if (yesNo == null) {
                     continue;
@@ -1091,7 +1111,9 @@
                     mFlagsRequired |= feat.mValue;
                 }
                 mFlagsSupported |= feat.mValue;
-                mDefaultFormat.setInteger(key, 1);
+                if (!feat.mInternal) {
+                    mDefaultFormat.setInteger(key, 1);
+                }
                 // TODO restrict features by mFlagsVerified once all codecs reliably verify them
             }
         }
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 08a648a..4891d74 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -754,7 +754,7 @@
      * <p>This key is only used during decoding.
      */
     public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT =
-            "max-output-channel_count";
+            "max-output-channel-count";
 
     /**
      * A key describing the number of frames to trim from the start of the decoded audio stream.
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 9dc4949..405f9ee 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -751,8 +751,13 @@
      */
     @Result
     public int tune(@NonNull FrontendSettings settings) {
-        Log.d(TAG, "Tune to " + settings.getFrequency());
-        mFrontendType = settings.getType();
+        final int type = settings.getType();
+        if (mFrontendHandle != null && type != mFrontendType) {
+            Log.e(TAG, "Frontend was opened with type " + mFrontendType + ", new type is " + type);
+            return RESULT_INVALID_STATE;
+        }
+        Log.d(TAG, "Tune to " + settings.getFrequencyLong());
+        mFrontendType = type;
         if (mFrontendType == FrontendSettings.TYPE_DTMB) {
             if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
                     TunerVersionChecker.TUNER_VERSION_1_1, "Tuner with DTMB Frontend")) {
diff --git a/packages/PackageInstaller/OWNERS b/packages/PackageInstaller/OWNERS
index c633113..2736870 100644
--- a/packages/PackageInstaller/OWNERS
+++ b/packages/PackageInstaller/OWNERS
@@ -1,7 +1,5 @@
 svetoslavganov@google.com
-toddke@google.com
-patb@google.com
-suprabh@google.com
+include /PACKAGE_MANAGER_OWNERS
 
 # For automotive related changes
 rogerxue@google.com
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 1d29966..ed12e83 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -68,7 +68,7 @@
             }
 
             for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) {
-                if (entry.getValue().equals(BluetoothUuid.BASE_UUID)) {
+                if (entry.getValue().equals(BluetoothUuid.CAP)) {
                     return entry.getKey();
                 }
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 3347920..5e2f310 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -338,7 +338,7 @@
                             .getGroupUuidMapByDevice(cachedDevice.getDevice());
                     if (groupIdMap != null) {
                         for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) {
-                            if (entry.getValue().equals(BluetoothUuid.BASE_UUID)) {
+                            if (entry.getValue().equals(BluetoothUuid.CAP)) {
                                 cachedDevice.setGroupId(entry.getKey());
                                 break;
                             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
index 274696b..468aa05 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
@@ -183,6 +183,20 @@
         return setBadge(badge);
     }
 
+    /**
+     * Sets the managed badge to this user icon if the device has a device owner.
+     */
+    public UserIconDrawable setBadgeIfManagedDevice(Context context) {
+        Drawable badge = null;
+        boolean deviceOwnerExists = context.getSystemService(DevicePolicyManager.class)
+                .getDeviceOwnerComponentOnAnyUser() != null;
+        if (deviceOwnerExists) {
+            badge = getDrawableForDisplayDensity(
+                    context, com.android.internal.R.drawable.ic_corp_badge_case);
+        }
+        return setBadge(badge);
+    }
+
     public void setBadgeRadius(float radius) {
         mBadgeRadius = radius;
         onBoundsChange(getBounds());
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS
index a0e28ba..5b2e1e3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS
@@ -1,5 +1,6 @@
 # Default reviewers for this and subdirectories.
 takaoka@google.com
 yukawa@google.com
+wilsonwu@google.com
 
 # Emergency approvers in case the above are not available
\ No newline at end of file
diff --git a/packages/SettingsProvider/OWNERS b/packages/SettingsProvider/OWNERS
index 6c61d4b9..5ade971 100644
--- a/packages/SettingsProvider/OWNERS
+++ b/packages/SettingsProvider/OWNERS
@@ -2,6 +2,4 @@
 hackbod@google.com
 narayan@google.com
 svetoslavganov@google.com
-schfan@google.com
-toddke@google.com
-patb@google.com
+include /PACKAGE_MANAGER_OWNERS
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 989010e..a16f5cd 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -123,18 +123,18 @@
 
     /** Interface for launching Intents, which can differ on the lockscreen */
     interface IntentStarter {
-        default void startFromAction(SmartspaceAction action, View v) {
+        default void startFromAction(SmartspaceAction action, View v, boolean showOnLockscreen) {
             if (action.getIntent() != null) {
-                startIntent(v, action.getIntent());
+                startIntent(v, action.getIntent(), showOnLockscreen);
             } else if (action.getPendingIntent() != null) {
-                startPendingIntent(action.getPendingIntent());
+                startPendingIntent(action.getPendingIntent(), showOnLockscreen);
             }
         }
 
         /** Start the intent */
-        void startIntent(View v, Intent i);
+        void startIntent(View v, Intent i, boolean showOnLockscreen);
 
         /** Start the PendingIntent */
-        void startPendingIntent(PendingIntent pi);
+        void startPendingIntent(PendingIntent pi, boolean showOnLockscreen);
     }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 757dc2e..b83ea4a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -55,13 +55,14 @@
 
     /**
      * Asks QS to update its presentation, according to {@code NotificationPanelViewController}.
-     *
      * @param qsExpansionFraction How much each UI element in QS should be expanded (QQS to QS.)
      * @param panelExpansionFraction Whats the expansion of the whole shade.
      * @param headerTranslation How much we should vertically translate QS.
+     * @param squishinessFraction Fraction that affects tile height. 0 when collapsed,
+     *                            1 when expanded.
      */
     void setQsExpansion(float qsExpansionFraction, float panelExpansionFraction,
-            float headerTranslation);
+            float headerTranslation, float squishinessFraction);
     void setHeaderListening(boolean listening);
     void notifyCustomizeChanged();
     void setContainerController(QSContainerController controller);
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 6114728..a110413 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -1,20 +1,3 @@
--keep class com.android.systemui.navigationbar.buttons.KeyButtonView {
-  public float getDrawingAlpha();
-  public void setDrawingAlpha(float);
-}
-
--keep class com.android.systemui.navigationbar.buttons.KeyButtonRipple {
-  public float getGlowAlpha();
-  public float getGlowScale();
-  public void setGlowAlpha(float);
-  public void setGlowScale(float);
-}
-
--keep class com.android.systemui.settings.brightness.BrightnessSliderView {
-  public float getSliderScaleY();
-  public void setSliderScaleY(float);
-}
-
 -keep class com.android.systemui.recents.OverviewProxyRecentsImpl
 -keep class com.android.systemui.statusbar.car.CarStatusBar
 -keep class com.android.systemui.statusbar.phone.StatusBar
diff --git a/packages/SystemUI/res-keyguard/drawable/face_auth_wallpaper.png b/packages/SystemUI/res-keyguard/drawable/face_auth_wallpaper.png
deleted file mode 100644
index b907f4e..0000000
--- a/packages/SystemUI/res-keyguard/drawable/face_auth_wallpaper.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
index d816b3a..17765b5 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
@@ -23,6 +23,6 @@
     <dimen name="widget_big_font_size">88dp</dimen>
 
     <dimen name="qs_header_system_icons_area_height">0dp</dimen>
-    <dimen name="qs_panel_padding_top">0dp</dimen>
+    <dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
 
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 6b14c96..10a2f4c 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -63,7 +63,7 @@
                 android:id="@+id/qqs_footer_actions"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="16dp"
+                android:layout_marginTop="@dimen/qqs_layout_margin_top"
                 android:layout_marginStart="@dimen/qs_footer_margin"
                 android:layout_marginEnd="@dimen/qs_footer_margin"
                 />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2de8b49..fc455be 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2878,7 +2878,7 @@
     <!-- Summary for media output group with the active device count [CHAR LIMIT=NONE] -->
     <string name="media_output_dialog_multiple_devices"><xliff:g id="count" example="2">%1$d</xliff:g> devices selected</string>
     <!-- Summary for disconnected status [CHAR LIMIT=50] -->
-    <string name="media_output_dialog_disconnected"><xliff:g id="device_name" example="My device">%1$s</xliff:g> (disconnected)</string>
+    <string name="media_output_dialog_disconnected">(disconnected)</string>
     <!-- Summary for connecting error message [CHAR LIMIT=NONE] -->
     <string name="media_output_dialog_connect_failed">Couldn\'t connect. Try again.</string>
     <!-- Title for pairing item [CHAR LIMIT=60] -->
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
index 5b6845f..3a8ee29 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
@@ -23,11 +23,18 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -43,7 +50,7 @@
  * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
  */
 @SysUISingleton
-public class FeatureFlagManager implements FlagReader, FlagWriter {
+public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
     private static final String TAG = "SysUIFlags";
 
     private static final String SYSPROP_PREFIX = "persist.systemui.flag_";
@@ -58,11 +65,13 @@
     private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>();
 
     @Inject
-    public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context) {
+    public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context,
+            DumpManager dumpManager) {
         mSystemPropertiesHelper = systemPropertiesHelper;
 
         IntentFilter filter = new IntentFilter(ACTION_SET_FLAG);
         context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null);
+        dumpManager.registerDumpable(TAG, this);
     }
 
     /** Return a {@link BooleanFlag}'s value. */
@@ -186,4 +195,16 @@
             }
         }
     };
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        ArrayList<String> flagStrings = new ArrayList<>(mBooleanFlagCache.size());
+        for (Map.Entry<Integer, Boolean> entry : mBooleanFlagCache.entrySet()) {
+            flagStrings.add("  sysui_flag_" + entry.getKey() + ": " + entry.getValue());
+        }
+        flagStrings.sort(String.CASE_INSENSITIVE_ORDER);
+        for (String flagString : flagStrings) {
+            pw.println(flagString);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index cac90ea..8c7ede2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -49,7 +49,7 @@
 
 public class KeyguardDisplayManager {
     protected static final String TAG = "KeyguardDisplayManager";
-    private static boolean DEBUG = KeyguardConstants.DEBUG;
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
 
     private MediaRouter mMediaRouter = null;
     private final DisplayManager mDisplayService;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
index b7398d8..e2a2d07 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -22,6 +22,7 @@
 import android.graphics.Rect
 import android.hardware.biometrics.BiometricOverlayConstants
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
 import android.hardware.display.DisplayManager
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
@@ -181,6 +182,7 @@
 @BiometricOverlayConstants.ShowReason
 private fun Int.isReasonToShow(): Boolean = when (this) {
     REASON_AUTH_KEYGUARD -> false
+    REASON_AUTH_SETTINGS -> false
     else -> true
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index b2a5409..11addf0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -40,7 +40,7 @@
 
     @Nullable private UdfpsEnrollHelper mEnrollHelper;
     @NonNull private List<UdfpsEnrollProgressBarSegment> mSegments = new ArrayList<>();
-    private int mTotalSteps = 1;
+    private int mTotalSteps = 0;
     private int mProgressSteps = 0;
     private boolean mIsShowingHelp = false;
 
@@ -67,22 +67,19 @@
 
     void onEnrollmentProgress(int remaining, int totalSteps) {
         mTotalSteps = totalSteps;
-        updateState(getProgressSteps(remaining, totalSteps), false /* isShowingHelp */);
+
+        // Show some progress for the initial touch.
+        updateState(Math.max(1, totalSteps - remaining), false /* isShowingHelp */);
     }
 
     void onEnrollmentHelp(int remaining, int totalSteps) {
-        updateState(getProgressSteps(remaining, totalSteps), true /* isShowingHelp */);
+        updateState(Math.max(0, totalSteps - remaining), true /* isShowingHelp */);
     }
 
     void onLastStepAcquired() {
         updateState(mTotalSteps, false /* isShowingHelp */);
     }
 
-    private static int getProgressSteps(int remaining, int totalSteps) {
-        // Show some progress for the initial touch.
-        return Math.max(1, totalSteps - remaining);
-    }
-
     private void updateState(int progressSteps, boolean isShowingHelp) {
         updateProgress(progressSteps);
         updateFillColor(isShowingHelp);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 9d0591e..5c3e07f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -659,7 +659,7 @@
         @Override
         @AnyThread
         public void onTrigger(TriggerEvent event) {
-            final Sensor sensor = mSensors[mDevicePosture];
+            final Sensor sensor = mSensors[mPosture];
             mDozeLog.traceSensor(mPulseReason);
             mHandler.post(mWakeLock.wrap(() -> {
                 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
index 85baed4..78f0b5f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
@@ -16,23 +16,48 @@
 
 package com.android.systemui.flags;
 
+import android.util.SparseBooleanArray;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 
 import javax.inject.Inject;
 
 /**
  * Default implementation of the a Flag manager that returns default values for release builds
+ *
+ * There's a version of this file in src-debug which allows overriding, and has documentation about
+ * how to set flags.
  */
 @SysUISingleton
-public class FeatureFlagManager implements FlagReader, FlagWriter {
+public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
+    SparseBooleanArray mAccessedFlags = new SparseBooleanArray();
     @Inject
-    public FeatureFlagManager() {}
+    public FeatureFlagManager(DumpManager dumpManager) {
+        dumpManager.registerDumpable("SysUIFlags", this);
+    }
     public boolean isEnabled(String key, boolean defaultValue) {
         return defaultValue;
     }
     public boolean isEnabled(int key, boolean defaultValue) {
+        mAccessedFlags.append(key, defaultValue);
         return defaultValue;
     }
     public void setEnabled(String key, boolean value) {}
     public void setEnabled(int key, boolean value) {}
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        int size = mAccessedFlags.size();
+        for (int i = 0; i < size; i++) {
+            pw.println("  sysui_flag_" + mAccessedFlags.keyAt(i)
+                    + ": " + mAccessedFlags.valueAt(i));
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessController.kt
deleted file mode 100644
index b4137fa..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessController.kt
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
-import android.content.res.Resources
-import android.database.ContentObserver
-import android.graphics.Bitmap
-import android.graphics.BitmapFactory
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
-import android.hardware.biometrics.BiometricSourceType
-import android.os.Handler
-import android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT
-import android.util.MathUtils
-import android.view.View
-import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
-import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-import com.android.internal.annotations.VisibleForTesting
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Dumpable
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SystemSettings
-import java.io.FileDescriptor
-import java.io.PrintWriter
-import java.lang.Float.max
-import java.util.concurrent.TimeUnit
-
-val DEFAULT_ANIMATION_DURATION = TimeUnit.SECONDS.toMillis(4)
-val MAX_SCREEN_BRIGHTNESS = 100 // 0..100
-val MAX_SCRIM_OPACTY = 50 // 0..100
-val DEFAULT_USE_FACE_WALLPAPER = false
-
-/**
- * This class is responsible for ramping up the display brightness (and white overlay) in order
- * to mitigate low light conditions when running face auth without an IR camera.
- */
-@SysUISingleton
-open class FaceAuthScreenBrightnessController(
-    private val notificationShadeWindowController: NotificationShadeWindowController,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val resources: Resources,
-    private val globalSettings: GlobalSettings,
-    private val systemSettings: SystemSettings,
-    private val mainHandler: Handler,
-    private val dumpManager: DumpManager,
-    private val enabled: Boolean
-) : Dumpable {
-
-    private var userDefinedBrightness: Float = 1f
-    @VisibleForTesting
-    var useFaceAuthWallpaper = globalSettings
-            .getInt("sysui.use_face_auth_wallpaper", if (DEFAULT_USE_FACE_WALLPAPER) 1 else 0) == 1
-    private val brightnessAnimationDuration = globalSettings
-            .getLong("sysui.face_brightness_anim_duration", DEFAULT_ANIMATION_DURATION)
-    private val maxScreenBrightness = globalSettings
-            .getInt("sysui.face_max_brightness", MAX_SCREEN_BRIGHTNESS) / 100f
-    private val maxScrimOpacity = globalSettings
-            .getInt("sysui.face_max_scrim_opacity", MAX_SCRIM_OPACTY) / 100f
-    private val keyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() {
-        override fun onBiometricRunningStateChanged(
-            running: Boolean,
-            biometricSourceType: BiometricSourceType?
-        ) {
-            if (biometricSourceType != BiometricSourceType.FACE) {
-                return
-            }
-            // TODO enable only when receiving a low-light error
-            overridingBrightness = if (enabled) running else false
-        }
-    }
-    private lateinit var whiteOverlay: View
-    private var brightnessAnimator: ValueAnimator? = null
-    private var overridingBrightness = false
-    set(value) {
-        if (field == value) {
-            return
-        }
-        field = value
-        brightnessAnimator?.cancel()
-
-        if (!value) {
-            notificationShadeWindowController.setFaceAuthDisplayBrightness(BRIGHTNESS_OVERRIDE_NONE)
-            if (whiteOverlay.alpha > 0) {
-                brightnessAnimator = createAnimator(whiteOverlay.alpha, 0f).apply {
-                    duration = 200
-                    addUpdateListener {
-                        whiteOverlay.alpha = it.animatedValue as Float
-                    }
-                    addListener(object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
-                            whiteOverlay.visibility = View.INVISIBLE
-                            brightnessAnimator = null
-                        }
-                    })
-                    start()
-                }
-            }
-            return
-        }
-
-        val targetBrightness = max(maxScreenBrightness, userDefinedBrightness)
-        whiteOverlay.visibility = View.VISIBLE
-        brightnessAnimator = createAnimator(0f, 1f).apply {
-            duration = brightnessAnimationDuration
-            addUpdateListener {
-                val progress = it.animatedValue as Float
-                val brightnessProgress = MathUtils.constrainedMap(
-                        userDefinedBrightness, targetBrightness, 0f, 0.5f, progress)
-                val scrimProgress = MathUtils.constrainedMap(
-                        0f, maxScrimOpacity, 0.5f, 1f, progress)
-                notificationShadeWindowController.setFaceAuthDisplayBrightness(brightnessProgress)
-                whiteOverlay.alpha = scrimProgress
-            }
-            addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
-                    brightnessAnimator = null
-                }
-            })
-            start()
-        }
-    }
-
-    @VisibleForTesting
-    open fun createAnimator(start: Float, end: Float) = ValueAnimator.ofFloat(start, end)
-
-    /**
-     * Returns a bitmap that should be used by the lock screen as a wallpaper, if face auth requires
-     * a secure wallpaper.
-     */
-    var faceAuthWallpaper: Bitmap? = null
-    get() {
-        val user = KeyguardUpdateMonitor.getCurrentUser()
-        if (useFaceAuthWallpaper && keyguardUpdateMonitor.isFaceAuthEnabledForUser(user)) {
-            val options = BitmapFactory.Options().apply {
-                inScaled = false
-            }
-            return BitmapFactory.decodeResource(resources, R.drawable.face_auth_wallpaper, options)
-        }
-        return null
-    }
-    private set
-
-    fun attach(overlayView: View) {
-        whiteOverlay = overlayView
-        whiteOverlay.focusable = FLAG_NOT_FOCUSABLE
-        whiteOverlay.background = ColorDrawable(Color.WHITE)
-        whiteOverlay.isEnabled = false
-        whiteOverlay.alpha = 0f
-        whiteOverlay.visibility = View.INVISIBLE
-
-        dumpManager.registerDumpable(this.javaClass.name, this)
-        keyguardUpdateMonitor.registerCallback(keyguardUpdateCallback)
-        systemSettings.registerContentObserver(SCREEN_BRIGHTNESS_FLOAT,
-            object : ContentObserver(mainHandler) {
-                override fun onChange(selfChange: Boolean) {
-                    userDefinedBrightness = systemSettings.getFloat(SCREEN_BRIGHTNESS_FLOAT)
-                }
-            })
-        userDefinedBrightness = systemSettings.getFloat(SCREEN_BRIGHTNESS_FLOAT, 1f)
-    }
-
-    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
-        pw.apply {
-            println("overridingBrightness: $overridingBrightness")
-            println("useFaceAuthWallpaper: $useFaceAuthWallpaper")
-            println("brightnessAnimator: $brightnessAnimator")
-            println("brightnessAnimationDuration: $brightnessAnimationDuration")
-            println("maxScreenBrightness: $maxScreenBrightness")
-            println("userDefinedBrightness: $userDefinedBrightness")
-            println("enabled: $enabled")
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 11d4aac..9b0d69b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -20,9 +20,6 @@
 import android.app.trust.TrustManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.hardware.face.FaceManager;
-import android.os.Handler;
 import android.os.PowerManager;
 
 import com.android.internal.widget.LockPatternUtils;
@@ -37,17 +34,14 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
@@ -59,10 +53,7 @@
 import com.android.systemui.unfold.config.UnfoldTransitionConfig;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.sensors.AsyncSensorManager;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SystemSettings;
 
-import java.util.Optional;
 import java.util.concurrent.Executor;
 
 import dagger.Lazy;
@@ -150,35 +141,4 @@
         return new KeyguardLiftController(statusBarStateController, asyncSensorManager,
                 keyguardUpdateMonitor, dumpManager);
     }
-
-    @SysUISingleton
-    @Provides
-    static Optional<FaceAuthScreenBrightnessController> provideFaceAuthScreenBrightnessController(
-            Context context,
-            NotificationShadeWindowController notificationShadeWindowController,
-            @Main Resources resources,
-            Handler handler,
-            @Nullable FaceManager faceManager,
-            PackageManager packageManager,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            GlobalSettings globalSetting,
-            SystemSettings systemSettings,
-            DumpManager dumpManager) {
-        if (faceManager == null || !packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
-            return Optional.empty();
-        }
-
-        // Cameras that support "self illumination," via IR for example, don't need low light
-        // environment mitigation.
-        boolean needsLowLightMitigation = faceManager.getSensorPropertiesInternal().stream()
-                .anyMatch((properties) -> !properties.supportsSelfIllumination);
-        if (!needsLowLightMitigation) {
-            return Optional.empty();
-        }
-
-        // currently disabled (doesn't ramp up brightness or use scrim) see b/175918367
-        return Optional.of(new FaceAuthScreenBrightnessController(
-                notificationShadeWindowController, keyguardUpdateMonitor, resources,
-                globalSetting, systemSettings, handler, dumpManager, false));
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 125b87b..113ba59 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,14 +16,10 @@
 
 package com.android.systemui.media.dialog;
 
-import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
-
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
-import android.text.SpannableString;
 import android.text.TextUtils;
-import android.text.style.ForegroundColorSpan;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -99,23 +95,6 @@
         return mController.getMediaDevices().size();
     }
 
-    @Override
-    CharSequence getItemTitle(MediaDevice device) {
-        if (device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
-                && !device.isConnected()) {
-            final CharSequence deviceName = device.getName();
-            // Append status to title only for the disconnected Bluetooth device.
-            final SpannableString spannableTitle = new SpannableString(
-                    mContext.getString(R.string.media_output_dialog_disconnected, deviceName));
-            spannableTitle.setSpan(new ForegroundColorSpan(
-                    Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorSecondary)),
-                    deviceName.length(),
-                    spannableTitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE);
-            return spannableTitle;
-        }
-        return super.getItemTitle(device);
-    }
-
     class MediaDeviceViewHolder extends MediaDeviceBaseViewHolder {
 
         MediaDeviceViewHolder(View view) {
@@ -166,6 +145,14 @@
                             false /* showProgressBar */, false /* showSubtitle */);
                     initSeekbar(device);
                     mCurrentActivePosition = position;
+                } else if (
+                        device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
+                                && !device.isConnected()) {
+                    setTwoLineLayout(device, false /* bFocused */,
+                            false /* showSeekBar */, false /* showProgressBar */,
+                            true /* showSubtitle */);
+                    mSubTitleText.setText(R.string.media_output_dialog_disconnected);
+                    mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
                 } else {
                     setSingleLineLayout(getItemTitle(device), false /* bFocused */);
                     mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
@@ -175,7 +162,6 @@
 
         @Override
         void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
-            super.onBind(customizedItem, topMargin, bottomMargin);
             if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
                 mCheckBox.setVisibility(View.GONE);
                 mAddIcon.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 1ffc2c4..868193b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -153,9 +153,7 @@
             });
         }
 
-        void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
-            // TODO (b/201718621): clean up method after adjustment
-        }
+        abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin);
 
         void setSingleLineLayout(CharSequence title, boolean bFocused) {
             mTwoLineLayout.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
index 11d76db..a201c07 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
@@ -126,7 +126,6 @@
 
         @Override
         void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
-            super.onBind(customizedItem, topMargin, bottomMargin);
             if (customizedItem == CUSTOMIZED_ITEM_GROUP) {
                 setTwoLineLayout(mContext.getText(R.string.media_output_dialog_group),
                         true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index e3e2367..2ea706c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -34,6 +34,8 @@
 import android.view.ViewConfiguration;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.Keep;
+
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 
@@ -184,19 +186,27 @@
         }
     }
 
+    /** Gets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+    @Keep
     public float getGlowAlpha() {
         return mGlowAlpha;
     }
 
+    /** Sets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+    @Keep
     public void setGlowAlpha(float x) {
         mGlowAlpha = x;
         invalidateSelf();
     }
 
+    /** Gets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+    @Keep
     public float getGlowScale() {
         return mGlowScale;
     }
 
+    /** Sets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+    @Keep
     public void setGlowScale(float x) {
         mGlowScale = x;
         invalidateSelf();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 1a7a306..7e5ff8a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -166,6 +166,11 @@
         updateListening();
     }
 
+    @Override
+    public void setSquishinessFraction(float squishinessFraction) {
+        // No-op, paged layouts are not squishy.
+    }
+
     private void updateListening() {
         for (TileLayout tilePage : mPages) {
             tilePage.setListening(tilePage.getParent() != null && mListening);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 90d3448..44d5e21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -316,8 +316,8 @@
 
                     if (mQQSTileHeightAnimator == null) {
                         mQQSTileHeightAnimator = new HeightExpansionAnimator(this,
-                                quickTileView.getHeight(), tileView.getHeight());
-                        qqsTileHeight = quickTileView.getHeight();
+                                quickTileView.getMeasuredHeight(), tileView.getMeasuredHeight());
+                        qqsTileHeight = quickTileView.getMeasuredHeight();
                     }
 
                     mQQSTileHeightAnimator.addView(quickTileView);
@@ -380,7 +380,7 @@
                     if (mOtherTilesExpandAnimator == null) {
                         mOtherTilesExpandAnimator =
                                 new HeightExpansionAnimator(
-                                        this, qqsTileHeight, tileView.getHeight());
+                                        this, qqsTileHeight, tileView.getMeasuredHeight());
                     }
                     mOtherTilesExpandAnimator.addView(tileView);
                     tileView.setClipChildren(true);
@@ -658,7 +658,7 @@
         mTranslateWhileExpanding = shouldTranslate;
     }
 
-    static class HeightExpansionAnimator {
+    private static class HeightExpansionAnimator {
         private final List<View> mViews = new ArrayList<>();
         private final ValueAnimator mAnimator;
         private final TouchAnimator.Listener mListener;
@@ -673,9 +673,10 @@
                 int height = (Integer) valueAnimator.getAnimatedValue();
                 for (int i = 0; i < viewCount; i++) {
                     View v = mViews.get(i);
-                    v.setBottom(v.getTop() + height);
                     if (v instanceof HeightOverrideable) {
                         ((HeightOverrideable) v).setHeightOverride(height);
+                    } else {
+                        v.setBottom(v.getTop() + height);
                     }
                 }
                 if (t == 0f) {
@@ -713,9 +714,10 @@
             final int viewsCount = mViews.size();
             for (int i = 0; i < viewsCount; i++) {
                 View v = mViews.get(i);
-                v.setBottom(v.getTop() + v.getMeasuredHeight());
                 if (v instanceof HeightOverrideable) {
                     ((HeightOverrideable) v).resetOverride();
+                } else {
+                    v.setBottom(v.getTop() + v.getMeasuredHeight());
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 89bbcf5..eeca239 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -81,6 +81,7 @@
 
     private QSAnimator mQSAnimator;
     private HeightListener mPanelView;
+    private QSSquishinessController mQSSquishinessController;
     protected QuickStatusBarHeader mHeader;
     protected NonInterceptingScrollView mQSPanelScrollView;
     private QSDetail mQSDetail;
@@ -90,6 +91,7 @@
     private QSFooter mFooter;
     private float mLastQSExpansion = -1;
     private float mLastPanelFraction;
+    private float mSquishinessFraction = 1;
     private boolean mQsDisabled;
     private ImageView mQsDragHandler;
 
@@ -210,6 +212,7 @@
 
         mQSDetail.setQsPanel(mQSPanelController, mHeader, mFooter, mFalsingManager);
         mQSAnimator = qsFragmentComponent.getQSAnimator();
+        mQSSquishinessController = qsFragmentComponent.getQSSquishinessController();
 
         mQSCustomizerController = qsFragmentComponent.getQSCustomizerController();
         mQSCustomizerController.init();
@@ -231,7 +234,7 @@
                     boolean sizeChanged = (oldTop - oldBottom) != (top - bottom);
                     if (sizeChanged) {
                         setQsExpansion(mLastQSExpansion, mLastPanelFraction,
-                                mLastHeaderTranslation);
+                                mLastHeaderTranslation, mSquishinessFraction);
                     }
                 });
         mQSPanelController.setUsingHorizontalLayoutChangeListener(
@@ -413,7 +416,8 @@
                 mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed);
             }
             if (!showCollapsed && isKeyguardState()) {
-                setQsExpansion(mLastQSExpansion, mLastPanelFraction, 0);
+                setQsExpansion(mLastQSExpansion, mLastPanelFraction, 0,
+                        mSquishinessFraction);
             }
         }
     }
@@ -494,12 +498,13 @@
             updateShowCollapsedOnKeyguard();
         }
         mFullShadeProgress = progress;
-        setQsExpansion(mLastQSExpansion, mLastPanelFraction, mLastHeaderTranslation);
+        setQsExpansion(mLastQSExpansion, mLastPanelFraction, mLastHeaderTranslation,
+                isTransitioningToFullShade ? progress : mSquishinessFraction);
     }
 
     @Override
     public void setQsExpansion(float expansion, float panelExpansionFraction,
-            float proposedTranslation) {
+            float proposedTranslation, float squishinessFraction) {
         float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation;
         float progress = mTransitioningToFullShade ? mFullShadeProgress : panelExpansionFraction;
         setAlphaAnimationProgress(mInSplitShade ? progress : 1);
@@ -517,11 +522,13 @@
         if (expansion == mLastQSExpansion
                 && mLastKeyguardAndExpanded == onKeyguardAndExpanded
                 && mLastViewHeight == currentHeight
-                && mLastHeaderTranslation == headerTranslation) {
+                && mLastHeaderTranslation == headerTranslation
+                && mSquishinessFraction == squishinessFraction) {
             return;
         }
         mLastHeaderTranslation = headerTranslation;
         mLastPanelFraction = panelExpansionFraction;
+        mSquishinessFraction = squishinessFraction;
         mLastQSExpansion = expansion;
         mLastKeyguardAndExpanded = onKeyguardAndExpanded;
         mLastViewHeight = currentHeight;
@@ -557,6 +564,9 @@
         }
         updateQsBounds();
 
+        if (mQSSquishinessController != null) {
+            mQSSquishinessController.setSquishiness(mSquishinessFraction);
+        }
         if (mQSAnimator != null) {
             mQSAnimator.setPosition(expansion);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 28aa884..2665f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -736,6 +736,11 @@
         void setListening(boolean listening, UiEventLogger uiEventLogger);
 
         /**
+         * Sets a size modifier for the tile. Where 0 means collapsed, and 1 expanded.
+         */
+        void setSquishinessFraction(float squishinessFraction);
+
+        /**
          * Sets the minimum number of rows to show
          *
          * @param minRows the minimum.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt
new file mode 100644
index 0000000..6de8370
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt
@@ -0,0 +1,64 @@
+package com.android.systemui.qs
+
+import android.view.ViewGroup
+import com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER
+import com.android.systemui.qs.dagger.QSScope
+import com.android.systemui.qs.tileimpl.HeightOverrideable
+import javax.inject.Inject
+import javax.inject.Named
+
+@QSScope
+class QSSquishinessController @Inject constructor(
+    private val qsTileHost: QSTileHost,
+    @Named(QQS_FOOTER) private val qqsFooterActionsView: FooterActionsView,
+    private val qsAnimator: QSAnimator,
+    private val quickQSPanelController: QuickQSPanelController
+) {
+
+    /**
+     * Fraction from 0 to 1, where 0 is collapsed and 1 expanded.
+     */
+    var squishiness: Float = 1f
+    set(value) {
+        if (field == value) {
+            return
+        }
+        if ((field != 1f && value == 1f) || (field != 0f && value == 0f)) {
+            qsAnimator.requestAnimatorUpdate()
+        }
+        field = value
+        updateSquishiness()
+    }
+
+    /**
+     * Change the height of all tiles and repositions their siblings.
+     */
+    private fun updateSquishiness() {
+        // Start by updating the height of all tiles
+        for (tile in qsTileHost.tiles) {
+            val tileView = quickQSPanelController.getTileView(tile)
+            (tileView as? HeightOverrideable)?.let {
+                it.squishinessFraction = squishiness
+            }
+        }
+
+        // Update tile positions in the layout
+        val tileLayout = quickQSPanelController.tileLayout as TileLayout
+        tileLayout.setSquishinessFraction(squishiness)
+
+        // Calculate how much we should move the footer
+        val tileHeightOffset = tileLayout.height - tileLayout.tilesHeight
+        val footerTopMargin = (qqsFooterActionsView.layoutParams as ViewGroup.MarginLayoutParams)
+                .topMargin
+        val nextTop = tileLayout.bottom - tileHeightOffset + footerTopMargin
+        val amountMoved = nextTop - qqsFooterActionsView.top
+
+        // Move the footer and other siblings (MediaPlayer)
+        (qqsFooterActionsView.parent as ViewGroup?)?.let { parent ->
+            val index = parent.indexOfChild(qqsFooterActionsView)
+            for (i in index until parent.childCount) {
+                parent.getChildAt(i).top += amountMoved
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 1a890a7..ee5d5ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -41,6 +41,8 @@
     private int mMinRows = 1;
     private int mMaxColumns = NO_MAX_COLUMNS;
     protected int mResourceColumns;
+    private float mSquishinessFraction = 1f;
+    private int mLastTileBottom;
 
     public TileLayout(Context context) {
         this(context, null);
@@ -210,10 +212,11 @@
         return mMaxCellHeight;
     }
 
-    protected void layoutTileRecords(int numRecords) {
+    private void layoutTileRecords(int numRecords) {
         final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         int row = 0;
         int column = 0;
+        mLastTileBottom = 0;
 
         // Layout each QS tile.
         final int tilesToLayout = Math.min(numRecords, mRows * mColumns);
@@ -228,7 +231,9 @@
             final int top = getRowTop(row);
             final int left = getColumnStart(isRtl ? mColumns - column - 1 : column);
             final int right = left + mCellWidth;
-            record.tileView.layout(left, top, right, top + record.tileView.getMeasuredHeight());
+            final int bottom = top + record.tileView.getMeasuredHeight();
+            record.tileView.layout(left, top, right, bottom);
+            mLastTileBottom = bottom;
         }
     }
 
@@ -238,7 +243,7 @@
     }
 
     protected int getRowTop(int row) {
-        return row * (mCellHeight + mCellMarginVertical);
+        return (int) (row * (mCellHeight * mSquishinessFraction + mCellMarginVertical));
     }
 
     protected int getColumnStart(int column) {
@@ -264,4 +269,17 @@
         // up.
         return Math.max(mColumns * mRows, 1);
     }
+
+    public int getTilesHeight() {
+        return mLastTileBottom + getPaddingBottom();
+    }
+
+    @Override
+    public void setSquishinessFraction(float squishinessFraction) {
+        if (Float.compare(mSquishinessFraction, squishinessFraction) == 0) {
+            return;
+        }
+        mSquishinessFraction = squishinessFraction;
+        layoutTileRecords(mRecords.size());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
index 8cc0502..63cbc21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
@@ -21,6 +21,7 @@
 import com.android.systemui.qs.QSFooter;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.qs.QSSquishinessController;
 import com.android.systemui.qs.QuickQSPanelController;
 import com.android.systemui.qs.customize.QSCustomizerController;
 
@@ -57,4 +58,7 @@
 
     /** Construct a {@link QSCustomizerController}. */
     QSCustomizerController getQSCustomizerController();
+
+    /** Construct a {@link QSSquishinessController}. */
+    QSSquishinessController getQSSquishinessController();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 3b85e5d..210ee93 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -55,6 +55,8 @@
         internal const val DISMISSED = StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED
     }
 
+    private var dialogCanceller: ((String) -> Unit)? = null
+
     private val commandQueueCallback = object : CommandQueue.Callbacks {
         override fun requestAddTile(
             componentName: ComponentName,
@@ -67,6 +69,10 @@
                 callback.onTileRequest(it)
             }
         }
+
+        override fun cancelRequestAddTile(packageName: String) {
+            dialogCanceller?.invoke(packageName)
+        }
     }
 
     fun init() {
@@ -95,16 +101,21 @@
             callback.accept(TILE_ALREADY_ADDED)
             return
         }
-        val dialogResponse = object : Consumer<Int> {
-            override fun accept(response: Int) {
-                if (response == ADD_TILE) {
-                    addTile(componentName)
-                }
-                callback.accept(response)
+        val dialogResponse = Consumer<Int> { response ->
+            if (response == ADD_TILE) {
+                addTile(componentName)
             }
+            callback.accept(response)
         }
         val tileData = TileRequestDialog.TileData(appName, label, icon)
-        createDialog(tileData, dialogResponse).show()
+        createDialog(tileData, dialogResponse).also { dialog ->
+            dialogCanceller = {
+                if (componentName.packageName == it) {
+                    dialog.cancel()
+                }
+                dialogCanceller = null
+            }
+        }.show()
     }
 
     private fun createDialog(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt
index 866fa09..61d68ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt
@@ -21,9 +21,8 @@
         const val NO_OVERRIDE = -1
     }
 
-    var heightOverride: Int
+    abstract var heightOverride: Int
+    abstract fun resetOverride()
 
-    fun resetOverride() {
-        heightOverride = NO_OVERRIDE
-    }
+    abstract var squishinessFraction: Float
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index ee5e4df..67c311e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -68,6 +68,18 @@
     }
 
     override var heightOverride: Int = HeightOverrideable.NO_OVERRIDE
+        set(value) {
+            if (field == value) return
+            field = value
+            updateHeight()
+        }
+
+    override var squishinessFraction: Float = 1f
+        set(value) {
+            if (field == value) return
+            field = value
+            updateHeight()
+        }
 
     private val colorActive = Utils.getColorAttrDefaultColor(context,
             com.android.internal.R.attr.colorAccentPrimary)
@@ -148,6 +160,11 @@
         updateResources()
     }
 
+    override fun resetOverride() {
+        heightOverride = HeightOverrideable.NO_OVERRIDE
+        updateHeight()
+    }
+
     fun updateResources() {
         FontSizeUtils.updateFontSize(label, R.dimen.qs_tile_text_size)
         FontSizeUtils.updateFontSize(secondaryLabel, R.dimen.qs_tile_text_size)
@@ -218,9 +235,17 @@
 
     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
         super.onLayout(changed, l, t, r, b)
-        if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
-            bottom = top + heightOverride
-        }
+        updateHeight()
+    }
+
+    private fun updateHeight() {
+        val actualHeight = (if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
+            heightOverride
+        } else {
+            measuredHeight
+        } * squishinessFraction).toInt()
+        bottom = top + actualHeight
+        scrollY = (actualHeight - height) / 2
     }
 
     override fun updateAccessibilityOrder(previousView: View?): View {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index bc21b2d..80ec0ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -112,20 +112,9 @@
         }
 
         mUiHandler.post {
-            if (keyguardStateController.isUnlocked) {
-                mActivityStarter.startActivity(
-                        intent, true /* dismissShade */, animationController)
-            } else {
-                if (state.state == Tile.STATE_ACTIVE) {
-                    mHost.collapsePanels()
-                    // With an active tile, don't use ActivityStarter so that the activity is
-                    // started without prompting keyguard unlock.
-                    mContext.startActivity(intent)
-                } else {
-                    mActivityStarter.postStartActivityDismissingKeyguard(
-                            intent, 0 /* delay */, animationController)
-                }
-            }
+            val showOverLockscreenWhenLocked = state.state == Tile.STATE_ACTIVE
+            mActivityStarter.startActivity(
+                intent, true /* dismissShade */, animationController, showOverLockscreenWhenLocked)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 15b78e7..58e8992 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -331,7 +331,7 @@
         showProgressBar();
         final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
         final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
-        final boolean isWifiScanEnabled = mWifiManager.isScanAlwaysAvailable();
+        final boolean isWifiScanEnabled = mInternetDialogController.isWifiScanEnabled();
         updateWifiToggle(isWifiEnabled, isDeviceLocked);
         updateConnectedWifi(isWifiEnabled, isDeviceLocked);
         updateWifiScanNotify(isWifiEnabled, isWifiScanEnabled, isDeviceLocked);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 1ade5ce..40590a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -78,6 +78,7 @@
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.toast.SystemUIToast;
 import com.android.systemui.toast.ToastFactory;
 import com.android.systemui.util.CarrierConfigTracker;
@@ -150,6 +151,7 @@
     private WindowManager mWindowManager;
     private ToastFactory mToastFactory;
     private SignalDrawable mSignalDrawable;
+    private LocationController mLocationController;
 
     @VisibleForTesting
     static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -199,7 +201,8 @@
             GlobalSettings globalSettings, KeyguardStateController keyguardStateController,
             WindowManager windowManager, ToastFactory toastFactory,
             @Background Handler workerHandler,
-            CarrierConfigTracker carrierConfigTracker) {
+            CarrierConfigTracker carrierConfigTracker,
+            LocationController locationController) {
         if (DEBUG) {
             Log.d(TAG, "Init InternetDialogController");
         }
@@ -227,6 +230,7 @@
         mWindowManager = windowManager;
         mToastFactory = toastFactory;
         mSignalDrawable = new SignalDrawable(mContext);
+        mLocationController = locationController;
     }
 
     void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
@@ -788,6 +792,14 @@
         return false;
     }
 
+    @WorkerThread
+    boolean isWifiScanEnabled() {
+        if (!mLocationController.isLocationEnabled()) {
+            return false;
+        }
+        return mWifiManager.isScanAlwaysAvailable();
+    }
+
     static class WifiEntryConnectCallback implements WifiEntry.ConnectCallback {
         final ActivityStarter mActivityStarter;
         final WifiEntry mWifiEntry;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index 15aa2b7..a4e1537 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -27,6 +27,7 @@
 import android.widget.FrameLayout;
 import android.widget.SeekBar.OnSeekBarChangeListener;
 
+import androidx.annotation.Keep;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -183,6 +184,7 @@
      *
      * Used in {@link com.android.systemui.qs.QSAnimator}.
      */
+    @Keep
     public void setSliderScaleY(float scale) {
         if (scale != mScale) {
             mScale = scale;
@@ -199,6 +201,7 @@
         }
     }
 
+    @Keep
     public float getSliderScaleY() {
         return mScale;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index f95dd34..c25bc84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -48,6 +48,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -152,6 +153,7 @@
     private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT;
     private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT;
     private static final int MSG_TILE_SERVICE_REQUEST_ADD = 61 << MSG_SHIFT;
+    private static final int MSG_TILE_SERVICE_REQUEST_CANCEL = 62 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -416,6 +418,11 @@
                 @NonNull CharSequence label,
                 @NonNull Icon icon,
                 @NonNull IAddTileResultCallback callback) {}
+
+        /**
+         * @see IStatusBar#cancelRequestAddTile
+         */
+        default void cancelRequestAddTile(@NonNull String packageName) {}
     }
 
     public CommandQueue(Context context) {
@@ -1138,6 +1145,11 @@
         mHandler.obtainMessage(MSG_TILE_SERVICE_REQUEST_ADD, args).sendToTarget();
     }
 
+    @Override
+    public void cancelRequestAddTile(@NonNull String s) throws RemoteException {
+        mHandler.obtainMessage(MSG_TILE_SERVICE_REQUEST_CANCEL, s).sendToTarget();
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -1524,6 +1536,11 @@
                     }
                     args.recycle();
                     break;
+                case MSG_TILE_SERVICE_REQUEST_CANCEL:
+                    String packageName = (String) msg.obj;
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).cancelRequestAddTile(packageName);
+                    }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index bacb85a..cf9daf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -34,6 +34,7 @@
 import android.view.ViewGroup
 import com.android.settingslib.Utils
 import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
@@ -188,13 +189,28 @@
 
         val ssView = plugin.getView(parent)
         ssView.registerDataProvider(plugin)
+
+        val animationController = ActivityLaunchAnimator.Controller.fromView(
+            ssView as View,
+            null /* cujType */
+        )
+
         ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
-            override fun startIntent(v: View?, i: Intent?) {
-                activityStarter.startActivity(i, true /* dismissShade */)
+            override fun startIntent(v: View?, i: Intent?, showOnLockscreen: Boolean) {
+                activityStarter.startActivity(
+                    i,
+                    true, /* dismissShade */
+                    animationController,
+                    showOnLockscreen
+                )
             }
 
-            override fun startPendingIntent(pi: PendingIntent?) {
-                activityStarter.startPendingIntentDismissingKeyguard(pi)
+            override fun startPendingIntent(pi: PendingIntent?, showOnLockscreen: Boolean) {
+                if (showOnLockscreen) {
+                    pi?.send()
+                } else {
+                    activityStarter.startPendingIntentDismissingKeyguard(pi)
+                }
             }
         })
         ssView.setFalsingManager(falsingManager)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
index bac5223..e8f352f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
@@ -15,7 +15,7 @@
     fun logGutsOpened(key: String, guts: NotificationGuts) {
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = key
-            str2 = guts::class.simpleName
+            str2 = guts.gutsContent::class.simpleName
             bool1 = guts.isLeavebehind
         }, {
             "Guts of type $str2 (leave behind: $bool1) opened for class $str1"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index 18f3b45..cd6f35b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -61,7 +61,9 @@
             ConversationCoordinator conversationCoordinator,
             PreparationCoordinator preparationCoordinator,
             MediaCoordinator mediaCoordinator,
+            ShadeEventCoordinator shadeEventCoordinator,
             SmartspaceDedupingCoordinator smartspaceDedupingCoordinator,
+            ViewConfigCoordinator viewConfigCoordinator,
             VisualStabilityCoordinator visualStabilityCoordinator,
             CommunalCoordinator communalCoordinator) {
         dumpManager.registerDumpable(TAG, this);
@@ -75,6 +77,8 @@
         mCoordinators.add(bubbleCoordinator);
         mCoordinators.add(conversationCoordinator);
         mCoordinators.add(mediaCoordinator);
+        mCoordinators.add(shadeEventCoordinator);
+        mCoordinators.add(viewConfigCoordinator);
         mCoordinators.add(visualStabilityCoordinator);
         mCoordinators.add(communalCoordinator);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
new file mode 100644
index 0000000..f9648a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.service.notification.NotificationListenerService
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource
+import javax.inject.Inject
+
+/**
+ * A coordinator which provides callbacks to a view surfaces for various events relevant to the
+ * shade, such as when the user removes a notification, or when the shade is emptied.
+ */
+@SysUISingleton
+class ShadeEventCoordinator @Inject internal constructor(
+    private val mLogger: ShadeEventCoordinatorLogger
+) : Coordinator, NotifShadeEventSource {
+    private var mNotifRemovedByUserCallback: Runnable? = null
+    private var mShadeEmptiedCallback: Runnable? = null
+    private var mEntryRemoved = false
+    private var mEntryRemovedByUser = false
+
+    override fun attach(pipeline: NotifPipeline) {
+        pipeline.addCollectionListener(mNotifCollectionListener)
+        pipeline.addOnBeforeRenderListListener(this::onBeforeRenderList)
+    }
+
+    private val mNotifCollectionListener = object : NotifCollectionListener {
+        override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+            mEntryRemoved = true
+            mEntryRemovedByUser =
+                    reason == NotificationListenerService.REASON_CLICK ||
+                    reason == NotificationListenerService.REASON_CANCEL_ALL ||
+                    reason == NotificationListenerService.REASON_CANCEL
+        }
+    }
+
+    override fun setNotifRemovedByUserCallback(callback: Runnable) {
+        check(mNotifRemovedByUserCallback == null) { "mNotifRemovedByUserCallback already set" }
+        mNotifRemovedByUserCallback = callback
+    }
+
+    override fun setShadeEmptiedCallback(callback: Runnable) {
+        check(mShadeEmptiedCallback == null) { "mShadeEmptiedCallback already set" }
+        mShadeEmptiedCallback = callback
+    }
+
+    private fun onBeforeRenderList(entries: List<ListEntry>) {
+        if (mEntryRemoved && entries.isEmpty()) {
+            mLogger.logShadeEmptied()
+            mShadeEmptiedCallback?.run()
+        }
+        if (mEntryRemoved && mEntryRemovedByUser) {
+            mLogger.logNotifRemovedByUser()
+            mNotifRemovedByUserCallback?.run()
+        }
+        mEntryRemoved = false
+        mEntryRemovedByUser = false
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
new file mode 100644
index 0000000..c687e1b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+private const val TAG = "ShadeEventCoordinator"
+
+/** Logger for the [ShadeEventCoordinator] */
+class ShadeEventCoordinatorLogger @Inject constructor(
+    @NotificationLog private val buffer: LogBuffer
+) {
+
+    fun logShadeEmptied() {
+        buffer.log(TAG, LogLevel.DEBUG, { }, { "Shade emptied" })
+    }
+
+    fun logNotifRemovedByUser() {
+        buffer.log(TAG, LogLevel.DEBUG, { }, { "Notification removed by user" })
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
new file mode 100644
index 0000000..df1132b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingMessage
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
+import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.policy.ConfigurationController
+import javax.inject.Inject
+
+/**
+ * A coordinator which ensures that notifications within the new pipeline are correctly inflated
+ * for the current uiMode and screen properties; additionally deferring those changes when a user
+ * change is in progress until that process has completed.
+ */
+@SysUISingleton
+class ViewConfigCoordinator @Inject internal constructor(
+    configurationController: ConfigurationController,
+    lockscreenUserManager: NotificationLockscreenUserManagerImpl,
+    featureFlags: FeatureFlags,
+    private val mGutsManager: NotificationGutsManager,
+    private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+) : Coordinator, UserChangedListener, ConfigurationController.ConfigurationListener {
+
+    private var mReinflateNotificationsOnUserSwitched = false
+    private var mDispatchUiModeChangeOnUserSwitched = false
+    private var mPipeline: NotifPipeline? = null
+
+    init {
+        if (featureFlags.isNewNotifPipelineRenderingEnabled) {
+            lockscreenUserManager.addUserChangedListener(this)
+            configurationController.addCallback(this)
+        }
+    }
+
+    override fun attach(pipeline: NotifPipeline) {
+        mPipeline = pipeline
+    }
+
+    override fun onDensityOrFontScaleChanged() {
+        MessagingMessage.dropCache()
+        MessagingGroup.dropCache()
+        if (!mKeyguardUpdateMonitor.isSwitchingUser) {
+            updateNotificationsOnDensityOrFontScaleChanged()
+        } else {
+            mReinflateNotificationsOnUserSwitched = true
+        }
+    }
+
+    override fun onUiModeChanged() {
+        if (!mKeyguardUpdateMonitor.isSwitchingUser) {
+            updateNotificationsOnUiModeChanged()
+        } else {
+            mDispatchUiModeChangeOnUserSwitched = true
+        }
+    }
+
+    override fun onThemeChanged() {
+        onDensityOrFontScaleChanged()
+    }
+
+    override fun onUserChanged(userId: Int) {
+        if (mReinflateNotificationsOnUserSwitched) {
+            updateNotificationsOnDensityOrFontScaleChanged()
+            mReinflateNotificationsOnUserSwitched = false
+        }
+        if (mDispatchUiModeChangeOnUserSwitched) {
+            updateNotificationsOnUiModeChanged()
+            mDispatchUiModeChangeOnUserSwitched = false
+        }
+    }
+
+    private fun updateNotificationsOnUiModeChanged() {
+        mPipeline?.allNotifs?.forEach { entry ->
+            val row = entry.row
+            row?.onUiModeChanged()
+        }
+    }
+
+    private fun updateNotificationsOnDensityOrFontScaleChanged() {
+        mPipeline?.allNotifs?.forEach { entry ->
+            entry.onDensityOrFontScaleChanged()
+            val exposedGuts = entry.areGutsExposed()
+            if (exposedGuts) {
+                mGutsManager.onDensityOrFontScaleChanged(entry)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java
new file mode 100644
index 0000000..4ee08ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.legacy;
+
+import static com.android.systemui.statusbar.phone.StatusBar.SPEW;
+
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.inject.Inject;
+
+/**
+ * This is some logic extracted from the
+ * {@link com.android.systemui.statusbar.phone.StatusBarNotificationPresenter}
+ * into a class that implements a new-pipeline interface so that the new pipeline can implement it
+ * correctly.
+ *
+ * Specifically, this is the logic which updates notifications when uiMode and screen properties
+ * change, and which closes the shade when the last notification disappears.
+ */
+public class LegacyNotificationPresenterExtensions implements NotifShadeEventSource {
+    private static final String TAG = "LegacyNotifPresenter";
+    private final NotificationEntryManager mEntryManager;
+    private boolean mEntryListenerAdded;
+    private Runnable mShadeEmptiedCallback;
+    private Runnable mNotifRemovedByUserCallback;
+
+    @Inject
+    public LegacyNotificationPresenterExtensions(NotificationEntryManager entryManager) {
+        mEntryManager = entryManager;
+    }
+
+    private void ensureEntryListenerAdded() {
+        if (mEntryListenerAdded) return;
+        mEntryListenerAdded = true;
+        mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+            @Override
+            public void onEntryRemoved(
+                    @NotNull NotificationEntry entry,
+                    NotificationVisibility visibility,
+                    boolean removedByUser,
+                    int reason) {
+                StatusBarNotification old = entry.getSbn();
+                if (SPEW) {
+                    Log.d(TAG, "removeNotification key=" + entry.getKey()
+                            + " old=" + old + " reason=" + reason);
+                }
+
+                if (old != null && !mEntryManager.hasActiveNotifications()) {
+                    if (mShadeEmptiedCallback != null) mShadeEmptiedCallback.run();
+                }
+                if (removedByUser) {
+                    if (mNotifRemovedByUserCallback != null) mNotifRemovedByUserCallback.run();
+                }
+            }
+        });
+    }
+
+    @Override
+    public void setNotifRemovedByUserCallback(@NonNull Runnable callback) {
+        if (mNotifRemovedByUserCallback != null) {
+            throw new IllegalStateException("mNotifRemovedByUserCallback already set");
+        }
+        mNotifRemovedByUserCallback = callback;
+        ensureEntryListenerAdded();
+    }
+
+    @Override
+    public void setShadeEmptiedCallback(@NonNull Runnable callback) {
+        if (mShadeEmptiedCallback != null) {
+            throw new IllegalStateException("mShadeEmptiedCallback already set");
+        }
+        mShadeEmptiedCallback = callback;
+        ensureEntryListenerAdded();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt
new file mode 100644
index 0000000..e24f6a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+/**
+ * This is an object which provides callbacks for certain important events related to the
+ * notification shade, such as notifications being removed by the user, or the shade becoming empty.
+ */
+interface NotifShadeEventSource {
+    /**
+     * Registers a callback to be invoked when the last notification has been removed from
+     * the shade for any reason
+     */
+    fun setShadeEmptiedCallback(callback: Runnable)
+
+    /**
+     * Registers a callback to be invoked when a notification has been removed from
+     * the shade by a user action
+     */
+    fun setNotifRemovedByUserCallback(callback: Runnable)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index dfa1f5f..540216c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -45,10 +45,12 @@
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator;
 import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
+import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationPresenterExtensions;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -59,6 +61,7 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl;
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -90,7 +93,7 @@
 /**
  * Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
  */
-@Module(includes = { NotificationSectionHeadersModule.class })
+@Module(includes = {NotificationSectionHeadersModule.class})
 public interface NotificationsModule {
     @Binds
     StackScrollAlgorithm.SectionProvider bindSectionProvider(
@@ -271,6 +274,20 @@
     }
 
     /**
+     * Provide the active implementation for presenting notifications.
+     */
+    @Provides
+    @SysUISingleton
+    static NotifShadeEventSource provideNotifShadeEventSource(
+            FeatureFlags featureFlags,
+            Lazy<ShadeEventCoordinator> shadeEventCoordinatorLazy,
+            Lazy<LegacyNotificationPresenterExtensions> legacyNotificationPresenterExtensionsLazy) {
+        return featureFlags.isNewNotifPipelineRenderingEnabled()
+                ? shadeEventCoordinatorLazy.get()
+                : legacyNotificationPresenterExtensionsLazy.get();
+    }
+
+    /**
      * Provide a dismissal callback that's triggered when a user manually dismissed a notification
      * from the notification shade or it gets auto-cancelled by click.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index e956046..02b1210 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1249,6 +1249,7 @@
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
             mMenuRow.onConfigurationChanged();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index c5a1f48..4795f08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -653,6 +653,10 @@
         return 0f;
     }
 
+    public float getNotificationSquishinessFraction() {
+        return mStackScrollAlgorithm.getNotificationSquishinessFraction(mAmbientState);
+    }
+
     void reinflateViews() {
         inflateFooterView();
         inflateEmptyShadeView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 09ab90e..94720e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1078,6 +1078,10 @@
         mView.setOnStackYChanged(onStackYChanged);
     }
 
+    public float getNotificationSquishinessFraction() {
+        return mView.getNotificationSquishinessFraction();
+    }
+
     public float calculateAppearFractionBypass() {
         return mView.calculateAppearFractionBypass();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index afe0bba..015edb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -110,6 +110,15 @@
         getNotificationChildrenStates(algorithmState, ambientState);
     }
 
+    /**
+     * How expanded or collapsed notifications are when pulling down the shade.
+     * @param ambientState Current ambient state.
+     * @return 0 when fully collapsed, 1 when expanded.
+     */
+    public float getNotificationSquishinessFraction(AmbientState ambientState) {
+        return getExpansionFractionWithoutShelf(mTempAlgorithmState, ambientState);
+    }
+
     private void resetChildViewStates() {
         int numChildren = mHostView.getChildCount();
         for (int i = 0; i < numChildren; i++) {
@@ -364,7 +373,6 @@
 
         final float stackHeight = ambientState.getStackHeight()  - shelfHeight - scrimPadding;
         final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding;
-
         return stackHeight / stackEndHeight;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index c639eec..e26f75f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -1078,10 +1078,14 @@
                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
                 .putExtra(ControlsUiController.EXTRA_ANIMATE, true);
 
+        ActivityLaunchAnimator.Controller controller =
+                v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */)
+                        : null;
         if (mControlsComponent.getVisibility() == AVAILABLE) {
-            mContext.startActivity(intent);
+            mActivityStarter.startActivity(intent, true /* dismissShade */, controller,
+                    true /* showOverLockscreenWhenLocked */);
         } else {
-            mActivityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */);
+            mActivityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */, controller);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 2a13e6b..e565a44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -45,7 +45,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 
 import libcore.io.IoUtils;
@@ -53,7 +52,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Objects;
-import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -70,7 +68,6 @@
     private final WallpaperManager mWallpaperManager;
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final Handler mH;
-    private final Optional<FaceAuthScreenBrightnessController> mFaceAuthScreenBrightnessController;
 
     private boolean mCached;
     private Bitmap mCache;
@@ -86,14 +83,12 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             DumpManager dumpManager,
             NotificationMediaManager mediaManager,
-            Optional<FaceAuthScreenBrightnessController> faceAuthScreenBrightnessController,
             @Main Handler mainHandler) {
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
         mWallpaperManager = wallpaperManager;
         mCurrentUserId = ActivityManager.getCurrentUser();
         mUpdateMonitor = keyguardUpdateMonitor;
         mMediaManager = mediaManager;
-        mFaceAuthScreenBrightnessController = faceAuthScreenBrightnessController;
         mH = mainHandler;
 
         if (iWallpaperManager != null) {
@@ -132,14 +127,6 @@
             return LoaderResult.success(null);
         }
 
-        Bitmap faceAuthWallpaper = null;
-        if (mFaceAuthScreenBrightnessController.isPresent()) {
-            faceAuthWallpaper = mFaceAuthScreenBrightnessController.get().getFaceAuthWallpaper();
-            if (faceAuthWallpaper != null) {
-                return LoaderResult.success(faceAuthWallpaper);
-            }
-        }
-
         // Prefer the selected user (when specified) over the current user for the FLAG_SET_LOCK
         // wallpaper.
         final int lockWallpaperUserId =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 917f132..4ce33b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -468,7 +468,6 @@
 
     private boolean mPulsing;
     private boolean mUserSetupComplete;
-    private int mQsNotificationTopPadding;
     private boolean mHideIconsDuringLaunchAnimation = true;
     private int mStackScrollerMeasuringPass;
     private ArrayList<Consumer<ExpandableNotificationRow>>
@@ -2360,7 +2359,8 @@
     private void updateQsExpansion() {
         if (mQs == null) return;
         float qsExpansionFraction = computeQsExpansionFraction();
-        mQs.setQsExpansion(qsExpansionFraction, getExpandedFraction(), getHeaderTranslation());
+        mQs.setQsExpansion(qsExpansionFraction, getExpandedFraction(), getHeaderTranslation(),
+                mNotificationStackScrollLayoutController.getNotificationSquishinessFraction());
         mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
         int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
         mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
@@ -2522,7 +2522,7 @@
                     // qsTranslation should only be positive during pulse expansion because it's
                     // already translating in from the top
                     qsTranslation = Math.max(0, (top - mQs.getHeader().getHeight()) / 2.0f);
-                } else {
+                } else if (!mShouldUseSplitNotificationShade) {
                     qsTranslation = (top - mQs.getHeader().getHeight()) * QS_PARALLAX_AMOUNT;
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
index a7ecd06..8732891 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
@@ -41,15 +41,28 @@
     private val iconManager: StatusBarIconController.IconManager
     private val qsCarrierGroupController: QSCarrierGroupController
     private var visible = false
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            updateListeners()
+        }
 
     var shadeExpanded = false
         set(value) {
+            if (field == value) {
+                return
+            }
             field = value
             updateVisibility()
         }
 
     var splitShadeMode = false
         set(value) {
+            if (field == value) {
+                return
+            }
             field = value
             updateVisibility()
         }
@@ -78,15 +91,20 @@
     }
 
     private fun updateVisibility() {
-        val shouldBeVisible = shadeExpanded && splitShadeMode
-        if (visible != shouldBeVisible) {
-            visible = shouldBeVisible
-            statusBar.visibility = if (shouldBeVisible) View.VISIBLE else View.GONE
-            updateListeners(shouldBeVisible)
+        val visibility = if (!splitShadeMode) {
+            View.GONE
+        } else if (shadeExpanded) {
+            View.VISIBLE
+        } else {
+            View.INVISIBLE
+        }
+        if (statusBar.visibility != visibility) {
+            statusBar.visibility = visibility
+            visible = visibility == View.VISIBLE
         }
     }
 
-    private fun updateListeners(visible: Boolean) {
+    private fun updateListeners() {
         qsCarrierGroupController.setListening(visible)
         if (visible) {
             statusBarIconController.addIconGroup(iconManager)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 0e75a45..bce6ccc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -207,6 +207,7 @@
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
@@ -535,6 +536,7 @@
 
     private final int[] mAbsPos = new int[2];
 
+    private final NotifShadeEventSource mNotifShadeEventSource;
     protected final NotificationEntryManager mEntryManager;
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
@@ -718,6 +720,7 @@
             FalsingManager falsingManager,
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
+            NotifShadeEventSource notifShadeEventSource,
             NotificationEntryManager notificationEntryManager,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
@@ -823,6 +826,7 @@
         mFalsingCollector = falsingCollector;
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
+        mNotifShadeEventSource = notifShadeEventSource;
         mEntryManager = notificationEntryManager;
         mGutsManager = notificationGutsManager;
         mNotificationLogger = notificationLogger;
@@ -1506,6 +1510,7 @@
                 mDynamicPrivacyController,
                 mKeyguardStateController,
                 mKeyguardIndicationController,
+                mFeatureFlags,
                 this /* statusBar */,
                 mShadeController,
                 mLockscreenShadeTransitionController,
@@ -1513,6 +1518,7 @@
                 mViewHierarchyManager,
                 mLockscreenUserManager,
                 mStatusBarStateController,
+                mNotifShadeEventSource,
                 mEntryManager,
                 mMediaManager,
                 mGutsManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 16d7f98..6ae8331 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -156,18 +156,21 @@
         val isRtl = rotatedResources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
         val roundedCornerPadding = rotatedResources
                 .getDimensionPixelSize(R.dimen.rounded_corner_content_padding)
-        val minDotWidth = if (isPrivacyDotEnabled)
+        val minDotPadding = if (isPrivacyDotEnabled)
                 rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_dot_min_padding)
             else 0
+        val dotWidth = if (isPrivacyDotEnabled)
+                rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_dot_diameter)
+            else 0
 
         val minLeft: Int
         val minRight: Int
         if (isRtl) {
-            minLeft = max(minDotWidth, roundedCornerPadding)
+            minLeft = max(minDotPadding, roundedCornerPadding)
             minRight = roundedCornerPadding
         } else {
             minLeft = roundedCornerPadding
-            minRight = max(minDotWidth, roundedCornerPadding)
+            minRight = max(minDotPadding, roundedCornerPadding)
         }
 
         return calculateInsetsForRotationWithRotatedResources(
@@ -177,7 +180,9 @@
                 context.resources.configuration.windowConfiguration.maxBounds,
                 SystemBarUtils.getStatusBarHeight(context),
                 minLeft,
-                minRight)
+                minRight,
+                isRtl,
+                dotWidth)
     }
 
     fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int {
@@ -246,10 +251,13 @@
  *
  * @param currentRotation current device rotation
  * @param targetRotation rotation for which to calculate the status bar content rect
- * @param displayCutout [DisplayCutout] for the curren display. possibly null
+ * @param displayCutout [DisplayCutout] for the current display. possibly null
  * @param windowMetrics [WindowMetrics] for the current window
  * @param statusBarHeight height of the status bar for the target rotation
- * @param roundedCornerPadding from rounded_corner_content_padding
+ * @param minLeft the minimum padding to enforce on the left
+ * @param minRight the minimum padding to enforce on the right
+ * @param isRtl current layout direction is Right-To-Left or not
+ * @param dotWidth privacy dot image width (0 if privacy dot is disabled)
  *
  * @see [RotationUtils#getResourcesForRotation]
  */
@@ -260,7 +268,9 @@
     maxBounds: Rect,
     statusBarHeight: Int,
     minLeft: Int,
-    minRight: Int
+    minRight: Int,
+    isRtl: Boolean,
+    dotWidth: Int
 ): Rect {
     /*
     TODO: Check if this is ever used for devices with no rounded corners
@@ -279,6 +289,8 @@
             maxBounds.height(),
             minLeft,
             minRight,
+            isRtl,
+            dotWidth,
             targetRotation,
             currentRotation)
 
@@ -296,6 +308,8 @@
  * @param cHeight display height in our current rotation
  * @param minLeft the minimum padding to enforce on the left
  * @param minRight the minimum padding to enforce on the right
+ * @param isRtl current layout direction is Right-To-Left or not
+ * @param dotWidth privacy dot image width (0 if privacy dot is disabled)
  * @param targetRotation the rotation for which to calculate margins
  * @param currentRotation the rotation from which the display cutout was generated
  *
@@ -311,6 +325,8 @@
     cHeight: Int,
     minLeft: Int,
     minRight: Int,
+    isRtl: Boolean,
+    dotWidth: Int,
     @Rotation targetRotation: Int,
     @Rotation currentRotation: Int
 ): Rect {
@@ -345,13 +361,16 @@
         }
 
         if (cutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
-
-            val l = max(minLeft, cutoutRect.logicalWidth(relativeRotation))
-            leftMargin = max(l, leftMargin)
+            var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+            if (isRtl) logicalWidth += dotWidth
+            leftMargin = max(logicalWidth, leftMargin)
         } else if (cutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
-            val logicalWidth = cutoutRect.logicalWidth(relativeRotation)
-            rightMargin = max(minRight, logicalWidth)
+            var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+            if (!isRtl) logicalWidth += dotWidth
+            rightMargin = max(rightMargin, logicalWidth)
         }
+        // TODO(b/203626889): Fix the scenario when config_mainBuiltInDisplayCutoutRectApproximation
+        //                    is very close to but not directly touch edges.
     }
 
     return Rect(leftMargin, 0, logicalDisplayWidth - rightMargin, sbHeight)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 07489a9..cd95cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -51,7 +51,6 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -70,7 +69,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Objects;
-import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -109,7 +107,6 @@
     private final ConfigurationController mConfigurationController;
     private final NavigationModeController mNavigationModeController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final Optional<FaceAuthScreenBrightnessController> mFaceAuthScreenBrightnessController;
     private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@@ -242,7 +239,6 @@
             DockManager dockManager,
             NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController,
-            Optional<FaceAuthScreenBrightnessController> faceAuthScreenBrightnessController,
             NotificationMediaManager notificationMediaManager,
             KeyguardBouncer.Factory keyguardBouncerFactory,
             WakefulnessLifecycle wakefulnessLifecycle,
@@ -260,7 +256,6 @@
         mKeyguardUpdateManager = keyguardUpdateMonitor;
         mStatusBarStateController = sysuiStatusBarStateController;
         mDockManager = dockManager;
-        mFaceAuthScreenBrightnessController = faceAuthScreenBrightnessController;
         mKeyguardBouncerFactory = keyguardBouncerFactory;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
@@ -285,11 +280,6 @@
         mNotificationContainer = notificationContainer;
         mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
             KeyguardMessageArea.findSecurityMessageDisplay(container));
-        mFaceAuthScreenBrightnessController.ifPresent((it) -> {
-            View overlay = new View(mContext);
-            container.addView(overlay);
-            it.attach(overlay);
-        });
 
         registerListeners();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index c655964..cf9b2c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -17,9 +17,7 @@
 import static com.android.systemui.statusbar.phone.StatusBar.CLOSE_PANEL_WHEN_EMPTIED;
 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
 import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG;
-import static com.android.systemui.statusbar.phone.StatusBar.SPEW;
 
-import android.annotation.Nullable;
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.os.RemoteException;
@@ -36,7 +34,6 @@
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.MessagingGroup;
 import com.android.internal.widget.MessagingMessage;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -44,6 +41,7 @@
 import com.android.systemui.ForegroundServiceNotificationListener;
 import com.android.systemui.InitController;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.statusbar.CommandQueue;
@@ -59,10 +57,10 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -88,6 +86,7 @@
     private final NotificationViewHierarchyManager mViewHierarchyManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final SysuiStatusBarStateController mStatusBarStateController;
+    private final NotifShadeEventSource mNotifShadeEventSource;
     private final NotificationEntryManager mEntryManager;
     private final NotificationMediaManager mMediaManager;
     private final NotificationGutsManager mGutsManager;
@@ -100,6 +99,7 @@
     private final DozeScrimController mDozeScrimController;
     private final ScrimController mScrimController;
     private final KeyguardIndicationController mKeyguardIndicationController;
+    private final FeatureFlags mFeatureFlags;
     private final StatusBar mStatusBar;
     private final ShadeController mShadeController;
     private final LockscreenShadeTransitionController mShadeTransitionController;
@@ -127,6 +127,7 @@
             DynamicPrivacyController dynamicPrivacyController,
             KeyguardStateController keyguardStateController,
             KeyguardIndicationController keyguardIndicationController,
+            FeatureFlags featureFlags,
             StatusBar statusBar,
             ShadeController shadeController,
             LockscreenShadeTransitionController shadeTransitionController,
@@ -134,6 +135,7 @@
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             NotificationLockscreenUserManager lockscreenUserManager,
             SysuiStatusBarStateController sysuiStatusBarStateController,
+            NotifShadeEventSource notifShadeEventSource,
             NotificationEntryManager notificationEntryManager,
             NotificationMediaManager notificationMediaManager,
             NotificationGutsManager notificationGutsManager,
@@ -148,6 +150,7 @@
         mHeadsUpManager = headsUp;
         mDynamicPrivacyController = dynamicPrivacyController;
         mKeyguardIndicationController = keyguardIndicationController;
+        mFeatureFlags = featureFlags;
         // TODO: use KeyguardStateController#isOccluded to remove this dependency
         mStatusBar = statusBar;
         mShadeController = shadeController;
@@ -156,6 +159,7 @@
         mViewHierarchyManager = notificationViewHierarchyManager;
         mLockscreenUserManager = lockscreenUserManager;
         mStatusBarStateController = sysuiStatusBarStateController;
+        mNotifShadeEventSource = notifShadeEventSource;
         mEntryManager = notificationEntryManager;
         mMediaManager = notificationMediaManager;
         mGutsManager = notificationGutsManager;
@@ -186,30 +190,18 @@
                 mNotificationPanel.createRemoteInputDelegate());
 
         initController.addPostInitTask(() -> {
-            NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
-                @Override
-                public void onEntryRemoved(
-                        @Nullable NotificationEntry entry,
-                        NotificationVisibility visibility,
-                        boolean removedByUser,
-                        int reason) {
-                    StatusBarNotificationPresenter.this.onNotificationRemoved(
-                            entry.getKey(), entry.getSbn(), reason);
-                    if (removedByUser) {
-                        maybeEndAmbientPulse();
-                    }
-                }
-            };
-
             mKeyguardIndicationController.init();
             mViewHierarchyManager.setUpWithPresenter(this,
                     stackScrollerController.getNotificationListContainer());
-            mEntryManager.setUpWithPresenter(this);
-            mEntryManager.addNotificationEntryListener(notificationEntryListener);
-            mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
-            mEntryManager.addNotificationLifetimeExtender(mGutsManager);
-            mEntryManager.addNotificationLifetimeExtenders(
-                    remoteInputManager.getLifetimeExtenders());
+            mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
+            mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
+            if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+                mEntryManager.setUpWithPresenter(this);
+                mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
+                mEntryManager.addNotificationLifetimeExtender(mGutsManager);
+                mEntryManager.addNotificationLifetimeExtenders(
+                        remoteInputManager.getLifetimeExtenders());
+            }
             notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
             mLockscreenUserManager.setUpWithPresenter(this);
             mMediaManager.setUpWithPresenter(this);
@@ -226,8 +218,21 @@
         configurationController.addCallback(this);
     }
 
+    /** Called when the shade has been emptied to attempt to close the shade */
+    private void maybeClosePanelForShadeEmptied() {
+        if (CLOSE_PANEL_WHEN_EMPTIED
+                && !mNotificationPanel.isTracking()
+                && !mNotificationPanel.isQsExpanded()
+                && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
+                && !isCollapsing()) {
+            mStatusBarStateController.setState(StatusBarState.KEYGUARD);
+        }
+    }
+
     @Override
     public void onDensityOrFontScaleChanged() {
+        // TODO(b/145659174): Remove legacy pipeline code
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
         MessagingMessage.dropCache();
         MessagingGroup.dropCache();
         if (!mKeyguardUpdateMonitor.isSwitchingUser()) {
@@ -239,8 +244,10 @@
 
     @Override
     public void onUiModeChanged() {
+        // TODO(b/145659174): Remove legacy pipeline code
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
         if (!mKeyguardUpdateMonitor.isSwitchingUser()) {
-            updateNotificationOnUiModeChanged();
+            updateNotificationsOnUiModeChanged();
         } else {
             mDispatchUiModeChangeOnUserSwitched = true;
         }
@@ -251,7 +258,9 @@
         onDensityOrFontScaleChanged();
     }
 
-    private void updateNotificationOnUiModeChanged() {
+    private void updateNotificationsOnUiModeChanged() {
+        // TODO(b/145659174): Remove legacy pipeline code
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
         List<NotificationEntry> userNotifications =
                 mEntryManager.getActiveNotificationsForCurrentUser();
         for (int i = 0; i < userNotifications.size(); i++) {
@@ -264,6 +273,8 @@
     }
 
     private void updateNotificationsOnDensityOrFontScaleChanged() {
+        // TODO(b/145659174): Remove legacy pipeline code
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
         List<NotificationEntry> userNotifications =
                 mEntryManager.getActiveNotificationsForCurrentUser();
         for (int i = 0; i < userNotifications.size(); i++) {
@@ -276,6 +287,7 @@
         }
     }
 
+
     @Override
     public boolean isCollapsing() {
         return mNotificationPanel.isCollapsing()
@@ -308,21 +320,6 @@
         mNotificationPanel.updateNotificationViews(reason);
     }
 
-    private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
-        if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
-
-        if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
-                && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()
-                && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
-                        && !isCollapsing()) {
-                mStatusBarStateController.setState(StatusBarState.KEYGUARD);
-        }
-    }
-
-    public boolean hasActiveNotifications() {
-        return mEntryManager.hasActiveNotifications();
-    }
-
     @Override
     public void onUserSwitched(int newUserId) {
         // Begin old BaseStatusBar.userSwitched
@@ -335,7 +332,7 @@
             mReinflateNotificationsOnUserSwitched = false;
         }
         if (mDispatchUiModeChangeOnUserSwitched) {
-            updateNotificationOnUiModeChanged();
+            updateNotificationsOnUiModeChanged();
             mDispatchUiModeChangeOnUserSwitched = false;
         }
         updateNotificationViews("user switched");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index c452a48..2681d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -68,6 +68,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
@@ -157,6 +158,7 @@
             FalsingManager falsingManager,
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
+            NotifShadeEventSource notifShadeEventSource,
             NotificationEntryManager notificationEntryManager,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
@@ -260,6 +262,7 @@
                 falsingManager,
                 falsingCollector,
                 broadcastDispatcher,
+                notifShadeEventSource,
                 notificationEntryManager,
                 notificationGutsManager,
                 notificationLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
index 89297fd..d5d3efd 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
@@ -26,6 +26,7 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.util.Log;
+import android.view.WindowManager;
 
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
@@ -47,6 +48,8 @@
 
     @Override
     public void onCreate(Bundle icicle) {
+       getWindow().addSystemFlags(
+                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
        super.onCreate(icicle);
 
        Intent intent = getIntent();
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
index 4850a02..2871263 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
@@ -30,6 +30,7 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.util.Log;
+import android.view.WindowManager;
 
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
@@ -51,6 +52,8 @@
 
     @Override
     public void onCreate(Bundle icicle) {
+        getWindow().addSystemFlags(
+                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
         super.onCreate(icicle);
 
         if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
index c420a6d..02a7637 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
@@ -33,6 +33,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Log;
+import android.view.WindowManager;
 import android.widget.CheckBox;
 
 import com.android.internal.app.ResolverActivity;
@@ -60,6 +61,8 @@
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
+        getWindow().addSystemFlags(
+                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
         Intent intent = getIntent();
         Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
         if (!(targetParcelable instanceof Intent)) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index fd9783a..0a33930 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -463,13 +463,13 @@
             @ShellMainThread ShellExecutor mainExecutor,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, Transitions transitions,
-            TransactionPool transactionPool,
+            TransactionPool transactionPool, IconProvider iconProvider,
             Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
         if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
             return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context,
                     rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
-                    displayInsetsController, transitions,
-                    transactionPool, stageTaskUnfoldControllerProvider));
+                    displayInsetsController, transitions, transactionPool, iconProvider,
+                    stageTaskUnfoldControllerProvider));
         } else {
             return Optional.empty();
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 5fee7fb..ca7d506 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
 import android.hardware.biometrics.SensorProperties
 import android.hardware.display.DisplayManager
@@ -182,7 +183,16 @@
 
     @Test
     fun testIgnoredForKeyguard() {
-        overlayController.show(SENSOR_ID, REASON_AUTH_KEYGUARD)
+        testIgnoredFor(REASON_AUTH_KEYGUARD)
+    }
+
+    @Test
+    fun testIgnoredForSettings() {
+        testIgnoredFor(REASON_AUTH_SETTINGS)
+    }
+
+    private fun testIgnoredFor(reason: Int) {
+        overlayController.show(SENSOR_ID, reason)
         executor.runAllReady()
 
         verify(windowManager, never()).addView(any(), any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
index 172dcda..b3c098c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
@@ -18,23 +18,35 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 @SmallTest
 public class FeatureFlagManagerTest extends SysuiTestCase {
     FeatureFlagManager mFeatureFlagManager;
 
+    @Mock private DumpManager mDumpManager;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mFeatureFlagManager = new FeatureFlagManager();
+        mFeatureFlagManager = new FeatureFlagManager(mDumpManager);
     }
 
     @Test
@@ -43,4 +55,31 @@
         // Again, nothing changes.
         assertThat(mFeatureFlagManager.isEnabled(1, false)).isFalse();
     }
+
+    @Test
+    public void testDump() {
+        // Even if a flag is set before
+        mFeatureFlagManager.setEnabled(1, true);
+
+        // WHEN the flags have been accessed
+        assertFalse(mFeatureFlagManager.isEnabled(1, false));
+        assertTrue(mFeatureFlagManager.isEnabled(2, true));
+
+        // Even if a flag is set after
+        mFeatureFlagManager.setEnabled(2, false);
+
+        // THEN the dump contains the flags and the default values
+        String dump = dumpToString();
+        assertThat(dump).contains(" sysui_flag_1: false\n");
+        assertThat(dump).contains(" sysui_flag_2: true\n");
+    }
+
+    private String dumpToString() {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        mFeatureFlagManager.dump(mock(FileDescriptor.class), pw, new String[0]);
+        pw.flush();
+        String dump = sw.toString();
+        return dump;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessControllerTest.kt
deleted file mode 100644
index cb05a6b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessControllerTest.kt
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard
-
-import android.animation.ValueAnimator
-import android.content.res.Resources
-import android.hardware.biometrics.BiometricSourceType
-import android.os.Handler
-import android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT
-import android.testing.AndroidTestingRunner
-import android.util.TypedValue
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Dumpable
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SystemSettings
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyString
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-const val INITIAL_BRIGHTNESS = 0.5f
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class FaceAuthScreenBrightnessControllerTest : SysuiTestCase() {
-
-    @Mock
-    lateinit var whiteOverlay: View
-    @Mock
-    lateinit var dumpManager: DumpManager
-    @Mock
-    lateinit var resources: Resources
-    @Mock
-    lateinit var mainHandler: Handler
-    @Mock
-    lateinit var globalSettings: GlobalSettings
-    @Mock
-    lateinit var systemSettings: SystemSettings
-    @Mock
-    lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock
-    lateinit var notificationShadeWindowController: NotificationShadeWindowController
-    @Mock
-    lateinit var animator: ValueAnimator
-    @Captor
-    lateinit var keyguardUpdateCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
-    lateinit var faceAuthScreenBrightnessController: FaceAuthScreenBrightnessController
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        faceAuthScreenBrightnessController = object : FaceAuthScreenBrightnessController(
-                notificationShadeWindowController, keyguardUpdateMonitor, resources, globalSettings,
-                systemSettings, mainHandler, dumpManager, true) {
-            override fun createAnimator(start: Float, end: Float) = animator
-        }
-        `when`(systemSettings.getFloat(eq(SCREEN_BRIGHTNESS_FLOAT))).thenReturn(INITIAL_BRIGHTNESS)
-        `when`(systemSettings.getFloat(eq(SCREEN_BRIGHTNESS_FLOAT), eq(1f)))
-                .thenReturn(INITIAL_BRIGHTNESS)
-        faceAuthScreenBrightnessController.attach(whiteOverlay)
-        verify(keyguardUpdateMonitor).registerCallback(capture(keyguardUpdateCallback))
-    }
-
-    @Test
-    fun init_registersDumpManager() {
-        verify(dumpManager).registerDumpable(anyString(), any(Dumpable::class.java))
-    }
-
-    @Test
-    fun init_registersKeyguardCallback() {
-        verify(keyguardUpdateMonitor)
-                .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
-    }
-
-    @Test
-    fun onBiometricRunningChanged_animatesBrightness() {
-        clearInvocations(whiteOverlay)
-        keyguardUpdateCallback.value
-                .onBiometricRunningStateChanged(true, BiometricSourceType.FACE)
-        verify(whiteOverlay).visibility = eq(View.VISIBLE)
-        verify(animator).start()
-    }
-
-    @Test
-    fun faceAuthWallpaper_whenFaceIsDisabledForUser() {
-        faceAuthScreenBrightnessController.useFaceAuthWallpaper = true
-        faceAuthScreenBrightnessController.faceAuthWallpaper
-        verify(resources, never()).openRawResource(anyInt(), any(TypedValue::class.java))
-    }
-
-    @Test
-    fun faceAuthWallpaper_whenFaceFlagIsDisabled() {
-        faceAuthScreenBrightnessController.useFaceAuthWallpaper = true
-        faceAuthScreenBrightnessController.faceAuthWallpaper
-        verify(resources, never()).openRawResource(anyInt(), any(TypedValue::class.java))
-    }
-
-    @Test
-    fun faceAuthWallpaper_whenFaceIsEnabledForUser() {
-        faceAuthScreenBrightnessController.useFaceAuthWallpaper = true
-        `when`(keyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(true)
-        faceAuthScreenBrightnessController.faceAuthWallpaper
-        verify(resources).openRawResource(anyInt(), any(TypedValue::class.java))
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 750600ad..52173c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -212,12 +212,14 @@
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
 
         assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(
-                mContext.getString(R.string.media_output_dialog_disconnected, TEST_DEVICE_NAME_2));
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo(
+                TEST_DEVICE_NAME_2);
+        assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(
+                mContext.getString(R.string.media_output_dialog_disconnected));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
new file mode 100644
index 0000000..3059aa1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
@@ -0,0 +1,62 @@
+package com.android.systemui.qs
+
+import android.testing.AndroidTestingRunner
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.tileimpl.QSTileViewImpl
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class QSSquishinessControllerTest : SysuiTestCase() {
+
+    @Mock private lateinit var qsTileHost: QSTileHost
+    @Mock private lateinit var qqsFooterActionsView: FooterActionsView
+    @Mock private lateinit var qqsFooterActionsViewLP: ViewGroup.MarginLayoutParams
+    @Mock private lateinit var qsAnimator: QSAnimator
+    @Mock private lateinit var quickQsPanelController: QuickQSPanelController
+    @Mock private lateinit var qstileView: QSTileViewImpl
+    @Mock private lateinit var qstile: QSTile
+    @Mock private lateinit var tileLayout: TileLayout
+
+    @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
+
+    private lateinit var qsSquishinessController: QSSquishinessController
+
+    @Before
+    fun setup() {
+        qsSquishinessController = QSSquishinessController(qsTileHost, qqsFooterActionsView,
+                qsAnimator, quickQsPanelController)
+        `when`(qsTileHost.tiles).thenReturn(mutableListOf(qstile))
+        `when`(quickQsPanelController.getTileView(any())).thenReturn(qstileView)
+        `when`(quickQsPanelController.tileLayout).thenReturn(tileLayout)
+        `when`(qqsFooterActionsView.layoutParams).thenReturn(qqsFooterActionsViewLP)
+    }
+
+    @Test
+    fun setSquishiness_requestsAnimatorUpdate() {
+        qsSquishinessController.squishiness = 0.5f
+        verify(qsAnimator, never()).requestAnimatorUpdate()
+
+        qsSquishinessController.squishiness = 0f
+        verify(qsAnimator).requestAnimatorUpdate()
+    }
+
+    @Test
+    fun setSquishiness_updatesTiles() {
+        qsSquishinessController.squishiness = 0.5f
+        verify(qstileView).squishinessFraction = 0.5f
+        verify(tileLayout).setSquishinessFraction(0.5f)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index 94af10a..98c7274 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -52,13 +52,11 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.never
 import org.mockito.Mockito.nullable
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
@@ -272,28 +270,7 @@
     }
 
     @Test
-    fun handleClick_availableAndLocked_activityStarted() {
-        verify(controlsListingController).observe(
-                any(LifecycleOwner::class.java),
-                capture(listingCallbackCaptor)
-        )
-        `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
-        `when`(keyguardStateController.isUnlocked).thenReturn(false)
-
-        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
-        testableLooper.processAllMessages()
-
-        tile.click(null /* view */)
-        testableLooper.processAllMessages()
-
-        // The activity should be started right away and not require a keyguard dismiss.
-        verifyZeroInteractions(activityStarter)
-        verify(spiedContext).startActivity(intentCaptor.capture())
-        assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
-    }
-
-    @Test
-    fun handleClick_availableAndUnlocked_activityStarted() {
+    fun handleClick_available_shownOverLockscreenWhenLocked() {
         verify(controlsListingController).observe(
                 any(LifecycleOwner::class.java),
                 capture(listingCallbackCaptor)
@@ -307,16 +284,16 @@
         tile.click(null /* view */)
         testableLooper.processAllMessages()
 
-        verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
         verify(activityStarter).startActivity(
                 intentCaptor.capture(),
                 eq(true) /* dismissShade */,
-                nullable(ActivityLaunchAnimator.Controller::class.java))
+                nullable(ActivityLaunchAnimator.Controller::class.java),
+                eq(true) /* showOverLockscreenWhenLocked */)
         assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
     }
 
     @Test
-    fun handleClick_availableAfterUnlockAndIsLocked_keyguardDismissRequired() {
+    fun handleClick_availableAfterUnlock_notShownOverLockscreenWhenLocked() {
         verify(controlsListingController).observe(
             any(LifecycleOwner::class.java),
             capture(listingCallbackCaptor)
@@ -331,38 +308,11 @@
         tile.click(null /* view */)
         testableLooper.processAllMessages()
 
-        verify(activityStarter, never()).startActivity(
-                any(),
-                anyBoolean() /* dismissShade */,
-                nullable(ActivityLaunchAnimator.Controller::class.java))
-        verify(activityStarter).postStartActivityDismissingKeyguard(
-                intentCaptor.capture(),
-                anyInt(),
-                nullable(ActivityLaunchAnimator.Controller::class.java))
-        assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
-    }
-
-    @Test
-    fun handleClick_availableAfterUnlockAndIsUnlocked_activityStarted() {
-        verify(controlsListingController).observe(
-                any(LifecycleOwner::class.java),
-                capture(listingCallbackCaptor)
-        )
-        `when`(controlsComponent.getVisibility())
-                .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
-        `when`(keyguardStateController.isUnlocked).thenReturn(true)
-
-        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
-        testableLooper.processAllMessages()
-
-        tile.click(null /* view */)
-        testableLooper.processAllMessages()
-
-        verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
         verify(activityStarter).startActivity(
                 intentCaptor.capture(),
-                eq(true) /* dismissShade */,
-                nullable(ActivityLaunchAnimator.Controller::class.java))
+                anyBoolean() /* dismissShade */,
+                nullable(ActivityLaunchAnimator.Controller::class.java),
+                eq(false) /* showOverLockscreenWhenLocked */)
         assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index fe32839..5cea763 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.toast.SystemUIToast;
 import com.android.systemui.toast.ToastFactory;
 import com.android.systemui.util.CarrierConfigTracker;
@@ -135,6 +136,8 @@
     private Animator mAnimator;
     @Mock
     private CarrierConfigTracker mCarrierConfigTracker;
+    @Mock
+    private LocationController mLocationController;
 
     private TestableResources mTestableResources;
     private MockInternetDialogController mInternetDialogController;
@@ -170,7 +173,8 @@
                 mSubscriptionManager, mTelephonyManager, mWifiManager,
                 mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
                 mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
-                mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker);
+                mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker,
+                mLocationController);
         mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
                 mInternetDialogController.mOnSubscriptionsChangedListener);
         mInternetDialogController.onStart(mInternetDialogCallback, true);
@@ -602,6 +606,30 @@
         verify(mMergedCarrierEntry).setEnabled(false);
     }
 
+    @Test
+    public void isWifiScanEnabled_locationOff_returnFalse() {
+        when(mLocationController.isLocationEnabled()).thenReturn(false);
+        when(mWifiManager.isScanAlwaysAvailable()).thenReturn(false);
+
+        assertThat(mInternetDialogController.isWifiScanEnabled()).isFalse();
+
+        when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true);
+
+        assertThat(mInternetDialogController.isWifiScanEnabled()).isFalse();
+    }
+
+    @Test
+    public void isWifiScanEnabled_locationOn_returnIsScanAlwaysAvailable() {
+        when(mLocationController.isLocationEnabled()).thenReturn(true);
+        when(mWifiManager.isScanAlwaysAvailable()).thenReturn(false);
+
+        assertThat(mInternetDialogController.isWifiScanEnabled()).isFalse();
+
+        when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true);
+
+        assertThat(mInternetDialogController.isWifiScanEnabled()).isTrue();
+    }
+
     private String getResourcesString(String name) {
         return mContext.getResources().getString(getResourcesId(name));
     }
@@ -625,12 +653,13 @@
                 KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings,
                 KeyguardStateController keyguardStateController, WindowManager windowManager,
                 ToastFactory toastFactory, Handler workerHandler,
-                CarrierConfigTracker carrierConfigTracker) {
+                CarrierConfigTracker carrierConfigTracker,
+                LocationController locationController) {
             super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
                     telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
                     broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
                     keyguardStateController, windowManager, toastFactory, workerHandler,
-                    carrierConfigTracker);
+                    carrierConfigTracker, locationController);
             mGlobalSettings = globalSettings;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 6688960..5e1fea5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -282,7 +282,7 @@
     @Test
     public void updateDialog_wifiOffAndWifiScanOff_hideWifiScanNotify() {
         when(mWifiManager.isWifiEnabled()).thenReturn(false);
-        when(mWifiManager.isScanAlwaysAvailable()).thenReturn(false);
+        when(mInternetDialogController.isWifiScanEnabled()).thenReturn(false);
 
         mInternetDialog.updateDialog(false);
 
@@ -292,7 +292,7 @@
     @Test
     public void updateDialog_wifiOffAndWifiScanOnAndDeviceLocked_hideWifiScanNotify() {
         when(mWifiManager.isWifiEnabled()).thenReturn(false);
-        when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true);
+        when(mInternetDialogController.isWifiScanEnabled()).thenReturn(true);
         when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
 
         mInternetDialog.updateDialog(false);
@@ -303,7 +303,7 @@
     @Test
     public void updateDialog_wifiOffAndWifiScanOnAndDeviceUnlocked_showWifiScanNotify() {
         when(mWifiManager.isWifiEnabled()).thenReturn(false);
-        when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true);
+        when(mInternetDialogController.isWifiScanEnabled()).thenReturn(true);
         when(mInternetDialogController.isDeviceLocked()).thenReturn(false);
 
         mInternetDialog.updateDialog(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
new file mode 100644
index 0000000..5915cd7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.service.notification.NotificationListenerService.REASON_APP_CANCEL
+import android.service.notification.NotificationListenerService.REASON_CANCEL
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.mockito.argumentCaptor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class ShadeEventCoordinatorTest : SysuiTestCase() {
+    private lateinit var coordinator: ShadeEventCoordinator
+    private lateinit var notifCollectionListener: NotifCollectionListener
+    private lateinit var onBeforeRenderListListener: OnBeforeRenderListListener
+
+    private lateinit var entry1: NotificationEntry
+    private lateinit var entry2: NotificationEntry
+
+    @Mock private lateinit var pipeline: NotifPipeline
+    @Mock private lateinit var logger: ShadeEventCoordinatorLogger
+    @Mock private lateinit var notifRemovedByUserCallback: Runnable
+    @Mock private lateinit var shadeEmptiedCallback: Runnable
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+        coordinator = ShadeEventCoordinator(logger)
+        coordinator.attach(pipeline)
+        notifCollectionListener = argumentCaptor<NotifCollectionListener>().let {
+            verify(pipeline).addCollectionListener(it.capture())
+            it.value!!
+        }
+        onBeforeRenderListListener = argumentCaptor<OnBeforeRenderListListener>().let {
+            verify(pipeline).addOnBeforeRenderListListener(it.capture())
+            it.value!!
+        }
+        coordinator.setNotifRemovedByUserCallback(notifRemovedByUserCallback)
+        coordinator.setShadeEmptiedCallback(shadeEmptiedCallback)
+        entry1 = NotificationEntryBuilder().setId(1).build()
+        entry2 = NotificationEntryBuilder().setId(2).build()
+    }
+
+    @Test
+    fun testUserCancelLastNotification() {
+        notifCollectionListener.onEntryRemoved(entry1, REASON_CANCEL)
+        verify(shadeEmptiedCallback, never()).run()
+        verify(notifRemovedByUserCallback, never()).run()
+        onBeforeRenderListListener.onBeforeRenderList(listOf())
+        verify(shadeEmptiedCallback).run()
+        verify(notifRemovedByUserCallback).run()
+    }
+
+    @Test
+    fun testAppCancelLastNotification() {
+        notifCollectionListener.onEntryRemoved(entry1, REASON_APP_CANCEL)
+        onBeforeRenderListListener.onBeforeRenderList(listOf())
+        verify(shadeEmptiedCallback).run()
+        verify(notifRemovedByUserCallback, never()).run()
+    }
+
+    @Test
+    fun testUserCancelOneOfTwoNotifications() {
+        notifCollectionListener.onEntryRemoved(entry1, REASON_CANCEL)
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry2))
+        verify(shadeEmptiedCallback, never()).run()
+        verify(notifRemovedByUserCallback).run()
+    }
+
+    @Test
+    fun testAppCancelOneOfTwoNotifications() {
+        notifCollectionListener.onEntryRemoved(entry1, REASON_APP_CANCEL)
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry2))
+        verify(shadeEmptiedCallback, never()).run()
+        verify(notifRemovedByUserCallback, never()).run()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
new file mode 100644
index 0000000..a9e8164
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
@@ -0,0 +1,87 @@
+package com.android.systemui.statusbar.phone
+
+import android.content.Context
+import android.content.res.Resources
+import android.test.suitebuilder.annotation.SmallTest
+import android.view.View
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+class SplitShadeHeaderControllerTest : SysuiTestCase() {
+
+    @Mock private lateinit var view: View
+    @Mock private lateinit var statusIcons: StatusIconContainer
+    @Mock private lateinit var statusBarIconController: StatusBarIconController
+    @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
+    @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
+    @Mock private lateinit var featureFlags: FeatureFlags
+    @Mock private lateinit var batteryMeterView: BatteryMeterView
+    @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
+    @Mock private lateinit var resources: Resources
+    @Mock private lateinit var context: Context
+    @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
+    var viewVisibility = View.GONE
+
+    private lateinit var splitShadeHeaderController: SplitShadeHeaderController
+
+    @Before
+    fun setup() {
+        whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
+                .thenReturn(batteryMeterView)
+        whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
+        whenever(statusIcons.context).thenReturn(context)
+        whenever(context.resources).thenReturn(resources)
+        whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
+                .thenReturn(qsCarrierGroupControllerBuilder)
+        whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
+        whenever(view.setVisibility(anyInt())).then {
+            viewVisibility = it.arguments[0] as Int
+            null
+        }
+        whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+        splitShadeHeaderController = SplitShadeHeaderController(view, statusBarIconController,
+        qsCarrierGroupControllerBuilder, featureFlags, batteryMeterViewController)
+    }
+
+    @Test
+    fun setVisible_onlyInSplitShade() {
+        splitShadeHeaderController.splitShadeMode = true
+        splitShadeHeaderController.shadeExpanded = true
+        assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+
+        splitShadeHeaderController.splitShadeMode = false
+        assertThat(viewVisibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun updateListeners_registersWhenVisible() {
+        splitShadeHeaderController.splitShadeMode = true
+        splitShadeHeaderController.shadeExpanded = true
+        verify(qsCarrierGroupController).setListening(true)
+        verify(statusBarIconController).addIconGroup(any())
+    }
+
+    @Test
+    fun shadeExpandedFraction_updatesAlpha() {
+        splitShadeHeaderController.splitShadeMode = true
+        splitShadeHeaderController.shadeExpanded = true
+        splitShadeHeaderController.shadeExpandedFraction = 0.5f
+        verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f))
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 1503af8..e5158e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -86,7 +86,9 @@
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
 
         var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
         /* 1080 - 20 (rounded corner) - 30 (chip),
@@ -115,7 +117,9 @@
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
 
         chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
         /* 2160 - 20 (rounded corner) - 30 (chip),
@@ -146,6 +150,8 @@
         val sbHeightPortrait = 100
         val sbHeightLandscape = 60
         val currentRotation = ROTATION_NONE
+        val isRtl = false
+        val dotWidth = 10
 
         `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
 
@@ -164,7 +170,9 @@
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
@@ -181,7 +189,9 @@
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
@@ -200,7 +210,9 @@
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
@@ -208,7 +220,7 @@
         targetRotation = ROTATION_SEASCAPE
         expectedBounds = Rect(minLeftPadding,
                 0,
-                screenBounds.height() - dcBounds.height(),
+                screenBounds.height() - dcBounds.height() - dotWidth,
                 sbHeightLandscape)
 
         bounds = calculateInsetsForRotationWithRotatedResources(
@@ -218,7 +230,9 @@
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
     }
@@ -237,6 +251,8 @@
         val sbHeightPortrait = 100
         val sbHeightLandscape = 60
         val currentRotation = ROTATION_NONE
+        val isRtl = false
+        val dotWidth = 10
 
         `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
 
@@ -255,7 +271,9 @@
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
@@ -272,7 +290,9 @@
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
@@ -289,14 +309,16 @@
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_SEASCAPE
         expectedBounds = Rect(minLeftPadding,
                 0,
-                screenBounds.height() - dcBounds.height(),
+                screenBounds.height() - dcBounds.height() - dotWidth,
                 sbHeightLandscape)
 
         bounds = calculateInsetsForRotationWithRotatedResources(
@@ -306,7 +328,9 @@
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
     }
@@ -320,6 +344,8 @@
         val minRightPadding = 20
         val sbHeightPortrait = 100
         val sbHeightLandscape = 60
+        val isRtl = false
+        val dotWidth = 10
 
         // THEN content insets should only use rounded corner padding
         var targetRotation = ROTATION_NONE
@@ -335,7 +361,9 @@
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_LANDSCAPE
@@ -351,7 +379,9 @@
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_UPSIDE_DOWN
@@ -367,7 +397,9 @@
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_LANDSCAPE
@@ -383,7 +415,9 @@
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
     }
 
@@ -397,6 +431,8 @@
         val sbHeightPortrait = 100
         val sbHeightLandscape = 60
         val currentRotation = ROTATION_NONE
+        val isRtl = false
+        val dotWidth = 10
 
         `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
 
@@ -414,7 +450,9 @@
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
-                minRightPadding)
+                minRightPadding,
+                isRtl,
+                dotWidth)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 3fcd071..35d15af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -44,7 +44,6 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
@@ -60,8 +59,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Optional;
-
 import dagger.Lazy;
 
 @SmallTest
@@ -92,8 +89,6 @@
     @Mock
     private KeyguardBypassController mBypassController;
     @Mock
-    private FaceAuthScreenBrightnessController mFaceAuthScreenBrightnessController;
-    @Mock
     private KeyguardBouncer.Factory mKeyguardBouncerFactory;
     @Mock
     private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
@@ -133,7 +128,6 @@
                 mock(DockManager.class),
                 mock(NotificationShadeWindowController.class),
                 mKeyguardStateController,
-                Optional.of(mFaceAuthScreenBrightnessController),
                 mock(NotificationMediaManager.class),
                 mKeyguardBouncerFactory,
                 mWakefulnessLifecycle,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index c80c072..4e6b0a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -37,6 +37,7 @@
 import com.android.systemui.ForegroundServiceNotificationListener;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -51,6 +52,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -109,12 +111,15 @@
                 mock(DozeScrimController.class), mock(ScrimController.class),
                 mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class),
                 mock(KeyguardStateController.class),
-                mock(KeyguardIndicationController.class), mStatusBar,
+                mock(KeyguardIndicationController.class),
+                mock(FeatureFlags.class),
+                mStatusBar,
                 mock(ShadeControllerImpl.class), mock(LockscreenShadeTransitionController.class),
                 mCommandQueue,
                 mock(NotificationViewHierarchyManager.class),
                 mock(NotificationLockscreenUserManager.class),
                 mock(SysuiStatusBarStateController.class),
+                mock(NotifShadeEventSource.class),
                 mock(NotificationEntryManager.class),
                 mock(NotificationMediaManager.class),
                 mock(NotificationGutsManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 943d3c7..ca8b6c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -121,6 +121,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
@@ -210,6 +211,7 @@
     @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private AssistManager mAssistManager;
+    @Mock private NotifShadeEventSource mNotifShadeEventSource;
     @Mock private NotificationEntryManager mNotificationEntryManager;
     @Mock private NotificationGutsManager mNotificationGutsManager;
     @Mock private NotificationMediaManager mNotificationMediaManager;
@@ -377,6 +379,7 @@
                 new FalsingManagerFake(),
                 new FalsingCollectorFake(),
                 mBroadcastDispatcher,
+                mNotifShadeEventSource,
                 mNotificationEntryManager,
                 mNotificationGutsManager,
                 notificationLogger,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8205d35..e6f91a25f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -128,6 +128,7 @@
 import com.android.server.SystemService;
 import com.android.server.accessibility.magnification.MagnificationController;
 import com.android.server.accessibility.magnification.MagnificationProcessor;
+import com.android.server.accessibility.magnification.MagnificationScaleProvider;
 import com.android.server.accessibility.magnification.WindowMagnificationManager;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -338,7 +339,8 @@
         mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
                 mWindowManagerService, this, mSecurityPolicy, this, mTraceManager);
         mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
-        mMagnificationController = new MagnificationController(this, mLock, mContext);
+        mMagnificationController = new MagnificationController(this, mLock, mContext,
+                new MagnificationScaleProvider(mContext));
         mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
         init();
     }
@@ -1364,6 +1366,7 @@
     }
 
     private void switchUser(int userId) {
+        mMagnificationController.updateUserIdIfNeeded(userId);
         synchronized (mLock) {
             if (mCurrentUserId == userId && mInitialized) {
                 return;
@@ -1386,8 +1389,6 @@
 
             // The user changed.
             mCurrentUserId = userId;
-
-            mMagnificationController.updateUserIdIfNeeded(mCurrentUserId);
             AccessibilityUserState userState = getCurrentUserStateLocked();
 
             readConfigurationForUserStateLocked(userState);
@@ -1444,6 +1445,7 @@
         synchronized (mLock) {
             mUserStates.remove(userId);
         }
+        getMagnificationController().onUserRemoved(userId);
     }
 
     // Called only during settings restore; currently supports only the owner user
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 8f30aa9a..c62473d 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -28,10 +28,8 @@
 import android.content.IntentFilter;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Message;
-import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.MathUtils;
 import android.util.Slog;
@@ -59,7 +57,8 @@
  * holding the current state of magnification and animation, and it handles
  * communication between the accessibility manager and window manager.
  *
- * Magnification is limited to the range [MIN_SCALE, MAX_SCALE], and can only occur inside the
+ * Magnification is limited to the range controlled by
+ * {@link MagnificationScaleProvider#constrainScale(float)}, and can only occur inside the
  * magnification region. If a value is out of bounds, it will be adjusted to guarantee these
  * constraints.
  */
@@ -69,13 +68,9 @@
 
     private static final MagnificationAnimationCallback STUB_ANIMATION_CALLBACK = success -> {
     };
-    public static final float MIN_SCALE = 1.0f;
-    public static final float MAX_SCALE = 8.0f;
 
     private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
 
-    private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
-
     private final Object mLock;
 
     private final ControllerContext mControllerCtx;
@@ -84,7 +79,7 @@
 
     private final MagnificationInfoChangedCallback mMagnificationInfoChangedCallback;
 
-    private int mUserId;
+    private final MagnificationScaleProvider mScaleProvider;
 
     private final long mMainThreadId;
 
@@ -489,7 +484,7 @@
                 return false;
             }
             // Constrain scale immediately for use in the pivot calculations.
-            scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+            scale = MagnificationScaleProvider.constrainScale(scale);
 
             final Rect viewport = mTempRect;
             mMagnificationRegion.getBounds(viewport);
@@ -557,7 +552,7 @@
             // Compute changes.
             boolean changed = false;
 
-            final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+            final float normScale = MagnificationScaleProvider.constrainScale(scale);
             if (Float.compare(mCurrentMagnificationSpec.scale, normScale) != 0) {
                 mCurrentMagnificationSpec.scale = normScale;
                 changed = true;
@@ -658,12 +653,13 @@
      */
     public FullScreenMagnificationController(@NonNull Context context,
             @NonNull AccessibilityManagerService ams, @NonNull Object lock,
-            @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback) {
+            @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
+            @NonNull MagnificationScaleProvider scaleProvider) {
         this(new ControllerContext(context, ams,
                 LocalServices.getService(WindowManagerInternal.class),
                 new Handler(context.getMainLooper()),
                 context.getResources().getInteger(R.integer.config_longAnimTime)), lock,
-                magnificationInfoChangedCallback);
+                magnificationInfoChangedCallback, scaleProvider);
     }
 
     /**
@@ -672,12 +668,14 @@
     @VisibleForTesting
     public FullScreenMagnificationController(@NonNull ControllerContext ctx,
             @NonNull Object lock,
-            @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback) {
+            @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
+            @NonNull MagnificationScaleProvider scaleProvider) {
         mControllerCtx = ctx;
         mLock = lock;
         mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
         mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
         mMagnificationInfoChangedCallback = magnificationInfoChangedCallback;
+        mScaleProvider = scaleProvider;
     }
 
     /**
@@ -1096,18 +1094,9 @@
     /**
      * Persists the default display magnification scale to the current user's settings.
      */
-    public void persistScale() {
-        // TODO: b/123047354, Need support multi-display?
+    public void persistScale(int displayId) {
         final float scale = getScale(Display.DEFAULT_DISPLAY);
-        final int userId = mUserId;
-
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                mControllerCtx.putMagnificationScale(scale, userId);
-                return null;
-            }
-        }.execute();
+        mScaleProvider.putScale(scale, displayId);
     }
 
     /**
@@ -1117,21 +1106,8 @@
      * @return the previously persisted magnification scale, or the default
      *         scale if none is available
      */
-    public float getPersistedScale() {
-        return mControllerCtx.getMagnificationScale(mUserId);
-    }
-
-    /**
-     * Sets the currently active user ID.
-     *
-     * @param userId the currently active user ID
-     */
-    public void setUserId(int userId) {
-        if (mUserId == userId) {
-            return;
-        }
-        mUserId = userId;
-        resetAllIfNeeded(false);
+    public float getPersistedScale(int displayId) {
+        return mScaleProvider.getScale(displayId);
     }
 
     /**
@@ -1225,7 +1201,11 @@
         mControllerCtx.getHandler().sendMessage(m);
     }
 
-    private void resetAllIfNeeded(boolean animate) {
+    /**
+     * Resets magnification on all displays.
+     * @param animate reset the magnification with animation
+     */
+    void resetAllIfNeeded(boolean animate) {
         synchronized (mLock) {
             for (int i = 0; i < mDisplays.size(); i++) {
                 resetIfNeeded(mDisplays.keyAt(i), animate);
@@ -1288,8 +1268,8 @@
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("MagnificationController[");
-        builder.append("mUserId=").append(mUserId);
         builder.append(", mDisplays=").append(mDisplays);
+        builder.append(", mScaleProvider=").append(mScaleProvider);
         builder.append("]");
         return builder.toString();
     }
@@ -1570,23 +1550,6 @@
         }
 
         /**
-         * Write Settings of magnification scale.
-         */
-        public void putMagnificationScale(float value, int userId) {
-            Settings.Secure.putFloatForUser(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, value, userId);
-        }
-
-        /**
-         * Get Settings of magnification scale.
-         */
-        public float getMagnificationScale(int userId) {
-            return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
-                    DEFAULT_MAGNIFICATION_SCALE, userId);
-        }
-
-        /**
          * @return Configuration of animation duration.
          */
         public long getAnimationDuration() {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 8f4a5cb..935df99 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -119,11 +119,11 @@
     private static final boolean DEBUG_DETECTING = false | DEBUG_ALL;
     private static final boolean DEBUG_PANNING_SCALING = false | DEBUG_ALL;
 
-    // The MIN_SCALE is different from MagnificationController.MIN_SCALE due
+    // The MIN_SCALE is different from MagnificationScaleProvider.MIN_SCALE due
     // to AccessibilityService.MagnificationController#setScale() has
     // different scale range
     private static final float MIN_SCALE = 2.0f;
-    private static final float MAX_SCALE = FullScreenMagnificationController.MAX_SCALE;
+    private static final float MAX_SCALE = MagnificationScaleProvider.MAX_SCALE;
 
     @VisibleForTesting final FullScreenMagnificationController mFullScreenMagnificationController;
 
@@ -341,7 +341,7 @@
         }
 
         public void persistScaleAndTransitionTo(State state) {
-            mFullScreenMagnificationController.persistScale();
+            mFullScreenMagnificationController.persistScale(mDisplayId);
             clear();
             transitionTo(state);
         }
@@ -945,7 +945,7 @@
         if (DEBUG_DETECTING) Slog.i(mLogTag, "zoomOn(" + centerX + ", " + centerY + ")");
 
         final float scale = MathUtils.constrain(
-                mFullScreenMagnificationController.getPersistedScale(),
+                mFullScreenMagnificationController.getPersistedScale(mDisplayId),
                 MIN_SCALE, MAX_SCALE);
         mFullScreenMagnificationController.setScaleAndCenter(mDisplayId,
                 scale, centerX, centerY,
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 5a6836c..3708c7a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -23,11 +23,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.Context;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -75,12 +77,15 @@
     private final SparseArray<DisableMagnificationCallback>
             mMagnificationEndRunnableSparseArray = new SparseArray();
 
+    private final MagnificationScaleProvider mScaleProvider;
     private FullScreenMagnificationController mFullScreenMagnificationController;
     private WindowMagnificationManager mWindowMagnificationMgr;
     private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 
     @GuardedBy("mLock")
     private int mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
+    // Track the active user to reset the magnification and get the associated user settings.
+    private @UserIdInt int mUserId = UserHandle.USER_SYSTEM;
     @GuardedBy("mLock")
     private boolean mImeWindowVisible = false;
     private long mWindowModeEnabledTime = 0;
@@ -98,17 +103,19 @@
     }
 
     public MagnificationController(AccessibilityManagerService ams, Object lock,
-            Context context) {
+            Context context, MagnificationScaleProvider scaleProvider) {
         mAms = ams;
         mLock = lock;
         mContext = context;
+        mScaleProvider = scaleProvider;
     }
 
     @VisibleForTesting
     public MagnificationController(AccessibilityManagerService ams, Object lock,
             Context context, FullScreenMagnificationController fullScreenMagnificationController,
-            WindowMagnificationManager windowMagnificationManager) {
-        this(ams, lock, context);
+            WindowMagnificationManager windowMagnificationManager,
+            MagnificationScaleProvider scaleProvider) {
+        this(ams, lock, context, scaleProvider);
         mFullScreenMagnificationController = fullScreenMagnificationController;
         mWindowMagnificationMgr = windowMagnificationManager;
     }
@@ -194,7 +201,7 @@
         final FullScreenMagnificationController screenMagnificationController =
                 getFullScreenMagnificationController();
         final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
-        final float scale = windowMagnificationMgr.getPersistedScale();
+        final float scale = mScaleProvider.getScale(displayId);
         final DisableMagnificationCallback animationEndCallback =
                 new DisableMagnificationCallback(transitionCallBack, displayId, targetMode,
                         scale, magnificationCenter);
@@ -313,13 +320,23 @@
      * @param userId the currently active user ID
      */
     public void updateUserIdIfNeeded(int userId) {
+        if (mUserId == userId) {
+            return;
+        }
+        mUserId = userId;
+        final FullScreenMagnificationController fullMagnificationController;
+        final WindowMagnificationManager windowMagnificationManager;
         synchronized (mLock) {
-            if (mFullScreenMagnificationController != null) {
-                mFullScreenMagnificationController.setUserId(userId);
-            }
-            if (mWindowMagnificationMgr != null) {
-                mWindowMagnificationMgr.setUserId(userId);
-            }
+            fullMagnificationController = mFullScreenMagnificationController;
+            windowMagnificationManager = mWindowMagnificationMgr;
+        }
+
+        mScaleProvider.onUserChanged(userId);
+        if (fullMagnificationController != null) {
+            fullMagnificationController.resetAllIfNeeded(false);
+        }
+        if (windowMagnificationManager != null) {
+            windowMagnificationManager.disableAllWindowMagnifiers();
         }
     }
 
@@ -337,6 +354,14 @@
                 mWindowMagnificationMgr.onDisplayRemoved(displayId);
             }
         }
+        mScaleProvider.onDisplayRemoved(displayId);
+    }
+
+    /**
+     * Called when the given user is removed.
+     */
+    public void onUserRemoved(int userId) {
+        mScaleProvider.onUserRemoved(userId);
     }
 
     public void setMagnificationCapabilities(int capabilities) {
@@ -378,8 +403,7 @@
         synchronized (mLock) {
             if (mFullScreenMagnificationController == null) {
                 mFullScreenMagnificationController = new FullScreenMagnificationController(mContext,
-                        mAms, mLock, this);
-                mFullScreenMagnificationController.setUserId(mAms.getCurrentUserIdLocked());
+                        mAms, mLock, this, mScaleProvider);
             }
         }
         return mFullScreenMagnificationController;
@@ -404,7 +428,8 @@
         synchronized (mLock) {
             if (mWindowMagnificationMgr == null) {
                 mWindowMagnificationMgr = new WindowMagnificationManager(mContext,
-                        mAms.getCurrentUserIdLocked(), this, mAms.getTraceManager());
+                        mUserId, this, mAms.getTraceManager(),
+                        mScaleProvider);
             }
             return mWindowMagnificationMgr;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
new file mode 100644
index 0000000..8e1aa38
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.MathUtils;
+import android.util.SparseArray;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+
+/**
+ * Supplies setter/getter of the magnification scale for the given display. Only the value of the
+ * default play is persisted. It also constraints the range of applied magnification scale between
+ * [MIN_SCALE, MAX_SCALE] which is consistent with the range provided by
+ * {@code AccessibilityService.MagnificationController#setScale()}.
+ */
+public class MagnificationScaleProvider {
+
+    @VisibleForTesting
+    protected static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
+    public static final float MIN_SCALE = 1.0f;
+    public static final float MAX_SCALE = 8.0f;
+
+    private final Context mContext;
+    // Stores the scale for non-default displays.
+    @GuardedBy("mLock")
+    private final SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
+    private int mCurrentUserId = UserHandle.USER_SYSTEM;
+    private final Object mLock = new Object();
+
+    public MagnificationScaleProvider(Context context) {
+        mContext = context;
+    }
+
+    /**
+     *  Stores the user settings scale associated to the given display. Only the scale of the
+     *  default display is persistent.
+     *
+     * @param scale the magnification scale
+     * @param displayId the id of the display
+     */
+    void putScale(float scale, int displayId) {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            BackgroundThread.getHandler().post(
+                    () -> Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                            Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
+                            mCurrentUserId));
+        } else {
+            synchronized (mLock) {
+                getScalesWithCurrentUser().put(displayId, scale);
+            }
+        }
+    }
+
+    /**
+     * Gets the user settings scale with the given display.
+     *
+     * @param displayId the id of the display
+     * @return the magnification scale.
+     */
+    float getScale(int displayId) {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                    DEFAULT_MAGNIFICATION_SCALE, mCurrentUserId);
+        } else {
+            synchronized (mLock) {
+                return getScalesWithCurrentUser().get(displayId, DEFAULT_MAGNIFICATION_SCALE);
+            }
+        }
+    }
+
+
+    @GuardedBy("mLock")
+    private SparseArray<Float> getScalesWithCurrentUser() {
+        SparseArray<Float> scales = mUsersScales.get(mCurrentUserId);
+        if (scales == null) {
+            scales = new SparseArray<>();
+            mUsersScales.put(mCurrentUserId, scales);
+        }
+
+        return scales;
+    }
+
+    void onUserChanged(int userId) {
+        synchronized (mLock) {
+            mCurrentUserId = userId;
+        }
+    }
+
+    void onUserRemoved(int userId) {
+        synchronized (mLock) {
+            mUsersScales.remove(userId);
+        }
+    }
+
+    void onDisplayRemoved(int displayId) {
+        synchronized (mLock) {
+            final int userCounts = mUsersScales.size();
+            for (int i = userCounts - 1; i >= 0; i--) {
+                mUsersScales.get(i).remove(displayId);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        synchronized (mLock) {
+            return "MagnificationScaleProvider{"
+                    + "mCurrentUserId=" + mCurrentUserId
+                    + "Scale on the default display=" + getScale(Display.DEFAULT_DISPLAY)
+                    + "Scales on non-default displays=" + getScalesWithCurrentUser()
+                    + '}';
+        }
+    }
+
+    static float constrainScale(float scale) {
+        return MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index bc61284..7d8f545 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -69,7 +69,7 @@
 
     //Ensure the range has consistency with FullScreenMagnificationGestureHandler.
     private static final float MIN_SCALE = 2.0f;
-    private static final float MAX_SCALE = WindowMagnificationManager.MAX_SCALE;
+    private static final float MAX_SCALE = MagnificationScaleProvider.MAX_SCALE;
 
     private final WindowMagnificationManager mWindowMagnificationMgr;
     @VisibleForTesting
@@ -177,8 +177,7 @@
         }
 
         final float scale = MathUtils.constrain(
-                mWindowMagnificationMgr.getPersistedScale(),
-                MIN_SCALE, MAX_SCALE);
+                mWindowMagnificationMgr.getPersistedScale(mDisplayId), MIN_SCALE, MAX_SCALE);
         mWindowMagnificationMgr.enableWindowMagnification(mDisplayId, scale, centerX, centerY);
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 7a111d8..ce7ba75 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -29,8 +29,6 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.provider.Settings;
-import android.util.MathUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.MotionEvent;
@@ -40,7 +38,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -49,6 +46,8 @@
  * A class to manipulate window magnification through {@link WindowMagnificationConnectionWrapper}
  * create by {@link #setConnection(IWindowMagnificationConnection)}. To set the connection with
  * SysUI, call {@code StatusBarManagerInternal#requestWindowMagnificationConnection(boolean)}.
+ * The applied magnification scale is constrained by
+ * {@link MagnificationScaleProvider#constrainScale(float)}
  */
 public class WindowMagnificationManager implements
         PanningScalingHandler.MagnificationDelegate {
@@ -57,10 +56,6 @@
 
     private static final String TAG = "WindowMagnificationMgr";
 
-    //Ensure the range has consistency with full screen.
-    static final float MAX_SCALE = FullScreenMagnificationController.MAX_SCALE;
-    static final float MIN_SCALE = FullScreenMagnificationController.MIN_SCALE;
-
     private final Object mLock = new Object();
     private final Context mContext;
     @VisibleForTesting
@@ -71,7 +66,6 @@
     private ConnectionCallback mConnectionCallback;
     @GuardedBy("mLock")
     private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>();
-    private int mUserId;
 
     private boolean mReceiverRegistered = false;
     @VisibleForTesting
@@ -116,13 +110,14 @@
 
     private final Callback mCallback;
     private final AccessibilityTraceManager mTrace;
+    private final MagnificationScaleProvider mScaleProvider;
 
     public WindowMagnificationManager(Context context, int userId, @NonNull Callback callback,
-            AccessibilityTraceManager trace) {
+            AccessibilityTraceManager trace, MagnificationScaleProvider scaleProvider) {
         mContext = context;
-        mUserId = userId;
         mCallback = callback;
         mTrace = trace;
+        mScaleProvider = scaleProvider;
     }
 
     /**
@@ -159,15 +154,6 @@
     }
 
     /**
-     * Sets the currently active user ID.
-     *
-     * @param userId the currently active user ID
-     */
-    public void setUserId(int userId) {
-        mUserId = userId;
-    }
-
-    /**
      * @return {@code true} if {@link IWindowMagnificationConnection} is available
      */
     public boolean isConnected() {
@@ -219,13 +205,18 @@
         return true;
     }
 
-    @GuardedBy("mLock")
-    private void disableAllWindowMagnifiers() {
-        for (int i = 0; i < mWindowMagnifiers.size(); i++) {
-            final WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i);
-            magnifier.disableWindowMagnificationInternal(null);
+    /**
+     * Disables window magnifier on all displays without animation.
+     */
+    void disableAllWindowMagnifiers() {
+        synchronized (mLock) {
+            for (int i = 0; i < mWindowMagnifiers.size(); i++) {
+                final WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i);
+                magnifier.disableWindowMagnificationInternal(null);
+            }
+            mWindowMagnifiers.clear();
         }
-        mWindowMagnifiers.clear();
+
     }
 
     private void resetWindowMagnifiers() {
@@ -378,29 +369,24 @@
     }
 
     /**
-     * Retrieves a previously persisted magnification scale from the current
-     * user's settings.
+     * Retrieves a previously magnification scale from the current
+     * user's settings. Only the value of the default display is persisted.
      *
-     * @return the previously persisted magnification scale, or the default
+     * @return the previously magnification scale, or the default
      *         scale if none is available
      */
-    float getPersistedScale() {
-        return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
-                MIN_SCALE, mUserId);
+    float getPersistedScale(int displayId) {
+        return mScaleProvider.getScale(displayId);
     }
 
     /**
-     * Persists the default display magnification scale to the current user's settings.
+     * Persists the default display magnification scale to the current user's settings. Only the
+     * value of the default display is persisted in user's settings.
      */
     void persistScale(int displayId) {
-
         float scale = getScale(displayId);
         if (scale != 1.0f) {
-            BackgroundThread.getHandler().post(() -> {
-                Settings.Secure.putFloatForUser(mContext.getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, mUserId);
-            });
+            mScaleProvider.putScale(scale, displayId);
         }
     }
 
@@ -511,7 +497,7 @@
      *
      * @param displayId The logical display id.
      */
-    void onDisplayRemoved(int displayId) {
+    public void onDisplayRemoved(int displayId) {
         disableWindowMagnification(displayId, true);
     }
 
@@ -613,7 +599,7 @@
     private static class WindowMagnifier {
 
         private final int mDisplayId;
-        private float mScale = MIN_SCALE;
+        private float mScale = MagnificationScaleProvider.MIN_SCALE;
         private boolean mEnabled;
 
         private final WindowMagnificationManager mWindowMagnificationManager;
@@ -633,7 +619,7 @@
             if (mEnabled) {
                 return false;
             }
-            final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+            final float normScale = MagnificationScaleProvider.constrainScale(scale);
             if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale,
                     centerX, centerY, animationCallback)) {
                 mScale = normScale;
@@ -664,7 +650,7 @@
             if (!mEnabled) {
                 return;
             }
-            final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+            final float normScale = MagnificationScaleProvider.constrainScale(scale);
             if (Float.compare(mScale, normScale) != 0
                     && mWindowMagnificationManager.setScaleInternal(mDisplayId, scale)) {
                 mScale = normScale;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java
index 1fdd908..ecf999e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java
@@ -160,7 +160,7 @@
         intentFilter.addAction(ACTION_DISMISS);
         intentFilter.addAction(ACTION_TURN_ON_IN_SETTINGS);
         mContext.registerReceiver(mNotificationActionReceiver, intentFilter,
-                Manifest.permission.MANAGE_ACCESSIBILITY, null);
+                Manifest.permission.MANAGE_ACCESSIBILITY, null, Context.RECEIVER_EXPORTED);
     }
 
     private void launchMagnificationSettings() {
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 4946ad4..1af8ad3 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -187,7 +187,7 @@
             @NonNull IPredictionCallback callback) {
         final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
         if (sessionInfo == null) return;
-        final boolean serviceExists = resolveService(sessionId, false,
+        final boolean serviceExists = resolveService(sessionId, true,
                 sessionInfo.mUsesPeopleService,
                 s -> s.registerPredictionUpdates(sessionId, callback));
         if (serviceExists) {
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index c32543a..78d9095 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -147,7 +147,7 @@
         OnCrossProfileWidgetProvidersChangeListener {
     private static final String TAG = "AppWidgetServiceImpl";
 
-    private static boolean DEBUG = false;
+    private static final boolean DEBUG = false;
 
     private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
     private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
new file mode 100644
index 0000000..2e8fb47
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -0,0 +1,320 @@
+/*
+ * 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.companion;
+
+import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static com.android.internal.util.CollectionUtils.filter;
+import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
+import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
+import static com.android.server.companion.CompanionDeviceManagerService.getCallingUserId;
+
+import static java.util.Collections.unmodifiableMap;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
+import android.companion.CompanionDeviceManager;
+import android.companion.ICompanionDeviceDiscoveryService;
+import android.companion.IFindDeviceCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.Signature;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.PackageUtils;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.PerUser;
+import com.android.internal.infra.ServiceConnector;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.FgThread;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+class AssociationRequestsProcessor {
+    private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor";
+
+    private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION;
+    static {
+        final Map<String, String> map = new ArrayMap<>();
+        map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
+        map.put(DEVICE_PROFILE_APP_STREAMING,
+                Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING);
+
+        DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
+    }
+
+    private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
+            CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
+            ".CompanionDeviceDiscoveryService");
+
+    private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5;
+    private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;
+
+    private final Context mContext;
+    private final CompanionDeviceManagerService mService;
+
+    private AssociationRequest mRequest;
+    private IFindDeviceCallback mFindDeviceCallback;
+    private String mCallingPackage;
+    private AndroidFuture<?> mOngoingDeviceDiscovery;
+
+    private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
+
+    AssociationRequestsProcessor(CompanionDeviceManagerService service) {
+        mContext = service.getContext();
+        mService = service;
+
+        final Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
+        mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
+            @Override
+            protected ServiceConnector<ICompanionDeviceDiscoveryService> create(int userId) {
+                return new ServiceConnector.Impl<>(
+                        mContext,
+                        serviceIntent, 0/* bindingFlags */, userId,
+                        ICompanionDeviceDiscoveryService.Stub::asInterface);
+            }
+        };
+    }
+
+    void process(AssociationRequest request, IFindDeviceCallback callback, String callingPackage)
+            throws RemoteException {
+        if (DEBUG) {
+            Slog.d(TAG, "process(request=" + request + ", from=" + callingPackage + ")");
+        }
+
+        checkNotNull(request, "Request cannot be null");
+        checkNotNull(callback, "Callback cannot be null");
+        mService.checkCallerIsSystemOr(callingPackage);
+        int userId = getCallingUserId();
+        mService.checkUsesFeature(callingPackage, userId);
+        final String deviceProfile = request.getDeviceProfile();
+        validateDeviceProfileAndCheckPermission(deviceProfile);
+
+        mFindDeviceCallback = callback;
+        mRequest = request;
+        mCallingPackage = callingPackage;
+        request.setCallingPackage(callingPackage);
+
+        if (mayAssociateWithoutPrompt(callingPackage, userId)) {
+            Slog.i(TAG, "setSkipPrompt(true)");
+            request.setSkipPrompt(true);
+        }
+        callback.asBinder().linkToDeath(mBinderDeathRecipient /* recipient */, 0);
+
+        mOngoingDeviceDiscovery = getDeviceProfilePermissionDescription(deviceProfile)
+                .thenComposeAsync(description -> {
+                    if (DEBUG) {
+                        Slog.d(TAG, "fetchProfileDescription done: " + description);
+                    }
+
+                    request.setDeviceProfilePrivilegesDescription(description);
+
+                    return mServiceConnectors.forUser(userId).postAsync(service -> {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Connected to CDM service -> "
+                                    + "Starting discovery for " + request);
+                        }
+
+                        AndroidFuture<String> future = new AndroidFuture<>();
+                        service.startDiscovery(request, callingPackage, callback, future);
+                        return future;
+                    }).cancelTimeout();
+
+                }, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> {
+                    if (err == null) {
+                        mService.createAssociationInternal(
+                                userId, deviceAddress, callingPackage, deviceProfile);
+                    } else {
+                        Slog.e(TAG, "Failed to discover device(s)", err);
+                        callback.onFailure("No devices found: " + err.getMessage());
+                    }
+                    cleanup();
+                }));
+    }
+
+    void stopScan(AssociationRequest request, IFindDeviceCallback callback, String callingPackage) {
+        if (DEBUG) {
+            Slog.d(TAG, "stopScan(request = " + request + ")");
+        }
+        if (Objects.equals(request, mRequest)
+                && Objects.equals(callback, mFindDeviceCallback)
+                && Objects.equals(callingPackage, mCallingPackage)) {
+            cleanup();
+        }
+    }
+
+    private void validateDeviceProfileAndCheckPermission(@Nullable String deviceProfile) {
+        // Device profile can be null.
+        if (deviceProfile == null) return;
+
+        if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) {
+            // TODO: remove, when properly supporting this profile.
+            throw new UnsupportedOperationException(
+                    "DEVICE_PROFILE_APP_STREAMING is not fully supported yet.");
+        }
+
+        if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) {
+            throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
+        }
+
+        final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
+        if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
+            throw new SecurityException("Application must hold " + permission + " to associate "
+                    + "with a device with " + deviceProfile + " profile.");
+        }
+    }
+
+    private void cleanup() {
+        if (DEBUG) {
+            Slog.d(TAG, "cleanup(); discovery = "
+                    + mOngoingDeviceDiscovery + ", request = " + mRequest);
+        }
+        synchronized (mService.mLock) {
+            AndroidFuture<?> ongoingDeviceDiscovery = mOngoingDeviceDiscovery;
+            if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) {
+                ongoingDeviceDiscovery.cancel(true);
+            }
+            if (mFindDeviceCallback != null) {
+                mFindDeviceCallback.asBinder().unlinkToDeath(mBinderDeathRecipient, 0);
+                mFindDeviceCallback = null;
+            }
+            mRequest = null;
+            mCallingPackage = null;
+        }
+    }
+
+    private boolean mayAssociateWithoutPrompt(String packageName, int userId) {
+        String[] sameOemPackages = mContext.getResources()
+                .getStringArray(com.android.internal.R.array.config_companionDevicePackages);
+        if (!ArrayUtils.contains(sameOemPackages, packageName)) {
+            Slog.w(TAG, packageName
+                    + " can not silently create associations due to no package found."
+                    + " Packages from OEM: " + Arrays.toString(sameOemPackages)
+            );
+            return false;
+        }
+
+        // Throttle frequent associations
+        long now = System.currentTimeMillis();
+        Set<AssociationInfo> recentAssociations = filter(
+                mService.getAllAssociations(userId, packageName),
+                a -> now - a.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS);
+
+        if (recentAssociations.size() >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) {
+            Slog.w(TAG, "Too many associations. " + packageName
+                    + " already associated " + recentAssociations.size()
+                    + " devices within the last " + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS
+                    + "ms: " + recentAssociations);
+            return false;
+        }
+        String[] sameOemCerts = mContext.getResources()
+                .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
+
+        Signature[] signatures = mService.mPackageManagerInternal
+                .getPackage(packageName).getSigningDetails().getSignatures();
+        String[] apkCerts = PackageUtils.computeSignaturesSha256Digests(signatures);
+
+        Set<String> sameOemPackageCerts =
+                getSameOemPackageCerts(packageName, sameOemPackages, sameOemCerts);
+
+        for (String cert : apkCerts) {
+            if (sameOemPackageCerts.contains(cert)) {
+                return true;
+            }
+        }
+
+        Slog.w(TAG, packageName
+                + " can not silently create associations. " + packageName
+                + " has SHA256 certs from APK: " + Arrays.toString(apkCerts)
+                + " and from OEM: " + Arrays.toString(sameOemCerts)
+        );
+
+        return false;
+    }
+
+    @NonNull
+    private AndroidFuture<String> getDeviceProfilePermissionDescription(
+            @Nullable String deviceProfile) {
+        if (deviceProfile == null) {
+            return AndroidFuture.completedFuture(null);
+        }
+
+        final AndroidFuture<String> result = new AndroidFuture<>();
+        mService.mPermissionControllerManager.getPrivilegesDescriptionStringForProfile(
+                deviceProfile, FgThread.getExecutor(), desc -> {
+                    try {
+                        result.complete(String.valueOf(desc));
+                    } catch (Exception e) {
+                        result.completeExceptionally(e);
+                    }
+                });
+        return result;
+    }
+
+
+    void dump(@NonNull PrintWriter pw) {
+        pw.append("Discovery Service State:").append('\n');
+        for (int i = 0, size = mServiceConnectors.size(); i < size; i++) {
+            int userId = mServiceConnectors.keyAt(i);
+            pw.append("  ")
+                    .append("u").append(Integer.toString(userId)).append(": ")
+                    .append(Objects.toString(mServiceConnectors.valueAt(i)))
+                    .append('\n');
+        }
+    }
+
+    private final IBinder.DeathRecipient mBinderDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "binderDied()");
+            }
+            mService.mMainHandler.post(AssociationRequestsProcessor.this::cleanup);
+        }
+    };
+
+    private static Set<String> getSameOemPackageCerts(
+            String packageName, String[] oemPackages, String[] sameOemCerts) {
+        Set<String> sameOemPackageCerts = new HashSet<>();
+
+        // Assume OEM may enter same package name in the parallel string array with
+        // multiple APK certs corresponding to it
+        for (int i = 0; i < oemPackages.length; i++) {
+            if (oemPackages[i].equals(packageName)) {
+                sameOemPackageCerts.add(sameOemCerts[i].replaceAll(":", ""));
+            }
+        }
+
+        return sameOemPackageCerts;
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a48172b..cdce3e6 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -19,8 +19,6 @@
 
 import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
 import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
 import static android.companion.DeviceId.TYPE_MAC_ADDRESS;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -31,7 +29,6 @@
 import static com.android.internal.util.CollectionUtils.find;
 import static com.android.internal.util.CollectionUtils.forEach;
 import static com.android.internal.util.CollectionUtils.map;
-import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.internal.util.Preconditions.checkState;
@@ -39,13 +36,10 @@
 import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
 
 import static java.util.Collections.emptySet;
-import static java.util.Collections.unmodifiableMap;
 import static java.util.Collections.unmodifiableSet;
 import static java.util.Objects.requireNonNull;
 import static java.util.concurrent.TimeUnit.MINUTES;
 
-import android.Manifest;
-import android.annotation.CheckResult;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -62,12 +56,10 @@
 import android.bluetooth.le.ScanFilter;
 import android.bluetooth.le.ScanResult;
 import android.bluetooth.le.ScanSettings;
-import android.companion.Association;
+import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
-import android.companion.CompanionDeviceManager;
 import android.companion.DeviceId;
 import android.companion.DeviceNotAssociatedException;
-import android.companion.ICompanionDeviceDiscoveryService;
 import android.companion.ICompanionDeviceManager;
 import android.companion.IFindDeviceCallback;
 import android.content.BroadcastReceiver;
@@ -81,14 +73,11 @@
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.Signature;
 import android.content.pm.UserInfo;
 import android.net.NetworkPolicyManager;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
-import android.os.IBinder;
-import android.os.IInterface;
 import android.os.Parcel;
 import android.os.PowerWhitelistManager;
 import android.os.Process;
@@ -103,7 +92,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.ExceptionUtils;
-import android.util.PackageUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -111,9 +99,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.content.PackageMonitor;
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.infra.PerUser;
-import com.android.internal.infra.ServiceConnector;
 import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
@@ -146,27 +131,13 @@
 
 /** @hide */
 @SuppressLint("LongLogTag")
-public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient {
+public class CompanionDeviceManagerService extends SystemService {
     static final String LOG_TAG = "CompanionDeviceManagerService";
     static final boolean DEBUG = false;
 
-    private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION;
-    static {
-        final Map<String, String> map = new ArrayMap<>();
-        map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
-        map.put(DEVICE_PROFILE_APP_STREAMING,
-                Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING);
-
-        DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
-    }
-
     /** Range of Association IDs allocated for a user.*/
     static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
 
-    private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
-            CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
-            ".CompanionDeviceDiscoveryService");
-
     private static final long DEVICE_DISAPPEARED_TIMEOUT_MS = 10 * 1000;
     private static final long DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS = 10 * 60 * 1000;
 
@@ -177,9 +148,6 @@
     private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
     private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
 
-    private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5;
-    private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;
-
     private static DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
     static {
         sDateFormat.setTimeZone(TimeZone.getDefault());
@@ -188,19 +156,15 @@
     private final CompanionDeviceManagerImpl mImpl;
     // Persistent data store for all Associations.
     private final PersistentDataStore mPersistentDataStore;
+    private final AssociationRequestsProcessor mAssociationRequestsProcessor;
     private PowerWhitelistManager mPowerWhitelistManager;
-    private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
     private IAppOpsService mAppOpsManager;
     private RoleManager mRoleManager;
     private BluetoothAdapter mBluetoothAdapter;
     private UserManager mUserManager;
 
-    private IFindDeviceCallback mFindDeviceCallback;
     private ScanCallback mBleScanCallback = new BleScanCallback();
-    private AssociationRequest mRequest;
-    private String mCallingPackage;
-    private AndroidFuture<?> mOngoingDeviceDiscovery;
-    private PermissionControllerManager mPermissionControllerManager;
+    PermissionControllerManager mPermissionControllerManager;
 
     private BluetoothDeviceConnectedListener mBluetoothDeviceConnectedListener =
             new BluetoothDeviceConnectedListener();
@@ -212,13 +176,13 @@
     private ArrayMap<String, TriggerDeviceDisappearedRunnable> mTriggerDeviceDisappearedRunnables =
             new ArrayMap<>();
 
-    private final Object mLock = new Object();
-    private final Handler mMainHandler = Handler.getMain();
+    final Object mLock = new Object();
+    final Handler mMainHandler = Handler.getMain();
     private CompanionDevicePresenceController mCompanionDevicePresenceController;
 
     /** Maps a {@link UserIdInt} to a set of associations for the user. */
     @GuardedBy("mLock")
-    private final SparseArray<Set<Association>> mCachedAssociations = new SparseArray<>();
+    private final SparseArray<Set<AssociationInfo>> mCachedAssociations = new SparseArray<>();
     /**
      * A structure that consist of two nested maps, and effectively maps (userId + packageName) to
      * a list of IDs that have been previously assigned to associations for that package.
@@ -236,6 +200,7 @@
         super(context);
         mImpl = new CompanionDeviceManagerImpl();
         mPersistentDataStore = new PersistentDataStore();
+        mAssociationRequestsProcessor = new AssociationRequestsProcessor(this);
 
         mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
         mRoleManager = context.getSystemService(RoleManager.class);
@@ -249,17 +214,6 @@
         mUserManager = context.getSystemService(UserManager.class);
         mCompanionDevicePresenceController = new CompanionDevicePresenceController();
 
-        Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
-        mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
-            @Override
-            protected ServiceConnector<ICompanionDeviceDiscoveryService> create(int userId) {
-                return new ServiceConnector.Impl<>(
-                        getContext(),
-                        serviceIntent, 0/* bindingFlags */, userId,
-                        ICompanionDeviceDiscoveryService.Stub::asInterface);
-            }
-        };
-
         registerPackageMonitor();
     }
 
@@ -316,7 +270,7 @@
     @Override
     public void onUserUnlocking(@NonNull TargetUser user) {
         int userHandle = user.getUserIdentifier();
-        Set<Association> associations = getAllAssociations(userHandle);
+        Set<AssociationInfo> associations = getAllAssociations(userHandle);
         if (associations == null || associations.isEmpty()) {
             return;
         }
@@ -339,11 +293,11 @@
             }
 
             try {
-                Set<Association> associations = getAllAssociations(userId);
+                Set<AssociationInfo> associations = getAllAssociations(userId);
                 if (associations == null) {
                     continue;
                 }
-                for (Association a : associations) {
+                for (AssociationInfo a : associations) {
                     try {
                         int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
                         exemptFromAutoRevoke(a.getPackageName(), uid);
@@ -357,39 +311,6 @@
         }
     }
 
-    @Override
-    public void binderDied() {
-        Slog.w(LOG_TAG, "binderDied()");
-        mMainHandler.post(this::cleanup);
-    }
-
-    private void cleanup() {
-        Slog.d(LOG_TAG, "cleanup(); discovery = "
-                + mOngoingDeviceDiscovery + ", request = " + mRequest);
-        synchronized (mLock) {
-            AndroidFuture<?> ongoingDeviceDiscovery = mOngoingDeviceDiscovery;
-            if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) {
-                ongoingDeviceDiscovery.cancel(true);
-            }
-            mFindDeviceCallback = unlinkToDeath(mFindDeviceCallback, this, 0);
-            mRequest = null;
-            mCallingPackage = null;
-        }
-    }
-
-    /**
-     * Usage: {@code a = unlinkToDeath(a, deathRecipient, flags); }
-     */
-    @Nullable
-    @CheckResult
-    private static <T extends IInterface> T unlinkToDeath(T iinterface,
-            IBinder.DeathRecipient deathRecipient, int flags) {
-        if (iinterface != null) {
-            iinterface.asBinder().unlinkToDeath(deathRecipient, flags);
-        }
-        return null;
-    }
-
     class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
 
         @Override
@@ -410,61 +331,15 @@
                 String callingPackage) throws RemoteException {
             Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
                     + ", callingPackage = " + callingPackage + ")");
-            checkNotNull(request, "Request cannot be null");
-            checkNotNull(callback, "Callback cannot be null");
-            checkCallerIsSystemOr(callingPackage);
-            int userId = getCallingUserId();
-            checkUsesFeature(callingPackage, userId);
-            final String deviceProfile = request.getDeviceProfile();
-            validateDeviceProfileAndCheckPermission(deviceProfile);
-
-            mFindDeviceCallback = callback;
-            mRequest = request;
-            mCallingPackage = callingPackage;
-            request.setCallingPackage(callingPackage);
-
-            if (mayAssociateWithoutPrompt(callingPackage, userId)) {
-                Slog.i(LOG_TAG, "setSkipPrompt(true)");
-                request.setSkipPrompt(true);
-            }
-            callback.asBinder().linkToDeath(CompanionDeviceManagerService.this /* recipient */, 0);
-
-            mOngoingDeviceDiscovery = getDeviceProfilePermissionDescription(deviceProfile)
-                    .thenComposeAsync(description -> {
-                Slog.d(LOG_TAG, "fetchProfileDescription done: " + description);
-
-                request.setDeviceProfilePrivilegesDescription(description);
-
-                return mServiceConnectors.forUser(userId).postAsync(service -> {
-                    Slog.d(LOG_TAG, "Connected to CDM service; starting discovery for " + request);
-
-                    AndroidFuture<String> future = new AndroidFuture<>();
-                    service.startDiscovery(request, callingPackage, callback, future);
-                    return future;
-                }).cancelTimeout();
-
-            }, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> {
-                if (err == null) {
-                    createAssociationInternal(
-                            userId, deviceAddress, callingPackage, deviceProfile);
-                } else {
-                    Slog.e(LOG_TAG, "Failed to discover device(s)", err);
-                    callback.onFailure("No devices found: " + err.getMessage());
-                }
-                cleanup();
-            }));
+            mAssociationRequestsProcessor.process(request, callback, callingPackage);
         }
 
         @Override
         public void stopScan(AssociationRequest request,
                 IFindDeviceCallback callback,
                 String callingPackage) {
-            Slog.d(LOG_TAG, "stopScan(request = " + request + ")");
-            if (Objects.equals(request, mRequest)
-                    && Objects.equals(callback, mFindDeviceCallback)
-                    && Objects.equals(callingPackage, mCallingPackage)) {
-                cleanup();
-            }
+            Slog.i(LOG_TAG, "stopScan(request = " + request + ")");
+            mAssociationRequestsProcessor.stopScan(request, callback, callingPackage);
         }
 
         @Override
@@ -480,7 +355,7 @@
         }
 
         @Override
-        public List<Association> getAssociationsForUser(int userId) {
+        public List<AssociationInfo> getAssociationsForUser(int userId) {
             if (!callerCanManageCompanionDevices()) {
                 throw new SecurityException("Caller must hold "
                         + android.Manifest.permission.MANAGE_COMPANION_DEVICES);
@@ -505,44 +380,6 @@
                     == PERMISSION_GRANTED;
         }
 
-        private void checkCallerIsSystemOr(String pkg) throws RemoteException {
-            checkCallerIsSystemOr(pkg, getCallingUserId());
-        }
-
-        private void checkCallerIsSystemOr(String pkg, int userId) throws RemoteException {
-            if (isCallerSystem()) {
-                return;
-            }
-
-            checkArgument(getCallingUserId() == userId,
-                    "Must be called by either same user or system");
-            int callingUid = Binder.getCallingUid();
-            if (mAppOpsManager.checkPackage(callingUid, pkg) != AppOpsManager.MODE_ALLOWED) {
-                throw new SecurityException(pkg + " doesn't belong to uid " + callingUid);
-            }
-        }
-
-        private void validateDeviceProfileAndCheckPermission(@Nullable String deviceProfile) {
-            // Device profile can be null.
-            if (deviceProfile == null) return;
-
-            if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) {
-                // TODO: remove, when properly supporting this profile.
-                throw new UnsupportedOperationException(
-                        "DEVICE_PROFILE_APP_STREAMING is not fully supported yet.");
-            }
-
-            if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) {
-                throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
-            }
-
-            final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
-            if (getContext().checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
-                throw new SecurityException("Application must hold " + permission + " to associate "
-                        + "with a device with " + deviceProfile + " profile.");
-            }
-        }
-
         @Override
         public PendingIntent requestNotificationAccess(ComponentName component)
                 throws RemoteException {
@@ -630,7 +467,7 @@
             checkCallerIsSystemOr(packageName);
 
             int userId = getCallingUserId();
-            Set<Association> deviceAssociations = filter(
+            Set<AssociationInfo> deviceAssociations = filter(
                     getAllAssociations(userId, packageName),
                     association -> deviceAddress.equals(association.getDeviceMacAddress()));
 
@@ -674,23 +511,6 @@
             checkUsesFeature(callingPackage, userId);
         }
 
-        private void checkUsesFeature(String pkg, int userId) {
-            if (isCallerSystem()) {
-                // Drop the requirement for calls from system process
-                return;
-            }
-
-            FeatureInfo[] reqFeatures = getPackageInfo(pkg, userId).reqFeatures;
-            String requiredFeature = PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
-            int numFeatures = ArrayUtils.size(reqFeatures);
-            for (int i = 0; i < numFeatures; i++) {
-                if (requiredFeature.equals(reqFeatures[i].name)) return;
-            }
-            throw new IllegalStateException("Must declare uses-feature "
-                    + requiredFeature
-                    + " in manifest to use this API");
-        }
-
         @Override
         public boolean canPairWithoutPrompt(
                 String packageName, String deviceMacAddress, int userId) {
@@ -740,14 +560,7 @@
                         .append(sDateFormat.format(time)).append('\n');
             }
 
-            fout.append("Discovery Service State:").append('\n');
-            for (int i = 0, size = mServiceConnectors.size(); i < size; i++) {
-                int userId = mServiceConnectors.keyAt(i);
-                fout.append("  ")
-                        .append("u").append(Integer.toString(userId)).append(": ")
-                        .append(Objects.toString(mServiceConnectors.valueAt(i)))
-                        .append('\n');
-            }
+            mAssociationRequestsProcessor.dump(fout);
 
             fout.append("Device Listener Services State:").append('\n');
             for (int i = 0, size =  mCompanionDevicePresenceController.mBoundServices.size();
@@ -762,7 +575,24 @@
         }
     }
 
-    private static int getCallingUserId() {
+    void checkCallerIsSystemOr(String pkg) throws RemoteException {
+        checkCallerIsSystemOr(pkg, getCallingUserId());
+    }
+
+    private void checkCallerIsSystemOr(String pkg, int userId) throws RemoteException {
+        if (isCallerSystem()) {
+            return;
+        }
+
+        checkArgument(getCallingUserId() == userId,
+                "Must be called by either same user or system");
+        int callingUid = Binder.getCallingUid();
+        if (mAppOpsManager.checkPackage(callingUid, pkg) != AppOpsManager.MODE_ALLOWED) {
+            throw new SecurityException(pkg + " doesn't belong to uid " + callingUid);
+        }
+    }
+
+    static int getCallingUserId() {
         return UserHandle.getUserId(Binder.getCallingUid());
     }
 
@@ -770,9 +600,26 @@
         return Binder.getCallingUid() == Process.SYSTEM_UID;
     }
 
+    void checkUsesFeature(String pkg, int userId) {
+        if (isCallerSystem()) {
+            // Drop the requirement for calls from system process
+            return;
+        }
+
+        FeatureInfo[] reqFeatures = getPackageInfo(pkg, userId).reqFeatures;
+        String requiredFeature = PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
+        int numFeatures = ArrayUtils.size(reqFeatures);
+        for (int i = 0; i < numFeatures; i++) {
+            if (requiredFeature.equals(reqFeatures[i].name)) return;
+        }
+        throw new IllegalStateException("Must declare uses-feature "
+                + requiredFeature
+                + " in manifest to use this API");
+    }
+
     void createAssociationInternal(
             int userId, String deviceMacAddress, String packageName, String deviceProfile) {
-        final Association association = new Association(
+        final AssociationInfo association = new AssociationInfo(
                 getNewAssociationIdForPackage(userId, packageName),
                 userId,
                 packageName,
@@ -801,7 +648,7 @@
 
             // First: collect all IDs currently in use for this user's Associations.
             final SparseBooleanArray usedIds = new SparseBooleanArray();
-            for (Association it : getAllAssociations(userId)) {
+            for (AssociationInfo it : getAllAssociations(userId)) {
                 usedIds.put(it.getAssociationId(), true);
             }
 
@@ -851,7 +698,7 @@
         }
     }
 
-    void onAssociationPreRemove(Association association) {
+    void onAssociationPreRemove(AssociationInfo association) {
         if (association.isNotifyOnDeviceNearby()) {
             mCompanionDevicePresenceController.unbindDevicePresenceListener(
                     association.getPackageName(), association.getUserId());
@@ -859,7 +706,7 @@
 
         String deviceProfile = association.getDeviceProfile();
         if (deviceProfile != null) {
-            Association otherAssociationWithDeviceProfile = find(
+            AssociationInfo otherAssociationWithDeviceProfile = find(
                     getAllAssociations(association.getUserId()),
                     a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile()));
             if (otherAssociationWithDeviceProfile != null) {
@@ -890,7 +737,7 @@
         }
     }
 
-    private void updateSpecialAccessPermissionForAssociatedPackage(Association association) {
+    private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
         PackageInfo packageInfo = getPackageInfo(
                 association.getPackageName(),
                 association.getUserId());
@@ -904,7 +751,7 @@
     }
 
     private void updateSpecialAccessPermissionAsSystem(
-            Association association, PackageInfo packageInfo) {
+            AssociationInfo association, PackageInfo packageInfo) {
         if (containsEither(packageInfo.requestedPermissions,
                 android.Manifest.permission.RUN_IN_BACKGROUND,
                 android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
@@ -950,72 +797,6 @@
         }
     }
 
-    private Set<String> getSameOemPackageCerts(
-            String packageName, String[] oemPackages, String[] sameOemCerts) {
-        Set<String> sameOemPackageCerts = new HashSet<>();
-
-        // Assume OEM may enter same package name in the parallel string array with
-        // multiple APK certs corresponding to it
-        for (int i = 0; i < oemPackages.length; i++) {
-            if (oemPackages[i].equals(packageName)) {
-                sameOemPackageCerts.add(sameOemCerts[i].replaceAll(":", ""));
-            }
-        }
-
-        return sameOemPackageCerts;
-    }
-
-    boolean mayAssociateWithoutPrompt(String packageName, int userId) {
-        String[] sameOemPackages = getContext()
-                .getResources()
-                .getStringArray(com.android.internal.R.array.config_companionDevicePackages);
-        if (!ArrayUtils.contains(sameOemPackages, packageName)) {
-            Slog.w(LOG_TAG, packageName
-                    + " can not silently create associations due to no package found."
-                    + " Packages from OEM: " + Arrays.toString(sameOemPackages)
-            );
-            return false;
-        }
-
-        // Throttle frequent associations
-        long now = System.currentTimeMillis();
-        Set<Association> recentAssociations = filter(
-                getAllAssociations(userId, packageName),
-                a -> now - a.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS);
-
-        if (recentAssociations.size() >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) {
-            Slog.w(LOG_TAG, "Too many associations. " + packageName
-                    + " already associated " + recentAssociations.size()
-                    + " devices within the last " + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS
-                    + "ms: " + recentAssociations);
-            return false;
-        }
-        String[] sameOemCerts = getContext()
-                .getResources()
-                .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
-
-        Signature[] signatures = mPackageManagerInternal
-                .getPackage(packageName).getSigningDetails().getSignatures();
-        String[] apkCerts = PackageUtils.computeSignaturesSha256Digests(signatures);
-
-        Set<String> sameOemPackageCerts =
-                getSameOemPackageCerts(packageName, sameOemPackages, sameOemCerts);
-
-        for (String cert : apkCerts) {
-            if (sameOemPackageCerts.contains(cert)) {
-                return true;
-            }
-        }
-
-        Slog.w(LOG_TAG, packageName
-                + " can not silently create associations. " + packageName
-                + " has SHA256 certs from APK: " + Arrays.toString(apkCerts)
-                + " and from OEM: " + Arrays.toString(sameOemCerts)
-        );
-
-        return false;
-    }
-
     private static <T> boolean containsEither(T[] array, T a, T b) {
         return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
     }
@@ -1035,20 +816,20 @@
         }, getContext(), packageName, userId).recycleOnUse());
     }
 
-    private void recordAssociation(Association association, int userId) {
+    private void recordAssociation(AssociationInfo association, int userId) {
         Slog.i(LOG_TAG, "recordAssociation(" + association + ")");
         updateAssociations(associations -> add(associations, association), userId);
     }
 
-    private void updateAssociations(Function<Set<Association>, Set<Association>> update,
+    private void updateAssociations(Function<Set<AssociationInfo>, Set<AssociationInfo>> update,
             int userId) {
         synchronized (mLock) {
             if (DEBUG) Slog.d(LOG_TAG, "Updating Associations set...");
 
-            final Set<Association> prevAssociations = getAllAssociations(userId);
+            final Set<AssociationInfo> prevAssociations = getAllAssociations(userId);
             if (DEBUG) Slog.d(LOG_TAG, "  > Before : " + prevAssociations + "...");
 
-            final Set<Association> updatedAssociations = update.apply(
+            final Set<AssociationInfo> updatedAssociations = update.apply(
                     new ArraySet<>(prevAssociations));
             if (DEBUG) Slog.d(LOG_TAG, "  > After: " + updatedAssociations);
 
@@ -1065,9 +846,9 @@
         }
     }
 
-    private void updateAtm(int userId, Set<Association> associations) {
+    private void updateAtm(int userId, Set<AssociationInfo> associations) {
         final Set<Integer> companionAppUids = new ArraySet<>();
-        for (Association association : associations) {
+        for (AssociationInfo association : associations) {
             final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(),
                     0, userId);
             if (uid >= 0) {
@@ -1083,7 +864,7 @@
         }
     }
 
-    @NonNull Set<Association> getAllAssociations(int userId) {
+    @NonNull Set<AssociationInfo> getAllAssociations(int userId) {
         synchronized (mLock) {
             readPersistedStateForUserIfNeededLocked(userId);
             // This returns non-null, because the readAssociationsInfoForUserIfNeededLocked() method
@@ -1098,7 +879,7 @@
 
         Slog.i(LOG_TAG, "Reading state for user " + userId + "  from the disk");
 
-        final Set<Association> associations = new ArraySet<>();
+        final Set<AssociationInfo> associations = new ArraySet<>();
         final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
         mPersistentDataStore.readStateForUser(userId, associations, previouslyUsedIds);
 
@@ -1120,17 +901,17 @@
         }
     }
 
-    private Set<Association> getAllAssociations(int userId, @Nullable String packageFilter) {
+    Set<AssociationInfo> getAllAssociations(int userId, @Nullable String packageFilter) {
         return filter(
                 getAllAssociations(userId),
                 // Null filter == get all associations
                 a -> packageFilter == null || Objects.equals(packageFilter, a.getPackageName()));
     }
 
-    private Set<Association> getAllAssociations() {
+    private Set<AssociationInfo> getAllAssociations() {
         final long identity = Binder.clearCallingIdentity();
         try {
-            ArraySet<Association> result = new ArraySet<>();
+            ArraySet<AssociationInfo> result = new ArraySet<>();
             for (UserInfo user : mUserManager.getAliveUsers()) {
                 result.addAll(getAllAssociations(user.id));
             }
@@ -1140,7 +921,7 @@
         }
     }
 
-    private Set<Association> getAllAssociations(
+    private Set<AssociationInfo> getAllAssociations(
             int userId, @Nullable String packageFilter, @Nullable String addressFilter) {
         return filter(
                 getAllAssociations(userId),
@@ -1156,7 +937,7 @@
         mCurrentlyConnectedDevices.add(address);
 
         for (UserInfo user : getAllUsers()) {
-            for (Association association : getAllAssociations(user.id)) {
+            for (AssociationInfo association : getAllAssociations(user.id)) {
                 if (Objects.equals(address, association.getDeviceMacAddress())) {
                     if (association.getDeviceProfile() != null) {
                         Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
@@ -1171,7 +952,7 @@
         onDeviceNearby(address);
     }
 
-    private void grantDeviceProfile(Association association) {
+    private void grantDeviceProfile(AssociationInfo association) {
         Slog.i(LOG_TAG, "grantDeviceProfile(association = " + association + ")");
 
         if (association.getDeviceProfile() != null) {
@@ -1278,7 +1059,7 @@
                 Date lastNearby = mDevicesLastNearby.valueAt(i);
 
                 if (isDeviceDisappeared(lastNearby)) {
-                    for (Association association : getAllAssociations(address)) {
+                    for (AssociationInfo association : getAllAssociations(address)) {
                         if (association.isNotifyOnDeviceNearby()) {
                             mCompanionDevicePresenceController.unbindDevicePresenceListener(
                                     association.getPackageName(), association.getUserId());
@@ -1320,12 +1101,12 @@
         }
     }
 
-    private Set<Association> getAllAssociations(String deviceAddress) {
+    private Set<AssociationInfo> getAllAssociations(String deviceAddress) {
         List<UserInfo> aliveUsers = mUserManager.getAliveUsers();
-        Set<Association> result = new ArraySet<>();
+        Set<AssociationInfo> result = new ArraySet<>();
         for (int i = 0, size = aliveUsers.size(); i < size; i++) {
             UserInfo user = aliveUsers.get(i);
-            for (Association association : getAllAssociations(user.id)) {
+            for (AssociationInfo association : getAllAssociations(user.id)) {
                 if (Objects.equals(association.getDeviceMacAddress(), deviceAddress)) {
                     result.add(association);
                 }
@@ -1349,7 +1130,7 @@
                 || timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS;
         if (justAppeared) {
             Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")");
-            for (Association association : getAllAssociations(address)) {
+            for (AssociationInfo association : getAllAssociations(address)) {
                 if (association.isNotifyOnDeviceNearby()) {
                     mCompanionDevicePresenceController.onDeviceNotifyAppeared(association,
                             getContext(), mMainHandler);
@@ -1362,7 +1143,7 @@
         Slog.i(LOG_TAG, "onDeviceDisappeared(address = " + address + ")");
 
         boolean hasDeviceListeners = false;
-        for (Association association : getAllAssociations(address)) {
+        for (AssociationInfo association : getAllAssociations(address)) {
             if (association.isNotifyOnDeviceNearby()) {
                 mCompanionDevicePresenceController.onDeviceNotifyDisappeared(
                         association, getContext(), mMainHandler);
@@ -1431,7 +1212,7 @@
     private List<ScanFilter> getBleScanFilters() {
         ArrayList<ScanFilter> result = new ArrayList<>();
         ArraySet<String> addressesSeen = new ArraySet<>();
-        for (Association association : getAllAssociations()) {
+        for (AssociationInfo association : getAllAssociations()) {
             String address = association.getDeviceMacAddress();
             if (addressesSeen.contains(address)) {
                 continue;
@@ -1444,25 +1225,6 @@
         return result;
     }
 
-    @NonNull
-    private AndroidFuture<String> getDeviceProfilePermissionDescription(
-            @Nullable String deviceProfile) {
-        if (deviceProfile == null) {
-            return AndroidFuture.completedFuture(null);
-        }
-
-        final AndroidFuture<String> result = new AndroidFuture<>();
-        mPermissionControllerManager.getPrivilegesDescriptionStringForProfile(
-                deviceProfile, FgThread.getExecutor(), desc -> {
-                        try {
-                            result.complete(String.valueOf(desc));
-                        } catch (Exception e) {
-                            result.completeExceptionally(e);
-                        }
-                });
-        return result;
-    }
-
     static int getFirstAssociationIdForUser(@UserIdInt int userId) {
         // We want the IDs to start from 1, not 0.
         return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
index 328a8b3..a79db2c 100644
--- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
+++ b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
@@ -22,7 +22,7 @@
 import static com.android.internal.util.CollectionUtils.filter;
 
 import android.annotation.NonNull;
-import android.companion.Association;
+import android.companion.AssociationInfo;
 import android.companion.CompanionDeviceService;
 import android.companion.ICompanionDeviceService;
 import android.content.ComponentName;
@@ -60,7 +60,7 @@
         };
     }
 
-    void onDeviceNotifyAppeared(Association association, Context context, Handler handler) {
+    void onDeviceNotifyAppeared(AssociationInfo association, Context context, Handler handler) {
         ServiceConnector<ICompanionDeviceService> primaryConnector =
                 getPrimaryServiceConnector(association, context, handler);
         if (primaryConnector != null) {
@@ -71,7 +71,7 @@
         }
     }
 
-    void onDeviceNotifyDisappeared(Association association, Context context, Handler handler) {
+    void onDeviceNotifyDisappeared(AssociationInfo association, Context context, Handler handler) {
         ServiceConnector<ICompanionDeviceService> primaryConnector =
                 getPrimaryServiceConnector(association, context, handler);
         if (primaryConnector != null) {
@@ -94,7 +94,7 @@
     }
 
     private ServiceConnector<ICompanionDeviceService> getPrimaryServiceConnector(
-            Association association, Context context, Handler handler) {
+            AssociationInfo association, Context context, Handler handler) {
         for (BoundService boundService: getDeviceListenerServiceConnector(association, context,
                 handler)) {
             if (boundService.mIsPrimary) {
@@ -104,15 +104,15 @@
         return null;
     }
 
-    private List<BoundService> getDeviceListenerServiceConnector(Association a, Context context,
+    private List<BoundService> getDeviceListenerServiceConnector(AssociationInfo a, Context context,
             Handler handler) {
         return mBoundServices.forUser(a.getUserId()).computeIfAbsent(
                 a.getPackageName(),
                 pkg -> createDeviceListenerServiceConnector(a, context, handler));
     }
 
-    private List<BoundService> createDeviceListenerServiceConnector(Association a, Context context,
-            Handler handler) {
+    private List<BoundService> createDeviceListenerServiceConnector(AssociationInfo a,
+            Context context, Handler handler) {
         List<ResolveInfo> resolveInfos = context
                 .getPackageManager()
                 .queryIntentServicesAsUser(new Intent(CompanionDeviceService.SERVICE_INTERFACE),
@@ -161,7 +161,7 @@
     }
 
     private boolean validatePackageInfo(List<ResolveInfo> packageResolveInfos,
-            Association association) {
+            AssociationInfo association) {
         if (packageResolveInfos.size() == 0 || packageResolveInfos.size() > 5) {
             Slog.e(LOG_TAG, "Device presence listener package must have at least one and not "
                     + "more than five CompanionDeviceService(s) declared. But "
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index 73d45ad..5b8d7e5 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -34,7 +34,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.companion.Association;
+import android.companion.AssociationInfo;
 import android.companion.DeviceId;
 import android.os.Environment;
 import android.util.AtomicFile;
@@ -179,11 +179,11 @@
      * Reads previously persisted data for the given user "into" the provided containers.
      *
      * @param userId Android UserID
-     * @param associationsOut a container to read the {@link Association}s "into".
+     * @param associationsOut a container to read the {@link AssociationInfo}s "into".
      * @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
      */
     void readStateForUser(@UserIdInt int userId,
-            @NonNull Set<Association> associationsOut,
+            @NonNull Set<AssociationInfo> associationsOut,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
         Slog.i(LOG_TAG, "Reading associations for user " + userId + " from disk");
         final AtomicFile file = getStorageFileForUser(userId);
@@ -244,7 +244,7 @@
      * @param associations a set of user's associations.
      * @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
      */
-    void persistStateForUser(@UserIdInt int userId, @NonNull Set<Association> associations,
+    void persistStateForUser(@UserIdInt int userId, @NonNull Set<AssociationInfo> associations,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
         Slog.i(LOG_TAG, "Writing associations for user " + userId + " to disk");
         if (DEBUG) Slog.d(LOG_TAG, "  > " + associations);
@@ -257,7 +257,7 @@
     }
 
     private int readStateFromFileLocked(@UserIdInt int userId, @NonNull AtomicFile file,
-            @NonNull String rootTag, @Nullable Set<Association> associationsOut,
+            @NonNull String rootTag, @Nullable Set<AssociationInfo> associationsOut,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
         try (FileInputStream in = file.openRead()) {
             final TypedXmlPullParser parser = Xml.resolvePullParser(in);
@@ -289,7 +289,7 @@
     }
 
     private void persistStateToFileLocked(@NonNull AtomicFile file,
-            @Nullable Set<Association> associations,
+            @Nullable Set<AssociationInfo> associations,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
         file.write(out -> {
             try {
@@ -328,7 +328,7 @@
     }
 
     private static void readAssociationsV0(@NonNull TypedXmlPullParser parser,
-            @UserIdInt int userId, @NonNull Set<Association> out)
+            @UserIdInt int userId, @NonNull Set<AssociationInfo> out)
             throws XmlPullParserException, IOException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
 
@@ -349,7 +349,7 @@
     }
 
     private static void readAssociationV0(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
-            int associationId, @NonNull Set<Association> out) throws XmlPullParserException {
+            int associationId, @NonNull Set<AssociationInfo> out) throws XmlPullParserException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATION);
 
         final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
@@ -366,12 +366,12 @@
         // "Convert" MAC address into a DeviceId.
         final List<DeviceId> deviceIds = Arrays.asList(
                 new DeviceId(TYPE_MAC_ADDRESS, deviceAddress));
-        out.add(new Association(associationId, userId, appPackage, deviceIds, profile,
+        out.add(new AssociationInfo(associationId, userId, appPackage, deviceIds, profile,
                 /* managedByCompanionApp */false, notify, timeApproved));
     }
 
     private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
-            @UserIdInt int userId, @NonNull Set<Association> out)
+            @UserIdInt int userId, @NonNull Set<AssociationInfo> out)
             throws XmlPullParserException, IOException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
 
@@ -385,7 +385,7 @@
     }
 
     private static void readAssociationV1(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
-            @NonNull Set<Association> out) throws XmlPullParserException, IOException {
+            @NonNull Set<AssociationInfo> out) throws XmlPullParserException, IOException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATION);
 
         final int associationId = readIntAttribute(parser, XML_ATTR_ID);
@@ -406,8 +406,8 @@
             deviceIds.add(new DeviceId(type, value));
         }
 
-        out.add(new Association(associationId, userId, appPackage, deviceIds, profile, managedByApp,
-                notify, timeApproved));
+        out.add(new AssociationInfo(associationId, userId, appPackage, deviceIds, profile,
+                managedByApp, notify, timeApproved));
     }
 
     private static void readPreviouslyUsedIdsV1(@NonNull TypedXmlPullParser parser,
@@ -437,13 +437,13 @@
     }
 
     private static void writeAssociations(@NonNull XmlSerializer parent,
-            @Nullable Set<Association> associations) throws IOException {
+            @Nullable Set<AssociationInfo> associations) throws IOException {
         final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS);
         forEach(associations, it -> writeAssociation(serializer, it));
         serializer.endTag(null, XML_TAG_ASSOCIATIONS);
     }
 
-    private static void writeAssociation(@NonNull XmlSerializer parent, @NonNull Association a)
+    private static void writeAssociation(@NonNull XmlSerializer parent, @NonNull AssociationInfo a)
             throws IOException {
         final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATION);
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
new file mode 100644
index 0000000..18cf6f8
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -0,0 +1,111 @@
+/*
+ * 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.companion.virtual;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.IVirtualDeviceManager;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.ExceptionUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.DumpUtils;
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+
+/** @hide */
+@SuppressLint("LongLogTag")
+public class VirtualDeviceManagerService extends SystemService {
+
+    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "VirtualDeviceManagerService";
+    private final VirtualDeviceManagerImpl mImpl;
+    @GuardedBy("mVirtualDevices")
+    private final ArrayList<VirtualDeviceImpl> mVirtualDevices = new ArrayList<>();
+
+    public VirtualDeviceManagerService(Context context) {
+        super(context);
+        mImpl = new VirtualDeviceManagerImpl();
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
+    }
+
+    private class VirtualDeviceImpl extends IVirtualDevice.Stub {
+
+        private VirtualDeviceImpl() {}
+
+        @Override
+        public void close() {
+            synchronized (mVirtualDevices) {
+                mVirtualDevices.remove(this);
+            }
+        }
+    }
+
+    class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
+
+        @Override
+        public IVirtualDevice createVirtualDevice() {
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                    "createVirtualDevice");
+            VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl();
+            synchronized (mVirtualDevices) {
+                mVirtualDevices.add(virtualDevice);
+            }
+            return virtualDevice;
+        }
+
+        @Override
+        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                throws RemoteException {
+            try {
+                return super.onTransact(code, data, reply, flags);
+            } catch (Throwable e) {
+                Slog.e(LOG_TAG, "Error during IPC", e);
+                throw ExceptionUtils.propagate(e, RemoteException.class);
+            }
+        }
+
+        @Override
+        public void dump(@NonNull FileDescriptor fd,
+                @NonNull PrintWriter fout,
+                @Nullable String[] args) {
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) {
+                return;
+            }
+            fout.println("Created virtual devices: ");
+            synchronized (mVirtualDevices) {
+                for (VirtualDeviceImpl virtualDevice : mVirtualDevices) {
+                    fout.println(virtualDevice.toString());
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 216224c..f135fbf 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -88,12 +88,21 @@
     out: ["com/android/internal/art/ArtStatsLog.java"],
 }
 
+genrule {
+    name: "statslog-contexthub-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module contexthub" +
+        " --javaPackage com.android.server.location.contexthub --javaClass ContextHubStatsLog",
+    out: ["com/android/server/location/contexthub/ContextHubStatsLog.java"],
+}
+
 java_library_static {
     name: "services.core.unboosted",
     defaults: ["platform_service_defaults"],
     srcs: [
         ":android.hardware.biometrics.face-V1-java-source",
         ":statslog-art-java-gen",
+        ":statslog-contexthub-java-gen",
         ":services.core-sources",
         ":services.core.protologsrc",
         ":dumpstate_aidl",
diff --git a/services/core/java/android/content/pm/OWNERS b/services/core/java/android/content/pm/OWNERS
index 5eed0b5..3993140 100644
--- a/services/core/java/android/content/pm/OWNERS
+++ b/services/core/java/android/content/pm/OWNERS
@@ -1 +1 @@
-include /core/java/android/content/pm/OWNERS
\ No newline at end of file
+include /PACKAGE_MANAGER_OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 1e608f5..0146aa8 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -81,8 +81,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
@@ -1435,11 +1433,7 @@
      */
     public static final class HealthServiceWrapper {
         private static final String TAG = "HealthServiceWrapper";
-        public static final String INSTANCE_HEALTHD = "backup";
         public static final String INSTANCE_VENDOR = "default";
-        // All interesting instances, sorted by priority high -> low.
-        private static final List<String> sAllInstances =
-                Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
 
         private final IServiceNotification mNotification = new Notification();
         private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceHwbinder");
@@ -1471,8 +1465,8 @@
         }
 
         /**
-         * Start monitoring registration of new IHealth services. Only instances that are in
-         * {@code sAllInstances} and in device / framework manifest are used. This function should
+         * Start monitoring registration of new IHealth services. Only instance
+         * {@link #INSTANCE_VENDOR} and in device / framework manifest are used. This function should
          * only be called once.
          *
          * mCallback.onRegistration() is called synchronously (aka in init thread) before
@@ -1481,7 +1475,7 @@
          * @throws RemoteException transaction error when talking to IServiceManager
          * @throws NoSuchElementException if one of the following cases:
          *         - No service manager;
-         *         - none of {@code sAllInstances} are in manifests (i.e. not
+         *         - {@link #INSTANCE_VENDOR} is not in manifests (i.e. not
          *           available on this device), or none of these instances are available to current
          *           process.
          * @throws NullPointerException when supplier is null
@@ -1499,26 +1493,23 @@
 
             // Initialize mLastService and call callback for the first time (in init thread)
             IHealth newService = null;
-            for (String name : sAllInstances) {
-                traceBegin("HealthInitGetService_" + name);
-                try {
-                    newService = healthSupplier.get(name);
-                } catch (NoSuchElementException ex) {
-                    /* ignored, handled below */
-                } finally {
-                    traceEnd();
-                }
-                if (newService != null) {
-                    mInstanceName = name;
-                    mLastService.set(newService);
-                    break;
-                }
+            traceBegin("HealthInitGetService_" + INSTANCE_VENDOR);
+            try {
+                newService = healthSupplier.get(INSTANCE_VENDOR);
+            } catch (NoSuchElementException ex) {
+                /* ignored, handled below */
+            } finally {
+                traceEnd();
+            }
+            if (newService != null) {
+                mInstanceName = INSTANCE_VENDOR;
+                mLastService.set(newService);
             }
 
             if (mInstanceName == null || newService == null) {
                 throw new NoSuchElementException(String.format(
-                        "No IHealth service instance among %s is available. Perhaps no permission?",
-                        sAllInstances.toString()));
+                        "IHealth service instance %s isn't available. Perhaps no permission?",
+                        INSTANCE_VENDOR));
             }
 
             if (callback != null) {
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 39321bf..9923274 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -301,17 +301,20 @@
     protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) {
         final List<UserInfo> allUsers = UserManager.get(mContext).getUsers();
         final List<Integer> supportedUsers = new ArrayList<>(allUsers.size());
-        for (UserInfo user : allUsers) {
-            supportedUsers.add(user.id);
+        for (int i = 0; i < allUsers.size(); i++) {
+            final UserInfo user = allUsers.get(i);
+            if (isUserSupported(new TargetUser(user))) {
+                supportedUsers.add(user.id);
+            }
         }
-        if (allUsers.isEmpty()) {
+        if (supportedUsers.isEmpty()) {
             pw.print(prefix); pw.println("No supported users");
-        } else {
-            final int size = supportedUsers.size();
-            pw.print(prefix); pw.print(size); pw.print(" supported user");
-            if (size > 1) pw.print("s");
-            pw.print(": "); pw.println(supportedUsers);
+            return;
         }
+        final int size = supportedUsers.size();
+        pw.print(prefix); pw.print(size); pw.print(" supported user");
+        if (size > 1) pw.print("s");
+        pw.print(": "); pw.println(supportedUsers);
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 1997897..b5623be 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5110,6 +5110,29 @@
         return didSomething;
     }
 
+    void makeServicesNonForegroundLocked(final String pkg, final @UserIdInt int userId) {
+        final ServiceMap smap = mServiceMap.get(userId);
+        if (smap != null) {
+            ArrayList<ServiceRecord> fgsList = new ArrayList<>();
+            for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) {
+                final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
+                if (sr.appInfo.packageName.equals(pkg) && sr.isForeground) {
+                    fgsList.add(sr);
+                }
+            }
+
+            final int numServices = fgsList.size();
+            if (DEBUG_FOREGROUND_SERVICE) {
+                Slog.i(TAG_SERVICE, "Forcing " + numServices + " services out of foreground in u"
+                        + userId + "/" + pkg);
+            }
+            for (int i = 0; i < numServices; i++) {
+                final ServiceRecord sr = fgsList.get(i);
+                setServiceForegroundInnerLocked(sr, 0, null, Service.STOP_FOREGROUND_REMOVE, 0);
+            }
+        }
+    }
+
     void forceStopPackageLocked(String packageName, int userId) {
         ServiceMap smap = mServiceMap.get(userId);
         if (smap != null && smap.mActiveForegroundApps.size() > 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f6c1106..53f7575 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2397,6 +2397,7 @@
 
         mBatteryStatsService.publish();
         mAppOpsService.publish();
+        mProcessStats.publish();
         Slog.d("AppOps", "AppOpsService published");
         LocalServices.addService(ActivityManagerInternal.class, mInternal);
         LocalManagerRegistry.addManager(ActivityManagerLocal.class,
@@ -3751,6 +3752,29 @@
     }
 
     @Override
+    public void makeServicesNonForeground(final String packageName, int userId) {
+        if (checkCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: makeServicesNonForeground() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        final int callingPid = Binder.getCallingPid();
+        userId = mUserController.handleIncomingUser(callingPid, Binder.getCallingUid(),
+                userId, true, ALLOW_FULL_ONLY, "makeServicesNonForeground", null);
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            makeServicesNonForegroundUnchecked(packageName, userId);
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    @Override
     public void forceStopPackage(final String packageName, int userId) {
         if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -4058,6 +4082,13 @@
         }
     }
 
+    private void makeServicesNonForegroundUnchecked(final String packageName,
+            final @UserIdInt int userId) {
+        synchronized (this) {
+            mServices.makeServicesNonForegroundLocked(packageName, userId);
+        }
+    }
+
     @GuardedBy("this")
     private void forceStopPackageLocked(final String packageName, int uid, String reason) {
         forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false,
@@ -16399,6 +16430,11 @@
         }
 
         @Override
+        public void makeServicesNonForeground(String pkg, int userId) {
+            ActivityManagerService.this.makeServicesNonForegroundUnchecked(pkg, userId);
+        }
+
+        @Override
         public void stopForegroundServicesForChannel(String pkg, int userId,
                 String channelId) {
             synchronized (ActivityManagerService.this) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 685d606..0f71639 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -229,6 +229,8 @@
                     return runBugReport(pw);
                 case "force-stop":
                     return runForceStop(pw);
+                case "stop-fgs":
+                    return runStopForegroundServices(pw);
                 case "fgs-notification-rate-limit":
                     return runFgsNotificationRateLimit(pw);
                 case "crash":
@@ -1105,6 +1107,22 @@
         return 0;
     }
 
+    int runStopForegroundServices(PrintWriter pw) throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        mInterface.makeServicesNonForeground(getNextArgRequired(), userId);
+        return 0;
+    }
+
     int runFgsNotificationRateLimit(PrintWriter pw) throws RemoteException {
         final String toggleValue = getNextArgRequired();
         final boolean enable;
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 34a0c1b..256cffd 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -38,8 +38,11 @@
 import com.android.internal.app.procstats.IProcessStats;
 import com.android.internal.app.procstats.ProcessState;
 import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.app.procstats.ProcessStatsInternal;
 import com.android.internal.app.procstats.ServiceState;
+import com.android.internal.app.procstats.UidState;
 import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -783,6 +786,64 @@
         }
     }
 
+    private SparseArray<long[]> getUidProcStateStatsOverTime(long minTime) {
+        final Parcel current = Parcel.obtain();
+        final ProcessStats stats = new ProcessStats();
+        long curTime;
+        synchronized (mLock) {
+            final long now = SystemClock.uptimeMillis();
+            mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+            mProcessStats.mTimePeriodEndUptime = now;
+            stats.add(mProcessStats);
+            curTime = mProcessStats.mTimePeriodEndRealtime - mProcessStats.mTimePeriodStartRealtime;
+        }
+        if (curTime < minTime) {
+            try {
+                mFileLock.lock();
+                // Need to add in older stats to reach desired time.
+                ArrayList<String> files = getCommittedFilesLF(0, false, true);
+                if (files != null && files.size() > 0) {
+                    int i = files.size() - 1;
+                    while (i >= 0 && (stats.mTimePeriodEndRealtime
+                            - stats.mTimePeriodStartRealtime) < minTime) {
+                        AtomicFile file = new AtomicFile(new File(files.get(i)));
+                        i--;
+                        ProcessStats moreStats = new ProcessStats(false);
+                        readLF(moreStats, file);
+                        if (moreStats.mReadError == null) {
+                            stats.add(moreStats);
+                        } else {
+                            Slog.w(TAG, "Failure reading " + files.get(i + 1) + "; "
+                                    + moreStats.mReadError);
+                            continue;
+                        }
+                    }
+                }
+            } finally {
+                mFileLock.unlock();
+            }
+        }
+        final SparseArray<UidState> uidStates = stats.mUidStates;
+        final SparseArray<long[]> results = new SparseArray<>();
+        for (int i = 0, size = uidStates.size(); i < size; i++) {
+            final int uid = uidStates.keyAt(i);
+            final UidState uidState = uidStates.valueAt(i);
+            results.put(uid, uidState.getAggregatedDurationsInStates());
+        }
+        return results;
+    }
+
+    void publish() {
+        LocalServices.addService(ProcessStatsInternal.class, new LocalService());
+    }
+
+    private final class LocalService extends ProcessStatsInternal {
+        @Override
+        public SparseArray<long[]> getUidProcStateStatsOverTime(long minTime) {
+            return ProcessStatsService.this.getUidProcStateStatsOverTime(minTime);
+        }
+    }
+
     private void dumpAggregatedStats(PrintWriter pw, long aggregateHours, long now,
             String reqPackage, boolean isCompact, boolean dumpDetails, boolean dumpFullDetails,
             boolean dumpAll, boolean activeOnly, int section) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 4cb786a..e5e29a6 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1774,19 +1774,36 @@
         MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION);
         MESSAGES_MUTE_MUSIC.add(MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT);
         MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE);
-        MESSAGES_MUTE_MUSIC.add(MSG_REPORT_NEW_ROUTES_A2DP);
     }
 
     private AtomicBoolean mMusicMuted = new AtomicBoolean(false);
 
+    boolean messageMutesMusic(int message) {
+        if (message == 0) {
+            return false;
+        }
+        // Do not mute if we are disconnecting an A2DP device and music is playing
+        // on a wired headset.
+        if ((message == MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED
+                || message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION)
+                && AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
+                && mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(
+                        mAudioService.getDevicesForStream(AudioSystem.STREAM_MUSIC))) {
+            return false;
+        }
+        return true;
+    }
+
     /** Mutes or unmutes music according to pending A2DP messages */
     private void checkMessagesMuteMusic(int message) {
-        boolean mute = message != 0;
+        boolean mute = messageMutesMusic(message);
         if (!mute) {
             for (int msg : MESSAGES_MUTE_MUSIC) {
                 if (mBrokerHandler.hasMessages(msg)) {
-                    mute = true;
-                    break;
+                    if (messageMutesMusic(msg)) {
+                        mute = true;
+                        break;
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 6c3c736..5909f80 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -581,7 +581,7 @@
         mDeviceBroker.postObserveDevicesForAllStreams();
     }
 
-    private static final Set<Integer> DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET;
+    /* package */ static final Set<Integer> DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET;
     static {
         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET = new HashSet<>();
         DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 34277a2..2cd63b5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6214,9 +6214,15 @@
      */
     public @AudioManager.DeviceVolumeBehavior
     int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
+        Objects.requireNonNull(device);
         // verify permissions
         enforceQueryStateOrModifyRoutingPermission();
 
+        return getDeviceVolumeBehaviorInt(device);
+    }
+
+    private @AudioManager.DeviceVolumeBehavior
+            int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) {
         // translate Java device type to native device type (for the devices masks for full / fixed)
         final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
                 device.getType());
@@ -6244,6 +6250,29 @@
         return AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE;
     }
 
+    /**
+     * @see AudioManager#isVolumeFixed()
+     * Note there are no permission checks on this operation, as this is part of API 21
+     * @return true if the current device's volume behavior for media is
+     *         DEVICE_VOLUME_BEHAVIOR_FIXED
+     */
+    public boolean isVolumeFixed() {
+        if (mUseFixedVolume) {
+            return true;
+        }
+        final AudioAttributes attributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .build();
+        // calling getDevice*Int to bypass permission check
+        final List<AudioDeviceAttributes> devices = getDevicesForAttributesInt(attributes);
+        for (AudioDeviceAttributes device : devices) {
+            if (getDeviceVolumeBehaviorInt(device) == AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0;
     /*package*/ static final int CONNECTION_STATE_CONNECTED = 1;
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 031f6ee..61b8ded 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -168,6 +168,10 @@
         return Utils.isKeyguard(getContext(), getOwnerString());
     }
 
+    private boolean isSettings() {
+        return Utils.isSettings(getContext(), getOwnerString());
+    }
+
     @Override
     protected boolean isCryptoOperation() {
         return mOperationId != 0;
@@ -499,6 +503,8 @@
     protected int getShowOverlayReason() {
         if (isKeyguard()) {
             return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
+        } else if (isSettings()) {
+            return BiometricOverlayConstants.REASON_AUTH_SETTINGS;
         } else if (isBiometricPrompt()) {
             return BiometricOverlayConstants.REASON_AUTH_BP;
         } else {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index ca051e9..e4d5fba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -229,6 +229,7 @@
 
     @Override
     public void onLockoutTimed(long durationMillis) {
+        super.onLockoutTimed(durationMillis);
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
@@ -246,6 +247,7 @@
 
     @Override
     public void onLockoutPermanent() {
+        super.onLockoutPermanent();
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 091e6c4..a56a8ea 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -227,7 +227,7 @@
             subscriberId = tele.getSubscriberId();
             mNetworkTemplate = new NetworkTemplate(
                     NetworkTemplate.MATCH_MOBILE, subscriberId, new String[] { subscriberId },
-                    null, NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+                    null, NetworkStats.METERED_YES, NetworkStats.ROAMING_ALL,
                     NetworkStats.DEFAULT_NETWORK_NO, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
                     SUBSCRIBER_ID_MATCH_RULE_EXACT);
             mUsageCallback = new UsageCallback() {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5178f35..8481ad0 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2117,9 +2117,6 @@
             pw.println();
             mLogicalDisplayMapper.dumpLocked(pw);
 
-            pw.println();
-            mDisplayModeDirector.dump(pw);
-
             final int callbackCount = mCallbacks.size();
             pw.println();
             pw.println("Callbacks: size=" + callbackCount);
@@ -2142,6 +2139,8 @@
             pw.println();
             mPersistentDataStore.dump(pw);
         }
+        pw.println();
+        mDisplayModeDirector.dump(pw);
     }
 
     private static float[] getFloatArray(TypedArray array) {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 87b9e6a..c82b16d 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -524,7 +524,10 @@
      */
     public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) {
         synchronized (mLock) {
-            mAlwaysRespectAppRequest = enabled;
+            if (mAlwaysRespectAppRequest != enabled) {
+                mAlwaysRespectAppRequest = enabled;
+                notifyDesiredDisplayModeSpecsChangedLocked();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 147050c..2f22d33 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -48,7 +48,7 @@
 final class DisplayPowerState {
     private static final String TAG = "DisplayPowerState";
 
-    private static boolean DEBUG = false;
+    private static final boolean DEBUG = false;
     private static String COUNTER_COLOR_FADE = "ColorFadeLevel";
 
     private final Handler mHandler;
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 5bc69943..76754d3 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -117,7 +117,8 @@
     }
 
     public void startDream(Binder token, ComponentName name,
-            boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) {
+            boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock,
+            ComponentName overlayComponentName) {
         stopDream(true /*immediate*/, "starting new dream");
 
         Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
@@ -138,6 +139,7 @@
             Intent intent = new Intent(DreamService.SERVICE_INTERFACE);
             intent.setComponent(name);
             intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            intent.putExtra(DreamService.EXTRA_DREAM_OVERLAY_COMPONENT, overlayComponentName);
             try {
                 if (!mContext.bindServiceAsUser(intent, mCurrentDream,
                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 3a7220f7..258689a 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -95,6 +95,8 @@
     private int mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
     private int mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
 
+    private ComponentName mDreamOverlayServiceName;
+
     private AmbientDisplayConfiguration mDozeConfig;
 
     @VisibleForTesting
@@ -421,7 +423,8 @@
             if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
                 mUiEventLogger.log(DreamManagerEvent.DREAM_START);
             }
-            mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock);
+            mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock,
+                    mDreamOverlayServiceName);
         }));
     }
 
@@ -592,6 +595,15 @@
         }
 
         @Override // Binder call
+        public void registerDreamOverlayService(ComponentName overlayComponent) {
+            checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+
+            // Store the overlay service component so that it can be passed to the dream when it is
+            // invoked.
+            mDreamOverlayServiceName = overlayComponent;
+        }
+
+        @Override // Binder call
         public ComponentName getDefaultDreamComponentForUser(int userId) {
             checkPermission(android.Manifest.permission.READ_DREAM_STATE);
             userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index f356c36..3aa2f42 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -208,6 +208,12 @@
     void init() {
         assertRunOnServiceThread();
         mPreferredAddress = getPreferredAddress();
+        if (mHandler.hasMessages(MSG_DISABLE_DEVICE_TIMEOUT)) {
+            // Remove and trigger the queued message for clearing all actions when going to standby.
+            // This is necessary because the device may wake up before the message is triggered.
+            mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
+            handleDisableDeviceTimeout();
+        }
         mPendingActionClearedCallback = null;
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index b5bb8bd..69f7af2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -3166,7 +3166,7 @@
                 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
                 devices.remove(device);
                 if (devices.isEmpty()) {
-                    onStandbyCompleted(standbyAction);
+                    onPendingActionsCleared(standbyAction);
                     // We will not clear local devices here, since some OEM/SOC will keep passing
                     // the received packets until the application processor enters to the sleep
                     // actually.
@@ -3227,10 +3227,17 @@
         mHdmiCecNetwork.clearLocalDevices();
     }
 
+    /**
+     * Normally called after all devices have cleared their pending actions, to execute the final
+     * phase of the standby flow.
+     *
+     * This can also be called during wakeup, when pending actions are cleared after failing to be
+     * cleared during standby. In this case, it does not execute the standby flow.
+     */
     @ServiceThreadOnly
-    private void onStandbyCompleted(int standbyAction) {
+    private void onPendingActionsCleared(int standbyAction) {
         assertRunOnServiceThread();
-        Slog.v(TAG, "onStandbyCompleted");
+        Slog.v(TAG, "onPendingActionsCleared");
 
         if (!mPowerStatusController.isPowerStatusTransientToStandby()) {
             return;
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 686926f..1c04d8c 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -49,6 +49,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
@@ -184,6 +185,7 @@
     }
 
     public ContextHubService(Context context) {
+        long startTimeNs = SystemClock.elapsedRealtimeNanos();
         mContext = context;
 
         mContextHubWrapper = getContextHubWrapper();
@@ -205,6 +207,9 @@
             Log.e(TAG, "RemoteException while getting Context Hub info", e);
             hubInfo = new Pair(Collections.emptyList(), Collections.emptyList());
         }
+        long bootTimeNs = SystemClock.elapsedRealtimeNanos() - startTimeNs;
+        int numContextHubs = hubInfo.first.size();
+        ContextHubStatsLog.write(ContextHubStatsLog.CONTEXT_HUB_BOOTED, bootTimeNs, numContextHubs);
 
         mContextHubIdToInfoMap = Collections.unmodifiableMap(
                 ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first));
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 83de0b3..cbca48c9 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -1996,6 +1996,11 @@
                         + TimeUtils.formatDuration(delayMs));
             }
 
+            if (mDelayedRegister != null) {
+                mAlarmHelper.cancel(mDelayedRegister);
+                mDelayedRegister = null;
+            }
+
             mDelayedRegister = new OnAlarmListener() {
                 @Override
                 public void onAlarm() {
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
index d285c43..4873467 100644
--- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -40,7 +40,7 @@
  * Monitors the state changes of audio players.
  */
 class AudioPlayerStateMonitor {
-    private static boolean DEBUG = MediaSessionService.DEBUG;
+    private static final boolean DEBUG = MediaSessionService.DEBUG;
     private static String TAG = "AudioPlayerStateMonitor";
 
     private static AudioPlayerStateMonitor sInstance;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index b45d87f..d0aa40b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -15,12 +15,14 @@
  */
 package com.android.server.net;
 
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
 import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
 import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED;
@@ -29,6 +31,7 @@
 import static android.os.PowerExemptionManager.reasonCodeToString;
 import static android.os.Process.INVALID_UID;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.ProcessCapability;
 import android.net.NetworkPolicyManager;
@@ -81,13 +84,18 @@
 
     private final Object mLock = new Object();
 
-    void networkBlocked(int uid, UidBlockedState uidBlockedState) {
+    void networkBlocked(int uid, @Nullable UidBlockedState uidBlockedState) {
         synchronized (mLock) {
             if (LOGD || uid == mDebugUid) {
                 Slog.d(TAG, "Blocked state of uid: " + uidBlockedState.toString());
             }
-            mNetworkBlockedBuffer.networkBlocked(uid, uidBlockedState.blockedReasons,
-                    uidBlockedState.allowedReasons, uidBlockedState.effectiveBlockedReasons);
+            if (uidBlockedState == null) {
+                mNetworkBlockedBuffer.networkBlocked(uid, BLOCKED_REASON_NONE, ALLOWED_REASON_NONE,
+                        BLOCKED_REASON_NONE);
+            } else {
+                mNetworkBlockedBuffer.networkBlocked(uid, uidBlockedState.blockedReasons,
+                        uidBlockedState.allowedReasons, uidBlockedState.effectiveBlockedReasons);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f14f7695..e5df3e7 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2824,6 +2824,12 @@
                 mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
 
         mPreferencesHelper.updateNotificationChannel(pkg, uid, channel, true);
+        if (mEnableAppSettingMigration) {
+            if (mPreferencesHelper.onlyHasDefaultChannel(pkg, uid)) {
+                mPermissionHelper.setNotificationPermission(pkg, UserHandle.getUserId(uid),
+                        channel.getImportance() != IMPORTANCE_NONE, true);
+            }
+        }
         maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
 
         if (!fromListener) {
@@ -5077,6 +5083,27 @@
 
         @Override
         public boolean matchesCallFilter(Bundle extras) {
+            // Because matchesCallFilter may use contact data to filter calls, the callers of this
+            // method need to either have notification listener access or permission to read
+            // contacts.
+            boolean listenerAccess = false;
+            try {
+                String[] pkgNames = mPackageManager.getPackagesForUid(Binder.getCallingUid());
+                for (int i = 0; i < pkgNames.length; i++) {
+                    // in most cases there should only be one package here
+                    listenerAccess |= mListeners.hasAllowedListener(pkgNames[i],
+                            Binder.getCallingUserHandle().getIdentifier());
+                }
+            } catch (RemoteException e) {
+            } finally {
+                if (!listenerAccess) {
+                    getContext().enforceCallingPermission(
+                            Manifest.permission.READ_CONTACTS,
+                            "matchesCallFilter requires listener permissions or "
+                                    + "contacts read access");
+                }
+            }
+
             return mZenModeHelper.matchesCallFilter(
                     Binder.getCallingUserHandle(),
                     extras,
@@ -10948,6 +10975,23 @@
             }
             return false;
         }
+
+        // Returns whether there is a component with listener access granted that is associated
+        // with the given package name / user ID.
+        boolean hasAllowedListener(String packageName, int userId) {
+            if (packageName == null) {
+                return false;
+            }
+
+            // Loop through allowed components to compare package names
+            List<ComponentName> allowedComponents = getAllowedComponents(userId);
+            for (int i = 0; i < allowedComponents.size(); i++) {
+                if (allowedComponents.get(i).getPackageName().equals(packageName)) {
+                    return true;
+                }
+            }
+            return false;
+        }
     }
 
     // TODO (b/194833441): remove when we've fully migrated to a permission
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index b94721a..c74813a 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1043,9 +1043,11 @@
             r.channels.put(updatedChannel.getId(), updatedChannel);
 
             if (onlyHasDefaultChannel(pkg, uid)) {
-                // copy settings to app level so they are inherited by new channels
-                // when the app migrates
-                r.importance = updatedChannel.getImportance();
+                if (!mPermissionHelper.isMigrationEnabled()) {
+                    // copy settings to app level so they are inherited by new channels
+                    // when the app migrates
+                    r.importance = updatedChannel.getImportance();
+                }
                 r.priority = updatedChannel.canBypassDnd()
                         ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
                 r.visibility = updatedChannel.getLockscreenVisibility();
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index aa4d6bb..569d651 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -798,7 +798,8 @@
         for (int n = 0; n < count; n++) {
             ResolveInfo info = candidates.get(n);
             if (blockInstant && (info.isInstantAppAvailable
-                    || isInstantApp(info.activityInfo.packageName, userId))) {
+                    || isInstantAppInternal(info.activityInfo.packageName, userId,
+                            Process.SYSTEM_UID))) {
                 continue;
             }
 
@@ -1075,7 +1076,8 @@
                     resolveInfos.remove(i);
                     continue;
                 }
-                if (blockInstant && isInstantApp(info.activityInfo.packageName, userId)) {
+                if (blockInstant && isInstantAppInternal(
+                        info.activityInfo.packageName, userId, Process.SYSTEM_UID)) {
                     resolveInfos.remove(i);
                     continue;
                 }
diff --git a/services/core/java/com/android/server/pm/IncrementalProgressListener.java b/services/core/java/com/android/server/pm/IncrementalProgressListener.java
index bb797cb..43a938e 100644
--- a/services/core/java/com/android/server/pm/IncrementalProgressListener.java
+++ b/services/core/java/com/android/server/pm/IncrementalProgressListener.java
@@ -37,7 +37,20 @@
             if (ps == null) {
                 return;
             }
-            ps.setLoadingProgress(progress);
+
+            boolean wasLoading = ps.isPackageLoading();
+            // Due to asynchronous progress reporting, incomplete progress might be received
+            // after the app is migrated off incremental. Ignore such progress updates.
+            if (wasLoading) {
+                ps.setLoadingProgress(progress);
+                // Only report the state change when loading state changes from loading to not
+                if (!ps.isPackageLoading()) {
+                    // Unregister progress listener
+                    mPm.mIncrementalManager.unregisterLoadingProgressCallbacks(ps.getPathString());
+                    // Make sure the information is preserved
+                    mPm.scheduleWriteSettingsLocked();
+                }
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java
deleted file mode 100644
index 3101ca7..0000000
--- a/services/core/java/com/android/server/pm/IncrementalStates.java
+++ /dev/null
@@ -1,232 +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.server.pm;
-
-import android.content.pm.IncrementalStatesInfo;
-import android.os.Handler;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.function.pooled.PooledLambda;
-
-/**
- * Manages state transitions of a package installed on Incremental File System. Currently manages:
- * 1. loading state (whether a package is still loading or has been fully loaded).
- *
- * The following events might change the states of a package:
- * 1. Installation commit
- * 2. Loading progress changes
- *
- * @hide
- */
-public final class IncrementalStates {
-    private static final String TAG = "IncrementalStates";
-    private static final boolean DEBUG = false;
-    private final Handler mHandler = BackgroundThread.getHandler();
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private final LoadingState mLoadingState;
-    @GuardedBy("mLock")
-    private Callback mCallback = null;
-
-    public IncrementalStates() {
-        // By default the package is not fully loaded (i.e., is loading)
-        this(true, 0);
-    }
-
-    public IncrementalStates(boolean isLoading, float loadingProgress) {
-        mLoadingState = new LoadingState(isLoading, loadingProgress);
-    }
-
-    /**
-     * Callback interface to report that the loading state of this package has changed.
-     */
-    public interface Callback {
-        /**
-         * Reports that package is fully loaded.
-         */
-        void onPackageFullyLoaded();
-    }
-
-    /**
-     * By calling this method, the caller indicates that package installation has just been
-     * committed. Set the initial loading state after the package
-     * is committed. Incremental packages are by-default loading; non-Incremental packages are not.
-     *
-     * @param isIncremental whether a package is installed on Incremental or not.
-     */
-    public void onCommit(boolean isIncremental) {
-        if (DEBUG) {
-            Slog.i(TAG, "received package commit event");
-        }
-        if (!isIncremental) {
-            synchronized (mLock) {
-                updateProgressLocked(1.0f);
-            }
-            onLoadingStateChanged();
-        }
-    }
-
-    private void onLoadingStateChanged() {
-        mHandler.post(PooledLambda.obtainRunnable(
-                IncrementalStates::reportFullyLoaded,
-                IncrementalStates.this).recycleOnUse());
-    }
-
-    private void reportFullyLoaded() {
-        final Callback callback;
-        synchronized (mLock) {
-            callback = mCallback;
-        }
-        if (callback != null) {
-            callback.onPackageFullyLoaded();
-        }
-    }
-
-    /**
-     * Use the specified callback to report state changing events.
-     *
-     * @param callback Object to report new state.
-     */
-    public void setCallback(Callback callback) {
-        if (DEBUG) {
-            Slog.i(TAG, "registered callback");
-        }
-        synchronized (mLock) {
-            mCallback = callback;
-        }
-    }
-
-    /**
-     * Update the package loading progress to specified value.
-     *
-     * @param progress Value between [0, 1].
-     */
-    public void setProgress(float progress) {
-        final boolean oldLoadingState;
-        final boolean newLoadingState;
-        synchronized (mLock) {
-            oldLoadingState = mLoadingState.isLoading();
-            if (oldLoadingState) {
-                // Due to asynchronous progress reporting, incomplete progress might be received
-                // after the app is migrated off incremental. Ignore such progress updates.
-                updateProgressLocked(progress);
-            }
-            newLoadingState = mLoadingState.isLoading();
-        }
-        if (oldLoadingState && !newLoadingState) {
-            // Only report the state change when loading state changes from true to false
-            onLoadingStateChanged();
-        }
-    }
-
-    /**
-     * @return all current states in a Parcelable.
-     */
-    public IncrementalStatesInfo getIncrementalStatesInfo() {
-        synchronized (mLock) {
-            return new IncrementalStatesInfo(
-                    mLoadingState.isLoading(),
-                    mLoadingState.getProgress());
-        }
-    }
-
-    private void updateProgressLocked(float progress) {
-        if (DEBUG) {
-            Slog.i(TAG, "received progress update: " + progress);
-        }
-        mLoadingState.setProgress(progress);
-        if (Math.abs(1.0f - progress) < 0.00000001f) {
-            if (DEBUG) {
-                Slog.i(TAG, "package is fully loaded");
-            }
-            mLoadingState.setProgress(1.0f);
-            if (mLoadingState.isLoading()) {
-                mLoadingState.adoptNewLoadingStateLocked(false);
-            }
-        }
-    }
-
-    private class LoadingState {
-        private boolean mIsLoading;
-        private float mProgress;
-
-        LoadingState(boolean isLoading, float loadingProgress) {
-            mIsLoading = isLoading;
-            // loading progress is reset to 1 if loading has finished
-            mProgress = isLoading ? loadingProgress : 1;
-        }
-
-        public boolean isLoading() {
-            return mIsLoading;
-        }
-
-        public float getProgress() {
-            return mProgress;
-        }
-
-        public void setProgress(float progress) {
-            mProgress = progress;
-        }
-
-        public void adoptNewLoadingStateLocked(boolean nextState) {
-            if (DEBUG) {
-                Slog.i(TAG, "Loading state changed from " + mIsLoading + " to " + nextState);
-            }
-            mIsLoading = nextState;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o == this) {
-                return true;
-            }
-            if (!(o instanceof LoadingState)) {
-                return false;
-            }
-            LoadingState l = (LoadingState) o;
-            return l.mIsLoading == mIsLoading && l.mProgress == mProgress;
-        }
-
-        @Override
-        public int hashCode() {
-            int hashCode = Boolean.hashCode(mIsLoading);
-            hashCode = 31 * hashCode + Float.hashCode(mProgress);
-            return hashCode;
-        }
-    }
-
-
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (!(o instanceof IncrementalStates)) {
-            return false;
-        }
-        IncrementalStates l = (IncrementalStates) o;
-        return l.mLoadingState.equals(mLoadingState);
-    }
-
-    @Override
-    public int hashCode() {
-        return mLoadingState.hashCode();
-    }
-}
diff --git a/services/core/java/com/android/server/pm/IncrementalStatesCallback.java b/services/core/java/com/android/server/pm/IncrementalStatesCallback.java
deleted file mode 100644
index 478c99b..0000000
--- a/services/core/java/com/android/server/pm/IncrementalStatesCallback.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-/**
- * Package states callback, used to listen for package state changes and send broadcasts
- */
-final class IncrementalStatesCallback implements IncrementalStates.Callback {
-    private final String mPackageName;
-    private final PackageManagerService mPm;
-
-    IncrementalStatesCallback(String packageName, PackageManagerService pm) {
-        mPackageName = packageName;
-        mPm = pm;
-    }
-
-    @Override
-    public void onPackageFullyLoaded() {
-        final String codePath;
-        synchronized (mPm.mLock) {
-            final PackageSetting ps = mPm.mSettings.getPackageLPr(mPackageName);
-            if (ps == null) {
-                return;
-            }
-            codePath = ps.getPathString();
-        }
-        // Unregister progress listener
-        mPm.mIncrementalManager.unregisterLoadingProgressCallbacks(codePath);
-        // Make sure the information is preserved
-        mPm.scheduleWriteSettingsLocked();
-    }
-}
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index 722198f..5ff0a6f 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -172,9 +172,16 @@
         scanSystemDirs(packageParser, executorService);
         // Parse overlay configuration files to set default enable state, mutability, and
         // priority of system overlays.
+        final ArrayMap<String, File> apkInApexPreInstalledPaths = new ArrayMap<>();
+        for (ApexManager.ActiveApexInfo apexInfo : mPm.mApexManager.getActiveApexInfos()) {
+            for (String packageName : mPm.mApexManager.getApksInApex(apexInfo.apexModuleName)) {
+                apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath);
+            }
+        }
         OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance(
                 consumer -> mPm.forEachPackage(
-                        pkg -> consumer.accept(pkg, pkg.isSystem())));
+                        pkg -> consumer.accept(pkg, pkg.isSystem(),
+                          apkInApexPreInstalledPaths.get(pkg.getPackageName()))));
         cleanupSystemPackagesAndInstallStubs(packageParser, executorService, packageSettings,
                 startTime, userIds);
         packageParser.close();
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7569900..4c70cda 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -59,6 +59,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.incremental.IncrementalManager;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -455,7 +456,10 @@
         if (pkgSetting.getInstantApp(userId)) {
             mPm.mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.getAppId());
         }
-        pkgSetting.setStatesOnCommit();
+
+        if (!IncrementalManager.isIncrementalPath(pkgSetting.getPathString())) {
+            pkgSetting.setLoadingProgress(1f);
+        }
 
         return pkg;
     }
diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java
index bfb5f76..e138188 100644
--- a/services/core/java/com/android/server/pm/InstallParams.java
+++ b/services/core/java/com/android/server/pm/InstallParams.java
@@ -1781,9 +1781,6 @@
                 final String codePath = ps.getPathString();
                 if (IncrementalManager.isIncrementalPath(codePath)
                         && mPm.mIncrementalManager != null) {
-                    final IncrementalStatesCallback incrementalStatesCallback =
-                            new IncrementalStatesCallback(ps.getPackageName(), mPm);
-                    ps.setIncrementalStatesCallback(incrementalStatesCallback);
                     mPm.mIncrementalManager.registerLoadingProgressCallback(codePath,
                             new IncrementalProgressListener(ps.getPackageName(), mPm));
                 }
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 3233819..1bdc9f3 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -3,27 +3,25 @@
 jsharkey@android.com
 jsharkey@google.com
 narayan@google.com
-patb@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
-toddke@android.com
-toddke@google.com
+include /PACKAGE_MANAGER_OWNERS
 
 # apex support
 per-file ApexManager.java = dariofreni@google.com, ioffe@google.com, olilan@google.com
 per-file StagingManager.java = dariofreni@google.com, ioffe@google.com, olilan@google.com
 
 # dex
-per-file AbstractStatsBase.java = calin@google.com, ngeoffray@google.com
-per-file BackgroundDexOptService.java = calin@google.com, ngeoffray@google.com
-per-file CompilerStats.java = calin@google.com, ngeoffray@google.com
-per-file DynamicCodeLoggingService.java = alanstokes@google.com, calin@google.com, ngeoffray@google.com
-per-file InstructionSets.java = calin@google.com, ngeoffray@google.com
-per-file OtaDexoptService.java = calin@google.com, ngeoffray@google.com
-per-file OtaDexoptShellCommand.java = calin@google.com, ngeoffray@google.com
-per-file PackageDexOptimizer.java = calin@google.com, ngeoffray@google.com
-per-file PackageManagerServiceCompilerMapping.java = calin@google.com, ngeoffray@google.com
-per-file PackageUsage.java = calin@google.com, ngeoffray@google.com
+per-file AbstractStatsBase.java = file:dex/OWNERS
+per-file BackgroundDexOptService.java = file:dex/OWNERS
+per-file CompilerStats.java = file:dex/OWNERS
+per-file DynamicCodeLoggingService.java = file:dex/OWNERS
+per-file InstructionSets.java = file:dex/OWNERS
+per-file OtaDexoptService.java = file:dex/OWNERS
+per-file OtaDexoptShellCommand.java = file:dex/OWNERS
+per-file PackageDexOptimizer.java = file:dex/OWNERS
+per-file PackageManagerServiceCompilerMapping.java = file:dex/OWNERS
+per-file PackageUsage.java = file:dex/OWNERS
 
 # multi user / cross profile
 per-file CrossProfileAppsServiceImpl.java = omakoto@google.com, yamasani@google.com
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2595bb4..fc266c8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -174,6 +174,7 @@
     private final File mSessionsDir;
 
     private final InternalCallback mInternalCallback = new InternalCallback();
+    private final PackageSessionVerifier mSessionVerifier;
 
     /**
      * Used for generating session IDs. Since this is created at boot time,
@@ -257,8 +258,9 @@
         mSessionsDir.mkdirs();
 
         mApexManager = ApexManager.getInstance();
-        mStagingManager = new StagingManager(context, apexParserSupplier,
-                mInstallThread.getLooper());
+        mStagingManager = new StagingManager(context);
+        mSessionVerifier = new PackageSessionVerifier(context, mPm, mApexManager,
+                apexParserSupplier, mInstallThread.getLooper());
 
         LocalServices.getService(SystemServiceManager.class).startService(
                 new Lifecycle(context, this));
@@ -1160,6 +1162,11 @@
     }
 
     @Override
+    public PackageSessionVerifier getSessionVerifier() {
+        return mSessionVerifier;
+    }
+
+    @Override
     public void bypassNextStagedInstallerCheck(boolean value) {
         if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
             throw new SecurityException("Caller not allowed to bypass staged installer check");
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3805977..97f8dc2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -732,11 +732,10 @@
         }
 
         /**
-         * Notified by the staging manager or PIS that pre-reboot verification has ended.
+         * Called when pre-reboot verification has ended.
          * Now it is safe to clean up the session if {@link #abandon()} has been called previously.
          */
-        @Override
-        public void notifyEndPreRebootVerification() {
+        private void notifyEndPreRebootVerification() {
             synchronized (mLock) {
                 if (!mInPreRebootVerification) {
                     throw new IllegalStateException("Pre-reboot verification not started");
@@ -2382,6 +2381,16 @@
 
     private void verify() {
         try {
+            List<PackageInstallerSession> children = getChildSessions();
+            if (isMultiPackage()) {
+                for (PackageInstallerSession child : children) {
+                    child.prepareInheritedFiles();
+                    child.parseApkAndExtractNativeLibraries();
+                }
+            } else {
+                prepareInheritedFiles();
+                parseApkAndExtractNativeLibraries();
+            }
             verifyNonStaged();
         } catch (PackageManagerException e) {
             final String completeMsg = ExceptionUtils.getCompleteMessage(e);
@@ -2401,6 +2410,118 @@
         }
     }
 
+    /**
+     * Prepares staged directory with any inherited APKs.
+     */
+    private void prepareInheritedFiles() throws PackageManagerException {
+        if (isApexSession() || params.mode != SessionParams.MODE_INHERIT_EXISTING) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mRelinquished) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Session relinquished");
+            }
+            if (mDestroyed) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Session destroyed");
+            }
+            if (!mSealed) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Session not sealed");
+            }
+            // Inherit any packages and native libraries from existing install that
+            // haven't been overridden.
+            try {
+                final List<File> fromFiles = mResolvedInheritedFiles;
+                final File toDir = stageDir;
+
+                if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
+                if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
+                    throw new IllegalStateException("mInheritedFilesBase == null");
+                }
+
+                if (isLinkPossible(fromFiles, toDir)) {
+                    if (!mResolvedInstructionSets.isEmpty()) {
+                        final File oatDir = new File(toDir, "oat");
+                        createOatDirs(mResolvedInstructionSets, oatDir);
+                    }
+                    // pre-create lib dirs for linking if necessary
+                    if (!mResolvedNativeLibPaths.isEmpty()) {
+                        for (String libPath : mResolvedNativeLibPaths) {
+                            // "/lib/arm64" -> ["lib", "arm64"]
+                            final int splitIndex = libPath.lastIndexOf('/');
+                            if (splitIndex < 0 || splitIndex >= libPath.length() - 1) {
+                                Slog.e(TAG,
+                                        "Skipping native library creation for linking due"
+                                                + " to invalid path: " + libPath);
+                                continue;
+                            }
+                            final String libDirPath = libPath.substring(1, splitIndex);
+                            final File libDir = new File(toDir, libDirPath);
+                            if (!libDir.exists()) {
+                                NativeLibraryHelper.createNativeLibrarySubdir(libDir);
+                            }
+                            final String archDirPath = libPath.substring(splitIndex + 1);
+                            NativeLibraryHelper.createNativeLibrarySubdir(
+                                    new File(libDir, archDirPath));
+                        }
+                    }
+                    linkFiles(fromFiles, toDir, mInheritedFilesBase);
+                } else {
+                    // TODO: this should delegate to DCS so the system process
+                    // avoids holding open FDs into containers.
+                    copyFiles(fromFiles, toDir);
+                }
+            } catch (IOException e) {
+                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+                        "Failed to inherit existing install", e);
+            }
+        }
+    }
+
+    private void parseApkAndExtractNativeLibraries() throws PackageManagerException {
+        synchronized (mLock) {
+            if (mRelinquished) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Session relinquished");
+            }
+            if (mDestroyed) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Session destroyed");
+            }
+            if (!mSealed) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Session not sealed");
+            }
+            Objects.requireNonNull(mPackageName);
+            Objects.requireNonNull(mSigningDetails);
+            Objects.requireNonNull(mResolvedBaseFile);
+            final PackageLite result;
+            if (!isApexSession()) {
+                // For mode inherit existing, it would link/copy existing files to stage dir in
+                // prepareInheritedFiles(). Therefore, we need to parse the complete package in
+                // stage dir here.
+                // Besides, PackageLite may be null for staged sessions that don't complete
+                // pre-reboot verification.
+                result = getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
+            } else {
+                result = getOrParsePackageLiteLocked(mResolvedBaseFile, /* flags */ 0);
+            }
+            if (result != null) {
+                mPackageLite = result;
+                if (!isApexSession()) {
+                    synchronized (mProgressLock) {
+                        mInternalProgress = 0.5f;
+                        computeProgressLocked(true);
+                    }
+                    extractNativeLibraries(
+                            mPackageLite, stageDir, params.abiOverride, mayInheritNativeLibs());
+                }
+            }
+        }
+    }
+
     private void verifyNonStaged()
             throws PackageManagerException {
         final VerificationParams verifyingSession = prepareForVerification();
@@ -2501,19 +2622,6 @@
                 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                         "Session not sealed");
             }
-            PackageLite result = parseApkLite();
-            if (result != null) {
-                mPackageLite = result;
-                if (!isApexSession()) {
-                    synchronized (mProgressLock) {
-                        mInternalProgress = 0.5f;
-                        computeProgressLocked(true);
-                    }
-
-                    extractNativeLibraries(
-                            mPackageLite, stageDir, params.abiOverride, mayInheritNativeLibs());
-                }
-            }
             return makeVerificationParamsLocked();
         }
     }
@@ -2533,82 +2641,6 @@
         closeInternal(false);
     }
 
-    /**
-     * Prepares staged directory with any inherited APKs and returns the parsed package.
-     */
-    @GuardedBy("mLock")
-    @Nullable
-    private PackageLite parseApkLite() throws PackageManagerException {
-
-
-        if (isMultiPackage()) {
-            return null;
-        }
-        Objects.requireNonNull(mPackageName);
-        Objects.requireNonNull(mSigningDetails);
-        Objects.requireNonNull(mResolvedBaseFile);
-
-        // Inherit any packages and native libraries from existing install that
-        // haven't been overridden.
-        if (!isApexSession() && params.mode == SessionParams.MODE_INHERIT_EXISTING) {
-            try {
-                final List<File> fromFiles = mResolvedInheritedFiles;
-                final File toDir = stageDir;
-
-                if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
-                if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
-                    throw new IllegalStateException("mInheritedFilesBase == null");
-                }
-
-                if (isLinkPossible(fromFiles, toDir)) {
-                    if (!mResolvedInstructionSets.isEmpty()) {
-                        final File oatDir = new File(toDir, "oat");
-                        createOatDirs(mResolvedInstructionSets, oatDir);
-                    }
-                    // pre-create lib dirs for linking if necessary
-                    if (!mResolvedNativeLibPaths.isEmpty()) {
-                        for (String libPath : mResolvedNativeLibPaths) {
-                            // "/lib/arm64" -> ["lib", "arm64"]
-                            final int splitIndex = libPath.lastIndexOf('/');
-                            if (splitIndex < 0 || splitIndex >= libPath.length() - 1) {
-                                Slog.e(TAG,
-                                        "Skipping native library creation for linking due"
-                                                + " to invalid path: " + libPath);
-                                continue;
-                            }
-                            final String libDirPath = libPath.substring(1, splitIndex);
-                            final File libDir = new File(toDir, libDirPath);
-                            if (!libDir.exists()) {
-                                NativeLibraryHelper.createNativeLibrarySubdir(libDir);
-                            }
-                            final String archDirPath = libPath.substring(splitIndex + 1);
-                            NativeLibraryHelper.createNativeLibrarySubdir(
-                                    new File(libDir, archDirPath));
-                        }
-                    }
-                    linkFiles(fromFiles, toDir, mInheritedFilesBase);
-                } else {
-                    // TODO: this should delegate to DCS so the system process
-                    // avoids holding open FDs into containers.
-                    copyFiles(fromFiles, toDir);
-                }
-            } catch (IOException e) {
-                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
-                        "Failed to inherit existing install", e);
-            }
-        }
-
-        if (!isApexSession()) {
-            // For mode inherit existing, it would link/copy existing files to stage dir in the
-            // above block. Therefore, we need to parse the complete package in stage dir here.
-            // Besides, PackageLite may be null for staged sessions that don't complete
-            // pre-reboot verification.
-            return getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
-        } else {
-            return getOrParsePackageLiteLocked(mResolvedBaseFile, /* flags */ 0);
-        }
-    }
-
     @GuardedBy("mLock")
     @Nullable
     /**
@@ -2659,7 +2691,12 @@
         // staged session or not. For a staged session, we will hand it over to the staging
         // manager to complete the installation.
         if (isStaged()) {
-            mStagingManager.commitSession(mStagedSession);
+            mSessionProvider.getSessionVerifier().verifyStaged(mStagedSession, (error, msg) -> {
+                mStagedSession.notifyEndPreRebootVerification();
+                if (error == SessionInfo.STAGED_SESSION_NO_ERROR) {
+                    mStagingManager.commitSession(mStagedSession);
+                }
+            });
             return;
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3eaa26e..3d916ae4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -142,7 +142,6 @@
 import android.content.pm.overlay.OverlayPaths;
 import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.component.ParsedActivity;
-import android.content.pm.parsing.component.ParsedActivityImpl;
 import android.content.pm.parsing.component.ParsedInstrumentation;
 import android.content.pm.parsing.component.ParsedIntentInfo;
 import android.content.pm.parsing.component.ParsedMainComponent;
@@ -4875,7 +4874,7 @@
                     return;
                 }
             } else {
-                if (isInstantApp(packageName, callingUserId)) {
+                if (isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID)) {
                     return;
                 }
             }
@@ -4982,7 +4981,8 @@
     public void reconcileSecondaryDexFiles(String packageName) {
         if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
             return;
-        } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) {
+        } else if (isInstantAppInternal(
+                packageName, UserHandle.getCallingUserId(), Process.SYSTEM_UID)) {
             return;
         }
         mDexManager.reconcileSecondaryDexFiles(packageName);
@@ -6597,7 +6597,8 @@
             if (DEBUG_BACKUP) {
                 Slog.i(TAG, "Package " + packageName + " sending normal FIRST_LAUNCH");
             }
-            final boolean isInstantApp = isInstantApp(packageName, userId);
+            final boolean isInstantApp = isInstantAppInternal(
+                    packageName, userId, Process.SYSTEM_UID);
             final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
             final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
             mBroadcastHelper.sendFirstLaunchBroadcast(
@@ -7992,7 +7993,7 @@
     void sendPackageChangedBroadcast(String packageName,
             boolean dontKillApp, ArrayList<String> componentNames, int packageUid, String reason) {
         final int userId = UserHandle.getUserId(packageUid);
-        final boolean isInstantApp = isInstantApp(packageName, userId);
+        final boolean isInstantApp = isInstantAppInternal(packageName, userId, Process.SYSTEM_UID);
         final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
         final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
         final SparseArray<int[]> broadcastAllowList = getBroadcastAllowList(
@@ -8914,7 +8915,8 @@
      * return {@code true} if any one application belongs to the shared user ID meets the criteria.
      */
     boolean canQueryPackage(int callingUid, @Nullable String targetPackageName) {
-        if (targetPackageName == null) {
+        // Since getSettingLPr returns null for ROOT_UID, add an extra check for it here.
+        if (callingUid == Process.ROOT_UID || targetPackageName == null) {
             return true;
         }
         synchronized (mLock) {
@@ -10006,7 +10008,7 @@
             if (ps == null) {
                 return null;
             }
-            return ps.getIncrementalStatesInfo();
+            return new IncrementalStatesInfo(ps.isPackageLoading(), ps.getLoadingProgress());
         }
 
         @Override
@@ -10340,7 +10342,7 @@
             throw new SecurityException(
                     "Caller uid " + callingUid + " does not own package " + packageName);
         }
-        if (isInstantAppInternal(packageName, userId, callingUid)) {
+        if (isInstantAppInternal(packageName, userId, Process.SYSTEM_UID)) {
             return false;
         }
         final AndroidPackage pkg;
diff --git a/services/core/java/com/android/server/pm/PackageSessionProvider.java b/services/core/java/com/android/server/pm/PackageSessionProvider.java
index af11e77..ad5cf13 100644
--- a/services/core/java/com/android/server/pm/PackageSessionProvider.java
+++ b/services/core/java/com/android/server/pm/PackageSessionProvider.java
@@ -16,7 +16,10 @@
 
 package com.android.server.pm;
 
-/** Provides access to individual sessions managed by the install service */
+/**
+ * Provides access to individual sessions managed by the install service as well as utilities
+ * used by the install process.
+ */
 public interface PackageSessionProvider {
 
     /**
@@ -25,4 +28,5 @@
      */
     PackageInstallerSession getSession(int sessionId);
 
+    PackageSessionVerifier getSessionVerifier();
 }
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
new file mode 100644
index 0000000..3f5410d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.apex.ApexInfo;
+import android.apex.ApexInfoList;
+import android.apex.ApexSessionInfo;
+import android.apex.ApexSessionParams;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.SigningDetails;
+import android.content.pm.parsing.PackageInfoWithoutStateUtils;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.apk.ApkSignatureVerifier;
+
+import com.android.internal.content.PackageHelper;
+import com.android.server.LocalServices;
+import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.rollback.RollbackManagerInternal;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+final class PackageSessionVerifier {
+    private static final String TAG = "PackageSessionVerifier";
+
+    interface Callback {
+        void onResult(int returnCode, String msg);
+    }
+
+    private final Context mContext;
+    private final PackageManagerService mPm;
+    private final ApexManager mApexManager;
+    private final Supplier<PackageParser2> mPackageParserSupplier;
+    // The handler thread to run verification tasks. Since all tasks run in the same thread,
+    // there is no need for synchronization.
+    private final Handler mHandler;
+    // Parent sessions for checking session conflicts.
+    private final List<StagingManager.StagedSession> mStagedSessions = new ArrayList<>();
+
+    PackageSessionVerifier(Context context, PackageManagerService pm,
+            ApexManager apexManager, Supplier<PackageParser2> packageParserSupplier,
+            Looper looper) {
+        mContext = context;
+        mPm = pm;
+        mApexManager = apexManager;
+        mPackageParserSupplier = packageParserSupplier;
+        mHandler = new Handler(looper);
+    }
+
+    /**
+     * Starts pre-reboot verification for the staged-session. This operation is broken into the
+     * following phases:
+     * <ul>
+     *     <li>Checks if multiple active sessions are supported.</li>
+     *     <li>Checks conflicts in rollbacks and overlapping packages.</li>
+     *     <li>Submits apex sessions to apex service.</li>
+     *     <li>Validates signatures of apex files.</li>
+     *     <li>Notifies the result of verification.</li>
+     * </ul>
+     *
+     * Note it is the responsibility of the caller to ensure the staging files remain unchanged
+     * while the verification is in progress.
+     */
+    void verifyStaged(StagingManager.StagedSession session, Callback callback) {
+        Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId());
+        mHandler.post(() -> {
+            try {
+                storeSession(session);
+                checkActiveSessions();
+                checkRollbacks(session);
+                if (session.isMultiPackage()) {
+                    for (StagingManager.StagedSession child : session.getChildSessions()) {
+                        checkOverlaps(session, child);
+                    }
+                } else {
+                    checkOverlaps(session, session);
+                }
+                dispatchVerifyApex(session, callback);
+            } catch (PackageManagerException e) {
+                onVerificationFailure(session, callback, e.error, e.getMessage());
+            }
+        });
+    }
+
+    /**
+     * Stores staged-sessions for checking package overlapping and rollback conflicts.
+     */
+    private void storeSession(StagingManager.StagedSession session) {
+        mStagedSessions.add(session);
+    }
+
+    private void onVerificationSuccess(StagingManager.StagedSession session, Callback callback) {
+        callback.onResult(SessionInfo.STAGED_SESSION_NO_ERROR, null);
+    }
+
+    private void onVerificationFailure(StagingManager.StagedSession session, Callback callback,
+            @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage) {
+        if (!ensureActiveApexSessionIsAborted(session)) {
+            Slog.e(TAG, "Failed to abort apex session " + session.sessionId());
+            // Safe to ignore active apex session abortion failure since session will be marked
+            // failed on next step and staging directory for session will be deleted.
+        }
+        session.setSessionFailed(errorCode, errorMessage);
+        callback.onResult(errorCode, errorMessage);
+    }
+
+    private void dispatchVerifyApex(StagingManager.StagedSession session, Callback callback) {
+        mHandler.post(() -> {
+            try {
+                verifyApex(session);
+                dispatchEndVerification(session, callback);
+            } catch (PackageManagerException e) {
+                onVerificationFailure(session, callback, e.error, e.getMessage());
+            }
+        });
+    }
+
+    private void dispatchEndVerification(StagingManager.StagedSession session, Callback callback) {
+        mHandler.post(() -> {
+            try {
+                endVerification(session);
+                onVerificationSuccess(session, callback);
+            } catch (PackageManagerException e) {
+                onVerificationFailure(session, callback, e.error, e.getMessage());
+            }
+        });
+    }
+
+    /**
+     * Pre-reboot verification phase for apex files:
+     *
+     * <p><ul>
+     *     <li>submits session to apex service</li>
+     *     <li>validates signatures of apex files</li>
+     * </ul></p>
+     */
+    private void verifyApex(StagingManager.StagedSession session) throws PackageManagerException {
+        int rollbackId = -1;
+        if ((session.sessionParams().installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
+            // If rollback is enabled for this session, we call through to the RollbackManager
+            // with the list of sessions it must enable rollback for. Note that
+            // notifyStagedSession is a synchronous operation.
+            final RollbackManagerInternal rm =
+                    LocalServices.getService(RollbackManagerInternal.class);
+            try {
+                // NOTE: To stay consistent with the non-staged install flow, we don't fail the
+                // entire install if rollbacks can't be enabled.
+                rollbackId = rm.notifyStagedSession(session.sessionId());
+            } catch (RuntimeException re) {
+                Slog.e(TAG, "Failed to notifyStagedSession for session: "
+                        + session.sessionId(), re);
+            }
+        } else if (isRollback(session)) {
+            rollbackId = retrieveRollbackIdForCommitSession(session.sessionId());
+        }
+
+        final boolean hasApex = session.containsApexSession();
+        // APEX checks. For single-package sessions, check if they contain an APEX. For
+        // multi-package sessions, find all the child sessions that contain an APEX.
+        if (hasApex) {
+            final List<PackageInfo> apexPackages = submitSessionToApexService(session, rollbackId);
+            for (int i = 0, size = apexPackages.size(); i < size; i++) {
+                validateApexSignature(apexPackages.get(i));
+            }
+            final PackageManagerInternal packageManagerInternal =
+                    LocalServices.getService(PackageManagerInternal.class);
+            packageManagerInternal.pruneCachedApksInApex(apexPackages);
+        }
+    }
+
+    /**
+     * Pre-reboot verification phase for wrapping up:
+     * <p><ul>
+     *     <li>enables checkpoint if supported</li>
+     *     <li>marks session as ready</li>
+     * </ul></p>
+     */
+    private void endVerification(StagingManager.StagedSession session)
+            throws PackageManagerException {
+        // Before marking the session as ready, start checkpoint service if available
+        try {
+            if (PackageHelper.getStorageManager().supportsCheckpoint()) {
+                PackageHelper.getStorageManager().startCheckpoint(2);
+            }
+        } catch (Exception e) {
+            // Failed to get hold of StorageManager
+            Slog.e(TAG, "Failed to get hold of StorageManager", e);
+            throw new PackageManagerException(
+                    SessionInfo.STAGED_SESSION_UNKNOWN,
+                    "Failed to get hold of StorageManager");
+        }
+        // Proactively mark session as ready before calling apexd. Although this call order
+        // looks counter-intuitive, this is the easiest way to ensure that session won't end up
+        // in the inconsistent state:
+        //  - If device gets rebooted right before call to apexd, then apexd will never activate
+        //      apex files of this staged session. This will result in StagingManager failing
+        //      the session.
+        // On the other hand, if the order of the calls was inverted (first call apexd, then
+        // mark session as ready), then if a device gets rebooted right after the call to apexd,
+        // only apex part of the train will be applied, leaving device in an inconsistent state.
+        Slog.d(TAG, "Marking session " + session.sessionId() + " as ready");
+        session.setSessionReady();
+        if (session.isSessionReady()) {
+            final boolean hasApex = session.containsApexSession();
+            if (hasApex) {
+                mApexManager.markStagedSessionReady(session.sessionId());
+            }
+        }
+    }
+
+    /**
+     * Validates the signature used to sign the container of the new apex package
+     *
+     * @param newApexPkg The new apex package that is being installed
+     */
+    private void validateApexSignature(PackageInfo newApexPkg) throws PackageManagerException {
+        // Get signing details of the new package
+        final String apexPath = newApexPkg.applicationInfo.sourceDir;
+        final String packageName = newApexPkg.packageName;
+        int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
+                newApexPkg.applicationInfo.targetSdkVersion);
+
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ParseResult<SigningDetails> newResult = ApkSignatureVerifier.verify(
+                input.reset(), apexPath, minSignatureScheme);
+        if (newResult.isError()) {
+            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Failed to parse APEX package " + apexPath + " : "
+                            + newResult.getException(), newResult.getException());
+        }
+        final SigningDetails newSigningDetails = newResult.getResult();
+
+        // Get signing details of the existing package
+        final PackageInfo existingApexPkg = mApexManager.getPackageInfo(packageName,
+                ApexManager.MATCH_ACTIVE_PACKAGE);
+        if (existingApexPkg == null) {
+            // This should never happen, because submitSessionToApexService ensures that no new
+            // apexes were installed.
+            throw new IllegalStateException("Unknown apex package " + packageName);
+        }
+
+        final ParseResult<SigningDetails> existingResult = ApkSignatureVerifier.verify(
+                input.reset(), existingApexPkg.applicationInfo.sourceDir,
+                SigningDetails.SignatureSchemeVersion.JAR);
+        if (existingResult.isError()) {
+            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir
+                            + " : " + existingResult.getException(), existingResult.getException());
+        }
+        final SigningDetails existingSigningDetails = existingResult.getResult();
+
+        // Verify signing details for upgrade
+        if (newSigningDetails.checkCapability(existingSigningDetails,
+                SigningDetails.CertCapabilities.INSTALLED_DATA)
+                || existingSigningDetails.checkCapability(newSigningDetails,
+                SigningDetails.CertCapabilities.ROLLBACK)) {
+            return;
+        }
+
+        throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                "APK-container signature of APEX package " + packageName + " with version "
+                        + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
+                        + " compatible with the one currently installed on device");
+    }
+
+    private List<PackageInfo> submitSessionToApexService(StagingManager.StagedSession session,
+            int rollbackId) throws PackageManagerException {
+        final IntArray childSessionIds = new IntArray();
+        if (session.isMultiPackage()) {
+            for (StagingManager.StagedSession s : session.getChildSessions()) {
+                if (s.isApexSession()) {
+                    childSessionIds.add(s.sessionId());
+                }
+            }
+        }
+        ApexSessionParams apexSessionParams = new ApexSessionParams();
+        apexSessionParams.sessionId = session.sessionId();
+        apexSessionParams.childSessionIds = childSessionIds.toArray();
+        if (session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
+            apexSessionParams.isRollback = true;
+            apexSessionParams.rollbackId = rollbackId;
+        } else {
+            if (rollbackId != -1) {
+                apexSessionParams.hasRollbackEnabled = true;
+                apexSessionParams.rollbackId = rollbackId;
+            }
+        }
+        // submitStagedSession will throw a PackageManagerException if apexd verification fails,
+        // which will be propagated to populate stagedSessionErrorMessage of this session.
+        final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
+        final List<PackageInfo> result = new ArrayList<>();
+        final List<String> apexPackageNames = new ArrayList<>();
+        for (ApexInfo apexInfo : apexInfoList.apexInfos) {
+            final PackageInfo packageInfo;
+            final int flags = PackageManager.GET_META_DATA;
+            try (PackageParser2 packageParser = mPackageParserSupplier.get()) {
+                File apexFile = new File(apexInfo.modulePath);
+                final ParsedPackage parsedPackage = packageParser.parsePackage(
+                        apexFile, flags, false);
+                packageInfo = PackageInfoWithoutStateUtils.generate(parsedPackage, apexInfo, flags);
+                if (packageInfo == null) {
+                    throw new PackageManagerException(
+                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                            "Unable to generate package info: " + apexInfo.modulePath);
+                }
+            } catch (PackageManagerException e) {
+                throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                        "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
+            }
+            result.add(packageInfo);
+            apexPackageNames.add(packageInfo.packageName);
+        }
+        Slog.d(TAG, "Session " + session.sessionId() + " has following APEX packages: "
+                + apexPackageNames);
+        return result;
+    }
+
+    private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
+        RollbackManager rm = mContext.getSystemService(RollbackManager.class);
+
+        final List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks();
+        for (int i = 0, size = rollbacks.size(); i < size; i++) {
+            final RollbackInfo rollback = rollbacks.get(i);
+            if (rollback.getCommittedSessionId() == sessionId) {
+                return rollback.getRollbackId();
+            }
+        }
+        throw new PackageManagerException(
+                SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                "Could not find rollback id for commit session: " + sessionId);
+    }
+
+    private static boolean isRollback(StagingManager.StagedSession session) {
+        return session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK;
+    }
+
+    private static boolean isApexSessionFinalized(ApexSessionInfo info) {
+        /* checking if the session is in a final state, i.e., not active anymore */
+        return info.isUnknown || info.isActivationFailed || info.isSuccess
+                || info.isReverted;
+    }
+
+    private boolean ensureActiveApexSessionIsAborted(StagingManager.StagedSession session) {
+        if (!session.containsApexSession()) {
+            return true;
+        }
+        final int sessionId = session.sessionId();
+        final ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(sessionId);
+        if (apexSession == null || isApexSessionFinalized(apexSession)) {
+            return true;
+        }
+        return mApexManager.abortStagedSession(sessionId);
+    }
+
+    /**
+     * Checks if multiple staged-sessions are supported. It is supported only when the system
+     * supports checkpoint.
+     */
+    private void checkActiveSessions() throws PackageManagerException {
+        final boolean supportsCheckpoint;
+        try {
+            supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
+        } catch (RemoteException e) {
+            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Can't query fs-checkpoint status : " + e);
+        }
+        int activeSessions = 0;
+        for (StagingManager.StagedSession stagedSession : mStagedSessions) {
+            if (stagedSession.isDestroyed() || stagedSession.isInTerminalState()) {
+                continue;
+            }
+            ++activeSessions;
+        }
+        if (!supportsCheckpoint && activeSessions > 1) {
+            throw new PackageManagerException(
+                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Cannot stage multiple sessions without checkpoint support");
+        }
+    }
+
+    /**
+     * Fails non-rollback sessions if any rollback session exists. A rollback session might cause
+     * downgrade of SDK extension which in turn will result in dependency violation of other
+     * non-rollback sessions.
+     */
+    private void checkRollbacks(StagingManager.StagedSession session)
+            throws PackageManagerException {
+        for (StagingManager.StagedSession stagedSession : mStagedSessions) {
+            if (stagedSession.isDestroyed() || stagedSession.isInTerminalState()) {
+                continue;
+            }
+            if (isRollback(session) && !isRollback(stagedSession)) {
+                // If the new session is a rollback, then it gets priority. The existing
+                // session is failed to reduce risk and avoid an SDK extension dependency
+                // violation.
+                if (!ensureActiveApexSessionIsAborted(stagedSession)) {
+                    Slog.e(TAG, "Failed to abort apex session " + stagedSession.sessionId());
+                    // Safe to ignore active apex session abort failure since session
+                    // will be marked failed on next step and staging directory for session
+                    // will be deleted.
+                }
+                stagedSession.setSessionFailed(
+                        SessionInfo.STAGED_SESSION_CONFLICT,
+                        "Session was failed by rollback session: " + session.sessionId());
+                Slog.i(TAG, "Session " + stagedSession.sessionId() + " is marked failed due to "
+                        + "rollback session: " + session.sessionId());
+            } else if (!isRollback(session) && isRollback(stagedSession)) {
+                throw new PackageManagerException(
+                        SessionInfo.STAGED_SESSION_CONFLICT,
+                        "Session was failed by rollback session: " + stagedSession.sessionId());
+
+            }
+        }
+    }
+
+    /**
+     * Fails the session that is committed later when overlapping packages are detected.
+     *
+     * @param parent The parent session.
+     * @param child The child session whose package name will be checked.
+     *              This will equal to {@code parent} for a single-package session.
+     */
+    private void checkOverlaps(StagingManager.StagedSession parent,
+            StagingManager.StagedSession child) throws PackageManagerException {
+        final String packageName = child.getPackageName();
+        if (packageName == null) {
+            throw new PackageManagerException(
+                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Cannot stage session " + child.sessionId() + " with package name null");
+        }
+        for (StagingManager.StagedSession stagedSession : mStagedSessions) {
+            if (stagedSession.isDestroyed() || stagedSession.isInTerminalState()
+                    || stagedSession.sessionId() == parent.sessionId()) {
+                continue;
+            }
+            if (stagedSession.sessionContains(s -> packageName.equals(s.getPackageName()))) {
+                if (stagedSession.getCommittedMillis() < parent.getCommittedMillis()) {
+                    // Fail the session committed later when there are overlapping packages
+                    throw new PackageManagerException(
+                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                            "Package: " + packageName + " in session: "
+                                    + child.sessionId()
+                                    + " has been staged already by session: "
+                                    + stagedSession.sessionId());
+                } else {
+                    stagedSession.setSessionFailed(
+                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                            "Package: " + packageName + " in session: "
+                                    + stagedSession.sessionId()
+                                    + " has been staged already by session: "
+                                    + child.sessionId());
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 1a5415c..b8e354e 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -25,7 +25,6 @@
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IncrementalStatesInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
@@ -36,7 +35,6 @@
 import android.content.pm.pkg.PackageUserState;
 import android.content.pm.pkg.PackageUserStateInternal;
 import android.os.PersistableBundle;
-import android.os.incremental.IncrementalManager;
 import android.service.pm.PackageProto;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -66,8 +64,8 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.function.Predicate;
 import java.util.UUID;
+import java.util.function.Predicate;
 
 /**
  * Settings data for a particular package we know about.
@@ -103,9 +101,6 @@
     @Nullable
     Set<String> mOldCodePaths;
 
-    @NonNull
-    private IncrementalStates incrementalStates;
-
     @Nullable
     String[] usesStaticLibraries;
 
@@ -153,6 +148,8 @@
     @NonNull
     private String mPathString;
 
+    private float mLoadingProgress;
+
     @Nullable
     private String mPrimaryCpuAbi;
 
@@ -183,6 +180,7 @@
     // TODO: Access is not locked.
     // Whether this package is currently stopped, thus can not be
     // started until explicitly launched by the user.
+    @NonNull
     private final SparseArray<PackageUserStateInternalImpl> mUserState = new SparseArray<>();
 
     @NonNull
@@ -242,7 +240,6 @@
         this.versionCode = longVersionCode;
         this.signatures = new PackageSignatures();
         this.installSource = InstallSource.EMPTY;
-        this.incrementalStates = new IncrementalStates();
         this.sharedUserId = sharedUserId;
         mDomainSetId = domainSetId;
         copyMimeGroups(mimeGroups);
@@ -614,7 +611,7 @@
         super.copySettingBase(other);
         sharedUserId = other.sharedUserId;
         mimeGroups = other.mimeGroups;
-        incrementalStates = other.incrementalStates;
+        mLoadingProgress = other.mLoadingProgress;
         legacyNativeLibraryPath = other.legacyNativeLibraryPath;
         mName = other.mName;
         mRealName = other.mRealName;
@@ -708,6 +705,7 @@
 
     void setInstalled(boolean inst, int userId) {
         modifyUserState(userId).setInstalled(inst);
+        onChanged();
     }
 
     boolean getInstalled(int userId) {
@@ -720,6 +718,7 @@
 
     void setInstallReason(int installReason, int userId) {
         modifyUserState(userId).setInstallReason(installReason);
+        onChanged();
     }
 
     int getUninstallReason(int userId) {
@@ -728,10 +727,15 @@
 
     void setUninstallReason(@PackageManager.UninstallReason int uninstallReason, int userId) {
         modifyUserState(userId).setUninstallReason(uninstallReason);
+        onChanged();
     }
 
     boolean setOverlayPaths(OverlayPaths overlayPaths, int userId) {
-        return modifyUserState(userId).setOverlayPaths(overlayPaths);
+        boolean changed = modifyUserState(userId).setOverlayPaths(overlayPaths);
+        if (changed) {
+            onChanged();
+        }
+        return changed;
     }
 
     @NonNull
@@ -740,7 +744,10 @@
     }
 
     boolean setOverlayPathsForLibrary(String libName, OverlayPaths overlayPaths, int userId) {
-        return modifyUserState(userId).setSharedLibraryOverlayPaths(libName, overlayPaths);
+        boolean changed = modifyUserState(userId)
+                .setSharedLibraryOverlayPaths(libName, overlayPaths);
+        onChanged();
+        return changed;
     }
 
     @NonNull
@@ -787,6 +794,7 @@
 
     void setCeDataInode(long ceDataInode, int userId) {
         modifyUserState(userId).setCeDataInode(ceDataInode);
+        onChanged();
     }
 
     boolean getStopped(int userId) {
@@ -795,6 +803,7 @@
 
     void setStopped(boolean stop, int userId) {
         modifyUserState(userId).setStopped(stop);
+        onChanged();
     }
 
     boolean getNotLaunched(int userId) {
@@ -803,6 +812,7 @@
 
     void setNotLaunched(boolean stop, int userId) {
         modifyUserState(userId).setNotLaunched(stop);
+        onChanged();
     }
 
     boolean getHidden(int userId) {
@@ -811,6 +821,7 @@
 
     void setHidden(boolean hidden, int userId) {
         modifyUserState(userId).setHidden(hidden);
+        onChanged();
     }
 
     int getDistractionFlags(int userId) {
@@ -819,6 +830,7 @@
 
     void setDistractionFlags(int distractionFlags, int userId) {
         modifyUserState(userId).setDistractionFlags(distractionFlags);
+        onChanged();
     }
 
     boolean getSuspended(int userId) {
@@ -886,6 +898,7 @@
 
     void setInstantApp(boolean instantApp, int userId) {
         modifyUserState(userId).setInstantApp(instantApp);
+        onChanged();
     }
 
     boolean getVirtualPreload(int userId) {
@@ -894,6 +907,7 @@
 
     void setVirtualPreload(boolean virtualPreload, int userId) {
         modifyUserState(userId).setVirtualPreload(virtualPreload);
+        onChanged();
     }
 
     void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
@@ -948,20 +962,24 @@
 
     void setEnabledComponents(ArraySet<String> components, int userId) {
         modifyUserState(userId).setEnabledComponents(components);
+        onChanged();
     }
 
     void setDisabledComponents(ArraySet<String> components, int userId) {
         modifyUserState(userId).setDisabledComponents(components);
+        onChanged();
     }
 
     void setEnabledComponentsCopy(ArraySet<String> components, int userId) {
         modifyUserState(userId).setEnabledComponents(components != null
                 ? new ArraySet<String>(components) : null);
+        onChanged();
     }
 
     void setDisabledComponentsCopy(ArraySet<String> components, int userId) {
         modifyUserState(userId).setDisabledComponents(components != null
                 ? new ArraySet<String>(components) : null);
+        onChanged();
     }
 
     PackageUserStateInternalImpl modifyUserStateComponents(int userId, boolean disabled,
@@ -985,11 +1003,13 @@
     void addDisabledComponent(String componentClassName, int userId) {
         modifyUserStateComponents(userId, true, false)
                 .getDisabledComponentsNoCopy().add(componentClassName);
+        onChanged();
     }
 
     void addEnabledComponent(String componentClassName, int userId) {
         modifyUserStateComponents(userId, false, true)
                 .getEnabledComponentsNoCopy().add(componentClassName);
+        onChanged();
     }
 
     boolean enableComponentLPw(String componentClassName, int userId) {
@@ -997,6 +1017,9 @@
         boolean changed = state.getDisabledComponentsNoCopy() != null
                 ? state.getDisabledComponentsNoCopy().remove(componentClassName) : false;
         changed |= state.getEnabledComponentsNoCopy().add(componentClassName);
+        if (changed) {
+            onChanged();
+        }
         return changed;
     }
 
@@ -1005,6 +1028,9 @@
         boolean changed = state.getEnabledComponentsNoCopy() != null
                 ? state.getEnabledComponentsNoCopy().remove(componentClassName) : false;
         changed |= state.getDisabledComponentsNoCopy().add(componentClassName);
+        if (changed) {
+            onChanged();
+        }
         return changed;
     }
 
@@ -1014,6 +1040,9 @@
                 ? state.getDisabledComponentsNoCopy().remove(componentClassName) : false;
         changed |= state.getEnabledComponentsNoCopy() != null
                 ? state.getEnabledComponentsNoCopy().remove(componentClassName) : false;
+        if (changed) {
+            onChanged();
+        }
         return changed;
     }
 
@@ -1131,6 +1160,7 @@
     PackageSetting setPath(@NonNull File path) {
         this.mPath = path;
         this.mPathString = path.toString();
+        onChanged();
         return this;
     }
 
@@ -1147,7 +1177,9 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public boolean overrideNonLocalizedLabelAndIcon(@NonNull ComponentName component,
             @Nullable String label, @Nullable Integer icon, @UserIdInt int userId) {
-        return modifyUserState(userId).overrideLabelAndIcon(component, label, icon);
+        boolean changed = modifyUserState(userId).overrideLabelAndIcon(component, label, icon);
+        onChanged();
+        return changed;
     }
 
     /**
@@ -1157,6 +1189,7 @@
      */
     public void resetOverrideComponentLabelIcon(@UserIdInt int userId) {
         modifyUserState(userId).resetOverrideComponentLabelIcon();
+        onChanged();
     }
 
     /**
@@ -1184,39 +1217,13 @@
      * @return True if package is still being loaded, false if the package is fully loaded.
      */
     public boolean isPackageLoading() {
-        return incrementalStates.getIncrementalStatesInfo().isLoading();
+        return Math.abs(1.0f - mLoadingProgress) >= 0.00000001f;
     }
 
-    /**
-     * @return all current states in a Parcelable.
-     */
-    public IncrementalStatesInfo getIncrementalStatesInfo() {
-        return incrementalStates.getIncrementalStatesInfo();
-    }
-
-    /**
-     * Called to indicate that the package installation has been committed. This will create a
-     * new startable state and a new loading state with default values. By default, the package is
-     * startable after commit. For a package installed on Incremental, the loading state is true.
-     * For non-Incremental packages, the loading state is false.
-     */
-    public void setStatesOnCommit() {
-        incrementalStates.onCommit(IncrementalManager.isIncrementalPath(getPathString()));
-    }
-
-    /**
-     * Called to set the callback to listen for startable state changes.
-     */
-    public void setIncrementalStatesCallback(IncrementalStates.Callback callback) {
-        incrementalStates.setCallback(callback);
-    }
-
-    /**
-     * Called to report progress changes. This might trigger loading state change.
-     * @see IncrementalStates#setProgress(float)
-     */
-    public void setLoadingProgress(float progress) {
-        incrementalStates.setProgress(progress);
+    public PackageSetting setLoadingProgress(float progress) {
+        mLoadingProgress = progress;
+        onChanged();
+        return this;
     }
 
     @NonNull
@@ -1342,13 +1349,6 @@
         return this;
     }
 
-    public PackageSetting setIncrementalStates(
-            IncrementalStates incrementalStates) {
-        this.incrementalStates = incrementalStates;
-        onChanged();
-        return this;
-    }
-
 
 
     // Code below generated by codegen v1.0.23.
@@ -1377,11 +1377,6 @@
         return mOldCodePaths;
     }
 
-    @DataClass.Generated.Member
-    public @NonNull IncrementalStates getIncrementalStates() {
-        return incrementalStates;
-    }
-
     /**
      * The path under which native libraries have been unpacked. This path is
      * always derived at runtime, and is only stored here for cleanup when a
@@ -1438,6 +1433,11 @@
     }
 
     @DataClass.Generated.Member
+    public float getLoadingProgress() {
+        return mLoadingProgress;
+    }
+
+    @DataClass.Generated.Member
     public @Nullable String getPrimaryCpuAbi() {
         return mPrimaryCpuAbi;
     }
@@ -1537,10 +1537,10 @@
     }
 
     @DataClass.Generated(
-            time = 1628017546382L,
+            time = 1635295317317L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "static final  android.content.pm.PackageUserState DEFAULT_USER_STATE\nprotected  int sharedUserId\n @android.annotation.Nullable java.util.Map<java.lang.String,android.util.ArraySet<java.lang.String>> mimeGroups\n @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.NonNull com.android.server.pm.IncrementalStates incrementalStates\n @android.annotation.Nullable java.lang.String[] usesStaticLibraries\n @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackage pkg\nprivate @android.annotation.Nullable com.android.server.pm.SharedUserSetting sharedUser\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long firstInstallTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<android.content.pm.PackageUserState> mUserState\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  java.util.List<java.lang.String> getMimeGroup(java.lang.String)\nprivate  android.util.ArraySet<java.lang.String> getMimeGroupInternal(java.lang.String)\npublic  boolean isMatch(int)\npublic  boolean isSharedUser()\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,java.util.List<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.parsing.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  int getSharedUserIdInt()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,android.util.ArraySet<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyFrom(com.android.server.pm.PackageSetting)\nprivate  void doCopy(com.android.server.pm.PackageSetting)\n @com.android.internal.annotations.VisibleForTesting android.content.pm.PackageUserState modifyUserState(int)\npublic @android.annotation.NonNull android.content.pm.PackageUserState readUserState(int)\npublic @android.annotation.Nullable android.content.pm.PackageUserState readUserStateNullable(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  java.lang.String getLastDisabledAppCaller(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n  boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getOverlayPathsForLibrary(int)\n @com.android.internal.annotations.VisibleForTesting android.util.SparseArray<android.content.pm.PackageUserState> getUserState()\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\n  boolean getSuspended(int)\n  boolean isSuspendedBy(java.lang.String,int)\n  void addOrUpdateSuspension(java.lang.String,android.content.pm.SuspendDialogInfo,android.os.PersistableBundle,android.os.PersistableBundle,int)\n  void removeSuspension(java.lang.String,int)\n  void removeSuspension(java.util.function.Predicate<java.lang.String>,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,boolean,android.util.ArrayMap<java.lang.String,android.content.pm.PackageUserState.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String)\n  void setUserState(int,android.content.pm.PackageUserState)\n  android.util.ArraySet<java.lang.String> getEnabledComponents(int)\n  android.util.ArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(android.util.ArraySet<java.lang.String>,int)\n  void setDisabledComponents(android.util.ArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n  android.content.pm.PackageUserState modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  void setHarmfulAppWarning(int,java.lang.String)\n  java.lang.String getHarmfulAppWarning(int)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\n  java.lang.String getPathString()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic  void setSplashScreenTheme(int,java.lang.String)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isPackageLoading()\npublic  android.content.pm.IncrementalStatesInfo getIncrementalStatesInfo()\npublic  void setStatesOnCommit()\npublic  void setIncrementalStatesCallback(com.android.server.pm.IncrementalStates.Callback)\npublic  void setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getLongVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackageApi getAndroidPackage()\npublic @android.annotation.Nullable @java.lang.Override java.lang.Integer getSharedUserId()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.SharedLibraryInfo> getUsesLibraryInfos()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfoOverride()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Deprecated @java.lang.Override int[] getUserIds()\npublic @java.lang.Override android.content.pm.PackageUserState getUserState(int)\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setSharedUser(com.android.server.pm.SharedUserSetting)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIncrementalStates(com.android.server.pm.IncrementalStates)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageState]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "protected  int sharedUserId\n @android.annotation.Nullable java.util.Map<java.lang.String,android.util.ArraySet<java.lang.String>> mimeGroups\n @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\n @android.annotation.Nullable java.lang.String[] usesStaticLibraries\n @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackage pkg\nprivate @android.annotation.Nullable com.android.server.pm.SharedUserSetting sharedUser\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float loadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long firstInstallTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateInternalImpl> mUserState\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  java.util.List<java.lang.String> getMimeGroup(java.lang.String)\nprivate  android.util.ArraySet<java.lang.String> getMimeGroupInternal(java.lang.String)\npublic  boolean isMatch(int)\npublic  boolean isSharedUser()\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,java.util.List<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.parsing.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  int getSharedUserIdInt()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,android.util.ArraySet<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateInternalImpl modifyUserState(int)\npublic @android.annotation.NonNull android.content.pm.pkg.PackageUserStateInternal readUserState(int)\npublic @android.annotation.Nullable android.content.pm.pkg.PackageUserState readUserStateNullable(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  java.lang.String getLastDisabledAppCaller(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n  boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getOverlayPathsForLibrary(int)\n @com.android.internal.annotations.VisibleForTesting android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateInternalImpl> getUserState()\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\n  boolean getSuspended(int)\n  boolean isSuspendedBy(java.lang.String,int)\n  boolean addOrUpdateSuspension(java.lang.String,android.content.pm.SuspendDialogInfo,android.os.PersistableBundle,android.os.PersistableBundle,int)\n  boolean removeSuspension(java.lang.String,int)\n  void removeSuspension(java.util.function.Predicate<java.lang.String>,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,boolean,android.util.ArrayMap<java.lang.String,android.content.pm.pkg.PackageUserState.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String)\n  void setUserState(int,android.content.pm.pkg.PackageUserStateInternal)\n  android.util.ArraySet<java.lang.String> getEnabledComponents(int)\n  android.util.ArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(android.util.ArraySet<java.lang.String>,int)\n  void setDisabledComponents(android.util.ArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateInternalImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  void setHarmfulAppWarning(int,java.lang.String)\n  java.lang.String getHarmfulAppWarning(int)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\n  java.lang.String getPathString()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic  void setSplashScreenTheme(int,java.lang.String)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isPackageLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getLongVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackageApi getAndroidPackage()\npublic @android.annotation.Nullable @java.lang.Override java.lang.Integer getSharedUserId()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.SharedLibraryInfo> getUsesLibraryInfos()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfoOverride()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Deprecated @java.lang.Override int[] getUserIds()\npublic @java.lang.Override android.content.pm.pkg.PackageUserState getUserState(int)\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setSharedUser(com.android.server.pm.SharedUserSetting)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageState]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java
index 8018011..731c6ca 100644
--- a/services/core/java/com/android/server/pm/ScanPackageHelper.java
+++ b/services/core/java/com/android/server/pm/ScanPackageHelper.java
@@ -1018,9 +1018,6 @@
         if (mPm.mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) {
             if (pkgSetting != null && pkgSetting.isPackageLoading()) {
                 // Continue monitoring loading progress of active incremental packages
-                final IncrementalStatesCallback incrementalStatesCallback =
-                        new IncrementalStatesCallback(parsedPackage.getPackageName(), mPm);
-                pkgSetting.setIncrementalStatesCallback(incrementalStatesCallback);
                 mPm.mIncrementalManager.registerLoadingProgressCallback(parsedPackage.getPath(),
                         new IncrementalProgressListener(parsedPackage.getPackageName(), mPm));
             }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f89d9eb..6df1006 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -73,7 +73,6 @@
 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;
@@ -1087,9 +1086,6 @@
                 pkgSetting.setLegacyNativeLibraryPath(legacyNativeLibraryPath);
             }
             pkgSetting.setPath(codePath);
-            if (IncrementalManager.isIncrementalPath(codePath.getAbsolutePath())) {
-                pkgSetting.setIncrementalStates(new IncrementalStates());
-            }
         }
         // If what we are scanning is a system (and possibly privileged) package,
         // then make it so, regardless of whether it was previously installed only
@@ -2708,8 +2704,7 @@
         } else {
             serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
         }
-        serializer.attributeFloat(null, "loadingProgress",
-                pkg.getIncrementalStates().getIncrementalStatesInfo().getProgress());
+        serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
 
         writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
 
@@ -2784,8 +2779,7 @@
         if (pkg.isPackageLoading()) {
             serializer.attributeBoolean(null, "isLoading", true);
         }
-        serializer.attributeFloat(null, "loadingProgress",
-                pkg.getIncrementalStates().getIncrementalStatesInfo().getProgress());
+        serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
 
         serializer.attribute(null, "domainSetId", pkg.getDomainSetId().toString());
 
@@ -3531,7 +3525,6 @@
         PackageSetting packageSetting = null;
         long versionCode = 0;
         boolean installedForceQueryable = false;
-        boolean isLoading = false;
         float loadingProgress = 0;
         UUID domainSetId;
         try {
@@ -3549,7 +3542,6 @@
             cpuAbiOverrideString = parser.getAttributeValue(null, "cpuAbiOverride");
             updateAvailable = parser.getAttributeBoolean(null, "updateAvailable", false);
             installedForceQueryable = parser.getAttributeBoolean(null, "forceQueryable", false);
-            isLoading = parser.getAttributeBoolean(null, "isLoading", false);
             loadingProgress = parser.getAttributeFloat(null, "loadingProgress", 0);
 
             if (primaryCpuAbiString == null && legacyCpuAbiString != null) {
@@ -3706,7 +3698,7 @@
                     .setSecondaryCpuAbi(secondaryCpuAbiString)
                     .setUpdateAvailable(updateAvailable)
                     .setForceQueryableOverride(installedForceQueryable)
-                    .setIncrementalStates(new IncrementalStates(isLoading, loadingProgress));
+                    .setLoadingProgress(loadingProgress);
             // Handle legacy string here for single-user mode
             final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
             if (enabledStr != null) {
@@ -4681,8 +4673,7 @@
         }
         if (ps.isPackageLoading()) {
             pw.print(prefix); pw.println("  loadingProgress=" +
-                    (int) (ps.getIncrementalStates().getIncrementalStatesInfo().getProgress()
-                            * 100) + "%");
+                    (int) (ps.getLoadingProgress() * 100) + "%");
         }
         if (ps.getVolumeUuid() != null) {
             pw.print(prefix); pw.print("  volumeUuid=");
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index a2f48c7..c83cdb4 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.apex.ApexInfo;
-import android.apex.ApexInfoList;
 import android.apex.ApexSessionInfo;
 import android.apex.ApexSessionParams;
 import android.content.BroadcastReceiver;
@@ -31,25 +30,14 @@
 import android.content.IntentSender;
 import android.content.pm.ApexStagedEvent;
 import android.content.pm.IStagedApexObserver;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.SigningDetails;
-import android.content.pm.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.StagedApexInfo;
-import android.content.pm.parsing.PackageInfoWithoutStateUtils;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
-import android.content.rollback.RollbackInfo;
-import android.content.rollback.RollbackManager;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -62,7 +50,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimingsTraceLog;
-import android.util.apk.ApkSignatureVerifier;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -72,10 +59,8 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.rollback.RollbackManagerInternal;
 import com.android.server.rollback.WatchdogRollbackLogger;
 
@@ -93,7 +78,6 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
-import java.util.function.Supplier;
 
 /**
  * This class handles staged install sessions, i.e. install sessions that require packages to
@@ -106,9 +90,6 @@
     private final ApexManager mApexManager;
     private final PowerManager mPowerManager;
     private final Context mContext;
-    @VisibleForTesting
-    final PreRebootVerificationHandler mPreRebootVerificationHandler;
-    private final Supplier<PackageParser2> mPackageParserSupplier;
 
     private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt");
     private String mFailureReason;
@@ -152,28 +133,19 @@
         boolean hasParentSessionId();
         long getCommittedMillis();
         void abandon();
-        void notifyEndPreRebootVerification();
         void verifySession();
     }
 
-    StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier, Looper looper) {
-        this(context, packageParserSupplier, ApexManager.getInstance(), looper);
+    StagingManager(Context context) {
+        this(context, ApexManager.getInstance());
     }
 
     @VisibleForTesting
-    StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier,
-            ApexManager apexManager) {
-        this(context, packageParserSupplier, apexManager, BackgroundThread.get().getLooper());
-    }
-
-    StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier,
-            ApexManager apexManager, Looper looper) {
+    StagingManager(Context context, ApexManager apexManager) {
         mContext = context;
-        mPackageParserSupplier = packageParserSupplier;
 
         mApexManager = apexManager;
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mPreRebootVerificationHandler = new PreRebootVerificationHandler(looper);
 
         if (mFailureReasonFile.exists()) {
             try (BufferedReader reader = new BufferedReader(new FileReader(mFailureReasonFile))) {
@@ -244,128 +216,6 @@
         }
     }
 
-    /**
-     * Validates the signature used to sign the container of the new apex package
-     *
-     * @param newApexPkg The new apex package that is being installed
-     */
-    private void validateApexSignature(PackageInfo newApexPkg)
-            throws PackageManagerException {
-        // Get signing details of the new package
-        final String apexPath = newApexPkg.applicationInfo.sourceDir;
-        final String packageName = newApexPkg.packageName;
-        int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
-                newApexPkg.applicationInfo.targetSdkVersion);
-
-        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-        final ParseResult<SigningDetails> newResult = ApkSignatureVerifier.verify(
-                input.reset(), apexPath, minSignatureScheme);
-        if (newResult.isError()) {
-            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                    "Failed to parse APEX package " + apexPath + " : "
-                            + newResult.getException(), newResult.getException());
-        }
-        final SigningDetails newSigningDetails = newResult.getResult();
-
-        // Get signing details of the existing package
-        final PackageInfo existingApexPkg = mApexManager.getPackageInfo(packageName,
-                ApexManager.MATCH_ACTIVE_PACKAGE);
-        if (existingApexPkg == null) {
-            // This should never happen, because submitSessionToApexService ensures that no new
-            // apexes were installed.
-            throw new IllegalStateException("Unknown apex package " + packageName);
-        }
-
-        final ParseResult<SigningDetails> existingResult = ApkSignatureVerifier.verify(
-                input.reset(), existingApexPkg.applicationInfo.sourceDir,
-                SignatureSchemeVersion.JAR);
-        if (existingResult.isError()) {
-            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                    "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir
-                            + " : " + existingResult.getException(), existingResult.getException());
-        }
-        final SigningDetails existingSigningDetails = existingResult.getResult();
-
-        // Verify signing details for upgrade
-        if (newSigningDetails.checkCapability(existingSigningDetails,
-                SigningDetails.CertCapabilities.INSTALLED_DATA)
-                || existingSigningDetails.checkCapability(newSigningDetails,
-                SigningDetails.CertCapabilities.ROLLBACK)) {
-            return;
-        }
-
-        throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                "APK-container signature of APEX package " + packageName + " with version "
-                        + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
-                        + " compatible with the one currently installed on device");
-    }
-
-    private List<PackageInfo> submitSessionToApexService(@NonNull StagedSession session,
-            int rollbackId) throws PackageManagerException {
-        final IntArray childSessionIds = new IntArray();
-        if (session.isMultiPackage()) {
-            for (StagedSession s : session.getChildSessions()) {
-                if (s.isApexSession()) {
-                    childSessionIds.add(s.sessionId());
-                }
-            }
-        }
-        ApexSessionParams apexSessionParams = new ApexSessionParams();
-        apexSessionParams.sessionId = session.sessionId();
-        apexSessionParams.childSessionIds = childSessionIds.toArray();
-        if (session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
-            apexSessionParams.isRollback = true;
-            apexSessionParams.rollbackId = rollbackId;
-        } else {
-            if (rollbackId != -1) {
-                apexSessionParams.hasRollbackEnabled = true;
-                apexSessionParams.rollbackId = rollbackId;
-            }
-        }
-        // submitStagedSession will throw a PackageManagerException if apexd verification fails,
-        // which will be propagated to populate stagedSessionErrorMessage of this session.
-        final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
-        final List<PackageInfo> result = new ArrayList<>();
-        final List<String> apexPackageNames = new ArrayList<>();
-        for (ApexInfo apexInfo : apexInfoList.apexInfos) {
-            final PackageInfo packageInfo;
-            final int flags = PackageManager.GET_META_DATA;
-            try (PackageParser2 packageParser = mPackageParserSupplier.get()) {
-                File apexFile = new File(apexInfo.modulePath);
-                final ParsedPackage parsedPackage = packageParser.parsePackage(
-                        apexFile, flags, false);
-                packageInfo = PackageInfoWithoutStateUtils.generate(parsedPackage, apexInfo, flags);
-                if (packageInfo == null) {
-                    throw new PackageManagerException(
-                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                            "Unable to generate package info: " + apexInfo.modulePath);
-                }
-            } catch (PackageManagerException e) {
-                throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                        "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
-            }
-            result.add(packageInfo);
-            apexPackageNames.add(packageInfo.packageName);
-        }
-        Slog.d(TAG, "Session " + session.sessionId() + " has following APEX packages: "
-                + apexPackageNames);
-        return result;
-    }
-
-    private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
-        RollbackManager rm = mContext.getSystemService(RollbackManager.class);
-
-        final List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks();
-        for (int i = 0, size = rollbacks.size(); i < size; i++) {
-            final RollbackInfo rollback = rollbacks.get(i);
-            if (rollback.getCommittedSessionId() == sessionId) {
-                return rollback.getRollbackId();
-            }
-        }
-        throw new PackageManagerException(
-                "Could not find rollback id for commit session: " + sessionId);
-    }
-
     // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
     private void abortCheckpoint(String failureReason, boolean supportsCheckpoint,
             boolean needsCheckpoint) {
@@ -663,129 +513,15 @@
         }
     }
 
-    void commitSession(@NonNull StagedSession session) {
-        // Store this parent session which will be used to check overlapping later
-        createSession(session);
-        mPreRebootVerificationHandler.startPreRebootVerification(session);
-    }
-
-    private int getSessionIdForParentOrSelf(StagedSession session) {
-        return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId();
-    }
-
-    private StagedSession getParentSessionOrSelf(StagedSession session) {
-        return session.hasParentSessionId()
-                ? getStagedSession(session.getParentSessionId())
-                : session;
-    }
-
-    private boolean isRollback(StagedSession session) {
-        final StagedSession root = getParentSessionOrSelf(session);
-        return root.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK;
-    }
-
-    /**
-     * <p> Check if the session provided is non-overlapping with the active staged sessions.
-     *
-     * <p> A session is non-overlapping if it meets one of the following conditions: </p>
-     * <ul>
-     *     <li>It is a parent session</li>
-     *     <li>It is already one of the active sessions</li>
-     *     <li>Its package name is not same as any of the active sessions</li>
-     * </ul>
-     * @throws PackageManagerException if session fails the check
-     */
-    // TODO(b/192625695): Rename this method which checks rollbacks in addition to overlapping
     @VisibleForTesting
-    void checkNonOverlappingWithStagedSessions(@NonNull StagedSession session)
-            throws PackageManagerException {
-        if (session.isMultiPackage()) {
-            // We cannot say a parent session overlaps until we process its children
-            return;
-        }
+    void commitSession(@NonNull StagedSession session) {
+        createSession(session);
+        handleCommittedSession(session);
+    }
 
-        String packageName = session.getPackageName();
-        if (packageName == null) {
-            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                    "Cannot stage session " + session.sessionId() + " with package name null");
-        }
-
-        boolean supportsCheckpoint;
-        try {
-            supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
-        } catch (RemoteException e) {
-            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                    "Can't query fs-checkpoint status : " + e);
-        }
-
-        final boolean isRollback = isRollback(session);
-
-        synchronized (mStagedSessions) {
-            for (int i = 0; i < mStagedSessions.size(); i++) {
-                final StagedSession stagedSession = mStagedSessions.valueAt(i);
-                if (stagedSession.hasParentSessionId() || !stagedSession.isCommitted()
-                        || stagedSession.isInTerminalState()
-                        || stagedSession.isDestroyed()) {
-                    continue;
-                }
-
-                // From here on, stagedSession is a parent active staged session
-
-                // Check if session is one of the active sessions
-                if (getSessionIdForParentOrSelf(session) == stagedSession.sessionId()) {
-                    Slog.w(TAG, "Session " + session.sessionId() + " is already staged");
-                    continue;
-                }
-
-                if (isRollback && !isRollback(stagedSession)) {
-                    // If the new session is a rollback, then it gets priority. The existing
-                    // session is failed to reduce risk and avoid an SDK extension dependency
-                    // violation.
-                    final StagedSession root = stagedSession;
-                    if (!ensureActiveApexSessionIsAborted(root)) {
-                        Slog.e(TAG, "Failed to abort apex session " + root.sessionId());
-                        // Safe to ignore active apex session abort failure since session
-                        // will be marked failed on next step and staging directory for session
-                        // will be deleted.
-                    }
-                    root.setSessionFailed(
-                            SessionInfo.STAGED_SESSION_CONFLICT,
-                            "Session was failed by rollback session: " + session.sessionId());
-                    Slog.i(TAG, "Session " + root.sessionId() + " is marked failed due to "
-                            + "rollback session: " + session.sessionId());
-                } else if (!isRollback && isRollback(stagedSession)) {
-                    throw new PackageManagerException(
-                            SessionInfo.STAGED_SESSION_CONFLICT,
-                            "Session was failed by rollback session: " + stagedSession.sessionId());
-                } else if (stagedSession.sessionContains(
-                        s -> s.getPackageName().equals(packageName))) {
-                    // Fail the session committed later when there are overlapping packages
-                    if (stagedSession.getCommittedMillis() < session.getCommittedMillis()) {
-                        throw new PackageManagerException(
-                                SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                                "Package: " + session.getPackageName() + " in session: "
-                                        + session.sessionId()
-                                        + " has been staged already by session: "
-                                        + stagedSession.sessionId(), null);
-                    } else {
-                        stagedSession.setSessionFailed(
-                                SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                                "Package: " + packageName + " in session: "
-                                        + stagedSession.sessionId()
-                                        + " has been staged already by session: "
-                                        + session.sessionId());
-                    }
-                }
-
-                // Staging multiple root sessions is not allowed if device doesn't support
-                // checkpoint. If session and stagedSession do not have common ancestor, they are
-                // from two different root sessions.
-                if (!supportsCheckpoint) {
-                    throw new PackageManagerException(
-                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                            "Cannot stage multiple sessions without checkpoint support", null);
-                }
-            }
+    private void handleCommittedSession(@NonNull StagedSession session) {
+        if (session.isSessionReady() && session.containsApexSession()) {
+            notifyStagedApexObservers();
         }
     }
 
@@ -1234,237 +970,4 @@
             }
         }
     }
-
-    @VisibleForTesting
-    final class PreRebootVerificationHandler extends Handler {
-
-        PreRebootVerificationHandler(Looper looper) {
-            super(looper);
-        }
-
-        /**
-         * Handler for states of pre reboot verification. The states are arranged linearly (shown
-         * below) with each state either calling the next state, or calling some other method that
-         * eventually calls the next state.
-         *
-         * <p><ul>
-         *     <li>MSG_PRE_REBOOT_VERIFICATION_START</li>
-         *     <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li>
-         *     <li>MSG_PRE_REBOOT_VERIFICATION_END</li>
-         * </ul></p>
-         *
-         * Details about each of state can be found in corresponding handler of node.
-         */
-        private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
-        private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
-        @VisibleForTesting
-        static final int MSG_PRE_REBOOT_VERIFICATION_END = 3;
-
-        @Override
-        public void handleMessage(Message msg) {
-            final int sessionId = msg.arg1;
-            final int rollbackId = msg.arg2;
-            final StagedSession session = (StagedSession) msg.obj;
-            if (session.isDestroyed() || session.isSessionFailed()) {
-                // No point in running verification on a destroyed/failed session
-                onPreRebootVerificationComplete(session);
-                return;
-            }
-            try {
-                switch (msg.what) {
-                    case MSG_PRE_REBOOT_VERIFICATION_START:
-                        handlePreRebootVerification_Start(session);
-                        break;
-                    case MSG_PRE_REBOOT_VERIFICATION_APEX:
-                        handlePreRebootVerification_Apex(session, rollbackId);
-                        break;
-                    case MSG_PRE_REBOOT_VERIFICATION_END:
-                        handlePreRebootVerification_End(session);
-                        break;
-                }
-            } catch (Exception e) {
-                Slog.e(TAG, "Pre-reboot verification failed due to unhandled exception", e);
-                onPreRebootVerificationFailure(session,
-                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        "Pre-reboot verification failed due to unhandled exception: " + e);
-            }
-        }
-
-        // Method for starting the pre-reboot verification
-        private synchronized void startPreRebootVerification(
-                @NonNull StagedSession session) {
-            mBootCompleted.thenRun(() -> {
-                int sessionId = session.sessionId();
-                Slog.d(TAG, "Starting preRebootVerification for session " + sessionId);
-                obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, -1, session)
-                        .sendToTarget();
-            });
-        }
-
-        private void onPreRebootVerificationFailure(StagedSession session,
-                @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage) {
-            if (!ensureActiveApexSessionIsAborted(session)) {
-                Slog.e(TAG, "Failed to abort apex session " + session.sessionId());
-                // Safe to ignore active apex session abortion failure since session will be marked
-                // failed on next step and staging directory for session will be deleted.
-            }
-            session.setSessionFailed(errorCode, errorMessage);
-            onPreRebootVerificationComplete(session);
-        }
-
-        // Things to do when pre-reboot verification completes for a particular sessionId
-        private void onPreRebootVerificationComplete(StagedSession session) {
-            int sessionId = session.sessionId();
-            Slog.d(TAG, "Stopping preRebootVerification for session " + sessionId);
-            session.notifyEndPreRebootVerification();
-        }
-
-        private void notifyPreRebootVerification_Start_Complete(
-                @NonNull StagedSession session, int rollbackId) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, session.sessionId(), rollbackId,
-                    session).sendToTarget();
-        }
-
-        private void notifyPreRebootVerification_Apex_Complete(
-                @NonNull StagedSession session) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, session.sessionId(), -1, session)
-                    .sendToTarget();
-        }
-
-        /**
-         * A placeholder state for starting the pre reboot verification.
-         *
-         * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
-         */
-        private void handlePreRebootVerification_Start(@NonNull StagedSession session) {
-            try {
-                if (session.isMultiPackage()) {
-                    for (StagedSession s : session.getChildSessions()) {
-                        checkNonOverlappingWithStagedSessions(s);
-                    }
-                } else {
-                    checkNonOverlappingWithStagedSessions(session);
-                }
-            } catch (PackageManagerException e) {
-                onPreRebootVerificationFailure(session, e.error, e.getMessage());
-                return;
-            }
-
-            int rollbackId = -1;
-            if ((session.sessionParams().installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK)
-                    != 0) {
-                // If rollback is enabled for this session, we call through to the RollbackManager
-                // with the list of sessions it must enable rollback for. Note that
-                // notifyStagedSession is a synchronous operation.
-                final RollbackManagerInternal rm =
-                        LocalServices.getService(RollbackManagerInternal.class);
-                try {
-                    // NOTE: To stay consistent with the non-staged install flow, we don't fail the
-                    // entire install if rollbacks can't be enabled.
-                    rollbackId = rm.notifyStagedSession(session.sessionId());
-                } catch (RuntimeException re) {
-                    Slog.e(TAG, "Failed to notifyStagedSession for session: "
-                            + session.sessionId(), re);
-                }
-            } else if (session.sessionParams().installReason
-                    == PackageManager.INSTALL_REASON_ROLLBACK) {
-                try {
-                    rollbackId = retrieveRollbackIdForCommitSession(session.sessionId());
-                } catch (PackageManagerException e) {
-                    onPreRebootVerificationFailure(session, e.error, e.getMessage());
-                    return;
-                }
-            }
-
-            notifyPreRebootVerification_Start_Complete(session, rollbackId);
-        }
-
-        /**
-         * Pre-reboot verification state for apex files:
-         *
-         * <p><ul>
-         *     <li>submits session to apex service</li>
-         *     <li>validates signatures of apex files</li>
-         * </ul></p>
-         */
-        private void handlePreRebootVerification_Apex(
-                @NonNull StagedSession session, int rollbackId) {
-            final boolean hasApex = session.containsApexSession();
-
-            // APEX checks. For single-package sessions, check if they contain an APEX. For
-            // multi-package sessions, find all the child sessions that contain an APEX.
-            if (hasApex) {
-                final List<PackageInfo> apexPackages;
-                try {
-                    apexPackages = submitSessionToApexService(session, rollbackId);
-                    for (int i = 0, size = apexPackages.size(); i < size; i++) {
-                        validateApexSignature(apexPackages.get(i));
-                    }
-                } catch (PackageManagerException e) {
-                    onPreRebootVerificationFailure(session, e.error, e.getMessage());
-                    return;
-                }
-
-                final PackageManagerInternal packageManagerInternal =
-                        LocalServices.getService(PackageManagerInternal.class);
-                packageManagerInternal.pruneCachedApksInApex(apexPackages);
-            }
-
-            notifyPreRebootVerification_Apex_Complete(session);
-        }
-
-        /**
-         * Pre-reboot verification state for wrapping up:
-         * <p><ul>
-         *     <li>enables rollback if required</li>
-         *     <li>marks session as ready</li>
-         * </ul></p>
-         */
-        private void handlePreRebootVerification_End(@NonNull StagedSession session) {
-            // Before marking the session as ready, start checkpoint service if available
-            try {
-                if (PackageHelper.getStorageManager().supportsCheckpoint()) {
-                    PackageHelper.getStorageManager().startCheckpoint(2);
-                }
-            } catch (Exception e) {
-                // Failed to get hold of StorageManager
-                Slog.e(TAG, "Failed to get hold of StorageManager", e);
-                onPreRebootVerificationFailure(session, SessionInfo.STAGED_SESSION_UNKNOWN,
-                        "Failed to get hold of StorageManager");
-                return;
-            }
-
-            // Stop pre-reboot verification before marking session ready. From this point on, if we
-            // abandon the session then it will be cleaned up immediately. If session is abandoned
-            // after this point, then even if for some reason system tries to install the session
-            // or activate its apex, there won't be any files to work with as they will be cleaned
-            // up by the system as part of abandonment. If session is abandoned before this point,
-            // then the session is already destroyed and cannot be marked ready anymore.
-            onPreRebootVerificationComplete(session);
-
-            // Proactively mark session as ready before calling apexd. Although this call order
-            // looks counter-intuitive, this is the easiest way to ensure that session won't end up
-            // in the inconsistent state:
-            //  - If device gets rebooted right before call to apexd, then apexd will never activate
-            //      apex files of this staged session. This will result in StagingManager failing
-            //      the session.
-            // On the other hand, if the order of the calls was inverted (first call apexd, then
-            // mark session as ready), then if a device gets rebooted right after the call to apexd,
-            // only apex part of the train will be applied, leaving device in an inconsistent state.
-            Slog.d(TAG, "Marking session " + session.sessionId() + " as ready");
-            session.setSessionReady();
-            if (session.isSessionReady()) {
-                final boolean hasApex = session.containsApexSession();
-                if (hasApex) {
-                    try {
-                        mApexManager.markStagedSessionReady(session.sessionId());
-                        notifyStagedApexObservers();
-                    } catch (PackageManagerException e) {
-                        session.setSessionFailed(e.error, e.getMessage());
-                        return;
-                    }
-                }
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index c8deffb..d54acb7 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -21,9 +21,6 @@
       "name": "CtsAppEnumerationTestCases"
     },
     {
-      "name": "AppEnumerationInternalTests"
-    },
-    {
       "name": "CtsMatchFlagTestCases"
     },
     {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 61076ce..33970a2 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1357,7 +1357,7 @@
         }
         try {
             ActivityManager.getService().startActivityFromRecents(targetTask.persistentId, null);
-        } catch (RemoteException e) {
+        } catch (RemoteException | IllegalArgumentException e) {
             Slog.e(TAG, "Failed to start task " + targetTask.persistentId + " from recents", e);
         }
     }
@@ -2937,6 +2937,24 @@
                 Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in"
                         + " interceptKeyBeforeQueueing");
                 return key_consumed;
+            case KeyEvent.KEYCODE_VIDEO_APP_1:
+            case KeyEvent.KEYCODE_VIDEO_APP_2:
+            case KeyEvent.KEYCODE_VIDEO_APP_3:
+            case KeyEvent.KEYCODE_VIDEO_APP_4:
+            case KeyEvent.KEYCODE_VIDEO_APP_5:
+            case KeyEvent.KEYCODE_VIDEO_APP_6:
+            case KeyEvent.KEYCODE_VIDEO_APP_7:
+            case KeyEvent.KEYCODE_VIDEO_APP_8:
+            case KeyEvent.KEYCODE_FEATURED_APP_1:
+            case KeyEvent.KEYCODE_FEATURED_APP_2:
+            case KeyEvent.KEYCODE_FEATURED_APP_3:
+            case KeyEvent.KEYCODE_FEATURED_APP_4:
+            case KeyEvent.KEYCODE_DEMO_APP_1:
+            case KeyEvent.KEYCODE_DEMO_APP_2:
+            case KeyEvent.KEYCODE_DEMO_APP_3:
+            case KeyEvent.KEYCODE_DEMO_APP_4:
+                Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing");
+                return key_consumed;
             case KeyEvent.KEYCODE_SYSRQ:
                 if (down && repeatCount == 0) {
                     mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
@@ -4040,6 +4058,26 @@
                 }
                 break;
             }
+            case KeyEvent.KEYCODE_VIDEO_APP_1:
+            case KeyEvent.KEYCODE_VIDEO_APP_2:
+            case KeyEvent.KEYCODE_VIDEO_APP_3:
+            case KeyEvent.KEYCODE_VIDEO_APP_4:
+            case KeyEvent.KEYCODE_VIDEO_APP_5:
+            case KeyEvent.KEYCODE_VIDEO_APP_6:
+            case KeyEvent.KEYCODE_VIDEO_APP_7:
+            case KeyEvent.KEYCODE_VIDEO_APP_8:
+            case KeyEvent.KEYCODE_FEATURED_APP_1:
+            case KeyEvent.KEYCODE_FEATURED_APP_2:
+            case KeyEvent.KEYCODE_FEATURED_APP_3:
+            case KeyEvent.KEYCODE_FEATURED_APP_4:
+            case KeyEvent.KEYCODE_DEMO_APP_1:
+            case KeyEvent.KEYCODE_DEMO_APP_2:
+            case KeyEvent.KEYCODE_DEMO_APP_3:
+            case KeyEvent.KEYCODE_DEMO_APP_4: {
+                // Just drop if keys are not intercepted for direct key.
+                result &= ~ACTION_PASS_TO_USER;
+                break;
+            }
         }
 
         // Intercept the Accessibility keychord (CTRL + ALT + Z) for keyboard users.
@@ -4878,6 +4916,12 @@
         return mKeyguardDelegate.isInputRestricted();
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public boolean isKeyguardUnoccluding() {
+        return keyguardOn() && !mWindowManagerFuncs.isAppTransitionStateIdle();
+    }
+
     @Override
     public void dismissKeyguardLw(IKeyguardDismissCallback callback, CharSequence message) {
         if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 81dd9c5..4895c0b 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -362,6 +362,12 @@
          * as the top display.
          */
         void moveDisplayToTop(int displayId);
+
+        /**
+         * Return whether the app transition state is idle.
+         * @return {@code true} if app transition state is idle on the default display.
+         */
+        boolean isAppTransitionStateIdle();
     }
 
     /**
@@ -962,6 +968,14 @@
     public boolean isKeyguardOccluded();
 
     /**
+     * Return whether the keyguard is unoccluding.
+     * @return {@code true} if the keyguard is unoccluding.
+     */
+    default boolean isKeyguardUnoccluding() {
+        return false;
+    }
+
+    /**
      * @return true if in keyguard is on.
      */
     boolean isKeyguardShowing();
diff --git a/services/core/java/com/android/server/security/OWNERS b/services/core/java/com/android/server/security/OWNERS
index e6f5826..41070f3 100644
--- a/services/core/java/com/android/server/security/OWNERS
+++ b/services/core/java/com/android/server/security/OWNERS
@@ -1,3 +1,4 @@
 # Bug component: 36824
 
+per-file AttestationVerificationService.java = file:/core/java/android/security/attestationverification/OWNERS
 per-file FileIntegrityService.java = victorhsieh@google.com
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 106cff1..0c78494 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -29,6 +29,7 @@
 import static android.net.NetworkIdentity.OEM_PRIVATE;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_YES;
 import static android.net.NetworkStats.ROAMING_ALL;
 import static android.net.NetworkTemplate.MATCH_ETHERNET;
 import static android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD;
@@ -1348,7 +1349,7 @@
     @Nullable private NetworkStats getUidNetworkStatsSnapshotForTransport(int transport) {
         final NetworkTemplate template = (transport == TRANSPORT_CELLULAR)
                 ? NetworkTemplate.buildTemplateMobileWithRatType(
-                /*subscriptionId=*/null, NETWORK_TYPE_ALL)
+                /*subscriptionId=*/null, NETWORK_TYPE_ALL, METERED_YES)
                 : NetworkTemplate.buildTemplateWifiWildcard();
         return getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
     }
@@ -1388,7 +1389,8 @@
         final List<NetworkStatsExt> ret = new ArrayList<>();
         for (final int ratType : getAllCollapsedRatTypes()) {
             final NetworkTemplate template =
-                    buildTemplateMobileWithRatType(subInfo.subscriberId, ratType);
+                    buildTemplateMobileWithRatType(subInfo.subscriberId, ratType,
+                    METERED_YES);
             final NetworkStats stats =
                     getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
             if (stats != null) {
@@ -1609,7 +1611,7 @@
 
     int pullBluetoothBytesTransferLocked(int atomTag, List<StatsEvent> pulledData) {
         BluetoothActivityEnergyInfo info = fetchBluetoothData();
-        if (info == null || info.getUidTraffic() == null) {
+        if (info == null) {
             return StatsManager.PULL_SKIP;
         }
         for (UidTraffic traffic : info.getUidTraffic()) {
@@ -2067,7 +2069,7 @@
         if (info == null) {
             return StatsManager.PULL_SKIP;
         }
-        pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, info.getTimeStamp(),
+        pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, info.getTimestampMillis(),
                 info.getBluetoothStackState(), info.getControllerTxTimeMillis(),
                 info.getControllerRxTimeMillis(), info.getControllerIdleTimeMillis(),
                 info.getControllerEnergyUsed()));
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 00d01e1..a0f0367 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -97,6 +97,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A note on locking:  We rely on the fact that calls onto mBar are oneway or
@@ -142,6 +143,10 @@
     @GuardedBy("mLock")
     private IUdfpsHbmListener mUdfpsHbmListener;
 
+    @GuardedBy("mCurrentRequestAddTilePackages")
+    private final ArrayMap<String, Long> mCurrentRequestAddTilePackages = new ArrayMap<>();
+    private static final long REQUEST_TIME_OUT = TimeUnit.MINUTES.toNanos(5);
+
     private class DeathRecipient implements IBinder.DeathRecipient {
         public void binderDied() {
             mBar.asBinder().unlinkToDeath(this,0);
@@ -1742,13 +1747,39 @@
             return;
         }
 
+        synchronized (mCurrentRequestAddTilePackages) {
+            Long lastTime = mCurrentRequestAddTilePackages.get(packageName);
+            final long currentTime = System.nanoTime();
+            if (lastTime != null && currentTime - lastTime < REQUEST_TIME_OUT) {
+                try {
+                    callback.onTileRequest(
+                            StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "requestAddTile", e);
+                }
+                return;
+            } else {
+                if (lastTime != null) {
+                    cancelRequestAddTileInternal(packageName);
+                }
+            }
+
+            mCurrentRequestAddTilePackages.put(packageName, currentTime);
+        }
+
         IAddTileResultCallback proxyCallback = new IAddTileResultCallback.Stub() {
             @Override
-            public void onTileRequest(int i) throws RemoteException {
+            public void onTileRequest(int i) {
                 if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED) {
                     i = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED;
                 }
-                callback.onTileRequest(i);
+                if (clearTileAddRequest(packageName)) {
+                    try {
+                        callback.onTileRequest(i);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "requestAddTile - callback", e);
+                    }
+                }
             }
         };
 
@@ -1757,11 +1788,12 @@
         if (mBar != null) {
             try {
                 mBar.requestAddTile(componentName, appName, label, icon, proxyCallback);
+                return;
             } catch (RemoteException e) {
                 Slog.e(TAG, "requestAddTile", e);
             }
-            return;
         }
+        clearTileAddRequest(packageName);
         try {
             callback.onTileRequest(StatusBarManager.TILE_ADD_REQUEST_ERROR_NO_STATUS_BAR_SERVICE);
         } catch (RemoteException e) {
@@ -1769,6 +1801,29 @@
         }
     }
 
+    @Override
+    public void cancelRequestAddTile(@NonNull String packageName) {
+        enforceStatusBar();
+        cancelRequestAddTileInternal(packageName);
+    }
+
+    private void cancelRequestAddTileInternal(String packageName) {
+        clearTileAddRequest(packageName);
+        if (mBar != null) {
+            try {
+                mBar.cancelRequestAddTile(packageName);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "requestAddTile", e);
+            }
+        }
+    }
+
+    private boolean clearTileAddRequest(String packageName) {
+        synchronized (mCurrentRequestAddTilePackages) {
+            return mCurrentRequestAddTilePackages.remove(packageName) != null;
+        }
+    }
+
     public String[] getStatusBarIcons() {
         return mContext.getResources().getStringArray(R.array.config_statusBarIcons);
     }
@@ -1898,6 +1953,16 @@
                 }
                 pw.println();
             }
+            ArrayList<String> requests;
+            synchronized (mCurrentRequestAddTilePackages) {
+                requests = new ArrayList<>(mCurrentRequestAddTilePackages.keySet());
+            }
+            pw.println("  mCurrentRequestAddTilePackages=[");
+            final int reqN = requests.size();
+            for (int i = 0; i < reqN; i++) {
+                pw.println("    " + requests.get(i) + ",");
+            }
+            pw.println("  ]");
         }
     }
 
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 0f37450..e98fa28 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -86,6 +86,9 @@
 
     private static final String LOG_TAG = "TextClassificationManagerService";
 
+    // TODO: consider using device config to control it.
+    private static final boolean DEBUG = false;
+
     private static final ITextClassifierCallback NO_OP_CALLBACK = new ITextClassifierCallback() {
         @Override
         public void onSuccess(Bundle result) {}
@@ -175,8 +178,6 @@
     private final String mDefaultTextClassifierPackage;
     @Nullable
     private final String mSystemTextClassifierPackage;
-    // TODO: consider using device config to control it.
-    private boolean DEBUG = false;
 
     private TextClassificationManagerService(Context context) {
         mContext = Objects.requireNonNull(context);
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 92e0845..f57a852 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -1075,17 +1075,22 @@
             }
             if (shouldRecreateAudioPatch) {
                 mCommittedVolume = volume;
-                if (mAudioPatch != null) {
-                    mAudioManager.releaseAudioPatch(mAudioPatch);
-                }
-                mAudioManager.createAudioPatch(
+                // only recreate if  something was updated or audioPath is null
+                if (mAudioPatch == null || sinkUpdated ||sourceUpdated ) {
+                    if (mAudioPatch != null) {
+                        mAudioManager.releaseAudioPatch(mAudioPatch);
+                        audioPatchArray[0] = null;
+                    }
+                    mAudioManager.createAudioPatch(
                         audioPatchArray,
                         new AudioPortConfig[] { sourceConfig },
                         sinkConfigs.toArray(new AudioPortConfig[sinkConfigs.size()]));
-                mAudioPatch = audioPatchArray[0];
-                if (sourceGainConfig != null) {
-                    mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig);
+                    mAudioPatch = audioPatchArray[0];
                 }
+             }
+
+            if (sourceGainConfig != null) {
+                mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 44767f3..9222b6d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -86,8 +86,6 @@
 import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_METADATA;
 import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_OVERRIDE;
 import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
-import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
-import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
 import static android.content.res.Configuration.ASSETS_SEQ_UNDEFINED;
 import static android.content.res.Configuration.EMPTY;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -1141,10 +1139,11 @@
             if (info.getMaxAspectRatio() != 0) {
                 pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio());
             }
-            if (info.getMinAspectRatio() != 0) {
-                pw.println(prefix + "minAspectRatio=" + info.getMinAspectRatio());
+            final float minAspectRatio = getMinAspectRatio();
+            if (minAspectRatio != 0) {
+                pw.println(prefix + "minAspectRatio=" + minAspectRatio);
             }
-            if (info.getMinAspectRatio() != info.getManifestMinAspectRatio()) {
+            if (minAspectRatio != info.getManifestMinAspectRatio()) {
                 // Log the fact that we've overridden the min aspect ratio from the manifest
                 pw.println(prefix + "manifestMinAspectRatio="
                         + info.getManifestMinAspectRatio());
@@ -1443,13 +1442,7 @@
         }
         final Task rootTask = getRootTask();
 
-        // If we reparent, make sure to remove ourselves from the old animation registry.
-        if (mAnimatingActivityRegistry != null) {
-            mAnimatingActivityRegistry.notifyFinished(this);
-        }
-        mAnimatingActivityRegistry = rootTask != null
-                ? rootTask.getAnimatingActivityRegistry()
-                : null;
+        updateAnimatingActivityRegistry();
 
         if (task == mLastParentBeforePip) {
             // Activity's reparented back from pip, clear the links once established
@@ -1482,6 +1475,20 @@
         }
     }
 
+    void updateAnimatingActivityRegistry() {
+        final Task rootTask = getRootTask();
+        final AnimatingActivityRegistry registry = rootTask != null
+                ? rootTask.getAnimatingActivityRegistry()
+                : null;
+
+        // If we reparent, make sure to remove ourselves from the old animation registry.
+        if (mAnimatingActivityRegistry != null && mAnimatingActivityRegistry != registry) {
+            mAnimatingActivityRegistry.notifyFinished(this);
+        }
+
+        mAnimatingActivityRegistry = registry;
+    }
+
     /**
      * Sets {@link #mLastParentBeforePip} to the current parent Task, it's caller's job to ensure
      * {@link #getTask()} is set before this is called.
@@ -4108,19 +4115,40 @@
      *         conditions a) above.
      *         Multi-windowing mode will be exited if {@code true} is returned.
      */
-    boolean canShowWhenLocked() {
-        if (!inPinnedWindowingMode() && (mShowWhenLocked || containsShowWhenLockedWindow())) {
+    private static boolean canShowWhenLocked(ActivityRecord r) {
+        if (r == null || r.getTaskFragment() == null) {
+            return false;
+        }
+        if (!r.inPinnedWindowingMode() && (r.mShowWhenLocked || r.containsShowWhenLockedWindow())) {
             return true;
-        } else if (mInheritShownWhenLocked) {
-            final ActivityRecord r = task.getActivityBelow(this);
-            return r != null && !r.inPinnedWindowingMode() && (r.mShowWhenLocked
-                    || r.containsShowWhenLockedWindow());
+        } else if (r.mInheritShownWhenLocked) {
+            final ActivityRecord activity = r.getTaskFragment().getActivityBelow(r);
+            return activity != null && !activity.inPinnedWindowingMode()
+                    && (activity.mShowWhenLocked || activity.containsShowWhenLockedWindow());
         } else {
             return false;
         }
     }
 
     /**
+     *  Determines if the activity can show while lock-screen is displayed. System displays
+     *  activities while lock-screen is displayed only if all activities
+     *  {@link #canShowWhenLocked(ActivityRecord)}.
+     *  @see #canShowWhenLocked(ActivityRecord)
+     */
+    boolean canShowWhenLocked() {
+        final TaskFragment taskFragment = getTaskFragment();
+        if (taskFragment != null && taskFragment.getAdjacentTaskFragment() != null
+                && taskFragment.isEmbedded()) {
+            final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+            final ActivityRecord r = adjacentTaskFragment.getTopNonFinishingActivity();
+            return canShowWhenLocked(this) && canShowWhenLocked(r);
+        } else {
+            return canShowWhenLocked(this);
+        }
+    }
+
+    /**
      * @return Whether we are allowed to show non-starting windows at the moment. We disallow
      *         showing windows during transitions in case we have windows that have wide-color-gamut
      *         color mode set to avoid jank in the middle of the transition.
@@ -4138,29 +4166,6 @@
                 true /* topToBottom */);
     }
 
-    WindowState getImeTargetBelowWindow(WindowState w) {
-        final int index = mChildren.indexOf(w);
-        if (index > 0) {
-            return mChildren.get(index - 1)
-                    .getWindow(WindowState::canBeImeTarget);
-        }
-        return null;
-    }
-
-    WindowState getHighestAnimLayerWindow(WindowState currentTarget) {
-        WindowState candidate = null;
-        for (int i = mChildren.indexOf(currentTarget); i >= 0; i--) {
-            final WindowState w = mChildren.get(i);
-            if (w.mRemoved) {
-                continue;
-            }
-            if (candidate == null) {
-                candidate = w;
-            }
-        }
-        return candidate;
-    }
-
     @Override
     boolean forAllActivities(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
         return callback.test(this);
@@ -6995,11 +7000,6 @@
         clearThumbnail();
     }
 
-    @VisibleForTesting
-    WindowContainerThumbnail getThumbnail() {
-        return mThumbnail;
-    }
-
     private void clearThumbnail() {
         if (mThumbnail == null) {
             return;
@@ -7012,9 +7012,6 @@
         return mTransit;
     }
 
-    int getTransitFlags() {
-        return mTransitFlags;
-    }
 
     void registerRemoteAnimations(RemoteAnimationDefinition definition) {
         mRemoteAnimationDefinition = definition;
@@ -7209,7 +7206,7 @@
                 return false;
             }
         }
-        return !isResizeable() && (info.isFixedOrientation() || info.hasFixedAspectRatio())
+        return !isResizeable() && (info.isFixedOrientation() || hasFixedAspectRatio())
                 // The configuration of non-standard type should be enforced by system.
                 // {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} is set when this activity is
                 // added to a task, but this function is called when resolving the launch params, at
@@ -7880,13 +7877,14 @@
                 return false;
             }
         }
-        if (info.getMinAspectRatio() > 0) {
+        final float minAspectRatio = getMinAspectRatio();
+        if (minAspectRatio > 0) {
             // The activity should have at least the min aspect ratio, so this checks if the
             // container still has available space to provide larger aspect ratio.
             final float containerAspectRatio =
                     (0.5f + Math.max(containerAppWidth, containerAppHeight))
                             / Math.min(containerAppWidth, containerAppHeight);
-            if (containerAspectRatio <= info.getMinAspectRatio()) {
+            if (containerAspectRatio <= minAspectRatio) {
                 // The long side has reached the parent.
                 return false;
             }
@@ -8066,20 +8064,6 @@
         super.onResize();
     }
 
-    /** Returns true if the configuration is compatible with this activity. */
-    boolean isConfigurationCompatible(Configuration config) {
-        final int orientation = getRequestedOrientation();
-        if (isFixedOrientationPortrait(orientation)
-                && config.orientation != ORIENTATION_PORTRAIT) {
-            return false;
-        }
-        if (isFixedOrientationLandscape(orientation)
-                && config.orientation != ORIENTATION_LANDSCAPE) {
-            return false;
-        }
-        return true;
-    }
-
     private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
             Rect containingBounds) {
         return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
@@ -8097,8 +8081,7 @@
             Rect containingBounds, float desiredAspectRatio, boolean fixedOrientationLetterboxed) {
         final float maxAspectRatio = info.getMaxAspectRatio();
         final Task rootTask = getRootTask();
-        final float minAspectRatio = info.getMinAspectRatio();
-
+        final float minAspectRatio = getMinAspectRatio();
         if (task == null || rootTask == null
                 || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets()
                 && !fixedOrientationLetterboxed)
@@ -8202,6 +8185,20 @@
     }
 
     /**
+     * Returns the min aspect ratio of this activity.
+     */
+    private float getMinAspectRatio() {
+        return info.getMinAspectRatio(getRequestedOrientation());
+    }
+
+    /**
+     * Returns true if the activity has maximum or minimum aspect ratio.
+     */
+    private boolean hasFixedAspectRatio() {
+        return info.hasFixedAspectRatio(getRequestedOrientation());
+    }
+
+    /**
      * Returns the aspect ratio of the given {@code rect}.
      */
     static float computeAspectRatio(Rect rect) {
@@ -8930,7 +8927,7 @@
         }
         proto.write(PIP_AUTO_ENTER_ENABLED, pictureInPictureArgs.isAutoEnterEnabled());
         proto.write(IN_SIZE_COMPAT_MODE, inSizeCompatMode());
-        proto.write(MIN_ASPECT_RATIO, info.getMinAspectRatio());
+        proto.write(MIN_ASPECT_RATIO, getMinAspectRatio());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a1a357e..690397f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -221,6 +221,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.IRecentsAnimationRunner;
+import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
@@ -455,6 +456,10 @@
     VrController mVrController;
     KeyguardController mKeyguardController;
     private final ClientLifecycleManager mLifecycleManager;
+
+    @Nullable
+    private final BackGestureController mBackGestureController;
+
     private TaskChangeNotificationController mTaskChangeNotificationController;
     /** The controller for all operations related to locktask. */
     private LockTaskController mLockTaskController;
@@ -496,9 +501,6 @@
      */
     private Configuration mTempConfig = new Configuration();
 
-    /** Temporary to avoid allocations. */
-    final StringBuilder mStringBuilder = new StringBuilder(256);
-
     /**
      * Whether normal application switches are allowed; a call to {@link #stopAppSwitches()
      * disables this.
@@ -814,6 +816,8 @@
         mSystemThread = ActivityThread.currentActivityThread();
         mUiContext = mSystemThread.getSystemUiContext();
         mLifecycleManager = new ClientLifecycleManager();
+        mBackGestureController = BackGestureController.isEnabled() ? new BackGestureController()
+                : null;
         mVisibleActivityProcessTracker = new VisibleActivityProcessTracker(this);
         mInternal = new LocalService();
         GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED);
@@ -1747,6 +1751,14 @@
         }
     }
 
+    @Override
+    public void startBackPreview(IRemoteAnimationRunner runner) {
+        if (mBackGestureController == null) {
+            return;
+        }
+        mBackGestureController.startBackPreview();
+    }
+
     /**
      * Public API to check if the client is allowed to start an activity on specified display.
      *
@@ -3081,11 +3093,6 @@
         throw new SecurityException(msg);
     }
 
-    @VisibleForTesting
-    int checkGetTasksPermission(String permission, int pid, int uid) {
-        return checkPermission(permission, pid, uid);
-    }
-
     static int checkPermission(String permission, int pid, int uid) {
         if (permission == null) {
             return PackageManager.PERMISSION_DENIED;
@@ -3105,10 +3112,10 @@
             return true;
         }
 
-        boolean allowed = checkGetTasksPermission(android.Manifest.permission.REAL_GET_TASKS,
+        boolean allowed = checkPermission(android.Manifest.permission.REAL_GET_TASKS,
                 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
         if (!allowed) {
-            if (checkGetTasksPermission(android.Manifest.permission.GET_TASKS,
+            if (checkPermission(android.Manifest.permission.GET_TASKS,
                     callingPid, callingUid) == PackageManager.PERMISSION_GRANTED) {
                 // Temporary compatibility: some existing apps on the system image may
                 // still be requesting the old permission and not switched to the new
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 7817f54..03d6590 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -292,6 +292,10 @@
         setAppTransitionState(APP_STATE_IDLE);
     }
 
+    boolean isIdle() {
+        return mAppTransitionState == APP_STATE_IDLE;
+    }
+
     boolean isTimeout() {
         return mAppTransitionState == APP_STATE_TIMEOUT;
     }
diff --git a/services/core/java/com/android/server/wm/BackGestureController.java b/services/core/java/com/android/server/wm/BackGestureController.java
new file mode 100644
index 0000000..f8f6254
--- /dev/null
+++ b/services/core/java/com/android/server/wm/BackGestureController.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.os.SystemProperties;
+
+/**
+ * Controller to handle actions related to the back gesture on the server side.
+ */
+public class BackGestureController {
+
+    private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
+
+    public static boolean isEnabled() {
+        return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
+    }
+
+    /**
+     * Start a remote animation the back gesture.
+     */
+    public void startBackPreview() {
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cfe1aef..806d27a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -114,10 +114,12 @@
 import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGET;
 import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET;
 import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS;
+import static com.android.server.wm.DisplayContentProto.IS_SLEEPING;
 import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
 import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
 import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
 import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
+import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
@@ -467,9 +469,6 @@
     @VisibleForTesting
     boolean isDefaultDisplay;
 
-    /** Window tokens that are in the process of exiting, but still on screen for animations. */
-    final ArrayList<WindowToken> mExitingTokens = new ArrayList<>();
-
     /** Detect user tapping outside of current focused task bounds .*/
     @VisibleForTesting
     final TaskTapPointerEventListener mTapDetector;
@@ -556,9 +555,6 @@
     /** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
     final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
 
-    /** Windows whose client's insets states are not up-to-date. */
-    final ArrayList<WindowState> mWinInsetsChanged = new ArrayList<>();
-
     private ScreenRotationAnimation mScreenRotationAnimation;
 
     /**
@@ -638,9 +634,6 @@
     /** The surface parent of the IME container. */
     private SurfaceControl mInputMethodSurfaceParent;
 
-    /** If {@code true} hold off on modifying the animation layer of {@link #mImeLayeringTarget} */
-    boolean mImeLayeringTargetWaitingAnim;
-
     /** The screenshot IME surface to place on the task while transitioning to the next task. */
     SurfaceControl mImeScreenshot;
 
@@ -805,13 +798,16 @@
     };
 
     private final Consumer<WindowState> mPerformLayout = w -> {
+        if (w.mLayoutAttached || w.skipLayout()) {
+            return;
+        }
+
         // Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
         // wasting time and funky changes while a window is animating away.
         final boolean gone = w.isGoneForLayout();
 
-        if (DEBUG_LAYOUT && !w.mLayoutAttached) {
+        if (DEBUG_LAYOUT) {
             Slog.v(TAG, "1ST PASS " + w + ": gone=" + gone + " mHaveFrame=" + w.mHaveFrame
-                    + " mLayoutAttached=" + w.mLayoutAttached
                     + " config reported=" + w.isLastConfigReportedToClient());
             final ActivityRecord activity = w.mActivityRecord;
             if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + w.mViewVisibility
@@ -827,7 +823,7 @@
         // If this view is GONE, then skip it -- keep the current frame, and let the caller know
         // so they can ignore it if they want.  (We do the normal layout for INVISIBLE windows,
         // since that means "perform layout as normal, just don't display").
-        if ((!gone || !w.mHaveFrame || w.mLayoutNeeded) && !w.mLayoutAttached) {
+        if (!gone || !w.mHaveFrame || w.mLayoutNeeded) {
             if (mTmpInitial) {
                 w.resetContentChanged();
             }
@@ -854,34 +850,34 @@
             }
 
             if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.getFrame()
-                    + " mContainingFrame=" + w.getContainingFrame()
+                    + " mParentFrame=" + w.getParentFrame()
                     + " mDisplayFrame=" + w.getDisplayFrame());
         }
     };
 
     private final Consumer<WindowState> mPerformLayoutAttached = w -> {
-        if (w.mLayoutAttached) {
-            if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
-                    + " mViewVisibility=" + w.mViewVisibility
-                    + " mRelayoutCalled=" + w.mRelayoutCalled);
-            // If this view is GONE, then skip it -- keep the current frame, and let the caller
-            // know so they can ignore it if they want.  (We do the normal layout for INVISIBLE
-            // windows, since that means "perform layout as normal, just don't display").
-            if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
-                    || w.mLayoutNeeded) {
-                if (mTmpInitial) {
-                    //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
-                    w.resetContentChanged();
-                }
-                w.mSurfacePlacementNeeded = true;
-                w.mLayoutNeeded = false;
-                w.prelayout();
-                getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
-                w.mLayoutSeq = mLayoutSeq;
-                if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame()
-                        + " mContainingFrame=" + w.getContainingFrame()
-                        + " mDisplayFrame=" + w.getDisplayFrame());
+        if (!w.mLayoutAttached || w.skipLayout()) {
+            return;
+        }
+        if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
+                + " mViewVisibility=" + w.mViewVisibility
+                + " mRelayoutCalled=" + w.mRelayoutCalled);
+        // If this view is GONE, then skip it -- keep the current frame, and let the caller
+        // know so they can ignore it if they want.  (We do the normal layout for INVISIBLE
+        // windows, since that means "perform layout as normal, just don't display").
+        if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
+                || w.mLayoutNeeded) {
+            if (mTmpInitial) {
+                w.resetContentChanged();
             }
+            w.mSurfacePlacementNeeded = true;
+            w.mLayoutNeeded = false;
+            w.prelayout();
+            getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
+            w.mLayoutSeq = mLayoutSeq;
+            if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame()
+                    + " mParentFrame=" + w.getParentFrame()
+                    + " mDisplayFrame=" + w.getDisplayFrame());
         }
     };
 
@@ -3264,6 +3260,11 @@
             proto.write(FOCUSED_ROOT_TASK_ID, INVALID_TASK_ID);
         }
         proto.write(DISPLAY_READY, isReady());
+        proto.write(IS_SLEEPING, isSleeping());
+        for (int i = 0; i < mAllSleepTokens.size(); ++i) {
+            mAllSleepTokens.get(i).writeTagToProto(proto, SLEEP_TOKENS);
+        }
+
         if (mImeLayeringTarget != null) {
             mImeLayeringTarget.dumpDebug(proto, INPUT_METHOD_TARGET, logLevel);
         }
@@ -3359,17 +3360,6 @@
         });
 
         pw.println();
-        if (!mExitingTokens.isEmpty()) {
-            pw.println();
-            pw.println("  Exiting tokens:");
-            for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
-                final WindowToken token = mExitingTokens.get(i);
-                pw.print("  Exiting #"); pw.print(i);
-                pw.print(' '); pw.print(token);
-                pw.println(':');
-                token.dump(pw, "    ", dumpAll);
-            }
-        }
 
         final ScreenRotationAnimation rotationAnimation = getRotationAnimation();
         if (rotationAnimation != null) {
@@ -4115,6 +4105,8 @@
                 target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
             }
             setImeInputTarget(target);
+            mInsetsStateController.updateAboveInsetsState(mInputMethodWindow, mInsetsStateController
+                    .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
             updateImeControlTarget();
         }
     }
@@ -4621,21 +4613,6 @@
         return ret;
     }
 
-    void setExitingTokensHasVisible(boolean hasVisible) {
-        for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
-            mExitingTokens.get(i).hasVisible = hasVisible;
-        }
-    }
-
-    void removeExistingTokensIfPossible() {
-        for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
-            final WindowToken token = mExitingTokens.get(i);
-            if (!token.hasVisible) {
-                mExitingTokens.remove(i);
-            }
-        }
-    }
-
     @Override
     void onDescendantOverrideConfigurationChanged() {
         setLayoutNeeded();
@@ -4993,12 +4970,6 @@
         }
     }
 
-    void assignRootTaskOrdering() {
-        forAllTaskDisplayAreas(taskDisplayArea -> {
-            taskDisplayArea.assignRootTaskOrdering(getPendingTransaction());
-        });
-    }
-
     /**
      * Increment the deferral count to determine whether to update the IME target.
      */
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 253b3a5..180ee61 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -43,12 +43,12 @@
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
+import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -65,7 +65,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
@@ -115,6 +114,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.gui.DropInputMode;
 import android.hardware.power.Boost;
 import android.os.Handler;
 import android.os.IBinder;
@@ -164,7 +164,6 @@
 import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wallpaper.WallpaperManagerInternal;
-import com.android.server.wm.InputMonitor.EventReceiverInputConsumer;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -395,7 +394,6 @@
 
     // -------- PolicyHandler --------
     private static final int MSG_REQUEST_TRANSIENT_BARS = 2;
-    private static final int MSG_DISPOSE_INPUT_CONSUMER = 3;
     private static final int MSG_ENABLE_POINTER_LOCATION = 4;
     private static final int MSG_DISABLE_POINTER_LOCATION = 5;
 
@@ -424,9 +422,6 @@
                         }
                     }
                     break;
-                case MSG_DISPOSE_INPUT_CONSUMER:
-                    disposeInputConsumer((EventReceiverInputConsumer) msg.obj);
-                    break;
                 case MSG_ENABLE_POINTER_LOCATION:
                     enablePointerLocation();
                     break;
@@ -962,6 +957,20 @@
     }
 
     /**
+     * Add additional policy if needed to ensure the window or its children should not receive any
+     * input.
+     */
+    public void setDropInputModePolicy(WindowState win, LayoutParams attrs) {
+        if (attrs.type == TYPE_TOAST
+                && (attrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) == 0) {
+            // Toasts should not receive input. These windows should not have any children, so
+            // force this hierarchy of windows to drop all input.
+            mService.mTransactionFactory.get()
+                    .setDropInputMode(win.getSurfaceControl(), DropInputMode.ALL).apply();
+        }
+    }
+
+    /**
      * Check if a window can be added to the system.
      *
      * Currently enforces that two window types are singletons per display:
@@ -1704,10 +1713,6 @@
             layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR));
             return;
         }
-        if (win.mActivityRecord != null && win.mActivityRecord.mWaitForEnteringPinnedMode) {
-            // Skip layout of the window when in transition to pip mode.
-            return;
-        }
         final WindowManager.LayoutParams attrs = win.getLayoutingAttrs(displayFrames.mRotation);
 
         final int type = attrs.type;
@@ -1719,37 +1724,50 @@
 
         final Rect pf = windowFrames.mParentFrame;
         final Rect df = windowFrames.mDisplayFrame;
+        final Rect f = windowFrames.mFrame;
         final Rect attachedWindowFrame = attached != null ? attached.getFrame() : null;
         sTmpLastParentFrame.set(pf);
 
-        // Override the bounds in window token has many side effects. Directly use the display
-        // frame set for the simulated layout for this case.
-        final Rect winBounds = windowFrames.mIsSimulatingDecorWindow ? df : win.getBounds();
+        final Rect winBounds;
+        final int requestedWidth;
+        final int requestedHeight;
+        if (windowFrames.mIsSimulatingDecorWindow) {
+            // Override the bounds in window token has many side effects. Directly use the display
+            // frame set for the simulated layout.
+            winBounds = df;
+
+            // The view hierarchy has not been measured in the simulated layout. Use
+            // UNSPECIFIED_LENGTH as the requested width and height so that WindowLayout will choose
+            // the proper values in this case.
+            requestedWidth = UNSPECIFIED_LENGTH;
+            requestedHeight = UNSPECIFIED_LENGTH;
+        } else {
+            winBounds = win.getBounds();
+            requestedWidth = win.mRequestedWidth;
+            requestedHeight = win.mRequestedHeight;
+        }
 
         final boolean clippedByDisplayCutout = mWindowLayout.computeWindowFrames(attrs,
-                win.getInsetsState(), displayFrames.mDisplayCutoutSafe, winBounds,
-                win.getRequestedVisibilities(), attachedWindowFrame, df, pf);
+                win.getInsetsState(), displayFrames.mDisplayCutoutSafe,
+                winBounds, win.getWindowingMode(), requestedWidth, requestedHeight,
+                win.getRequestedVisibilities(), attachedWindowFrame, win.mGlobalScale,
+                df, pf, f);
         windowFrames.setParentFrameWasClippedByDisplayCutout(clippedByDisplayCutout);
 
-        // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
-        // Also, we don't allow windows in multi-window mode to extend out of the screen.
-        if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
-                && !win.inMultiWindowMode()) {
-            df.left = df.top = -10000;
-            df.right = df.bottom = 10000;
-        }
-
         if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
                 + ": sim=#" + Integer.toHexString(sim)
                 + " attach=" + attached + " type=" + type
-                + String.format(" flags=0x%08x", fl)
-                + " pf=" + pf.toShortString() + " df=" + df.toShortString());
+                + " flags=" + ViewDebug.flagsToString(LayoutParams.class, "flags", fl)
+                + " pf=" + pf.toShortString() + " df=" + df.toShortString()
+                + " f=" + f.toShortString());
 
         if (!sTmpLastParentFrame.equals(pf)) {
             windowFrames.setContentChanged(true);
         }
 
-        win.computeFrameAndUpdateSourceFrame(displayFrames);
+        if (!windowFrames.mIsSimulatingDecorWindow) {
+            win.setFrame();
+        }
     }
 
     WindowState getTopFullscreenOpaqueWindow() {
@@ -2550,12 +2568,6 @@
         mImmersiveModeConfirmation.confirmCurrentPrompt();
     }
 
-    private void disposeInputConsumer(EventReceiverInputConsumer inputConsumer) {
-        if (inputConsumer != null) {
-            inputConsumer.dispose();
-        }
-    }
-
     boolean isKeyguardShowing() {
         return mService.mPolicy.isKeyguardShowing();
     }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index fa4d27c..d17c109 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -54,14 +54,12 @@
 import android.graphics.Region;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.EventLog;
 import android.util.Slog;
 import android.view.InputChannel;
-import android.view.InputEventReceiver;
 import android.view.InputWindowHandle;
 import android.view.SurfaceControl;
 
@@ -112,44 +110,6 @@
     private WeakReference<ActivityRecord> mActiveRecentsActivity = null;
     private WeakReference<ActivityRecord> mActiveRecentsLayerRef = null;
 
-    /**
-     * Representation of a input consumer that the policy has added to the window manager to consume
-     * input events going to windows below it.
-     */
-    static final class EventReceiverInputConsumer extends InputConsumerImpl {
-        private InputMonitor mInputMonitor;
-        private final InputEventReceiver mInputEventReceiver;
-
-        EventReceiverInputConsumer(WindowManagerService service, InputMonitor monitor,
-                                   Looper looper, String name,
-                                   InputEventReceiver.Factory inputEventReceiverFactory,
-                                   int clientPid, UserHandle clientUser, int displayId) {
-            super(service, null /* token */, name, null /* inputChannel */, clientPid, clientUser,
-                    displayId);
-            mInputMonitor = monitor;
-            mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
-                    mClientChannel, looper);
-        }
-
-        /** Removes the input consumer from the window manager. */
-        void dismiss() {
-            synchronized (mService.mGlobalLock) {
-                mInputMonitor.mInputConsumers.remove(mName);
-                hide(mInputMonitor.mInputTransaction);
-                mInputMonitor.updateInputWindowsLw(true /* force */);
-            }
-        }
-
-        /** Disposes the input consumer and input receiver from the associated thread. */
-        void dispose() {
-            synchronized (mService.mGlobalLock) {
-                disposeChannelsLw(mInputMonitor.mInputTransaction);
-                mInputEventReceiver.dispose();
-                mInputMonitor.updateInputWindowsLw(true /* force */);
-            }
-        }
-    }
-
     private class UpdateInputWindows implements Runnable {
         @Override
         public void run() {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index b46b2f7..3f573fa 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -291,19 +291,10 @@
         for (int i = mProviders.size() - 1; i >= 0; i--) {
             mProviders.valueAt(i).onPostLayout();
         }
-        final ArrayList<WindowState> winInsetsChanged = mDisplayContent.mWinInsetsChanged;
         if (!mLastState.equals(mState)) {
             mLastState.set(mState, true /* copySources */);
             notifyInsetsChanged();
-        } else {
-            // The global insets state has not changed but there might be windows whose conditions
-            // (e.g., z-order) have changed. They can affect the insets states that we dispatch to
-            // the clients.
-            for (int i = winInsetsChanged.size() - 1; i >= 0; i--) {
-                mDispatchInsetsChanged.accept(winInsetsChanged.get(i));
-            }
         }
-        winInsetsChanged.clear();
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 1277e35..e10ae37 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -839,12 +839,6 @@
                     UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
         }
 
-        // Initialize state of exiting tokens.
-        for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
-            final DisplayContent displayContent = mChildren.get(displayNdx);
-            displayContent.setExitingTokensHasVisible(false);
-        }
-
         mHoldScreen = null;
         mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mUserActivityTimeout = -1;
@@ -951,12 +945,6 @@
             mWmService.mDestroySurface.clear();
         }
 
-        // Time to remove any exiting tokens?
-        for (int displayNdx = mChildren.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent displayContent = mChildren.get(displayNdx);
-            displayContent.removeExistingTokensIfPossible();
-        }
-
         for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
             final DisplayContent displayContent = mChildren.get(displayNdx);
             if (displayContent.pendingLayoutChanges != 0) {
@@ -2651,6 +2639,7 @@
             token = new SleepToken(tag, displayId);
             mSleepTokens.put(tokenKey, token);
             display.mAllSleepTokens.add(token);
+            ProtoLog.d(WM_DEBUG_STATES, "Create sleep token: tag=%s, displayId=%d", tag, displayId);
         } else {
             throw new RuntimeException("Create the same sleep token twice: " + token);
         }
@@ -2669,6 +2658,8 @@
             return;
         }
 
+        ProtoLog.d(WM_DEBUG_STATES, "Remove sleep token: tag=%s, displayId=%d", token.mTag,
+                token.mDisplayId);
         display.mAllSleepTokens.remove(token);
         if (display.mAllSleepTokens.isEmpty()) {
             mService.updateSleepIfNeededLocked();
@@ -3676,6 +3667,10 @@
             return "{\"" + mTag + "\", display " + mDisplayId
                     + ", acquire at " + TimeUtils.formatUptime(mAcquireTime) + "}";
         }
+
+        void writeTagToProto(ProtoOutputStream proto, long fieldId) {
+            proto.write(fieldId, mTag);
+        }
     }
 
     private class RankTaskLayersRunnable implements Runnable {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 6b7f92a..b8b9bcc 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.Manifest.permission.DEVICE_POWER;
 import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
@@ -107,7 +106,6 @@
 
     final boolean mCanCreateSystemApplicationOverlay;
     final boolean mCanHideNonSystemOverlayWindows;
-    final boolean mCanAcquireSleepToken;
     private AlertWindowNotification mAlertWindowNotification;
     private boolean mShowingAlertWindowNotificationAllowed;
     private boolean mClientDead = false;
@@ -134,8 +132,6 @@
                         == PERMISSION_GRANTED;
         mCanStartTasksFromRecents = service.mContext.checkCallingOrSelfPermission(
                 START_TASKS_FROM_RECENTS) == PERMISSION_GRANTED;
-        mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER)
-                == PERMISSION_GRANTED;
         mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
         mDragDropController = mService.mDragDropController;
         StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6987cdb..9767c8b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -471,12 +471,6 @@
     // to layout without loading all the task snapshots
     final PersistedTaskSnapshotData mLastTaskSnapshotData;
 
-    // If set to true, the task will report that it is not in the floating
-    // state regardless of it's root task affiliation. As the floating state drives
-    // production of content insets this can be used to preserve them across
-    // root task moves and we in fact do so when moving from full screen to pinned.
-    private boolean mPreserveNonFloatingState = false;
-
     private Dimmer mDimmer = new Dimmer(this);
     private final Rect mTmpDimBoundsRect = new Rect();
 
@@ -1224,6 +1218,9 @@
         adjustBoundsForDisplayChangeIfNeeded(getDisplayContent());
 
         mRootWindowContainer.updateUIDsPresentOnDisplay();
+
+        // Ensure all animations are finished at same time in split-screen mode.
+        forAllActivities(ActivityRecord::updateAnimatingActivityRegistry);
     }
 
     @Override
@@ -2582,11 +2579,6 @@
         reparent(rootTask, position);
 
         rootTask.positionChildAt(position, this, moveParents);
-
-        // If we are moving from the fullscreen root task to the root pinned task then we want to
-        // preserve our insets so that there will not be a jump in the area covered by system
-        // decorations. We rely on the pinned animation to later unset this value.
-        mPreserveNonFloatingState = rootTask.inPinnedWindowingMode();
     }
 
     public int setBounds(Rect bounds, boolean forceResize) {
@@ -2899,16 +2891,6 @@
         mForceShowForAllUsers = forceShowForAllUsers;
     }
 
-    /**
-     * When we are in a floating root task (Freeform, Pinned, ...) we calculate
-     * insets differently. However if we are animating to the fullscreen root task
-     * we need to begin calculating insets as if we were fullscreen, otherwise
-     * we will have a jump at the end.
-     */
-    boolean isFloating() {
-        return getWindowConfiguration().tasksAreFloating() && !mPreserveNonFloatingState;
-    }
-
     /** Returns the top-most activity that occludes the given one, or {@code null} if none. */
     @Nullable
     ActivityRecord getOccludingActivityAbove(ActivityRecord activity) {
@@ -3246,10 +3228,6 @@
         return "Task=" + mTaskId;
     }
 
-    void clearPreserveNonFloatingState() {
-        mPreserveNonFloatingState = false;
-    }
-
     @Override
     Dimmer getDimmer() {
         // If the window is in multi-window mode, we want to dim at the Task level to ensure the dim
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 7e784ae..901f14f 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -798,12 +798,9 @@
         int layer = 0;
         // Place root home tasks to the bottom.
         layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer);
-        adjustRootTaskLayer(t, mTmpNormalChildren, layer);
-
-        // Always on top tasks layer should higher than split divider layer so set it as start.
-        t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER);
-        layer = SPLIT_DIVIDER_LAYER + 1;
+        layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer);
         adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer);
+        t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER);
     }
 
     /**
@@ -818,19 +815,33 @@
             ArrayList<WindowContainer> children, int startLayer) {
         mTmpNeedsZBoostIndexes.clear();
         final int childCount = children.size();
+        boolean hasAdjacentTask = false;
         for (int i = 0; i < childCount; i++) {
             final WindowContainer child = children.get(i);
             final TaskDisplayArea childTda = child.asTaskDisplayArea();
-
-            boolean childNeedsZBoost = childTda != null
+            final boolean childNeedsZBoost = childTda != null
                     ? childTda.childrenNeedZBoost()
                     : child.needsZBoost();
 
-            if (!childNeedsZBoost) {
-                child.assignLayer(t, startLayer++);
-            } else {
+            if (childNeedsZBoost) {
                 mTmpNeedsZBoostIndexes.add(i);
+                continue;
             }
+
+            final Task childTask = child.asTask();
+            final boolean inAdjacentTask = childTask != null
+                    && child.inMultiWindowMode()
+                    && childTask.getRootTask().getAdjacentTaskFragment() != null;
+
+            if (inAdjacentTask || child.inSplitScreenWindowingMode()) {
+                hasAdjacentTask = true;
+            } else if (hasAdjacentTask && startLayer < SPLIT_DIVIDER_LAYER) {
+                // Task on top of adjacent tasks should be higher than split divider layer so
+                // set it as start.
+                startLayer = SPLIT_DIVIDER_LAYER + 1;
+            }
+
+            child.assignLayer(t, startLayer++);
         }
 
         final int zBoostSize = mTmpNeedsZBoostIndexes.size();
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index d543c1f..b8ceb4a 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -818,7 +818,7 @@
         final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight;
 
         // Aspect ratio requirements.
-        final float minAspectRatio = info.getMinAspectRatio();
+        final float minAspectRatio = info.getMinAspectRatio(orientation);
         final float maxAspectRatio = info.getMaxAspectRatio();
 
         final int width = Math.min(defaultWidth, Math.max(phoneWidth, layoutMinWidth));
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 96c935a..63246ac 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -392,7 +392,7 @@
         final WindowState imeWindow = task.getDisplayContent().mInputMethodWindow;
         SurfaceControl.ScreenshotHardwareBuffer imeBuffer = null;
         if (imeWindow != null && imeWindow.isWinVisibleLw()) {
-            final Rect bounds = imeWindow.getContainingFrame();
+            final Rect bounds = imeWindow.getParentFrame();
             bounds.offsetTo(0, 0);
             imeBuffer = SurfaceControl.captureLayersExcluding(imeWindow.getSurfaceControl(),
                     bounds, 1.0f, pixelFormat, null);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index d93b649..08b1a2f 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -97,12 +97,6 @@
     // Time we wait after a timeout before trying to wait again.
     private static final long WALLPAPER_TIMEOUT_RECOVERY = 10000;
 
-    // Set to the wallpaper window we would like to hide once the transition animations are done.
-    // This is useful in cases where we don't want the wallpaper to be hidden when the close app
-    // is a wallpaper target and is done animating out, but the opening app isn't a wallpaper
-    // target and isn't done animating in.
-    WindowState mDeferredHideWallpaper = null;
-
     // We give a wallpaper up to 500ms to finish drawing before playing app transitions.
     private static final long WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION = 500;
     private static final int WALLPAPER_DRAW_NORMAL = 0;
@@ -165,7 +159,8 @@
         boolean needsShowWhenLockedWallpaper = false;
         if ((w.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0
                 && mService.mPolicy.isKeyguardLocked()
-                && mService.mPolicy.isKeyguardOccluded()) {
+                && (mService.mPolicy.isKeyguardOccluded()
+                || mService.mPolicy.isKeyguardUnoccluding())) {
             // The lowest show when locked window decides whether we need to put the wallpaper
             // behind.
             needsShowWhenLockedWallpaper = !isFullscreen(w.mAttrs)
diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java
index baea854..c3d3c82 100644
--- a/services/core/java/com/android/server/wm/WindowFrames.java
+++ b/services/core/java/com/android/server/wm/WindowFrames.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static com.android.server.wm.WindowFramesProto.COMPAT_FRAME;
-import static com.android.server.wm.WindowFramesProto.CONTAINING_FRAME;
 import static com.android.server.wm.WindowFramesProto.DISPLAY_FRAME;
 import static com.android.server.wm.WindowFramesProto.FRAME;
 import static com.android.server.wm.WindowFramesProto.PARENT_FRAME;
@@ -37,32 +36,16 @@
     private static final StringBuilder sTmpSB = new StringBuilder();
 
     /**
-     * In most cases, this is the area of the entire screen.
-     *
-     * TODO(b/111611553): The name is unclear and most likely should be swapped with
-     * {@link #mDisplayFrame}
-     * TODO(b/111611553): In some cases, it also includes top insets, like for IME. Determine
-     * whether this is still necessary to do.
+     * The frame to be referenced while applying gravity and MATCH_PARENT.
      */
     public final Rect mParentFrame = new Rect();
 
     /**
-     * The entire screen area of the {@link Task} this window is in. Usually equal to the
-     * screen area of the device.
-     *
-     * TODO(b/111611553): The name is unclear and most likely should be swapped with
-     * {@link #mParentFrame}
+     * The bounds that the window should fit.
      */
     public final Rect mDisplayFrame = new Rect();
 
     /**
-     * Similar to {@link #mDisplayFrame}
-     *
-     * TODO: Why is this different than mDisplayFrame
-     */
-    final Rect mContainingFrame = new Rect();
-
-    /**
      * "Real" frame that the application sees, in display coordinate space.
      */
     final Rect mFrame = new Rect();
@@ -124,10 +107,6 @@
         return (mLastFrame.width() != mFrame.width()) || (mLastFrame.height() != mFrame.height());
     }
 
-    void offsetFrames(int layoutXDiff, int layoutYDiff) {
-        mFrame.offset(layoutXDiff, layoutYDiff);
-    }
-
     /**
      * Updates info about whether the size of the window has changed since last reported.
      *
@@ -188,16 +167,13 @@
         final long token = proto.start(fieldId);
         mParentFrame.dumpDebug(proto, PARENT_FRAME);
         mDisplayFrame.dumpDebug(proto, DISPLAY_FRAME);
-        mContainingFrame.dumpDebug(proto, CONTAINING_FRAME);
         mFrame.dumpDebug(proto, FRAME);
         mCompatFrame.dumpDebug(proto, COMPAT_FRAME);
         proto.end(token);
     }
 
     public void dump(PrintWriter pw, String prefix) {
-        pw.println(prefix + "Frames: containing="
-                + mContainingFrame.toShortString(sTmpSB)
-                + " parent=" + mParentFrame.toShortString(sTmpSB)
+        pw.println(prefix + "Frames: parent=" + mParentFrame.toShortString(sTmpSB)
                 + " display=" + mDisplayFrame.toShortString(sTmpSB));
         pw.println(prefix + "mFrame=" + mFrame.toShortString(sTmpSB)
                 + " last=" + mLastFrame.toShortString(sTmpSB));
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c4a5183..9c81377 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -169,10 +169,8 @@
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
-import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.graphics.Region;
 import android.hardware.configstore.V1_0.OptionalBool;
 import android.hardware.configstore.V1_1.DisplayOrientation;
@@ -343,8 +341,6 @@
         implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowManagerService" : TAG_WM;
 
-    private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter";
-
     static final int LAYOUT_REPEAT_THRESHOLD = 4;
 
     static final boolean PROFILE_ORIENTATION = false;
@@ -646,13 +642,7 @@
     StrictModeFlash mStrictModeFlash;
     EmulatorDisplayOverlay mEmulatorDisplayOverlay;
 
-    final float[] mTmpFloats = new float[9];
     final Rect mTmpRect = new Rect();
-    final Rect mTmpRect2 = new Rect();
-    final Rect mTmpRect3 = new Rect();
-    final RectF mTmpRectF = new RectF();
-
-    final Matrix mTmpTransform = new Matrix();
 
     boolean mDisplayReady;
     boolean mSafeMode;
@@ -1059,8 +1049,6 @@
         public void focusChanged();
     }
 
-    final Configuration mTempConfiguration = new Configuration();
-
     final HighRefreshRateDenylist mHighRefreshRateDenylist;
 
     // Maintainer of a collection of all possible DisplayInfo for all configurations of the
@@ -1156,11 +1144,6 @@
         void onAppFreezeTimeout();
     }
 
-    private static WindowManagerService sInstance;
-    static WindowManagerService getInstance() {
-        return sInstance;
-    }
-
     public static WindowManagerService main(final Context context, final InputManagerService im,
             final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,
             ActivityTaskManagerService atm) {
@@ -1180,11 +1163,12 @@
             displayWindowSettingsProvider, Supplier<SurfaceControl.Transaction> transactionFactory,
             Supplier<Surface> surfaceFactory,
             Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
+        final WindowManagerService[] wms = new WindowManagerService[1];
         DisplayThread.getHandler().runWithScissors(() ->
-                sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,
+                wms[0] = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,
                         atm, displayWindowSettingsProvider, transactionFactory, surfaceFactory,
                         surfaceControlFactory), 0);
-        return sInstance;
+        return wms[0];
     }
 
     private void initPolicy() {
@@ -1783,6 +1767,7 @@
 
             win.mToken.addWindow(win);
             displayPolicy.addWindowLw(win, attrs);
+            displayPolicy.setDropInputModePolicy(win, win.mAttrs);
             if (type == TYPE_APPLICATION_STARTING && activity != null) {
                 activity.attachStartingWindow(win);
                 ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
@@ -2515,9 +2500,11 @@
             }
             win.mInRelayout = false;
 
-            if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) {
+            if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE
+                    && win.mNextRelayoutUseSync) {
                 win.prepareDrawHandlers();
                 win.markRedrawForSyncReported();
+                win.mNextRelayoutUseSync = false;
                 result |= RELAYOUT_RES_BLAST_SYNC;
             }
 
@@ -3014,15 +3001,6 @@
                 aspectRatio);
     }
 
-    void getRootTaskBounds(int windowingMode, int activityType, Rect bounds) {
-        final Task rootTask = mRoot.getRootTask(windowingMode, activityType);
-        if (rootTask != null) {
-            rootTask.getBounds(bounds);
-            return;
-        }
-        bounds.setEmpty();
-    }
-
     /**
      * Notifies window manager that {@link DisplayPolicy#isShowingDreamLw} has changed.
      */
@@ -3080,6 +3058,11 @@
         syncInputTransactions(true /* waitForAnimations */);
     }
 
+    @Override
+    public boolean isAppTransitionStateIdle() {
+        return getDefaultDisplayContentLocked().mAppTransition.isIdle();
+    }
+
     /**
      * Notifies activity manager that some Keyguard flags have changed and that it needs to
      * reevaluate the visibilities of the activities.
@@ -6989,12 +6972,6 @@
         }
     }
 
-    void intersectDisplayInsetBounds(Rect display, Rect insets, Rect inOutBounds) {
-        mTmpRect3.set(display);
-        mTmpRect3.inset(insets);
-        inOutBounds.intersect(mTmpRect3);
-    }
-
     MousePositionTracker mMousePositionTracker = new MousePositionTracker();
 
     private static class MousePositionTracker implements PointerEventListener {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d68a8ea..6ba21d5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -38,10 +38,9 @@
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
-import static android.view.WindowInsets.Type.displayCutout;
-import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
@@ -49,7 +48,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
@@ -60,7 +58,6 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -224,7 +221,6 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.Gravity;
 import android.view.IWindow;
 import android.view.IWindowFocusObserver;
 import android.view.IWindowId;
@@ -367,6 +363,7 @@
     private boolean mDragResizingChangeReported = true;
     private int mResizeMode;
     private boolean mRedrawForSyncReported;
+    boolean mNextRelayoutUseSync;
 
     /**
      * {@code true} when the client was still drawing for sync when the sync-set was finished or
@@ -473,9 +470,6 @@
     /** The frames used to compute a temporal layout appearance. */
     private WindowFrames mSimulatedWindowFrames;
 
-    /** Usually the same as {@link #getBounds()}. */
-    private final Rect mInsetFrame = new Rect();
-
     /**
      * List of rects where system gestures should be ignored.
      *
@@ -657,7 +651,6 @@
     private PowerManager.WakeLock mDrawLock;
 
     private final Rect mTmpRect = new Rect();
-    private final Rect mTmpRect2 = new Rect();
     private final Point mTmpPoint = new Point();
 
     private final Transaction mTmpTransaction;
@@ -1138,10 +1131,10 @@
         mWinAnimator = new WindowStateAnimator(this);
         mWinAnimator.mAlpha = a.alpha;
 
-        mRequestedWidth = 0;
-        mRequestedHeight = 0;
-        mLastRequestedWidth = 0;
-        mLastRequestedHeight = 0;
+        mRequestedWidth = UNSPECIFIED_LENGTH;
+        mRequestedHeight = UNSPECIFIED_LENGTH;
+        mLastRequestedWidth = UNSPECIFIED_LENGTH;
+        mLastRequestedHeight = UNSPECIFIED_LENGTH;
         mLayer = 0;
         mOverrideScale = mWmService.mAtmService.mCompatModePackages.getCompatScale(
                 mAttrs.packageName, s.mUid);
@@ -1235,145 +1228,26 @@
         return mOwnerCanAddInternalSystemWindow;
     }
 
-    /**
-     * Returns {@code true} if the window owner has the permission to acquire a sleep token when
-     * it's visible. That is, they have the permission
-     * {@link androidManifest.permission#DEVICE_POWER}.
-     */
-    boolean canAcquireSleepToken() {
-        return mSession.mCanAcquireSleepToken;
-    }
-
-    /**
-     * Subtracts the insets calculated by intersecting {@param layoutFrame} with {@param insetFrame}
-     * from {@param frame}. In other words, it applies the insets that would result if
-     * {@param frame} would be shifted to {@param layoutFrame} and then applying the insets from
-     * {@param insetFrame}. Also it respects {@param displayFrame} in case window has minimum
-     * width/height applied and insets should be overridden.
-     */
-    private void subtractInsets(Rect frame, Rect layoutFrame, Rect insetFrame, Rect displayFrame) {
-        final int left = Math.max(0, insetFrame.left - Math.max(layoutFrame.left, displayFrame.left));
-        final int top = Math.max(0, insetFrame.top - Math.max(layoutFrame.top, displayFrame.top));
-        final int right = Math.max(0, Math.min(layoutFrame.right, displayFrame.right) - insetFrame.right);
-        final int bottom = Math.max(0, Math.min(layoutFrame.bottom, displayFrame.bottom) - insetFrame.bottom);
-        frame.inset(left, top, right, bottom);
-    }
-
-    void computeFrameAndUpdateSourceFrame(DisplayFrames displayFrames) {
-        computeFrame(displayFrames);
-        // Update the source frame to provide insets to other windows during layout. If the
-        // simulated frames exist, then this is not computing a stable result so just skip.
-        if (mControllableInsetProvider != null && mSimulatedWindowFrames == null) {
-            mControllableInsetProvider.updateSourceFrame();
-        }
-    }
-
-    /**
-     * Perform standard frame computation. The result can be obtained with getFrame() if so desired.
-     */
-    void computeFrame(DisplayFrames displayFrames) {
+    boolean skipLayout() {
         if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
             // This window is being replaced and either already got information that it's being
             // removed or we are still waiting for some information. Because of this we don't
             // want to apply any more changes to it, so it remains in this state until new window
             // appears.
-            return;
+            return true;
         }
+        // Skip layout of the window when in transition to pip mode.
+        return mActivityRecord != null && mActivityRecord.mWaitForEnteringPinnedMode;
+    }
+
+    // TODO(b/161810301): Make the frame be passed from the client side.
+    void setFrame() {
+        // TODO(b/161810301): Set the window frame here. We don't have to do it now because
+        //                    DisplayPolicy has already done it for the window.
+
         mHaveFrame = true;
 
-        final Task task = getTask();
-        final boolean isFullscreenAndFillsArea = !inMultiWindowMode() && matchesDisplayAreaBounds();
-        final boolean windowsAreFloating = task != null && task.isFloating();
-        final DisplayContent dc = getDisplayContent();
-        final DisplayInfo displayInfo = getDisplayInfo();
-        final WindowFrames windowFrames = getLayoutingWindowFrames();
-
-        mInsetFrame.set(getBounds());
-
-        // Denotes the actual frame used to calculate the insets and to perform the layout. When
-        // resizing in docked mode, we'd like to freeze the layout, so we also need to freeze the
-        // insets temporarily. By the notion of a task having a different layout frame, we can
-        // achieve that while still moving the task around.
-        final Rect layoutContainingFrame;
-        final Rect layoutDisplayFrame;
-
-        // The offset from the layout containing frame to the actual containing frame.
-        final int layoutXDiff;
-        final int layoutYDiff;
-        final WindowState imeWin = mWmService.mRoot.getCurrentInputMethodWindow();
-        final InsetsControlTarget imeTarget = dc.getImeTarget(IME_TARGET_LAYERING);
-        final boolean isInputMethodAdjustTarget = windowsAreFloating
-                ? imeTarget != null && task == imeTarget.getWindow().getTask()
-                : isImeLayeringTarget();
-        final boolean isImeTarget =
-                imeWin != null && imeWin.isVisibleNow() && isInputMethodAdjustTarget;
-        if (isFullscreenAndFillsArea || layoutInParentFrame()) {
-            // We use the parent frame as the containing frame for fullscreen and child windows
-            windowFrames.mContainingFrame.set(windowFrames.mParentFrame);
-            layoutDisplayFrame = windowFrames.mDisplayFrame;
-            layoutContainingFrame = windowFrames.mParentFrame;
-            layoutXDiff = 0;
-            layoutYDiff = 0;
-        } else {
-            windowFrames.mContainingFrame.set(getBounds());
-            // IME is up and obscuring this window. Adjust the window position so it is visible.
-            if (isImeTarget) {
-                if (inFreeformWindowingMode()) {
-                    // Push the freeform window up to make room for the IME. However, don't push
-                    // it up past the bottom of the top bar.
-                    final InsetsState state = dc.getInsetsStateController().getRawInsetsState();
-                    final Rect visibleFrame = mTmpRect;
-                    visibleFrame.set(state.getDisplayFrame());
-                    visibleFrame.inset(state.calculateInsets(visibleFrame,
-                            systemBars() | ime() | displayCutout(), false /* ignoreVisibility */));
-                    final int bottomOverlap =
-                            windowFrames.mContainingFrame.bottom - visibleFrame.bottom;
-                    if (bottomOverlap > 0) {
-                        final int distanceToTop = Math.max(windowFrames.mContainingFrame.top
-                                - visibleFrame.top, 0);
-                        int offs = Math.min(bottomOverlap, distanceToTop);
-                        windowFrames.mContainingFrame.offset(0, -offs);
-                        mInsetFrame.offset(0, -offs);
-                    }
-                } else if (!inPinnedWindowingMode() && windowFrames.mContainingFrame.bottom
-                        > windowFrames.mParentFrame.bottom) {
-                    // But in docked we want to behave like fullscreen and behave as if the task
-                    // were given smaller bounds for the purposes of layout. Skip adjustments for
-                    // the root pinned task, they are handled separately in the
-                    // PinnedTaskController.
-                    windowFrames.mContainingFrame.bottom = windowFrames.mParentFrame.bottom;
-                }
-            }
-
-            if (windowsAreFloating) {
-                // In floating modes (e.g. freeform, pinned) we have only to set the rectangle
-                // if it wasn't set already. No need to intersect it with the (visible)
-                // "content frame" since it is allowed to be outside the visible desktop.
-                if (windowFrames.mContainingFrame.isEmpty()) {
-                    windowFrames.mContainingFrame.set(windowFrames.mDisplayFrame);
-                }
-            }
-
-            layoutDisplayFrame = mTmpRect2;
-            layoutDisplayFrame.set(windowFrames.mDisplayFrame);
-            windowFrames.mDisplayFrame.set(windowFrames.mContainingFrame);
-            layoutXDiff = mInsetFrame.left - windowFrames.mContainingFrame.left;
-            layoutYDiff = mInsetFrame.top - windowFrames.mContainingFrame.top;
-            layoutContainingFrame = mInsetFrame;
-            mTmpRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
-            subtractInsets(windowFrames.mDisplayFrame, layoutContainingFrame, layoutDisplayFrame,
-                    mTmpRect);
-            if (!layoutInParentFrame()) {
-                subtractInsets(windowFrames.mContainingFrame, layoutContainingFrame,
-                        windowFrames.mParentFrame, mTmpRect);
-                subtractInsets(mInsetFrame, layoutContainingFrame, windowFrames.mParentFrame,
-                        mTmpRect);
-            }
-            layoutDisplayFrame.intersect(layoutContainingFrame);
-        }
-
-        final int pw = windowFrames.mContainingFrame.width();
-        final int ph = windowFrames.mContainingFrame.height();
+        final WindowFrames windowFrames = mWindowFrames;
 
         if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
             mLastRequestedWidth = mRequestedWidth;
@@ -1381,21 +1255,6 @@
             windowFrames.setContentChanged(true);
         }
 
-        final int fw = windowFrames.mFrame.width();
-        final int fh = windowFrames.mFrame.height();
-
-        applyGravityAndUpdateFrame(windowFrames, layoutContainingFrame, layoutDisplayFrame,
-                displayFrames);
-
-        if (mAttrs.type == TYPE_DOCK_DIVIDER) {
-            if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) {
-                mMovedByResize = true;
-            }
-        }
-
-        // Offset the actual frame by the amount layout frame is off.
-        windowFrames.offsetFrames(-layoutXDiff, -layoutYDiff);
-
         windowFrames.mCompatFrame.set(windowFrames.mFrame);
         if (hasCompatScale()) {
             // Also the scaled frame that we report to the app needs to be
@@ -1403,14 +1262,9 @@
             windowFrames.mCompatFrame.scale(mInvGlobalScale);
         }
 
-        if (mIsWallpaper && (fw != windowFrames.mFrame.width()
-                || fh != windowFrames.mFrame.height())) {
-            dc.mWallpaperController.updateWallpaperOffset(this, false /* sync */);
-        }
-
         // Calculate relative frame
         windowFrames.mRelFrame.set(windowFrames.mFrame);
-        WindowContainer parent = getParent();
+        WindowContainer<?> parent = getParent();
         int parentLeft = 0;
         int parentTop = 0;
         if (mIsChildWindow) {
@@ -1425,12 +1279,37 @@
                 windowFrames.mFrame.top - parentTop);
 
         if (DEBUG_LAYOUT || DEBUG) {
+            final int pw = windowFrames.mParentFrame.width();
+            final int ph = windowFrames.mParentFrame.height();
             Slog.v(TAG, "Resolving (mRequestedWidth="
-                            + mRequestedWidth + ", mRequestedheight="
-                            + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
-                            + "): frame=" + windowFrames.mFrame.toShortString()
-                            + " " + mAttrs.getTitle());
+                    + mRequestedWidth + ", mRequestedheight="
+                    + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
+                    + "): frame=" + windowFrames.mFrame.toShortString()
+                    + " " + mAttrs.getTitle());
         }
+
+        if (mAttrs.type == TYPE_DOCK_DIVIDER) {
+            if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) {
+                mMovedByResize = true;
+            }
+        }
+
+        if (mIsWallpaper) {
+            final Rect lastFrame = windowFrames.mLastFrame;
+            final Rect frame = windowFrames.mFrame;
+            if (lastFrame.width() != frame.width() || lastFrame.height() != frame.height()) {
+                mDisplayContent.mWallpaperController.updateWallpaperOffset(this, false /* sync */);
+            }
+        }
+
+        // Update the source frame to provide insets to other windows during layout.
+        if (mControllableInsetProvider != null) {
+            mControllableInsetProvider.updateSourceFrame();
+        }
+    }
+
+    // TODO(b/161810301): Remove this after INSETS_LAYOUT_GENERALIZATION is removed.
+    void computeFrameAndUpdateSourceFrame(DisplayFrames displayFrames) {
     }
 
     @Override
@@ -1463,10 +1342,6 @@
         return mWindowFrames.mParentFrame;
     }
 
-    Rect getContainingFrame() {
-        return mWindowFrames.mContainingFrame;
-    }
-
     void getCompatFrameSize(Rect outFrame) {
         outFrame.set(0, 0, mWindowFrames.mCompatFrame.width(), mWindowFrames.mCompatFrame.height());
     }
@@ -1493,21 +1368,11 @@
         return mDisableFlags;
     }
 
-    /** Gets the layer at which this window's surface will be Z-ordered. */
-    int getSurfaceLayer() {
-        return mLayer;
-    }
-
     @Override
     public int getBaseType() {
         return getTopParentWindow().mAttrs.type;
     }
 
-    /** Returns true if this window is participating in voice interaction. */
-    boolean isVoiceInteraction() {
-        return mActivityRecord != null && mActivityRecord.mVoiceInteraction;
-    }
-
     boolean setReportResizeHints() {
         return mWindowFrames.setReportResizeHints();
     }
@@ -1834,14 +1699,6 @@
                 && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
     }
 
-    boolean isIdentityMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
-        if (dsdx < .99999f || dsdx > 1.00001f) return false;
-        if (dtdy < .99999f || dtdy > 1.00001f) return false;
-        if (dtdx < -.000001f || dtdx > .000001f) return false;
-        if (dsdy < -.000001f || dsdy > .000001f) return false;
-        return true;
-    }
-
     void prelayout() {
         if (hasCompatScale()) {
             if (mOverrideScale != 1f) {
@@ -3441,15 +3298,6 @@
         return mAnimatingExit || (mActivityRecord != null && mActivityRecord.isClosingOrEnteringPip());
     }
 
-    void addWinAnimatorToList(ArrayList<WindowStateAnimator> animators) {
-        animators.add(mWinAnimator);
-
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
-            c.addWinAnimatorToList(animators);
-        }
-    }
-
     void sendAppVisibilityToClients() {
         super.sendAppVisibilityToClients();
 
@@ -4023,11 +3871,6 @@
         return getDisplayContent().mCurrentFocus == this;
     }
 
-    /** Is this window in a container that takes up the entire screen space? */
-    private boolean inAppWindowThatMatchesParentBounds() {
-        return mActivityRecord == null || (mActivityRecord.matchParentBounds() && !inMultiWindowMode());
-    }
-
     /**
      * @return true if activity bounds are letterboxed or letterboxed for diplay cutout.
      *
@@ -4402,107 +4245,10 @@
         return mStringNameCache;
     }
 
-    private void applyGravityAndUpdateFrame(WindowFrames windowFrames, Rect containingFrame,
-            Rect displayFrame, DisplayFrames displayFrames) {
-        final int pw = containingFrame.width();
-        final int ph = containingFrame.height();
-        final Task task = getTask();
-        final boolean inNonFullscreenContainer = !inAppWindowThatMatchesParentBounds();
-        final WindowManager.LayoutParams attrs = getLayoutingAttrs(displayFrames.mRotation);
-        final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
-
-        // We need to fit it to the display if either
-        // a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
-        // for the taskless windows)
-        // b) If it's a secondary app window, we also need to fit it to the display unless
-        // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
-        // screen, but SurfaceViews want to be always at a specific location so we don't fit it to
-        // the display.
-        final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
-                || ((attrs.type != TYPE_BASE_APPLICATION) && !noLimits);
-        float x, y;
-        int w,h;
-
-        final boolean hasCompatScale = hasCompatScale();
-        if ((attrs.flags & FLAG_SCALED) != 0 || mAttrs != attrs) {
-            // For the window with different layout attrs for different rotations, we need to avoid
-            // using requested size. Otherwise, when finishing a simulated rotation, the information
-            // coming from WindowManagerServices to the ViewRootImpl may not contain the correct
-            // value for the new rotation, and there will be a quick flash of wrong layout when the
-            // simulated activity faded out.
-            if (attrs.width < 0) {
-                w = pw;
-            } else if (hasCompatScale) {
-                w = (int) (attrs.width * mGlobalScale + .5f);
-            } else {
-                w = attrs.width;
-            }
-            if (attrs.height < 0) {
-                h = ph;
-            } else if (hasCompatScale) {
-                h = (int) (attrs.height * mGlobalScale + .5f);
-            } else {
-                h = attrs.height;
-            }
-        } else {
-            if (attrs.width == MATCH_PARENT) {
-                w = pw;
-            } else if (hasCompatScale) {
-                w = (int) (mRequestedWidth * mGlobalScale + .5f);
-            } else {
-                w = mRequestedWidth;
-            }
-            if (attrs.height == MATCH_PARENT) {
-                h = ph;
-            } else if (hasCompatScale) {
-                h = (int) (mRequestedHeight * mGlobalScale + .5f);
-            } else {
-                h = mRequestedHeight;
-            }
-        }
-
-        if (hasCompatScale) {
-            x = attrs.x * mGlobalScale;
-            y = attrs.y * mGlobalScale;
-        } else {
-            x = attrs.x;
-            y = attrs.y;
-        }
-
-        if (inNonFullscreenContainer && !layoutInParentFrame()) {
-            // Make sure window fits in containing frame since it is in a non-fullscreen task as
-            // required by {@link Gravity#apply} call.
-            w = Math.min(w, pw);
-            h = Math.min(h, ph);
-        }
-
-        // Set mFrame
-        Gravity.apply(attrs.gravity, w, h, containingFrame,
-                (int) (x + attrs.horizontalMargin * pw),
-                (int) (y + attrs.verticalMargin * ph), windowFrames.mFrame);
-        // Now make sure the window fits in the overall display frame.
-        if (fitToDisplay) {
-            Gravity.applyDisplay(attrs.gravity, displayFrame, windowFrames.mFrame);
-        }
-
-        // We need to make sure we update the CompatFrame as it is used for
-        // cropping decisions, etc, on systems where we lack a decor layer.
-        windowFrames.mCompatFrame.set(windowFrames.mFrame);
-        if (hasCompatScale) {
-            // See comparable block in computeFrameLw.
-            windowFrames.mCompatFrame.scale(mInvGlobalScale);
-        }
-    }
-
     boolean isChildWindow() {
         return mIsChildWindow;
     }
 
-    boolean layoutInParentFrame() {
-        return mIsChildWindow
-                && (mAttrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) != 0;
-    }
-
     /**
      * Returns true if any window added by an application process that if of type
      * {@link android.view.WindowManager.LayoutParams#TYPE_TOAST} or that requires that requires
@@ -4876,20 +4622,27 @@
 
     private boolean applyImeWindowsIfNeeded(ToBooleanFunction<WindowState> callback,
             boolean traverseTopToBottom) {
-        // If this window is the current IME target, so we need to process the IME windows
-        // directly above it. The exception is if we are in split screen
-        // in which case we process the IME at the DisplayContent level to
-        // ensure it is above the docked divider.
-        // (i.e. Like {@link DisplayContent.ImeContainer#skipImeWindowsDuringTraversal}, the IME
-        // window will be ignored to traverse when the IME target is still in split-screen mode).
-        if (isImeLayeringTarget()
-                && (!mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()
-                         || getTask() == null)) {
-            if (mDisplayContent.forAllImeWindows(callback, traverseTopToBottom)) {
-                return true;
-            }
+        // No need to apply to IME window if the window is not the current IME layering target.
+        if (!isImeLayeringTarget()) {
+            return false;
         }
-        return false;
+        // If we are in split screen which case we process the IME at the DisplayContent level to
+        // ensure it is above the docked divider.
+        // i.e. Like {@link DisplayContent.ImeContainer#skipImeWindowsDuringTraversal}, the IME
+        // window will be ignored to traverse when the IME target is still in split-screen mode.
+        if (mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()
+                && getTask() != null) {
+            return false;
+        }
+        // Note that we don't process IME window if the IME input target is not on the screen.
+        // In case some unexpected IME visibility cases happen like starting the remote
+        // animation on the keyguard but seeing the IME window that originally on the app
+        // which behinds the keyguard.
+        final WindowState imeInputTarget = getImeInputTarget();
+        if (imeInputTarget != null && !(imeInputTarget.isDrawn() || imeInputTarget.isVisible())) {
+            return false;
+        }
+        return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom);
     }
 
     private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback,
@@ -5877,7 +5630,7 @@
             // animation target (which will be different than the task bounds)
             outFrame.set(getTask().getParent().getBounds());
         } else {
-            outFrame.set(getContainingFrame());
+            outFrame.set(getParentFrame());
         }
         outSurfaceInsets.set(getAttrs().surfaceInsets);
         final InsetsState state = getInsetsStateWithVisibilityOverride();
@@ -5907,6 +5660,7 @@
         // to draw even if the children draw first or don't need to sync, so we start
         // in WAITING state rather than READY.
         mSyncState = SYNC_STATE_WAITING_FOR_DRAW;
+        mNextRelayoutUseSync = true;
         requestRedrawForSync();
         return true;
     }
@@ -6088,6 +5842,7 @@
      */
     void applyWithNextDraw(Consumer<SurfaceControl.Transaction> consumer) {
         mPendingDrawHandlers.add(consumer);
+        mNextRelayoutUseSync = true;
         requestRedrawForSync();
 
         mWmService.mH.sendNewMessageDelayed(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this,
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 423b3a0..b147455 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -118,9 +118,6 @@
      * window is first added or shown, cleared when the callback has been made. */
     boolean mEnteringAnimation;
 
-    /** The pixel format of the underlying SurfaceControl */
-    int mSurfaceFormat;
-
     /** This is set when there is no Surface */
     static final int NO_SURFACE = 0;
     /** This is set after the Surface has been created but before the window has been drawn. During
@@ -347,8 +344,6 @@
             mSurfaceController.setColorSpaceAgnostic((attrs.privateFlags
                     & WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
 
-            mSurfaceFormat = format;
-
             w.setHasSurface(true);
             // The surface instance is changed. Make sure the input info can be applied to the
             // new surface, e.g. relaunch activity.
@@ -514,8 +509,6 @@
             return;
         }
 
-        boolean displayed = false;
-
         computeShownFrameLocked();
 
         setSurfaceBoundariesLocked(t);
@@ -535,7 +528,6 @@
             }
         } else if (mLastAlpha != mShownAlpha
                 || mLastHidden) {
-            displayed = true;
             mLastAlpha = mShownAlpha;
             ProtoLog.i(WM_SHOW_TRANSACTIONS,
                     "SURFACE controller=%s alpha=%f HScale=%f, VScale=%f: %s",
@@ -566,14 +558,10 @@
                     }
                 }
             }
-            if (hasSurface()) {
-                w.mToken.hasVisible = true;
-            }
         } else {
             if (DEBUG_ANIM && mWin.isAnimating(TRANSITION | PARENTS)) {
                 Slog.v(TAG, "prepareSurface: No changes in animation for " + this);
             }
-            displayed = true;
         }
 
         if (w.getOrientationChanging()) {
@@ -589,31 +577,6 @@
                 ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change complete in %s", w);
             }
         }
-
-        if (displayed) {
-            w.mToken.hasVisible = true;
-        }
-    }
-
-    /**
-     * Try to change the pixel format without recreating the surface. This
-     * will be common in the case of changing from PixelFormat.OPAQUE to
-     * PixelFormat.TRANSLUCENT in the hardware-accelerated case as both
-     * requested formats resolve to the same underlying SurfaceControl format
-     * @return True if format was succesfully changed, false otherwise
-     */
-    boolean tryChangeFormatInPlaceLocked() {
-        if (mSurfaceController == null) {
-            return false;
-        }
-        final LayoutParams attrs = mWin.getAttrs();
-        final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
-        final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
-        if (format == mSurfaceFormat) {
-            setOpaqueLocked(!PixelFormat.formatHasAlpha(attrs.format));
-            return true;
-        }
-        return false;
     }
 
     void setOpaqueLocked(boolean isOpaque) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index ad351f0..34b63ac 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -26,8 +26,6 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainerChildProto.WINDOW_TOKEN;
@@ -102,9 +100,6 @@
     // Is key dispatching paused for this token?
     boolean paused = false;
 
-    // Temporary for finding which tokens no longer have visible windows.
-    boolean hasVisible;
-
     // Set to true when this token is in a pending transaction where it
     // will be shown.
     boolean waitingToShow;
@@ -240,6 +235,7 @@
         }
     }
 
+    /** Starts exit animation or hides windows if needed. It is only used for non-activity token. */
     void setExiting(boolean animateExit) {
         if (isEmpty()) {
             super.removeImmediately();
@@ -255,27 +251,15 @@
 
         final int count = mChildren.size();
         boolean changed = false;
-        final boolean delayed = isAnimating(TRANSITION | PARENTS)
-                || (isAnimating(CHILDREN, ANIMATION_TYPE_WINDOW_ANIMATION) && animateExit);
-
         for (int i = 0; i < count; i++) {
             final WindowState win = mChildren.get(i);
             changed |= win.onSetAppExiting(animateExit);
         }
 
-        final ActivityRecord app = asActivityRecord();
-        if (app != null) {
-            app.setVisible(false);
-        }
-
         if (changed) {
             mWmService.mWindowPlacerLocked.performSurfacePlacement();
             mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /*updateInputWindows*/);
         }
-
-        if (delayed) {
-            mDisplayContent.mExitingTokens.add(this);
-        }
     }
 
     /**
@@ -381,11 +365,6 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newParentConfig) {
-        super.onConfigurationChanged(newParentConfig);
-    }
-
-    @Override
     void assignLayer(SurfaceControl.Transaction t, int layer) {
         if (windowType == TYPE_DOCK_DIVIDER) {
             // See {@link DisplayContent#mSplitScreenDividerAnchor}
@@ -723,7 +702,6 @@
         super.dump(pw, prefix, dumpAll);
         pw.print(prefix); pw.print("windows="); pw.println(mChildren);
         pw.print(prefix); pw.print("windowType="); pw.print(windowType);
-                pw.print(" hasVisible="); pw.print(hasVisible);
         if (waitingToShow) {
             pw.print(" waitingToShow=true");
         }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 4e4a5c3..eb2c8a6 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -114,6 +114,8 @@
         "libutils",
         "libui",
         "libvibratorservice",
+        "PlatformProperties",
+        "InputFlingerProperties",
         "libinput",
         "libinputflinger",
         "libinputflinger_base",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 94b1ad1..790acbf 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -26,30 +26,14 @@
 // Log debug messages about InputDispatcherPolicy
 #define DEBUG_INPUT_DISPATCHER_POLICY 0
 
+#include <InputFlingerProperties.sysprop.h>
 #include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
 #include <android/os/IInputConstants.h>
+#include <android/sysprop/InputProperties.sysprop.h>
+#include <android_os_MessageQueue.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
-#include <limits.h>
-#include <atomic>
-#include <cinttypes>
-
-#include <utils/Log.h>
-#include <utils/Looper.h>
-#include <utils/threads.h>
-#include <utils/Trace.h>
-
-#include <binder/IServiceManager.h>
-
-#include <input/PointerController.h>
-#include <input/SpriteController.h>
-#include <ui/Region.h>
-
-#include <batteryservice/include/batteryservice/BatteryServiceConstants.h>
-#include <inputflinger/InputManager.h>
-
-#include <android_os_MessageQueue.h>
 #include <android_view_InputChannel.h>
 #include <android_view_InputDevice.h>
 #include <android_view_KeyEvent.h>
@@ -57,11 +41,25 @@
 #include <android_view_PointerIcon.h>
 #include <android_view_VerifiedKeyEvent.h>
 #include <android_view_VerifiedMotionEvent.h>
-
+#include <batteryservice/include/batteryservice/BatteryServiceConstants.h>
+#include <binder/IServiceManager.h>
+#include <input/PointerController.h>
+#include <input/SpriteController.h>
+#include <inputflinger/InputManager.h>
+#include <limits.h>
 #include <nativehelper/ScopedLocalFrame.h>
 #include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
+#include <ui/Region.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+#include <utils/Trace.h>
+#include <utils/threads.h>
+
+#include <atomic>
+#include <cinttypes>
+#include <vector>
 
 #include "android_hardware_display_DisplayViewport.h"
 #include "android_hardware_input_InputApplicationHandle.h"
@@ -69,8 +67,6 @@
 #include "android_util_Binder.h"
 #include "com_android_server_power_PowerManagerService.h"
 
-#include <vector>
-
 #define INDENT "  "
 
 using android::base::ParseUint;
@@ -2089,11 +2085,24 @@
             InputReaderConfiguration::CHANGE_DEVICE_ALIAS);
 }
 
-static jstring nativeDump(JNIEnv* env, jclass /* clazz */, jlong ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static std::string dumpInputProperties() {
+    std::string out = "Input properties:\n";
+    const bool perWindowInputRotation =
+            sysprop::InputFlingerProperties::per_window_input_rotation().value_or(false);
+    out += StringPrintf("  per_window_input_rotation = %s\n", toString(perWindowInputRotation));
+    const std::string strategy =
+            sysprop::InputProperties::velocitytracker_strategy().value_or("default");
+    out += "  persist.input.velocitytracker.strategy = " + strategy + "\n";
+    out += "\n";
+    return out;
+}
 
-    std::string dump;
+static jstring nativeDump(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+    std::string dump = dumpInputProperties();
+
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
     im->dump(dump);
+
     return env->NewStringUTF(dump.c_str());
 }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f351a8c..ce03b59 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -251,6 +251,8 @@
             "com.android.server.print.PrintManagerService";
     private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
             "com.android.server.companion.CompanionDeviceManagerService";
+    private static final String VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS =
+            "com.android.server.companion.virtual.VirtualDeviceManagerService";
     private static final String STATS_COMPANION_APEX_PATH =
             "/apex/com.android.os.statsd/javalib/service-statsd.jar";
     private static final String SCHEDULING_APEX_PATH =
@@ -2322,6 +2324,11 @@
                 t.traceBegin("StartCompanionDeviceManager");
                 mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS);
                 t.traceEnd();
+
+                // VirtualDeviceManager depends on CDM to control the associations.
+                t.traceBegin("StartVirtualDeviceManager");
+                mSystemServiceManager.startService(VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS);
+                t.traceEnd();
             }
 
             t.traceBegin("StartRestrictionManager");
diff --git a/services/tests/PackageManager/TEST_MAPPING b/services/tests/PackageManager/TEST_MAPPING
new file mode 100644
index 0000000..72d4c82
--- /dev/null
+++ b/services/tests/PackageManager/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "presubmit": [
+    {
+      "name": "PackageInstallerTests"
+    }
+  ]
+}
+
diff --git a/services/tests/PackageManagerComponentOverrideTests/TEST_MAPPING b/services/tests/PackageManagerComponentOverrideTests/TEST_MAPPING
new file mode 100644
index 0000000..528c949
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "presubmit": [
+    {
+      "name": "PackageManagerComponentOverrideTests"
+    }
+  ]
+}
+
diff --git a/services/tests/PackageManagerServiceTests/OWNERS b/services/tests/PackageManagerServiceTests/OWNERS
index 182dfe8..86ae581 100644
--- a/services/tests/PackageManagerServiceTests/OWNERS
+++ b/services/tests/PackageManagerServiceTests/OWNERS
@@ -1,3 +1 @@
-chiuwinson@google.com
-patb@google.com
-toddke@google.com
+include /PACKAGE_MANAGER_OWNERS
diff --git a/services/tests/PackageManagerServiceTests/TEST_MAPPING b/services/tests/PackageManagerServiceTests/TEST_MAPPING
new file mode 100644
index 0000000..af0008c
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "AppEnumerationInternalTests"
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "PackageManagerServiceHostTests"
+    }
+  ],
+  "imports": [
+    {
+      "path": "frameworks/base/services/tests/PackageManagerServiceTests/unit"
+    }
+  ]
+}
+
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 6a4d69f..b72f05f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -43,7 +43,6 @@
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode;
 import android.content.pm.StagedApexInfo;
-import android.os.Message;
 import android.os.SystemProperties;
 import android.os.storage.IStorageManager;
 import android.platform.test.annotations.Presubmit;
@@ -113,7 +112,7 @@
         when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true");
 
         mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest");
-        mStagingManager = new StagingManager(mContext, null, mApexManager);
+        mStagingManager = new StagingManager(mContext, mApexManager);
     }
 
     @After
@@ -123,32 +122,6 @@
         }
     }
 
-    /**
-     * Tests that sessions committed later shouldn't cause earlier ones to fail the overlapping
-     * check.
-     */
-    @Test
-    public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes()
-            throws Exception {
-        // Create 2 sessions with overlapping packages
-        StagingManager.StagedSession session1 = createSession(111, "com.foo", 1);
-        StagingManager.StagedSession session2 = createSession(222, "com.foo", 2);
-
-        mStagingManager.createSession(session1);
-        mStagingManager.createSession(session2);
-        // Session1 should not fail in spite of the overlapping packages
-        mStagingManager.checkNonOverlappingWithStagedSessions(session1);
-        // setSessionFailed() should've been called when doing overlapping checks on session1
-        verify(session2, times(1)).setSessionFailed(anyInt(), anyString());
-
-        // Yet another session with overlapping packages
-        StagingManager.StagedSession session3 = createSession(333, "com.foo", 3);
-        mStagingManager.createSession(session3);
-        assertThrows(PackageManagerException.class,
-                () -> mStagingManager.checkNonOverlappingWithStagedSessions(session3));
-        verify(session3, never()).setSessionFailed(anyInt(), anyString());
-    }
-
     @Test
     public void restoreSessions_nonParentSession_throwsIAE() throws Exception {
         FakeStagedSession session = new FakeStagedSession(239);
@@ -726,10 +699,9 @@
         {
             FakeStagedSession session = new FakeStagedSession(239);
             session.setIsApex(true);
-            mStagingManager.createSession(session);
-
+            session.setSessionReady();
             mockApexManagerGetStagedApexInfoWithSessionId();
-            triggerEndOfPreRebootVerification(session);
+            mStagingManager.commitSession(session);
 
             assertThat(session.isSessionReady()).isTrue();
             ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass(
@@ -744,9 +716,8 @@
             Mockito.clearInvocations(observer);
             FakeStagedSession session = new FakeStagedSession(240);
             session.setIsApex(true);
-            mStagingManager.createSession(session);
-
-            triggerEndOfPreRebootVerification(session);
+            session.setSessionReady();
+            mStagingManager.commitSession(session);
 
             assertThat(session.isSessionReady()).isTrue();
             ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass(
@@ -762,9 +733,8 @@
             Mockito.clearInvocations(observer);
             FakeStagedSession session = new FakeStagedSession(241);
             session.setIsApex(true);
-            mStagingManager.createSession(session);
-
-            triggerEndOfPreRebootVerification(session);
+            session.setSessionReady();
+            mStagingManager.commitSession(session);
 
             assertThat(session.isSessionReady()).isTrue();
             verify(observer, never()).onApexStaged(any());
@@ -800,21 +770,13 @@
 
         //  Trigger end of pre-reboot verification
         FakeStagedSession session = new FakeStagedSession(239);
-        mStagingManager.createSession(session);
+        session.setSessionReady();
+        mStagingManager.commitSession(session);
 
-        triggerEndOfPreRebootVerification(session);
         assertThat(session.isSessionReady()).isTrue();
         verify(observer, never()).onApexStaged(any());
     }
 
-    private void triggerEndOfPreRebootVerification(StagingManager.StagedSession session) {
-        StagingManager.PreRebootVerificationHandler handler =
-                mStagingManager.mPreRebootVerificationHandler;
-        Message msg =  handler.obtainMessage(
-                handler.MSG_PRE_REBOOT_VERIFICATION_END, session.sessionId(), -1, session);
-        handler.handleMessage(msg);
-    }
-
     private StagingManager.StagedSession createSession(int sessionId, String packageName,
             long committedMillis) {
         PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
@@ -1071,9 +1033,6 @@
         }
 
         @Override
-        public void notifyEndPreRebootVerification() {}
-
-        @Override
         public void verifySession() {
             mVerificationStarted = true;
         }
diff --git a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
index cb12ba7..a2ecbc3 100644
--- a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
@@ -47,7 +47,6 @@
     @Mock BatteryService.HealthServiceWrapper.IHealthSupplier mHealthServiceSupplier;
     BatteryService.HealthServiceWrapper mWrapper;
 
-    private static final String HEALTHD = BatteryService.HealthServiceWrapper.INSTANCE_HEALTHD;
     private static final String VENDOR = BatteryService.HealthServiceWrapper.INSTANCE_VENDOR;
 
     @Override
@@ -117,7 +116,7 @@
 
     @SmallTest
     public void testWrapPreferVendor() throws Exception {
-        initForInstances(VENDOR, HEALTHD);
+        initForInstances(VENDOR);
         mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
         waitHandlerThreadFinish();
         verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
@@ -126,16 +125,6 @@
     }
 
     @SmallTest
-    public void testUseHealthd() throws Exception {
-        initForInstances(HEALTHD);
-        mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
-        waitHandlerThreadFinish();
-        verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(HEALTHD));
-        verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
-        verify(mCallback, times(1)).onRegistration(same(mMockedHal), same(mMockedHal2), eq(HEALTHD));
-    }
-
-    @SmallTest
     public void testNoService() throws Exception {
         initForInstances("unrelated");
         try {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index fb5c557..96af617 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -100,7 +100,11 @@
             MagnificationAnimationCallback.class);
     private final MagnificationInfoChangedCallback mRequestObserver = mock(
             MagnificationInfoChangedCallback.class);
-    final MessageCapturingHandler mMessageCapturingHandler = new MessageCapturingHandler(null);
+    private final MessageCapturingHandler mMessageCapturingHandler = new MessageCapturingHandler(
+            null);
+    private final MagnificationScaleProvider mScaleProvider = mock(
+            MagnificationScaleProvider.class);
+
 
     ValueAnimator mMockValueAnimator;
     ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
@@ -123,7 +127,7 @@
         initMockWindowManager();
 
         mFullScreenMagnificationController = new FullScreenMagnificationController(
-                mMockControllerCtx, new Object(), mRequestObserver);
+                mMockControllerCtx, new Object(), mRequestObserver, mScaleProvider);
     }
 
     @After
@@ -412,12 +416,12 @@
         MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
         PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter,
-                FullScreenMagnificationController.MAX_SCALE);
+                MagnificationScaleProvider.MAX_SCALE);
         MagnificationSpec endSpec = getMagnificationSpec(
-                FullScreenMagnificationController.MAX_SCALE, offsets);
+                MagnificationScaleProvider.MAX_SCALE, offsets);
 
         assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
-                FullScreenMagnificationController.MAX_SCALE + 1.0f,
+                MagnificationScaleProvider.MAX_SCALE + 1.0f,
                 newCenter.x, newCenter.y, false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
@@ -632,31 +636,6 @@
     }
 
     @Test
-    public void testSetUserId_resetsOnlyIfIdChanges() {
-        for (int i = 0; i < DISPLAY_COUNT; i++) {
-            testSetUserId_resetsOnlyIfIdChanges(i);
-            resetMockWindowManager();
-        }
-    }
-
-    private void testSetUserId_resetsOnlyIfIdChanges(int displayId) {
-        final int userId1 = 1;
-        final int userId2 = 2;
-
-        register(displayId);
-        mFullScreenMagnificationController.setUserId(userId1);
-        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
-        float scale = 2.0f;
-        mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
-                false, SERVICE_ID_1);
-
-        mFullScreenMagnificationController.setUserId(userId1);
-        assertTrue(mFullScreenMagnificationController.isMagnifying(displayId));
-        mFullScreenMagnificationController.setUserId(userId2);
-        assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
-    }
-
-    @Test
     public void testResetIfNeeded_doesWhatItSays() {
         for (int i = 0; i < DISPLAY_COUNT; i++) {
             testResetIfNeeded_doesWhatItSays(i);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 6c32f7e..2060223 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -22,6 +22,8 @@
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 import static com.android.server.testutils.TestUtils.strictMock;
 
 import static org.junit.Assert.assertFalse;
@@ -38,16 +40,15 @@
 
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
-import android.content.Context;
 import android.graphics.PointF;
 import android.os.Handler;
 import android.os.Message;
+import android.testing.TestableContext;
 import android.util.DebugUtils;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.accessibility.AccessibilityManagerService;
@@ -60,6 +61,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -121,7 +123,6 @@
 
     private static final int DISPLAY_0 = 0;
 
-    private Context mContext;
     FullScreenMagnificationController mFullScreenMagnificationController;
     @Mock
     MagnificationGestureHandler.Callback mMockCallback;
@@ -134,6 +135,9 @@
     @Mock
     AccessibilityTraceManager mMockTraceManager;
 
+    @Rule
+    public final TestableContext mContext = new TestableContext(getInstrumentation().getContext());
+
     private OffsettableClock mClock;
     private FullScreenMagnificationGestureHandler mMgh;
     private TestHandler mHandler;
@@ -143,7 +147,6 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mContext = InstrumentationRegistry.getContext();
         final FullScreenMagnificationController.ControllerContext mockController =
                 mock(FullScreenMagnificationController.ControllerContext.class);
         final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
@@ -157,14 +160,16 @@
         when(mockController.getAnimationDuration()).thenReturn(1000L);
         when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
         mFullScreenMagnificationController = new FullScreenMagnificationController(mockController,
-                new Object(), mMagnificationInfoChangedCallback) {
+                new Object(), mMagnificationInfoChangedCallback,
+                new MagnificationScaleProvider(mContext)) {
             @Override
             public boolean magnificationRegionContains(int displayId, float x, float y) {
                 return true;
             }
 
             @Override
-            void setForceShowMagnifiableBounds(int displayId, boolean show) {}
+            void setForceShowMagnifiableBounds(int displayId, boolean show) {
+            }
         };
         mFullScreenMagnificationController.register(DISPLAY_0);
         mClock = new OffsettableClock.Stopped();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 2cb3d27..69061c1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -46,6 +46,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
+import android.testing.DexmakerShareClassLoaderRule;
 import android.view.Display;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
@@ -58,6 +59,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -79,7 +81,8 @@
     private static final float MAGNIFIED_CENTER_X = 100;
     private static final float MAGNIFIED_CENTER_Y = 200;
     private static final float DEFAULT_SCALE = 3f;
-    private static final int CURRENT_USER_ID = UserHandle.USER_CURRENT;
+    private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
+    private static final int SECOND_USER_ID = CURRENT_USER_ID + 1;
     private static final int MODE_WINDOW = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
     private static final int MODE_FULLSCREEN =
             Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
@@ -94,6 +97,7 @@
     private Context mContext;
     @Mock
     private FullScreenMagnificationController mScreenMagnificationController;
+    private MagnificationScaleProvider mScaleProvider;
     @Captor
     private ArgumentCaptor<MagnificationAnimationCallback> mCallbackArgumentCaptor;
 
@@ -103,6 +107,11 @@
     private MagnificationController mMagnificationController;
     private FullScreenMagnificationControllerStubber mScreenMagnificationControllerStubber;
 
+    // To mock package-private class
+    @Rule
+    public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+            new DexmakerShareClassLoaderRule();
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -113,15 +122,17 @@
         Settings.Secure.putFloatForUser(mMockResolver,
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_SCALE,
                 CURRENT_USER_ID);
+        mScaleProvider = spy(new MagnificationScaleProvider(mContext));
         mWindowMagnificationManager = Mockito.spy(
                 new WindowMagnificationManager(mContext, CURRENT_USER_ID,
-                        mock(WindowMagnificationManager.Callback.class), mTraceManager));
+                        mock(WindowMagnificationManager.Callback.class), mTraceManager,
+                        mScaleProvider));
         mMockConnection = new MockWindowMagnificationConnection(true);
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mScreenMagnificationControllerStubber = new FullScreenMagnificationControllerStubber(
                 mScreenMagnificationController);
         mMagnificationController = spy(new MagnificationController(mService, new Object(), mContext,
-                mScreenMagnificationController, mWindowMagnificationManager));
+                mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider));
 
         mMagnificationController.setMagnificationCapabilities(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
@@ -283,14 +294,16 @@
 
         verify(mScreenMagnificationController).onDisplayRemoved(TEST_DISPLAY);
         verify(mWindowMagnificationManager).onDisplayRemoved(TEST_DISPLAY);
+        verify(mScaleProvider).onDisplayRemoved(TEST_DISPLAY);
     }
 
     @Test
-    public void updateUserIdIfNeeded_AllModulesAvailable_setUserId() {
-        mMagnificationController.updateUserIdIfNeeded(CURRENT_USER_ID);
+    public void updateUserIdIfNeeded_AllModulesAvailable_disableMagnificationAndChangeUserId() {
+        mMagnificationController.updateUserIdIfNeeded(SECOND_USER_ID);
 
-        verify(mScreenMagnificationController).setUserId(CURRENT_USER_ID);
-        verify(mWindowMagnificationManager).setUserId(CURRENT_USER_ID);
+        verify(mScreenMagnificationController).resetAllIfNeeded(false);
+        verify(mWindowMagnificationManager).disableAllWindowMagnifiers();
+        verify(mScaleProvider).onUserChanged(SECOND_USER_ID);
     }
 
     @Test
@@ -575,6 +588,13 @@
         verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt());
     }
 
+    @Test
+    public void onUserRemoved_notifyScaleProvider() {
+        mMagnificationController.onUserRemoved(SECOND_USER_ID);
+
+        verify(mScaleProvider).onUserRemoved(SECOND_USER_ID);
+    }
+
     private void setMagnificationEnabled(int mode) throws RemoteException {
         setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
     }
@@ -627,7 +647,8 @@
                     TEST_DISPLAY);
             doAnswer(invocation -> mIsMagnifying).when(
                     mScreenMagnificationController).isForceShowMagnifiableBounds(TEST_DISPLAY);
-            doAnswer(invocation -> mScale).when(mScreenMagnificationController).getPersistedScale();
+            doAnswer(invocation -> mScale).when(mScreenMagnificationController).getPersistedScale(
+                    TEST_DISPLAY);
             doAnswer(invocation -> mScale).when(mScreenMagnificationController).getScale(
                     TEST_DISPLAY);
             doAnswer(invocation -> mCenterX).when(mScreenMagnificationController).getCenterX(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationScaleProviderTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationScaleProviderTest.java
new file mode 100644
index 0000000..9b392b2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationScaleProviderTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.UserHandle;
+import android.testing.TestableContext;
+import android.view.Display;
+
+import com.android.compatibility.common.util.TestUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+/**
+ * Tests for {@link MagnificationScaleProvider}.
+ */
+public class MagnificationScaleProviderTest {
+
+    private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1;
+    private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
+    private static final int SECOND_USER_ID = CURRENT_USER_ID + 1;
+
+    private static final float TEST_SCALE = 3;
+    private static final float DEFAULT_SCALE =
+            MagnificationScaleProvider.DEFAULT_MAGNIFICATION_SCALE;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(getInstrumentation().getContext());
+
+    private MagnificationScaleProvider mScaleProvider;
+
+    @Before
+    public void setUp() {
+        mScaleProvider = new MagnificationScaleProvider(mContext);
+    }
+
+    @Test
+    public void putScaleOnDefaultDisplay_getExpectedValue() throws Exception {
+        mScaleProvider.putScale(TEST_SCALE, Display.DEFAULT_DISPLAY);
+
+        TestUtils.waitUntil("settings value is not changed",
+                () -> Float.compare(mScaleProvider.getScale(Display.DEFAULT_DISPLAY),
+                TEST_SCALE) == 0);
+    }
+
+    @Test
+    public void putScaleOnTestDisplay_getExpectedValue() {
+        mScaleProvider.putScale(TEST_SCALE, TEST_DISPLAY);
+
+        assertEquals(TEST_SCALE, mScaleProvider.getScale(TEST_DISPLAY), 0);
+    }
+
+    @Test
+    public void onUserChanged_putScale_fallbackToDefaultScale() {
+        mScaleProvider.putScale(TEST_SCALE, TEST_DISPLAY);
+
+        mScaleProvider.onUserChanged(SECOND_USER_ID);
+        assertEquals(DEFAULT_SCALE, mScaleProvider.getScale(TEST_DISPLAY), 0);
+    }
+
+    @Test
+    public void onUserRemoved_setScaleOnSecondUser_fallbackToDefaultScale() {
+        mScaleProvider.onUserChanged(SECOND_USER_ID);
+        mScaleProvider.putScale(TEST_SCALE, TEST_DISPLAY);
+        mScaleProvider.onUserChanged(CURRENT_USER_ID);
+
+        mScaleProvider.onUserRemoved(SECOND_USER_ID);
+        // Assume the second user is created with the same id
+        mScaleProvider.onUserChanged(SECOND_USER_ID);
+
+        assertEquals(DEFAULT_SCALE, mScaleProvider.getScale(TEST_DISPLAY), 0);
+    }
+
+    @Test
+    public void onTestDisplayRemoved_setScaleOnTestDisplay_fallbackToDefaultScale() {
+        mScaleProvider.putScale(TEST_SCALE, TEST_DISPLAY);
+
+        mScaleProvider.onDisplayRemoved(TEST_DISPLAY);
+
+        assertEquals(DEFAULT_SCALE, mScaleProvider.getScale(TEST_DISPLAY), 0);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index 95f4327..1b8aff5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -21,10 +21,10 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 
-import android.content.Context;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.RemoteException;
+import android.testing.TestableContext;
 import android.util.DebugUtils;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -39,6 +39,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -67,7 +68,10 @@
     public static final float DEFAULT_TAP_Y = 299;
     private static final int DISPLAY_0 = MockWindowMagnificationConnection.TEST_DISPLAY;
 
-    private Context mContext;
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
     private WindowMagnificationManager mWindowMagnificationManager;
     private MockWindowMagnificationConnection mMockConnection;
     private WindowMagnificationGestureHandler mWindowMagnificationGestureHandler;
@@ -79,9 +83,9 @@
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
-        mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mWindowMagnificationManager = new WindowMagnificationManager(mContext, 0,
-                mock(WindowMagnificationManager.Callback.class), mMockTrace);
+                mock(WindowMagnificationManager.Callback.class), mMockTrace,
+                new MagnificationScaleProvider(mContext));
         mMockConnection = new MockWindowMagnificationConnection();
         mWindowMagnificationGestureHandler = new WindowMagnificationGestureHandler(
                 mContext, mWindowMagnificationManager, mMockTrace, mMockCallback,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index af6d40f..da881c4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -67,7 +67,7 @@
 public class WindowMagnificationManagerTest {
 
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
-    private static final int CURRENT_USER_ID = UserHandle.USER_CURRENT;
+    private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
 
     private MockWindowMagnificationConnection mMockConnection;
     @Mock
@@ -91,7 +91,7 @@
         mResolver = new MockContentResolver();
         mMockConnection = new MockWindowMagnificationConnection();
         mWindowMagnificationManager = new WindowMagnificationManager(mContext, CURRENT_USER_ID,
-                mMockCallback, mMockTrace);
+                mMockCallback, mMockTrace, new MagnificationScaleProvider(mContext));
 
         when(mContext.getContentResolver()).thenReturn(mResolver);
         doAnswer((InvocationOnMock invocation) -> {
@@ -230,7 +230,7 @@
     public void getPersistedScale() {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
 
-        assertEquals(mWindowMagnificationManager.getPersistedScale(), 2.5f);
+        assertEquals(mWindowMagnificationManager.getPersistedScale(TEST_DISPLAY), 2.5f);
     }
 
     @Test
@@ -264,7 +264,7 @@
         mWindowMagnificationManager.setScale(TEST_DISPLAY, 10.0f);
 
         assertEquals(mWindowMagnificationManager.getScale(TEST_DISPLAY),
-                WindowMagnificationManager.MAX_SCALE);
+                MagnificationScaleProvider.MAX_SCALE);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
index 0602a55..f009488 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
@@ -334,10 +334,8 @@
             launchToken=${this.launchToken}
             lockTaskLaunchMode=${this.lockTaskLaunchMode}
             logo=${this.logo}
-            maxAspectRatio=${this.maxAspectRatio}
             maxRecents=${this.maxRecents}
             metaData=${this.metaData.dumpToString()}
-            minAspectRatio=${this.minAspectRatio}
             name=${this.name}
             nonLocalizedLabel=${
                 // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 50ebffc31..d4420bd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -24,8 +24,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -41,7 +42,6 @@
 import android.service.notification.NotificationListenerService;
 import android.util.ArraySet;
 import android.util.Pair;
-import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
@@ -355,4 +355,19 @@
                 .getDisallowedPackages()).isEmpty();
     }
 
+    @Test
+    public void testHasAllowedListener() {
+        final int uid1 = 1, uid2 = 2;
+        // enable mCn1 but not mCn2 for uid1
+        mListeners.addApprovedList(mCn1.flattenToString(), uid1, true);
+
+        // verify that:
+        // the package for mCn1 has an allowed listener for uid1 and not uid2
+        assertTrue(mListeners.hasAllowedListener(mCn1.getPackageName(), uid1));
+        assertFalse(mListeners.hasAllowedListener(mCn1.getPackageName(), uid2));
+
+        // and that mCn2 has no allowed listeners for either user id
+        assertFalse(mListeners.hasAllowedListener(mCn2.getPackageName(), uid1));
+        assertFalse(mListeners.hasAllowedListener(mCn2.getPackageName(), uid2));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f61d300..e4f889b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3708,6 +3708,8 @@
 
         assertEquals(IMPORTANCE_LOW,
                 mService.getNotificationRecord(sbn.getKey()).getImportance());
+        assertEquals(IMPORTANCE_UNSPECIFIED, mBinderService.getPackageImportance(
+                sbn.getPackageName()));
 
         nb = new Notification.Builder(mContext)
                 .setContentTitle("foo")
@@ -8354,4 +8356,53 @@
         verify(mPermissionHelper, never()).hasPermission(anyInt());
         verify(mPreferencesHelper, never()).getNotificationChannelsBypassingDnd(PKG, mUid);
     }
+
+    @Test
+    public void testMatchesCallFilter_noPermissionShouldThrow() throws Exception {
+        // make sure a caller without listener access or read_contacts permission can't call
+        // matchesCallFilter.
+        when(mListeners.hasAllowedListener(anyString(), anyInt())).thenReturn(false);
+        doThrow(new SecurityException()).when(mContext).enforceCallingPermission(
+                eq("android.permission.READ_CONTACTS"), anyString());
+
+        try {
+            // shouldn't matter what we're passing in, if we get past this line fail immediately
+            ((INotificationManager) mService.mService).matchesCallFilter(null);
+            fail("call to matchesCallFilter with no permissions should fail");
+        } catch (SecurityException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testMatchesCallFilter_hasListenerPermission() throws Exception {
+        // make sure a caller with only listener access and not read_contacts permission can call
+        // matchesCallFilter.
+        when(mListeners.hasAllowedListener(anyString(), anyInt())).thenReturn(true);
+        doThrow(new SecurityException()).when(mContext).enforceCallingPermission(
+                eq("android.permission.READ_CONTACTS"), anyString());
+
+        try {
+            ((INotificationManager) mService.mService).matchesCallFilter(null);
+            // pass, this is not a functionality test
+        } catch (SecurityException e) {
+            fail("call to matchesCallFilter with listener permissions should work");
+        }
+    }
+
+    @Test
+    public void testMatchesCallFilter_hasContactsPermission() throws Exception {
+        // make sure a caller with only read_contacts permission and not listener access can call
+        // matchesCallFilter.
+        when(mListeners.hasAllowedListener(anyString(), anyInt())).thenReturn(false);
+        doNothing().when(mContext).enforceCallingPermission(
+                eq("android.permission.READ_CONTACTS"), anyString());
+
+        try {
+            ((INotificationManager) mService.mService).matchesCallFilter(null);
+            // pass, this is not a functionality test
+        } catch (SecurityException e) {
+            fail("call to matchesCallFilter with listener permissions should work");
+        }
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index 3ec8729..016b579 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -21,6 +21,7 @@
 import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.UserHandle.USER_SYSTEM;
@@ -123,6 +124,7 @@
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -663,4 +665,17 @@
         verify(mWorkerHandler, never()).post(
                 any(NotificationManagerService.EnqueueNotificationRunnable.class));
     }
+
+    @Test
+    public void testDefaultChannelDoesNotUpdateApp_postMigrationToPermissions() throws Exception {
+        final NotificationChannel defaultChannel = mBinderService.getNotificationChannel(
+                PKG_N_MR1, ActivityManager.getCurrentUser(), PKG_N_MR1,
+                NotificationChannel.DEFAULT_CHANNEL_ID);
+        defaultChannel.setImportance(IMPORTANCE_NONE);
+
+        mBinderService.updateNotificationChannelForPackage(PKG_N_MR1, mUid, defaultChannel);
+
+        verify(mPermissionHelper).setNotificationPermission(
+                PKG_N_MR1, ActivityManager.getCurrentUser(), false, true);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 5324ec5f..d90f91a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -4072,6 +4072,28 @@
         assertTrue((channelA.getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0);
         assertTrue((channelB.getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0);
         assertTrue((channelC.getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0);
+    }
 
+    @Test
+    public void testDefaultChannelUpdatesApp_preMigrationToPermissions() throws Exception {
+        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG_N_MR1,
+                UID_N_MR1,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        defaultChannel.setImportance(IMPORTANCE_NONE);
+        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true);
+
+        assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_N_MR1, UID_N_MR1));
+    }
+
+    @Test
+    public void testDefaultChannelDoesNotUpdateApp_postMigrationToPermissions() throws Exception {
+        when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
+        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG_N_MR1,
+                UID_N_MR1,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        defaultChannel.setImportance(IMPORTANCE_NONE);
+        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true);
+
+        assertEquals(IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1, UID_N_MR1));
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 4957ab9..03132c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -50,7 +50,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.spy;
@@ -107,9 +106,6 @@
     @Before
     public void setUp() throws Exception {
         mWindow = spy(createWindow(null, TYPE_APPLICATION, "window"));
-        // We only test window frames set by DisplayPolicy, so here prevents computeFrameLw from
-        // changing those frames.
-        doNothing().when(mWindow).computeFrame(any());
 
         spyOn(mStatusBarWindow);
         spyOn(mNavBarWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 08312ef..575e082 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -36,12 +36,14 @@
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -699,6 +701,51 @@
         }
     }
 
+    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @Test
+    public void testLaunchRemoteAnimationWithoutImeBehind() {
+        final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
+        final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2");
+
+        // Simulating win1 has shown IME and being IME layering/input target
+        mDisplayContent.setImeLayeringTarget(win1);
+        mDisplayContent.setImeInputTarget(win1);
+        mImeWindow.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
+        mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test");
+        spyOn(mDisplayContent);
+        doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController).hasSurface();
+        doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController)
+                .prepareToShowInTransaction(any(), anyFloat());
+        makeWindowVisibleAndDrawn(mImeWindow);
+        assertTrue(mImeWindow.isOnScreen());
+        assertFalse(mImeWindow.isParentWindowHidden());
+
+        try {
+            // Simulating now win1 is being covered by the lockscreen which has no surface,
+            // and then launching an activity win2 with the remote animation
+            win1.mHasSurface = false;
+            mDisplayContent.mOpeningApps.add(win2.mActivityRecord);
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+                    win2.mActivityRecord, new Point(50, 100), null,
+                    new Rect(50, 100, 150, 150), null).mAdapter;
+            adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                    mFinishedCallback);
+
+            mDisplayContent.applySurfaceChangesTransaction();
+            mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+            verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
+                    any(), any(), any(), any());
+            // Verify the IME window won't apply surface change transaction with forAllImeWindows
+            verify(mDisplayContent, never()).forAllImeWindows(any(), eq(true));
+        } catch (Exception e) {
+            // no-op
+        } finally {
+            mDisplayContent.mOpeningApps.clear();
+        }
+    }
+
     private AnimationAdapter setupForNonAppTargetNavBar(int transit, boolean shouldAttachNavBar) {
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
         mDisplayContent.mOpeningApps.add(win.mActivityRecord);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index dd7ad21..73e571a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1123,6 +1123,98 @@
     }
 
     @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+    public void testOverrideMinAspectRatioScreenOrientationNotSetThenChangedToPortrait() {
+        // In this test, the activity's orientation isn't fixed to portrait, therefore the override
+        // isn't applied.
+
+        setUpDisplaySizeWithApp(1000, 1200);
+
+        // Create a size compat activity on the same task.
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setUid(android.os.Process.myUid())
+                .build();
+
+        // The per-package override should have no effect
+        assertEquals(1200, activity.getBounds().height());
+        assertEquals(1000, activity.getBounds().width());
+
+        // After changing the orientation to portrait the override should be applied.
+        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        activity.clearSizeCompatMode();
+
+        // The per-package override forces the activity into a 3:2 aspect ratio
+        assertEquals(1200, activity.getBounds().height());
+        assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+                activity.getBounds().width(), 0.5);
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+    public void testOverrideMinAspectRatioScreenOrientationLandscapeThenChangedToPortrait() {
+        // In this test, the activity's orientation isn't fixed to portrait, therefore the override
+        // isn't applied.
+
+        setUpDisplaySizeWithApp(1000, 1200);
+
+        // Create a size compat activity on the same task.
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setUid(android.os.Process.myUid())
+                .build();
+
+        // The per-package override should have no effect
+        assertEquals(1200, activity.getBounds().height());
+        assertEquals(1000, activity.getBounds().width());
+
+        // After changing the orientation to portrait the override should be applied.
+        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        activity.clearSizeCompatMode();
+
+        // The per-package override forces the activity into a 3:2 aspect ratio
+        assertEquals(1200, activity.getBounds().height());
+        assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+                activity.getBounds().width(), 0.5);
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+    public void testOverrideMinAspectRatioScreenOrientationPortraitThenChangedToUnspecified() {
+        setUpDisplaySizeWithApp(1000, 1200);
+
+        // Create a size compat activity on the same task.
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setUid(android.os.Process.myUid())
+                .build();
+
+        // The per-package override forces the activity into a 3:2 aspect ratio
+        assertEquals(1200, activity.getBounds().height());
+        assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+                activity.getBounds().width(), 0.5);
+
+        // After changing the orientation to landscape the override shouldn't be applied.
+        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+        activity.clearSizeCompatMode();
+
+        // The per-package override should have no effect
+        assertEquals(1200, activity.getBounds().height());
+        assertEquals(1000, activity.getBounds().width());
+    }
+
+    @Test
     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
     public void testOverrideMinAspectRatioWithoutGlobalOverride() {
         // In this test, only OVERRIDE_MIN_ASPECT_RATIO_1_5 is set, which has no effect without
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
deleted file mode 100644
index 316309c..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static org.junit.Assert.assertEquals;
-
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-import android.view.DisplayInfo;
-import android.view.Gravity;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.InsetsVisibilities;
-import android.view.WindowManager;
-
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-/**
- * Tests for the {@link WindowState#computeFrameLw} method and other window frame machinery.
- *
- * Build/Install/Run:
- *  atest WmTests:WindowFrameTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class WindowFrameTests extends WindowTestsBase {
-
-    private DisplayContent mTestDisplayContent;
-    private DisplayFrames mTestDisplayFrames;
-
-    @Before
-    public void setUp() throws Exception {
-        DisplayInfo testDisplayInfo = new DisplayInfo(mDisplayInfo);
-        testDisplayInfo.displayCutout = null;
-        mTestDisplayContent = createNewDisplay(testDisplayInfo);
-        mTestDisplayFrames = mTestDisplayContent.mDisplayFrames;
-    }
-
-    // Do not use this function directly in the tests below. Instead, use more explicit function
-    // such as assertFlame().
-    private void assertRect(Rect rect, int left, int top, int right, int bottom) {
-        assertEquals(left, rect.left);
-        assertEquals(top, rect.top);
-        assertEquals(right, rect.right);
-        assertEquals(bottom, rect.bottom);
-    }
-
-    private void assertFrame(WindowState w, Rect frame) {
-        assertEquals(w.getFrame(), frame);
-    }
-
-    private void assertFrame(WindowState w, int left, int top, int right, int bottom) {
-        assertRect(w.getFrame(), left, top, right, bottom);
-    }
-
-    private void assertRelFrame(WindowState w, int left, int top, int right, int bottom) {
-        assertRect(w.getRelativeFrame(), left, top, right, bottom);
-    }
-
-    @Test
-    public void testLayoutInFullscreenTask() {
-        // fullscreen task doesn't use bounds for computeFrame
-        WindowState w = createWindow();
-        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
-
-        // With no insets or system decor all the frames incoming from PhoneWindowManager
-        // are identical.
-        final Rect pf = new Rect(0, 0, 1000, 1000);
-
-        // Here the window has FILL_PARENT, FILL_PARENT
-        // so we expect it to fill the entire available frame.
-        w.getWindowFrames().setFrames(pf, pf);
-        w.computeFrame(mTestDisplayFrames);
-        assertFrame(w, 0, 0, 1000, 1000);
-        assertRelFrame(w, 0, 0, 1000, 1000);
-
-        // It can select various widths and heights within the bounds.
-        // Strangely the window attribute width is ignored for normal windows
-        // and we use mRequestedWidth/mRequestedHeight
-        w.mAttrs.width = 300;
-        w.mAttrs.height = 300;
-        w.computeFrame(mTestDisplayFrames);
-        // Explicit width and height without requested width/height
-        // gets us nothing.
-        assertFrame(w, 0, 0, 0, 0);
-
-        w.mRequestedWidth = 300;
-        w.mRequestedHeight = 300;
-        w.computeFrame(mTestDisplayFrames);
-        // With requestedWidth/Height we can freely choose our size within the
-        // parent bounds.
-        assertFrame(w, 0, 0, 300, 300);
-
-        // With FLAG_SCALED though, requestedWidth/height is used to control
-        // the unscaled surface size, and mAttrs.width/height becomes the
-        // layout controller.
-        w.mAttrs.flags = WindowManager.LayoutParams.FLAG_SCALED;
-        w.mRequestedHeight = -1;
-        w.mRequestedWidth = -1;
-        w.mAttrs.width = 100;
-        w.mAttrs.height = 100;
-        w.computeFrame(mTestDisplayFrames);
-        assertFrame(w, 0, 0, 100, 100);
-        w.mAttrs.flags = 0;
-
-        // But sizes too large will be clipped to the containing frame
-        w.mRequestedWidth = 1200;
-        w.mRequestedHeight = 1200;
-        w.computeFrame(mTestDisplayFrames);
-        assertFrame(w, 0, 0, 1000, 1000);
-
-        // Before they are clipped though windows will be shifted
-        w.mAttrs.x = 300;
-        w.mAttrs.y = 300;
-        w.mRequestedWidth = 1000;
-        w.mRequestedHeight = 1000;
-        w.computeFrame(mTestDisplayFrames);
-        assertFrame(w, 0, 0, 1000, 1000);
-
-        // If there is room to move around in the parent frame the window will be shifted according
-        // to gravity.
-        w.mAttrs.x = 0;
-        w.mAttrs.y = 0;
-        w.mRequestedWidth = 300;
-        w.mRequestedHeight = 300;
-        w.mAttrs.gravity = Gravity.RIGHT | Gravity.TOP;
-        w.computeFrame(mTestDisplayFrames);
-        assertFrame(w, 700, 0, 1000, 300);
-        assertRelFrame(w, 700, 0, 1000, 300);
-        w.mAttrs.gravity = Gravity.RIGHT | Gravity.BOTTOM;
-        w.computeFrame(mTestDisplayFrames);
-        assertFrame(w, 700, 700, 1000, 1000);
-        assertRelFrame(w, 700, 700, 1000, 1000);
-        // Window specified  x and y are interpreted as offsets in the opposite
-        // direction of gravity
-        w.mAttrs.x = 100;
-        w.mAttrs.y = 100;
-        w.computeFrame(mTestDisplayFrames);
-        assertFrame(w, 600, 600, 900, 900);
-        assertRelFrame(w, 600, 600, 900, 900);
-    }
-
-    @Test
-    public void testLayoutNonfullscreenTask() {
-        removeGlobalMinSizeRestriction();
-        final DisplayInfo displayInfo = mWm.getDefaultDisplayContentLocked().getDisplayInfo();
-        final int logicalWidth = displayInfo.logicalWidth;
-        final int logicalHeight = displayInfo.logicalHeight;
-
-        final Rect taskBounds = new Rect(
-                logicalWidth / 4, logicalHeight / 4, logicalWidth / 4 * 3, logicalHeight / 4 * 3);
-        WindowState w = createWindow();
-        final Task task = w.getTask();
-        // Use split-screen because it is non-fullscreen, but also not floating
-        task.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        task.setBounds(taskBounds);
-        // The bounds we are requesting might be different from what the system resolved based on
-        // other factors.
-        final Rect resolvedTaskBounds = task.getBounds();
-        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
-
-        final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
-        final WindowFrames windowFrames = w.getWindowFrames();
-        windowFrames.setFrames(pf, pf);
-        w.computeFrame(mTestDisplayFrames);
-        // For non fullscreen tasks the containing frame is based off the
-        // task bounds not the parent frame.
-        assertEquals(resolvedTaskBounds, w.getFrame());
-        assertEquals(0, w.getRelativeFrame().left);
-        assertEquals(0, w.getRelativeFrame().top);
-
-        pf.set(0, 0, logicalWidth, logicalHeight);
-        // We still produce insets against the containing frame the same way.
-        final int cfRight = logicalWidth / 2;
-        final int cfBottom = logicalHeight / 2;
-        final Rect cf = new Rect(0, 0, cfRight, cfBottom);
-        windowFrames.setFrames(pf, pf);
-        w.computeFrame(mTestDisplayFrames);
-        assertEquals(resolvedTaskBounds, w.getFrame());
-        assertEquals(0, w.getRelativeFrame().left);
-        assertEquals(0, w.getRelativeFrame().top);
-    }
-
-    @Test
-    @FlakyTest(bugId = 137879065)
-    public void testLayoutLetterboxedWindow() {
-        // First verify task behavior in multi-window mode.
-        final DisplayInfo displayInfo = mTestDisplayContent.getDisplayInfo();
-        final int logicalWidth = displayInfo.logicalWidth;
-        final int logicalHeight = displayInfo.logicalHeight;
-
-        final int taskLeft = logicalWidth / 5;
-        final int taskTop = logicalHeight / 5;
-        final int taskRight = logicalWidth / 4 * 3;
-        final int taskBottom = logicalHeight / 4 * 3;
-        final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom);
-        WindowState w = createWindow();
-        final Task task = w.getTask();
-        // Use split-screen because it is non-fullscreen, but also not floating
-        task.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        task.setBounds(taskBounds);
-        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
-
-        final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
-        final WindowFrames windowFrames = w.getWindowFrames();
-        windowFrames.setFrames(pf, pf);
-        w.computeFrame(mTestDisplayFrames);
-        // For non fullscreen tasks the containing frame is based off the
-        // task bounds not the parent frame.
-        assertFrame(w, taskLeft, taskTop, taskRight, taskBottom);
-
-        // Now simulate switch to fullscreen for letterboxed app.
-        final int xInset = logicalWidth / 10;
-        final Rect cf = new Rect(xInset, 0, logicalWidth - xInset, logicalHeight);
-        Configuration config = new Configuration(w.mActivityRecord.getRequestedOverrideConfiguration());
-        config.windowConfiguration.setBounds(cf);
-        config.windowConfiguration.setAppBounds(cf);
-        w.mActivityRecord.onRequestedOverrideConfigurationChanged(config);
-        pf.set(0, 0, logicalWidth, logicalHeight);
-        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        task.setBounds(null);
-        windowFrames.setFrames(pf, pf);
-        w.computeFrame(mTestDisplayFrames);
-        assertFrame(w, cf);
-    }
-
-    @Test
-    public void testFreeformContentInsets() {
-        removeGlobalMinSizeRestriction();
-        // fullscreen task doesn't use bounds for computeFrame
-        WindowState w = createWindow();
-        final Task task = w.getTask();
-        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
-        task.setWindowingMode(WINDOWING_MODE_FREEFORM);
-
-        // Make IME attach to the window and can produce insets.
-        final DisplayContent dc = mTestDisplayContent;
-        dc.setImeLayeringTarget(w);
-        WindowState mockIme = mock(WindowState.class);
-        Mockito.doReturn(true).when(mockIme).isVisibleNow();
-        dc.mInputMethodWindow = mockIme;
-        final InsetsState state = dc.getInsetsStateController().getRawInsetsState();
-        final InsetsSource imeSource = state.getSource(ITYPE_IME);
-        final Rect imeFrame = new Rect(state.getDisplayFrame());
-        imeFrame.top = 400;
-        imeSource.setFrame(imeFrame);
-        imeSource.setVisible(true);
-        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
-        requestedVisibilities.setVisibility(ITYPE_IME, true);
-        w.setRequestedVisibilities(requestedVisibilities);
-        w.mAboveInsetsState.addSource(imeSource);
-
-        // With no insets or system decor all the frames incoming from PhoneWindowManager
-        // are identical.
-        final Rect pf = new Rect(0, 0, 1000, 800);
-
-        // First check that it only gets moved up enough to show window.
-        final Rect winRect = new Rect(200, 200, 300, 500);
-        task.setBounds(winRect);
-        w.getWindowFrames().setFrames(pf, pf);
-        w.computeFrame(mTestDisplayFrames);
-        assertFrame(w, winRect.left, imeFrame.top - winRect.height(), winRect.right, imeFrame.top);
-
-        // Now check that it won't get moved beyond the top
-        winRect.bottom = 650;
-        task.setBounds(winRect);
-        w.setBounds(winRect);
-        w.getWindowFrames().setFrames(pf, pf);
-        w.computeFrame(mTestDisplayFrames);
-        assertFrame(w, winRect.left, 0, winRect.right, winRect.height());
-
-        // Now we have status bar. Check that it won't go into the status bar area.
-        final Rect statusBarFrame = new Rect(state.getDisplayFrame());
-        statusBarFrame.bottom = 60;
-        state.getSource(ITYPE_STATUS_BAR).setFrame(statusBarFrame);
-        w.getWindowFrames().setFrames(pf, pf);
-        w.computeFrame(mTestDisplayFrames);
-        assertFrame(w, winRect.left, statusBarFrame.bottom, winRect.right,
-                statusBarFrame.bottom + winRect.height());
-
-        // Check that it's moved back without ime insets
-        state.removeSource(ITYPE_IME);
-        w.getWindowFrames().setFrames(pf, pf);
-        w.computeFrame(mTestDisplayFrames);
-        assertEquals(winRect, w.getFrame());
-    }
-
-    private WindowState createWindow() {
-        final WindowState ws = createWindow(null, TYPE_APPLICATION, mTestDisplayContent, "WindowFrameTests");
-        spyOn(ws);
-        return ws;
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
new file mode 100644
index 0000000..117f2ff
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.WindowConfiguration;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.Gravity;
+import android.view.InsetsState;
+import android.view.InsetsVisibilities;
+import android.view.WindowLayout;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for the {@link WindowLayout#computeWindowFrames}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:WindowLayoutTests
+ */
+@SmallTest
+@Presubmit
+public class WindowLayoutTests {
+    private static final int DISPLAY_WIDTH = 500;
+    private static final int DISPLAY_HEIGHT = 1000;
+    private static final int STATUS_BAR_HEIGHT = 10;
+    private static final int NAVIGATION_BAR_HEIGHT = 15;
+
+    private final WindowLayout mWindowLayout = new WindowLayout();
+    private final Rect mDisplayFrame = new Rect();
+    private final Rect mParentFrame = new Rect();
+    private final Rect mFrame = new Rect();
+
+    private WindowManager.LayoutParams mAttrs;
+    private InsetsState mState;
+    private final Rect mDisplayCutoutSafe = new Rect();
+    private Rect mWindowBounds;
+    private int mWindowingMode;
+    private int mRequestedWidth;
+    private int mRequestedHeight;
+    private InsetsVisibilities mRequestedVisibilities;
+    private Rect mAttachedWindowFrame;
+    private float mCompatScale;
+
+    @Before
+    public void setUp() {
+        mAttrs = new WindowManager.LayoutParams();
+        mState = new InsetsState();
+        mState.setDisplayFrame(new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
+        mState.getSource(InsetsState.ITYPE_STATUS_BAR).setFrame(
+                0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT);
+        mState.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setFrame(
+                0, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+        mState.getDisplayCutoutSafe(mDisplayCutoutSafe);
+        mWindowBounds = new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+        mWindowingMode = WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+        mRequestedWidth = DISPLAY_WIDTH;
+        mRequestedHeight = DISPLAY_HEIGHT;
+        mRequestedVisibilities = new InsetsVisibilities();
+        mAttachedWindowFrame = null;
+        mCompatScale = 1f;
+    }
+
+    @Test
+    public void testDefaultLayoutParams() {
+        computeWindowFrames();
+
+        assertRect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
+                mDisplayFrame);
+        assertRect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
+                mParentFrame);
+        assertRect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
+                mFrame);
+    }
+
+    @Test
+    public void testUnmeasured() {
+        mRequestedWidth = UNSPECIFIED_LENGTH;
+        mRequestedHeight = UNSPECIFIED_LENGTH;
+        computeWindowFrames();
+
+        assertRect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
+                mDisplayFrame);
+        assertRect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
+                mParentFrame);
+        assertRect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
+                mFrame);
+    }
+
+    @Test
+    public void testUnmeasuredWithSizeSpecifiedInLayoutParams() {
+        final int width = DISPLAY_WIDTH / 2;
+        final int height = DISPLAY_HEIGHT / 2;
+        mRequestedWidth = UNSPECIFIED_LENGTH;
+        mRequestedHeight = UNSPECIFIED_LENGTH;
+        mAttrs.width = width;
+        mAttrs.height = height;
+        mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
+        computeWindowFrames();
+
+        assertRect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
+                mDisplayFrame);
+        assertRect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
+                mParentFrame);
+        assertRect(0, STATUS_BAR_HEIGHT, width, STATUS_BAR_HEIGHT + height,
+                mFrame);
+    }
+
+    @Test
+    public void testNonFullscreenWindowBounds() {
+        final int top = Math.max(DISPLAY_HEIGHT / 2, STATUS_BAR_HEIGHT);
+        mWindowBounds.set(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+        mRequestedWidth = UNSPECIFIED_LENGTH;
+        mRequestedHeight = UNSPECIFIED_LENGTH;
+        computeWindowFrames();
+
+        assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
+                mDisplayFrame);
+        assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
+                mParentFrame);
+        assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
+                mFrame);
+    }
+
+    // TODO(b/161810301): Move tests here from DisplayPolicyLayoutTests and add more tests.
+
+    private void computeWindowFrames() {
+        mWindowLayout.computeWindowFrames(mAttrs, mState, mDisplayCutoutSafe, mWindowBounds,
+                mWindowingMode, mRequestedWidth, mRequestedHeight, mRequestedVisibilities,
+                mAttachedWindowFrame, mCompatScale, mDisplayFrame, mParentFrame, mFrame);
+    }
+
+    private void assertRect(int left, int top, int right, int bottom, Rect actual) {
+        assertEquals(new Rect(left, top, right, bottom), actual);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ac61bb1..056a07a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -285,8 +285,6 @@
                 mStatusBarWindow.mAttrs.gravity = Gravity.TOP;
                 mStatusBarWindow.mAttrs.layoutInDisplayCutoutMode =
                         LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-                mStatusBarWindow.setRequestedSize(WindowManager.LayoutParams.MATCH_PARENT,
-                        STATUS_BAR_HEIGHT);
             }
         }
         if (addAll || ArrayUtils.contains(requestedWindows, W_NOTIFICATION_SHADE)) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 6c3a1f6..dfbdede 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -23,7 +23,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -469,24 +468,42 @@
 
     @Test
     public void testDockedDividerPosition() {
-        final WindowState pinnedStackWindow = createWindow(null, WINDOWING_MODE_PINNED,
-                ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent,
-                "pinnedStackWindow");
-        final WindowState splitScreenWindow = createWindow(null,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
-                mDisplayContent, "splitScreenWindow");
-        final WindowState splitScreenSecondaryWindow = createWindow(null,
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
-                TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow");
-        final WindowState assistantStackWindow = createWindow(null,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
-                mDisplayContent, "assistantStackWindow");
+        final Task pinnedTask =
+                createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        final WindowState pinnedWindow =
+                createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow");
+
+        final Task belowTask =
+                createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState belowTaskWindow =
+                createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow");
+
+        final Task splitScreenTask1 =
+                createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        final WindowState splitWindow1 =
+                createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1");
+        final Task splitScreenTask2 =
+                createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        final WindowState splitWindow2 =
+                createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2");
+        splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2);
+        splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1);
+
+        final Task aboveTask =
+                createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState aboveTaskWindow =
+                createAppWindow(aboveTask, ACTIVITY_TYPE_STANDARD, "aboveTaskWindow");
 
         mDisplayContent.assignChildLayers(mTransaction);
 
-        assertWindowHigher(mDockedDividerWindow, splitScreenWindow);
-        assertWindowHigher(mDockedDividerWindow, splitScreenSecondaryWindow);
-        assertWindowHigher(pinnedStackWindow, mDockedDividerWindow);
+        assertWindowHigher(splitWindow1, belowTaskWindow);
+        assertWindowHigher(splitWindow1, belowTaskWindow);
+        assertWindowHigher(splitWindow2, belowTaskWindow);
+        assertWindowHigher(splitWindow2, belowTaskWindow);
+        assertWindowHigher(mDockedDividerWindow, splitWindow1);
+        assertWindowHigher(mDockedDividerWindow, splitWindow2);
+        assertWindowHigher(aboveTaskWindow, mDockedDividerWindow);
+        assertWindowHigher(pinnedWindow, aboveTaskWindow);
     }
 
     @Test
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 7a424c8..10fba60 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -650,7 +650,13 @@
     private static boolean checkCarrierPrivilegeForAnySubId(Context context, int uid) {
         SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-        int[] activeSubIds = sm.getCompleteActiveSubscriptionIdList();
+        int[] activeSubIds;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            activeSubIds = sm.getCompleteActiveSubscriptionIdList();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
         for (int activeSubId : activeSubIds) {
             if (getCarrierPrivilegeStatus(context, activeSubId, uid)
                     == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index edfb7bf..22eb50a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1886,6 +1886,20 @@
             "lte_plus_threshold_bandwidth_khz_int";
 
     /**
+     * The combined channel bandwidth threshold (non-inclusive) in KHz required to display the
+     * NR advanced (i.e. 5G+) data icon. It is 0 by default, meaning minimum bandwidth check is
+     * not enabled. Other factors like bands or frequency can also determine whether the NR
+     * advanced data icon is shown or not.
+     *
+     * @see #KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY
+     * @see #KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT
+     *
+     * @hide
+     */
+    public static final String KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT =
+            "nr_advanced_threshold_bandwidth_khz_int";
+
+    /**
      * The string is used to filter redundant string from PLMN Network Name that's supplied by
      * specific carrier.
      *
@@ -3550,6 +3564,17 @@
             "nr_advanced_capable_pco_id_int";
 
     /**
+     * Enabled NR advanced (i.e. 5G+) icon while roaming. The default value is {@code true}, meaming
+     * the same NR advanced logic used for home network will be used for roaming network as well.
+     * Set this to {@code false} will disable NR advanced icon while the device is roaming,
+     * regardless meeting NR advanced criteria or not.
+     *
+     * @hide
+     */
+    public static final String KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL =
+            "enable_nr_advanced_for_roaming_bool";
+
+    /**
      * This configuration allows the framework to use user data communication to detect Idle state,
      * and this is used on the 5G icon.
      *
@@ -5227,6 +5252,16 @@
     public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";
 
     /**
+     * Flag specifying whether VoNR should be enabled for carrier.
+     * If true, VoNr will be enabled. If false, hard disabled.
+     *
+     * Disabled by default.
+     *
+     * @hide
+     */
+    public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
+
+    /**
      * Determine whether unthrottle data retry when tracking area code (TAC/LAC) from cell changes
      *
      * @hide
@@ -5622,6 +5657,7 @@
         sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, "");
         sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
         sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
+        sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0);
         sDefaults.putIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
                 new int[]{CARRIER_NR_AVAILABILITY_NSA, CARRIER_NR_AVAILABILITY_SA});
         sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
@@ -5717,6 +5753,7 @@
         sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000);
         sDefaults.putIntArray(KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY, new int[0]);
         sDefaults.putInt(KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0);
+        sDefaults.putBoolean(KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL, true);
         sDefaults.putBoolean(KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, false);
         sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_BOOL, false);
         sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, false);
@@ -5856,6 +5893,7 @@
         sDefaults.putBoolean(KEY_DISPLAY_NO_DATA_NOTIFICATION_ON_PERMANENT_FAILURE_BOOL, false);
         sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, false);
+        sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false);
     }
 
     /**
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 3b44a34..d5315ac 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -949,6 +949,15 @@
     public static final String VOIMS_OPT_IN_STATUS = SimInfo.COLUMN_VOIMS_OPT_IN_STATUS;
 
     /**
+     * TelephonyProvider column name for NR Advanced calling
+     * Determines if the user has enabled VoNR settings for this subscription.
+     *
+     * @hide
+     */
+    public static final String NR_ADVANCED_CALLING_ENABLED =
+            SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED;
+
+    /**
      * Profile class of the subscription
      * @hide
      */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a4afea1..9c52220 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2951,7 +2951,12 @@
      * currently in use on the device for data transmission.
      *
      * If this object has been created with {@link #createForSubscriptionId}, applies to the given
-     * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+     * subId. Otherwise, applies to {@link SubscriptionManager#getActiveDataSubscriptionId()}.
+     *
+     * Note: Before {@link SubscriptionManager#getActiveDataSubscriptionId()} was introduced in API
+     * level 30, it was applied to {@link SubscriptionManager#getDefaultDataSubscriptionId()} which
+     * may be different now from {@link SubscriptionManager#getActiveDataSubscriptionId()}, e.g.
+     * when opportunistic network is providing cellular internet connection to the user.
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index e890acb..9572154 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -36,6 +36,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -152,16 +153,9 @@
                     throw new RuntimeException(
                         "Failed to find NetworkScanInfo with id " + message.arg2);
                 }
-                NetworkScanCallback callback = nsi.mCallback;
-                Executor executor = nsi.mExecutor;
-                if (callback == null) {
-                    throw new RuntimeException(
-                        "Failed to find NetworkScanCallback with id " + message.arg2);
-                }
-                if (executor == null) {
-                    throw new RuntimeException(
-                        "Failed to find Executor with id " + message.arg2);
-                }
+
+                final NetworkScanCallback callback = nsi.mCallback;
+                final Executor executor = nsi.mExecutor;
 
                 switch (message.what) {
                     case CALLBACK_RESTRICTED_SCAN_RESULTS:
@@ -246,17 +240,24 @@
             NetworkScanRequest request, Executor executor, NetworkScanCallback callback,
             String callingPackage, @Nullable String callingFeatureId) {
         try {
+            Objects.requireNonNull(request, "Request was null");
+            Objects.requireNonNull(callback, "Callback was null");
+            Objects.requireNonNull(executor, "Executor was null");
             final ITelephony telephony = getITelephony();
             if (telephony == null) return null;
 
-            int scanId = telephony.requestNetworkScan(
-                    subId, request, mMessenger, new Binder(), callingPackage,
-                    callingFeatureId);
-            if (scanId == INVALID_SCAN_ID) {
-                Rlog.e(TAG, "Failed to initiate network scan");
-                return null;
-            }
+            // The lock must be taken before calling requestNetworkScan because the resulting
+            // scanId can be invoked asynchronously on another thread at any time after
+            // requestNetworkScan invoked, leaving a critical section between that call and adding
+            // the record to the ScanInfo cache.
             synchronized (mScanInfo) {
+                int scanId = telephony.requestNetworkScan(
+                        subId, request, mMessenger, new Binder(), callingPackage,
+                        callingFeatureId);
+                if (scanId == INVALID_SCAN_ID) {
+                    Rlog.e(TAG, "Failed to initiate network scan");
+                    return null;
+                }
                 // We link to death whenever a scan is started to ensure that we are linked
                 // at the point that phone process death might matter.
                 // We never unlink because:
diff --git a/tests/DynamicCodeLoggerIntegrationTests/OWNERS b/tests/DynamicCodeLoggerIntegrationTests/OWNERS
new file mode 100644
index 0000000..d9eb141
--- /dev/null
+++ b/tests/DynamicCodeLoggerIntegrationTests/OWNERS
@@ -0,0 +1 @@
+file:/services/core/java/com/android/server/pm/dex/OWNERS
diff --git a/tests/SharedLibraryLoadingTest/Android.bp b/tests/SharedLibraryLoadingTest/Android.bp
new file mode 100644
index 0000000..088278d
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_test_host {
+    name: "SharedLibraryLoadingTests",
+    libs: [
+        "tradefed",
+        "junit",
+    ],
+    test_suites: ["general-tests"],
+    data: [
+        ":SharedLibraryLoadingTests_StandardSharedLibrary",
+        ":SharedLibraryLoadingTests_SharedLibraryLoadedAfter",
+        ":SharedLibraryLoadingTests_SharedLibraryClientTests",
+        ":SharedLibraryLoadingTests_Overlay",
+    ],
+}
diff --git a/tests/SharedLibraryLoadingTest/AndroidTest.xml b/tests/SharedLibraryLoadingTest/AndroidTest.xml
new file mode 100644
index 0000000..947453d
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/AndroidTest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<configuration description="Host-driven test module config for SharedLibraryHostTests">
+    <option name="test-tag" value="SharedLibraryLoadingTests" />
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="false" />
+        <option name="remount-system" value="true" />
+        <option name="push"
+                value="SharedLibraryLoadingTests_StandardSharedLibrary.apk->/product/app/SharedLibraryLoadingTests_StandardSharedLibrary.apk" />
+        <option name="push"
+                value="SharedLibraryLoadingTests_SharedLibraryLoadedAfter.apk->/product/app/SharedLibraryLoadingTests_SharedLibraryLoadedAfter.apk" />
+        <option name="push"
+                value="SharedLibraryLoadingTests_Overlay.apk->/product/overlay/SharedLibraryLoadingTests_Overlay.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer" />
+
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="SharedLibraryLoadingTests_SharedLibraryClientTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.sharedlibloadingtest.client" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/SharedLibraryLoadingTest/OWNERS b/tests/SharedLibraryLoadingTest/OWNERS
new file mode 100644
index 0000000..d7b4569
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/OWNERS
@@ -0,0 +1,2 @@
+stenning@google.com
+
diff --git a/tests/SharedLibraryLoadingTest/test-apps/Overlay/Android.bp b/tests/SharedLibraryLoadingTest/test-apps/Overlay/Android.bp
new file mode 100644
index 0000000..b2f4e89
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/Overlay/Android.bp
@@ -0,0 +1,29 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "SharedLibraryLoadingTests_Overlay",
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: ["--no-resource-removal"],
+}
diff --git a/tests/SharedLibraryLoadingTest/test-apps/Overlay/AndroidManifest.xml b/tests/SharedLibraryLoadingTest/test-apps/Overlay/AndroidManifest.xml
new file mode 100644
index 0000000..ae2784c
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/Overlay/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.sharedlibloadingtest.overlay">
+    <application android:hasCode="false" />
+    <overlay android:targetPackage="android"
+             android:isStatic="true"
+             android:priority="1"/>
+</manifest>
\ No newline at end of file
diff --git a/core/java/android/companion/Association.aidl b/tests/SharedLibraryLoadingTest/test-apps/Overlay/res/values/config.xml
similarity index 60%
copy from core/java/android/companion/Association.aidl
copy to tests/SharedLibraryLoadingTest/test-apps/Overlay/res/values/config.xml
index 2a28f1f..15da3db 100644
--- a/core/java/android/companion/Association.aidl
+++ b/tests/SharedLibraryLoadingTest/test-apps/Overlay/res/values/config.xml
@@ -1,11 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,6 +15,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion;
-
-parcelable Association;
+-->
+<resources>
+    <string-array name="config_sharedLibrariesLoadedAfterApp" translatable="false">
+        <item>com.android.sharedlibloadingtest.shared_library_after</item>
+    </string-array>
+</resources>
\ No newline at end of file
diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/Android.bp b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/Android.bp
new file mode 100644
index 0000000..0d20497
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "SharedLibraryLoadingTests_SharedLibraryClientTests",
+    srcs: ["**/*.java"],
+    resource_dirs: ["res"],
+    libs: [
+        "SharedLibraryLoadingTests_StandardSharedLibrary",
+        "SharedLibraryLoadingTests_SharedLibraryLoadedAfter",
+        "android.test.base",
+    ],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "androidx.test.core",
+        "testng",
+    ],
+    platform_apis: true,
+}
diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/AndroidManifest.xml b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/AndroidManifest.xml
new file mode 100644
index 0000000..e3a9b9b
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.sharedlibloadingtest.client">
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="com.android.sharedlibloadingtest.shared_library"/>
+        <uses-library android:name="com.android.sharedlibloadingtest.shared_library_after"/>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.sharedlibloadingtest.client"  />
+</manifest>
\ No newline at end of file
diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/res/values/values.xml b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/res/values/values.xml
new file mode 100644
index 0000000..5e0544e
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <string name="identical_resource_key">client value</string>
+</resources>
\ No newline at end of file
diff --git a/core/java/android/companion/Association.aidl b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/ClientClass.java
similarity index 72%
copy from core/java/android/companion/Association.aidl
copy to tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/ClientClass.java
index 2a28f1f..e48fb83 100644
--- a/core/java/android/companion/Association.aidl
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/ClientClass.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -13,6 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion;
 
-parcelable Association;
+package com.android.sharedlibloadingtest;
+
+public class ClientClass {
+    @Override
+    public String toString() {
+        return "Client Code";
+    }
+}
diff --git a/core/java/android/companion/Association.aidl b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassA.java
similarity index 71%
copy from core/java/android/companion/Association.aidl
copy to tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassA.java
index 2a28f1f..4c77155 100644
--- a/core/java/android/companion/Association.aidl
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassA.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -13,6 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion;
 
-parcelable Association;
+package com.android.sharedlibloadingtest;
+
+public class DuplicateClassA {
+    @Override
+    public String toString() {
+        return "Client's Version";
+    }
+}
diff --git a/core/java/android/companion/Association.aidl b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassB.java
similarity index 71%
copy from core/java/android/companion/Association.aidl
copy to tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassB.java
index 2a28f1f..86aa6a1 100644
--- a/core/java/android/companion/Association.aidl
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/DuplicateClassB.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -13,6 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion;
 
-parcelable Association;
+package com.android.sharedlibloadingtest;
+
+public class DuplicateClassB {
+    @Override
+    public String toString() {
+        return "Client's Version B";
+    }
+}
diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/client/SharedLibraryLoadingOrderTest.java b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/client/SharedLibraryLoadingOrderTest.java
new file mode 100644
index 0000000..43bcb1a
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryClientTests/src/com/android/sharedlibloadingtest/client/SharedLibraryLoadingOrderTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sharedlibloadingtest.client;
+
+import static org.testng.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.util.Preconditions;
+import com.android.sharedlibloadingtest.ClientClass;
+import com.android.sharedlibloadingtest.DuplicateClassA;
+import com.android.sharedlibloadingtest.DuplicateClassB;
+import com.android.sharedlibloadingtest.SharedClassAfter;
+import com.android.sharedlibloadingtest.StdSharedClass;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.HashSet;
+
+@RunWith(AndroidJUnit4.class)
+public class SharedLibraryLoadingOrderTest {
+
+    @Test
+    public void testLoadingOfStdShareLibsShouldBeFirst() {
+        Preconditions.checkArgument(!getLibsLoadedAfter()
+                .contains("com.android.sharedlibloadingtest.shared_library"));
+        DuplicateClassA clazz = new DuplicateClassA();
+        assertEquals(clazz.toString(), "Standard Shared Lib's Version");
+
+        StdSharedClass stdSharedClass = new StdSharedClass();
+        assertEquals(stdSharedClass.toString(), "Nothing Special Lib");
+
+        ClientClass clientCode = new ClientClass();
+        assertEquals(clientCode.toString(), "Client Code");
+    }
+
+    @Test
+    public void testLoadingOfShareLibsIsAfter() {
+        Preconditions.checkArgument(getLibsLoadedAfter()
+                .contains("com.android.sharedlibloadingtest.shared_library_after"));
+        DuplicateClassB clazz = new DuplicateClassB();
+        assertEquals(clazz.toString(), "Client's Version B");
+
+        SharedClassAfter stdSharedClass = new SharedClassAfter();
+        assertEquals(stdSharedClass.toString(), "Also Nothing Special");
+
+        ClientClass clientCode = new ClientClass();
+        assertEquals(clientCode.toString(), "Client Code");
+    }
+
+    @Test
+    public void testLoadingOfResource() {
+        // aapt compiler gives each lib their own namespace so this test just confirming
+        // the resources can be loaded from the same context object
+        Context context = ApplicationProvider.getApplicationContext();
+        String clientString = context.getResources().getString(R.string.identical_resource_key);
+        assertEquals(clientString, "client value");
+        assertEquals(StdSharedClass.getResString(context), "std lib value");
+        assertEquals(SharedClassAfter.getResString(context), "loaded after value");
+
+    }
+
+    private HashSet<String> getLibsLoadedAfter() {
+        Resources systemR = Resources.getSystem();
+        HashSet<String> libsToLoadAfter = new HashSet<>();
+        Collections.addAll(libsToLoadAfter, systemR.getStringArray(
+                com.android.internal.R.array.config_sharedLibrariesLoadedAfterApp));
+        return libsToLoadAfter;
+    }
+}
diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/Android.bp b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/Android.bp
new file mode 100644
index 0000000..db9b3ed
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "SharedLibraryLoadingTests_SharedLibraryLoadedAfter",
+    srcs: ["**/*.java"],
+    resource_dirs: ["res"],
+    sdk_version: "current",
+    aaptflags: ["--shared-lib"],
+}
diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/AndroidManifest.xml b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/AndroidManifest.xml
new file mode 100644
index 0000000..efedfcf
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.sharedlibloadingtest.shared_library_after">
+    <application>
+        <library android:name="com.android.sharedlibloadingtest.shared_library_after" />
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/res/values/values.xml b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/res/values/values.xml
new file mode 100644
index 0000000..4525944
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <string name="identical_resource_key">loaded after value</string>
+</resources>
\ No newline at end of file
diff --git a/core/java/android/companion/Association.aidl b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/DuplicateClassB.java
similarity index 70%
copy from core/java/android/companion/Association.aidl
copy to tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/DuplicateClassB.java
index 2a28f1f..1e1f5aa 100644
--- a/core/java/android/companion/Association.aidl
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/DuplicateClassB.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -13,6 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion;
 
-parcelable Association;
+package com.android.sharedlibloadingtest;
+
+public class DuplicateClassB {
+    @Override
+    public String toString() {
+        return "Loaded After Shared Lib's Version";
+    }
+}
diff --git a/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/SharedClassAfter.java b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/SharedClassAfter.java
new file mode 100644
index 0000000..9e5b40f
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/SharedLibraryLoadedAfter/src/com/android/sharedlibloadingtest/SharedClassAfter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sharedlibloadingtest;
+
+import android.content.Context;
+
+import com.android.sharedlibloadingtest.shared_library_after.R;
+
+public class SharedClassAfter {
+    @Override
+    public String toString() {
+        return "Also Nothing Special";
+    }
+
+    public static String getResString(Context context) {
+        return context.getResources().getString(R.string.identical_resource_key);
+    }
+}
diff --git a/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/Android.bp b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/Android.bp
new file mode 100644
index 0000000..50456b0
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "SharedLibraryLoadingTests_StandardSharedLibrary",
+    srcs: ["**/*.java"],
+    resource_dirs: ["res"],
+    sdk_version: "current",
+    aaptflags: ["--shared-lib"],
+}
diff --git a/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/AndroidManifest.xml b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/AndroidManifest.xml
new file mode 100644
index 0000000..f1a079f
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.sharedlibloadingtest.shared_library">
+    <application>
+        <library android:name="com.android.sharedlibloadingtest.shared_library" />
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/res/values/values.xml b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/res/values/values.xml
new file mode 100644
index 0000000..941351a
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <string name="identical_resource_key">std lib value</string>
+</resources>
\ No newline at end of file
diff --git a/core/java/android/companion/Association.aidl b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/DuplicateClassA.java
similarity index 70%
copy from core/java/android/companion/Association.aidl
copy to tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/DuplicateClassA.java
index 2a28f1f..a3874aa 100644
--- a/core/java/android/companion/Association.aidl
+++ b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/DuplicateClassA.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -13,6 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion;
 
-parcelable Association;
+package com.android.sharedlibloadingtest;
+
+public class DuplicateClassA {
+    @Override
+    public String toString() {
+        return "Standard Shared Lib's Version";
+    }
+}
diff --git a/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/StdSharedClass.java b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/StdSharedClass.java
new file mode 100644
index 0000000..429d65c
--- /dev/null
+++ b/tests/SharedLibraryLoadingTest/test-apps/StandardSharedLibrary/src/com/android/sharedlibloadingtest/StdSharedClass.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sharedlibloadingtest;
+
+import android.content.Context;
+
+import com.android.sharedlibloadingtest.shared_library.R;
+
+public class StdSharedClass {
+    @Override
+    public String toString() {
+        return "Nothing Special Lib";
+    }
+
+    public static String getResString(Context context) {
+        return context.getResources().getString(R.string.identical_resource_key);
+    }
+}
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 72eaa35..11d02f0 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -120,6 +120,13 @@
                                   static_cast<int>(parser.chunk()->type)));
     }
   }
+
+  if (!staged_entries_to_remove_.empty()) {
+    diag_->Error(DiagMessage(source_) << "didn't find " << staged_entries_to_remove_.size()
+                                      << " original staged resources");
+    return false;
+  }
+
   return true;
 }
 
@@ -393,6 +400,12 @@
       return false;
     }
 
+    if (const auto to_remove_it = staged_entries_to_remove_.find({name, res_id});
+        to_remove_it != staged_entries_to_remove_.end()) {
+      staged_entries_to_remove_.erase(to_remove_it);
+      continue;
+    }
+
     NewResourceBuilder res_builder(name);
     res_builder.SetValue(std::move(resource_value), config)
         .SetId(res_id, OnIdConflict::CREATE_ENTRY)
@@ -533,9 +546,8 @@
     // Since a the finalized resource entry is cloned and added to the resource table under the
     // staged resource id, remove the cloned resource entry from the table.
     if (!table_->RemoveResource(resource_name, staged_id)) {
-      diag_->Error(DiagMessage(source_) << "failed to find resource entry for staged "
-                                        << " resource ID " << staged_id);
-      return false;
+      // If we haven't seen this resource yet let's add a record to skip it when parsing.
+      staged_entries_to_remove_.insert({resource_name, staged_id});
     }
   }
   return true;
@@ -565,6 +577,8 @@
                                                            const ResTable_map_entry* map) {
   switch (name.type) {
     case ResourceType::kStyle:
+      // fallthrough
+    case ResourceType::kConfigVarying:  // legacy thing used in tests
       return ParseStyle(name, config, map);
     case ResourceType::kAttrPrivate:
       // fallthrough
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index cd71d16..1c83166 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -119,6 +119,10 @@
 
   // A mapping of resource ID to type spec flags.
   std::unordered_map<ResourceId, uint32_t> entry_type_spec_flags_;
+
+  // A collection of staged resources that got finalized already and we're supposed to prune -
+  // but the original staged resource record hasn't been parsed yet.
+  std::set<std::pair<ResourceName, ResourceId>> staged_entries_to_remove_;
 };
 
 }  // namespace aapt