Merge "Apply scale to boundsInParent"
diff --git a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
index 762b16c..b7460cd 100644
--- a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
+++ b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
@@ -24,10 +24,12 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
+import android.util.Log;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Random;
 import org.junit.After;
 import org.junit.Before;
@@ -107,6 +109,51 @@
     }
 
     @Test
+    public void testSelectCacheMissRate() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        insertT1TestDataSet();
+
+        ArrayList<String> queryPool = new ArrayList<>();
+        queryPool.add("SELECT _ID, COL_A, COL_B, COL_C FROM T1 WHERE _ID=?");
+        queryPool.add("SELECT _ID FROM T1 WHERE _ID=?");
+        queryPool.add("SELECT COL_A FROM T1 WHERE _ID=?");
+        queryPool.add("SELECT COL_B FROM T1 WHERE _ID=?");
+        queryPool.add("SELECT COL_C FROM T1 WHERE _ID=?");
+        queryPool.add("SELECT _ID, COL_A FROM T1 WHERE _ID=?");
+        queryPool.add("SELECT _ID, COL_B FROM T1 WHERE _ID=?");
+        queryPool.add("SELECT _ID, COL_C FROM T1 WHERE _ID=?");
+        queryPool.add("SELECT COL_A, COL_B FROM T1 WHERE _ID=?");
+        queryPool.add("SELECT COL_A, COL_C FROM T1 WHERE _ID=?");
+        queryPool.add("SELECT COL_B, COL_C FROM T1 WHERE _ID=?");
+        while (state.keepRunning()) {
+            Random rnd = new Random(0);
+
+            int queries = 1000;
+            for (int iQuery = 0; iQuery < queries; ++iQuery) {
+                int queryIndex = rnd.nextInt(queryPool.size());
+                int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
+
+                try (Cursor cursor = mDatabase.rawQuery(
+                             queryPool.get(queryIndex), new String[] {String.valueOf(index)})) {
+                    assertTrue(cursor.moveToNext());
+                }
+            }
+        }
+
+        Log.d("testSelectMemory",
+                "cacheMissRate: " + mDatabase.getStatementCacheMissRate()
+                        + "Total Statements: " + mDatabase.getTotalPreparedStatements()
+                        + ". Misses: " + mDatabase.getTotalStatementCacheMisses());
+
+        // Make sure caching is working and our miss rate should definitely be less than 100%
+        // however, we would expect this number to be actually closer to 0.
+        assertTrue(mDatabase.getStatementCacheMissRate() < 1);
+        mDatabase.close();
+        mContext.deleteDatabase(DB_NAME);
+    }
+
+    @Test
     public void testSelectMultipleRows() {
         insertT1TestDataSet();
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 10957ff..bb32647 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9010,8 +9010,6 @@
     method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
     method public void requestNotificationAccess(android.content.ComponentName);
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
-    method @Deprecated public void startSystemDataTransfer(int) throws android.companion.DeviceNotAssociatedException;
-    method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
     field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
     field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
@@ -9027,19 +9025,14 @@
 
   public abstract class CompanionDeviceService extends android.app.Service {
     ctor public CompanionDeviceService();
-    method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessageToSystem(int, int, @NonNull byte[]) throws android.companion.DeviceNotAssociatedException;
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
     method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
     method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
     method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
-    method public void onMessageDispatchedFromSystem(int, int, @NonNull byte[]);
     field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
   }
 
-  public class CompanionException extends java.lang.RuntimeException {
-  }
-
   public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable {
   }
 
@@ -18215,8 +18208,6 @@
   }
 
   public final class Face {
-    ctor public Face(@NonNull android.graphics.Rect, int, int, @NonNull android.graphics.Point, @NonNull android.graphics.Point, @NonNull android.graphics.Point);
-    ctor public Face(@NonNull android.graphics.Rect, int);
     method public android.graphics.Rect getBounds();
     method public int getId();
     method public android.graphics.Point getLeftEyePosition();
@@ -18228,6 +18219,18 @@
     field public static final int SCORE_MIN = 1; // 0x1
   }
 
+  public static final class Face.Builder {
+    ctor public Face.Builder();
+    ctor public Face.Builder(@NonNull android.hardware.camera2.params.Face);
+    method @NonNull public android.hardware.camera2.params.Face build();
+    method @NonNull public android.hardware.camera2.params.Face.Builder setBounds(@NonNull android.graphics.Rect);
+    method @NonNull public android.hardware.camera2.params.Face.Builder setId(int);
+    method @NonNull public android.hardware.camera2.params.Face.Builder setLeftEyePosition(@NonNull android.graphics.Point);
+    method @NonNull public android.hardware.camera2.params.Face.Builder setMouthPosition(@NonNull android.graphics.Point);
+    method @NonNull public android.hardware.camera2.params.Face.Builder setRightEyePosition(@NonNull android.graphics.Point);
+    method @NonNull public android.hardware.camera2.params.Face.Builder setScore(int);
+  }
+
   public final class InputConfiguration {
     ctor public InputConfiguration(int, int, int);
     ctor public InputConfiguration(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int);
@@ -39637,6 +39640,7 @@
 package android.speech {
 
   public final class AlternativeSpan implements android.os.Parcelable {
+    ctor public AlternativeSpan(int, int, @NonNull java.util.List<java.lang.String>);
     method public int describeContents();
     method @NonNull public java.util.List<java.lang.String> getAlternatives();
     method public int getEndPosition();
@@ -39645,25 +39649,14 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.speech.AlternativeSpan> CREATOR;
   }
 
-  public static final class AlternativeSpan.Builder {
-    ctor public AlternativeSpan.Builder(int, int);
-    method @NonNull public android.speech.AlternativeSpan build();
-    method @NonNull public android.speech.AlternativeSpan.Builder setAlternatives(@NonNull java.util.List<java.lang.String>);
-  }
-
   public final class AlternativeSpans implements android.os.Parcelable {
+    ctor public AlternativeSpans(@NonNull java.util.List<android.speech.AlternativeSpan>);
     method public int describeContents();
     method @NonNull public java.util.List<android.speech.AlternativeSpan> getSpans();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.speech.AlternativeSpans> CREATOR;
   }
 
-  public static final class AlternativeSpans.Builder {
-    ctor public AlternativeSpans.Builder();
-    method @NonNull public android.speech.AlternativeSpans build();
-    method @NonNull public android.speech.AlternativeSpans.Builder setSpans(@NonNull java.util.List<android.speech.AlternativeSpan>);
-  }
-
   public interface RecognitionListener {
     method public void onBeginningOfSpeech();
     method public void onBufferReceived(byte[]);
@@ -46000,7 +45993,9 @@
     ctor public DrawableMarginSpan(@NonNull android.graphics.drawable.Drawable, int);
     method public void chooseHeight(@NonNull CharSequence, int, int, int, int, @NonNull android.graphics.Paint.FontMetricsInt);
     method public void drawLeadingMargin(@NonNull android.graphics.Canvas, @NonNull android.graphics.Paint, int, int, int, int, int, @NonNull CharSequence, int, int, boolean, @NonNull android.text.Layout);
+    method @NonNull public android.graphics.drawable.Drawable getDrawable();
     method public int getLeadingMargin(boolean);
+    method @Px public int getPadding();
   }
 
   public abstract class DynamicDrawableSpan extends android.text.style.ReplacementSpan {
@@ -46043,7 +46038,9 @@
     ctor public IconMarginSpan(@NonNull android.graphics.Bitmap, @IntRange(from=0) int);
     method public void chooseHeight(CharSequence, int, int, int, int, android.graphics.Paint.FontMetricsInt);
     method public void drawLeadingMargin(android.graphics.Canvas, android.graphics.Paint, int, int, int, int, int, CharSequence, int, int, boolean, android.text.Layout);
+    method @NonNull public android.graphics.Bitmap getBitmap();
     method public int getLeadingMargin(boolean);
+    method @Px public int getPadding();
   }
 
   public class ImageSpan extends android.text.style.DynamicDrawableSpan {
@@ -46291,6 +46288,7 @@
     method public String getFamily();
     method @Nullable public String getFontFeatureSettings();
     method @Nullable public String getFontVariationSettings();
+    method public float getLetterSpacing();
     method public android.content.res.ColorStateList getLinkTextColor();
     method public int getShadowColor();
     method public float getShadowDx();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b1d458c..631f0fb 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -167,6 +167,7 @@
     method public void setEligibleForLegacyPermissionPrompt(boolean);
     method public static void setExitTransitionTimeout(long);
     method public void setLaunchActivityType(int);
+    method public void setLaunchTaskDisplayAreaFeatureId(int);
     method public void setLaunchWindowingMode(int);
     method public void setLaunchedFromBubble(boolean);
     method public void setTaskAlwaysOnTop(boolean);
@@ -974,8 +975,8 @@
   }
 
   public static class SQLiteDebug.DbStats {
-    ctor public SQLiteDebug.DbStats(String, long, long, int, int, int, int);
-    field public String cache;
+    ctor public SQLiteDebug.DbStats(@NonNull String, long, long, int, int, int, int, boolean);
+    field public final boolean arePoolStats;
     field public String dbName;
     field public long dbSize;
     field public int lookaside;
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index d6441a2..00ab559 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -223,6 +224,14 @@
             "android.activity.launchTaskDisplayAreaToken";
 
     /**
+     * The task display area feature id the activity should be launched into.
+     * @see #setLaunchTaskDisplayAreaFeatureId(int)
+     * @hide
+     */
+    private static final String KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID =
+            "android.activity.launchTaskDisplayAreaFeatureId";
+
+    /**
      * The root task token the activity should be launched into.
      * @see #setLaunchRootTask(WindowContainerToken)
      * @hide
@@ -432,6 +441,7 @@
     private int mLaunchDisplayId = INVALID_DISPLAY;
     private int mCallerDisplayId = INVALID_DISPLAY;
     private WindowContainerToken mLaunchTaskDisplayArea;
+    private int mLaunchTaskDisplayAreaFeatureId = FEATURE_UNDEFINED;
     private WindowContainerToken mLaunchRootTask;
     private IBinder mLaunchTaskFragmentToken;
     @WindowConfiguration.WindowingMode
@@ -1225,6 +1235,8 @@
         mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
         mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY);
         mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN);
+        mLaunchTaskDisplayAreaFeatureId = opts.getInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID,
+                FEATURE_UNDEFINED);
         mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN);
         mLaunchTaskFragmentToken = opts.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
         mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
@@ -1585,6 +1597,23 @@
     }
 
     /** @hide */
+    public int getLaunchTaskDisplayAreaFeatureId() {
+        return mLaunchTaskDisplayAreaFeatureId;
+    }
+
+    /**
+     * Sets the TaskDisplayArea feature Id the activity should launch into.
+     * Note: It is possible to have TaskDisplayAreas with the same featureId on multiple displays.
+     * If launch display id is not specified, the TaskDisplayArea on the default display will be
+     * used.
+     * @hide
+     */
+    @TestApi
+    public void setLaunchTaskDisplayAreaFeatureId(int launchTaskDisplayAreaFeatureId) {
+        mLaunchTaskDisplayAreaFeatureId = launchTaskDisplayAreaFeatureId;
+    }
+
+    /** @hide */
     public WindowContainerToken getLaunchRootTask() {
         return mLaunchRootTask;
     }
@@ -2075,6 +2104,9 @@
         if (mLaunchTaskDisplayArea != null) {
             b.putParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN, mLaunchTaskDisplayArea);
         }
+        if (mLaunchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+            b.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mLaunchTaskDisplayAreaFeatureId);
+        }
         if (mLaunchRootTask != null) {
             b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask);
         }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 37e7b1b..8a9ec09 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1026,7 +1026,10 @@
     }
 
     private class ApplicationThread extends IApplicationThread.Stub {
-        private static final String DB_INFO_FORMAT = "  %8s %8s %14s %14s  %s";
+        private static final String DB_CONNECTION_INFO_HEADER = "  %8s %8s %14s %5s %5s %5s  %s";
+        private static final String DB_CONNECTION_INFO_FORMAT = "  %8s %8s %14s %5d %5d %5d  %s";
+        private static final String DB_POOL_INFO_HEADER = "  %13s %13s %13s  %s";
+        private static final String DB_POOL_INFO_FORMAT = "  %13d %13d %13d  %s";
 
         public final void scheduleReceiver(Intent intent, ActivityInfo info,
                 CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
@@ -1454,8 +1457,9 @@
                     pw.print(','); pw.print(dbStats.pageSize);
                     pw.print(','); pw.print(dbStats.dbSize);
                     pw.print(','); pw.print(dbStats.lookaside);
-                    pw.print(','); pw.print(dbStats.cache);
-                    pw.print(','); pw.print(dbStats.cache);
+                    pw.print(','); pw.print(dbStats.cacheHits);
+                    pw.print(','); pw.print(dbStats.cacheMisses);
+                    pw.print(','); pw.print(dbStats.cacheSize);
                 }
                 pw.println();
 
@@ -1490,15 +1494,34 @@
             int N = stats.dbStats.size();
             if (N > 0) {
                 pw.println(" DATABASES");
-                printRow(pw, DB_INFO_FORMAT, "pgsz", "dbsz", "Lookaside(b)", "cache",
-                        "Dbname");
+                printRow(pw, DB_CONNECTION_INFO_HEADER, "pgsz", "dbsz", "Lookaside(b)",
+                        "cache hits", "cache misses", "cache size", "Dbname");
+                pw.println("PER CONNECTION STATS");
                 for (int i = 0; i < N; i++) {
                     DbStats dbStats = stats.dbStats.get(i);
-                    printRow(pw, DB_INFO_FORMAT,
+                    if (dbStats.arePoolStats) {
+                        // these will be printed after
+                        continue;
+                    }
+                    printRow(pw, DB_CONNECTION_INFO_FORMAT,
                             (dbStats.pageSize > 0) ? String.valueOf(dbStats.pageSize) : " ",
                             (dbStats.dbSize > 0) ? String.valueOf(dbStats.dbSize) : " ",
                             (dbStats.lookaside > 0) ? String.valueOf(dbStats.lookaside) : " ",
-                            dbStats.cache, dbStats.dbName);
+                            dbStats.cacheHits, dbStats.cacheMisses, dbStats.cacheSize,
+                            dbStats.dbName);
+                }
+                // Print stats accumulated through all the connections that have existed in the
+                // pool since it was opened.
+                pw.println("POOL STATS");
+                printRow(pw, DB_POOL_INFO_HEADER, "cache hits", "cache misses", "cache size",
+                        "Dbname");
+                for (int i = 0; i < N; i++) {
+                    DbStats dbStats = stats.dbStats.get(i);
+                    if (!dbStats.arePoolStats) {
+                        continue;
+                    }
+                    printRow(pw, DB_POOL_INFO_FORMAT, dbStats.cacheHits, dbStats.cacheMisses,
+                            dbStats.cacheSize, dbStats.dbName);
                 }
             }
 
@@ -1623,7 +1646,12 @@
                 proto.write(MemInfoDumpProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize);
                 proto.write(MemInfoDumpProto.AppData.SqlStats.Database.LOOKASIDE_B,
                         dbStats.lookaside);
-                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.CACHE, dbStats.cache);
+                proto.write(
+                        MemInfoDumpProto.AppData.SqlStats.Database.CACHE_HITS, dbStats.cacheHits);
+                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.CACHE_MISSES,
+                        dbStats.cacheMisses);
+                proto.write(
+                        MemInfoDumpProto.AppData.SqlStats.Database.CACHE_SIZE, dbStats.cacheSize);
                 proto.end(dToken);
             }
             proto.end(sToken);
@@ -5857,16 +5885,16 @@
 
         final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),
                 displayId);
-        final Configuration currentResConfig = activity.getResources().getConfiguration();
-        final int diff = currentResConfig.diffPublicOnly(newConfig);
-        final boolean hasPublicResConfigChange = diff != 0;
+        final Configuration currentConfig = activity.mCurrentConfig;
+        final int diff = currentConfig.diffPublicOnly(newConfig);
+        final boolean hasPublicConfigChange = diff != 0;
         final ActivityClientRecord r = getActivityClient(activityToken);
         // TODO(b/173090263): Use diff instead after the improvement of AssetManager and
         // ResourcesImpl constructions.
-        final boolean shouldUpdateResources = hasPublicResConfigChange
-                || shouldUpdateResources(activityToken, currentResConfig, newConfig,
-                amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
-        final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig,
+        final boolean shouldUpdateResources = hasPublicConfigChange
+                || shouldUpdateResources(activityToken, currentConfig, newConfig, amOverrideConfig,
+                movedToDifferentDisplay, hasPublicConfigChange);
+        final boolean shouldReportChange = shouldReportChange(diff, currentConfig, newConfig,
                 r != null ? r.mSizeConfigurations : null,
                 activity.mActivityInfo.getRealConfigChanged());
         // Nothing significant, don't proceed with updating and reporting.
@@ -5890,6 +5918,9 @@
                 amOverrideConfig, contextThemeWrapperOverrideConfig);
         mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId);
 
+        activity.mConfigChangeFlags = 0;
+        activity.mCurrentConfig = new Configuration(newConfig);
+
         // Apply the ContextThemeWrapper override if necessary.
         // NOTE: Make sure the configurations are not modified, as they are treated as immutable
         // in many places.
@@ -5900,10 +5931,8 @@
             activity.dispatchMovedToDisplay(displayId, configToReport);
         }
 
-        activity.mConfigChangeFlags = 0;
         if (shouldReportChange) {
             activity.mCalled = false;
-            activity.mCurrentConfig = new Configuration(newConfig);
             activity.onConfigurationChanged(configToReport);
             if (!activity.mCalled) {
                 throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
@@ -5918,6 +5947,8 @@
      * Returns {@code true} if {@link Activity#onConfigurationChanged(Configuration)} should be
      * dispatched.
      *
+     * @param publicDiff Usually computed by {@link Configuration#diffPublicOnly(Configuration)}.
+     *                   This parameter is to prevent we compute it again.
      * @param currentConfig The current configuration cached in {@link Activity#mCurrentConfig}.
      *                      It is {@code null} before the first config update from the server side.
      * @param newConfig The updated {@link Configuration}
@@ -5926,10 +5957,9 @@
      * @return {@code true} if the config change should be reported to the Activity
      */
     @VisibleForTesting
-    public static boolean shouldReportChange(@Nullable Configuration currentConfig,
+    public static boolean shouldReportChange(int publicDiff, @Nullable Configuration currentConfig,
             @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets,
             int handledConfigChanges) {
-        final int publicDiff = currentConfig.diffPublicOnly(newConfig);
         // Don't report the change if there's no public diff between current and new config.
         if (publicDiff == 0) {
             return false;
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index fe75dd3..dc6825c 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -263,9 +263,12 @@
      * @param taskId the id of the task to retrieve the sAutoapshots for
      * @param isLowResolution if set, if the snapshot needs to be loaded from disk, this will load
      *                          a reduced resolution of it, which is much faster
+     * @param takeSnapshotIfNeeded if set, call {@link #takeTaskSnapshot} to trigger the snapshot
+                                   if no cache exists.
      * @return a graphic buffer representing a screenshot of a task
      */
-    android.window.TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution);
+    android.window.TaskSnapshot getTaskSnapshot(
+            int taskId, boolean isLowResolution, boolean takeSnapshotIfNeeded);
 
     /**
      * @param taskId the id of the task to take a snapshot of
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index b90408d..904db5f 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -518,7 +518,7 @@
 
     /**
      * Indicates that an event occurred as the device attempted to connect to
-     * a WiFi network. The log entry contains the following information about the
+     * a managed WiFi network. The log entry contains the following information about the
      * event, encapsulated in an {@link Object} array and accessible via
      * {@link SecurityEvent#getData()}:
      * <li> [0] Last 2 octets of the network BSSID ({@code String}, in the form "xx:xx:xx:xx:AA:BB")
@@ -530,7 +530,7 @@
     public static final int TAG_WIFI_CONNECTION = SecurityLogTags.SECURITY_WIFI_CONNECTION;
 
     /**
-     * Indicates that the device disconnects from a connected WiFi network.
+     * Indicates that the device disconnects from a managed WiFi network.
      * The log entry contains the following information about the
      * event, encapsulated in an {@link Object} array and accessible via
      * {@link SecurityEvent#getData()}:
diff --git a/core/java/android/app/smartspace/OWNERS b/core/java/android/app/smartspace/OWNERS
index 19ef9d7..4d9a633 100644
--- a/core/java/android/app/smartspace/OWNERS
+++ b/core/java/android/app/smartspace/OWNERS
@@ -1,2 +1 @@
-srazdan@google.com
-alexmang@google.com
\ No newline at end of file
+include /core/java/android/service/smartspace/OWNERS
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 1b51faf3..8ffc6a2 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -800,18 +800,15 @@
      *
      * @hide
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
     public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message)
             throws DeviceNotAssociatedException {
-        try {
-            mService.dispatchMessage(messageId, associationId, message);
-        } catch (RemoteException e) {
-            ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
-            throw e.rethrowFromSystemServer();
-        }
+        Log.w(LOG_TAG, "dispatchMessage replaced by attachSystemDataTransport");
     }
 
     /** {@hide} */
+    @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
     public final void attachSystemDataTransport(int associationId, @NonNull InputStream in,
             @NonNull OutputStream out) throws DeviceNotAssociatedException {
         synchronized (mTransports) {
@@ -830,6 +827,7 @@
     }
 
     /** {@hide} */
+    @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
     public final void detachSystemDataTransport(int associationId)
             throws DeviceNotAssociatedException {
         synchronized (mTransports) {
@@ -927,7 +925,7 @@
      *
      * <p>The permission transfer doesn't happen immediately after the call or user consented.
      * The app needs to trigger the system data transfer manually by calling
-     * {@link #startSystemDataTransfer(int)}, when it confirms the communication channel between
+     * {@code #startSystemDataTransfer(int)}, when it confirms the communication channel between
      * the two devices is established.</p>
      *
      * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the association
@@ -965,8 +963,8 @@
      * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
      *                      of the companion device recorded by CompanionDeviceManager
      * @throws DeviceNotAssociatedException Exception if the companion device is not associated
-     *
      * @deprecated Use {@link #startSystemDataTransfer(int, Executor, OutcomeReceiver)} instead.
+     * @hide
      */
     @Deprecated
     @UserHandleAware
@@ -993,6 +991,7 @@
      * @param executor The executor which will be used to invoke the result callback.
      * @param result The callback to notify the app of the result of the system data transfer.
      * @throws DeviceNotAssociatedException Exception if the companion device is not associated
+     * @hide
      */
     @UserHandleAware
     public void startSystemDataTransfer(
@@ -1125,12 +1124,14 @@
 
         public void start() throws IOException {
             final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair();
-            mLocalIn = new ParcelFileDescriptor.AutoCloseInputStream(pair[0]);
-            mLocalOut = new ParcelFileDescriptor.AutoCloseOutputStream(pair[0]);
+            final ParcelFileDescriptor localFd = pair[0];
+            final ParcelFileDescriptor remoteFd = pair[1];
+            mLocalIn = new ParcelFileDescriptor.AutoCloseInputStream(localFd);
+            mLocalOut = new ParcelFileDescriptor.AutoCloseOutputStream(localFd);
 
             try {
                 mService.attachSystemDataTransport(mContext.getPackageName(),
-                        mContext.getUserId(), mAssociationId, pair[1]);
+                        mContext.getUserId(), mAssociationId, remoteFd);
             } catch (RemoteException e) {
                 throw new IOException("Failed to configure transport", e);
             }
@@ -1160,17 +1161,17 @@
         public void stop() {
             mStopped = true;
 
-            IoUtils.closeQuietly(mRemoteIn);
-            IoUtils.closeQuietly(mRemoteOut);
-            IoUtils.closeQuietly(mLocalIn);
-            IoUtils.closeQuietly(mLocalOut);
-
             try {
                 mService.detachSystemDataTransport(mContext.getPackageName(),
                         mContext.getUserId(), mAssociationId);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, "Failed to detach transport", e);
             }
+
+            IoUtils.closeQuietly(mRemoteIn);
+            IoUtils.closeQuietly(mRemoteOut);
+            IoUtils.closeQuietly(mLocalIn);
+            IoUtils.closeQuietly(mLocalOut);
         }
 
         /**
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 83d2713..a79983a 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -156,9 +156,12 @@
      * @param messageId system assigned id of the message to be sent
      * @param associationId association id of the associated device
      * @param message message to be sent
+     * @hide
      */
+    @Deprecated
     public void onMessageDispatchedFromSystem(int messageId, int associationId,
             @NonNull byte[] message) {
+        Log.w(LOG_TAG, "Replaced by attachSystemDataTransport");
         // do nothing. Companion apps can override this function for system to send messages.
     }
 
@@ -185,22 +188,14 @@
      * @param messageId id of the message
      * @param associationId id of the associated device
      * @param message message received from the associated device
+     * @hide
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
     public final void dispatchMessageToSystem(int messageId, int associationId,
             @NonNull byte[] message)
             throws DeviceNotAssociatedException {
-        if (getBaseContext() == null) {
-            Log.e(LOG_TAG, "Dispatch failed. Start your service before calling this method.");
-            return;
-        }
-        CompanionDeviceManager companionDeviceManager =
-                getSystemService(CompanionDeviceManager.class);
-        if (companionDeviceManager != null) {
-            companionDeviceManager.dispatchMessage(messageId, associationId, message);
-        } else {
-            Log.e(LOG_TAG, "CompanionDeviceManager is null. Can't dispatch messages.");
-        }
+        Log.w(LOG_TAG, "Replaced by attachSystemDataTransport");
     }
 
     /**
@@ -223,10 +218,13 @@
      *            device
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
     public final void attachSystemDataTransport(int associationId, @NonNull InputStream in,
             @NonNull OutputStream out) throws DeviceNotAssociatedException {
         getSystemService(CompanionDeviceManager.class)
-                .attachSystemDataTransport(associationId, in, out);
+                .attachSystemDataTransport(associationId,
+                        Objects.requireNonNull(in),
+                        Objects.requireNonNull(out));
     }
 
     /**
@@ -236,6 +234,7 @@
      * @param associationId id of the associated device
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
     public final void detachSystemDataTransport(int associationId)
             throws DeviceNotAssociatedException {
         getSystemService(CompanionDeviceManager.class)
@@ -299,13 +298,5 @@
         public void onDeviceDisappeared(AssociationInfo associationInfo) {
             mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceDisappeared(associationInfo));
         }
-
-        @Override
-        public void onMessageDispatchedFromSystem(int messageId, int associationId,
-                @NonNull byte[] message) {
-            mMainHandler.postAtFrontOfQueue(
-                    () -> mService.onMessageDispatchedFromSystem(messageId, associationId,
-                            message));
-        }
     }
 }
diff --git a/core/java/android/companion/CompanionException.java b/core/java/android/companion/CompanionException.java
index fb78e8d..9a92401 100644
--- a/core/java/android/companion/CompanionException.java
+++ b/core/java/android/companion/CompanionException.java
@@ -19,7 +19,10 @@
 import android.annotation.NonNull;
 
 /**
- * {@code CompanionException} can be thrown during the companion system data transfer process.
+ * {@code CompanionException} can be thrown during the companion system data
+ * transfer process.
+ *
+ * @hide
  */
 public class CompanionException extends RuntimeException {
     /** @hide */
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 42f9083..6e6e187 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -63,8 +63,6 @@
     void createAssociation(in String packageName, in String macAddress, int userId,
         in byte[] certificate);
 
-    void dispatchMessage(int messageId, int associationId, in byte[] message);
-
     void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
 
     void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
diff --git a/core/java/android/companion/ICompanionDeviceService.aidl b/core/java/android/companion/ICompanionDeviceService.aidl
index 3c90b86..fa68508 100644
--- a/core/java/android/companion/ICompanionDeviceService.aidl
+++ b/core/java/android/companion/ICompanionDeviceService.aidl
@@ -22,5 +22,4 @@
 oneway interface ICompanionDeviceService {
     void onDeviceAppeared(in AssociationInfo associationInfo);
     void onDeviceDisappeared(in AssociationInfo associationInfo);
-    void onMessageDispatchedFromSystem(in int messageId, in int associationId, in byte[] message);
 }
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 6d6ec06..4b3eb3a 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -1053,6 +1053,7 @@
     }
 
     private PreparedStatement acquirePreparedStatement(String sql) {
+        ++mPool.mTotalPrepareStatements;
         PreparedStatement statement = mPreparedStatementCache.get(sql);
         boolean skipCache = false;
         if (statement != null) {
@@ -1064,7 +1065,7 @@
             // statement but do not cache it.
             skipCache = true;
         }
-
+        ++mPool.mTotalPrepareStatementCacheMiss;
         final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
         try {
             final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
@@ -1320,7 +1321,8 @@
                 if (!path.isEmpty()) {
                     label.append(": ").append(path);
                 }
-                dbStatsList.add(new DbStats(label.toString(), pageCount, pageSize, 0, 0, 0, 0));
+                dbStatsList.add(
+                        new DbStats(label.toString(), pageCount, pageSize, 0, 0, 0, 0, false));
             }
         } catch (SQLiteException ex) {
             // Ignore.
@@ -1349,9 +1351,8 @@
             label = mConfiguration.path + " (" + mConnectionId + ")";
         }
         return new DbStats(label, pageCount, pageSize, lookaside,
-                mPreparedStatementCache.hitCount(),
-                mPreparedStatementCache.missCount(),
-                mPreparedStatementCache.size());
+                mPreparedStatementCache.hitCount(), mPreparedStatementCache.missCount(),
+                mPreparedStatementCache.size(), false);
     }
 
     @Override
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index db0cac3..069c264 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -32,6 +32,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import dalvik.annotation.optimization.NeverCompile;
 import dalvik.system.CloseGuard;
 
 import java.io.Closeable;
@@ -103,6 +104,10 @@
             new ArrayList<SQLiteConnection>();
     private SQLiteConnection mAvailablePrimaryConnection;
 
+    // Prepare statement cache statistics
+    public int mTotalPrepareStatementCacheMiss = 0;
+    public int mTotalPrepareStatements = 0;
+
     @GuardedBy("mLock")
     private IdleConnectionHandler mIdleConnectionHandler;
 
@@ -507,6 +512,12 @@
             for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
                 connection.collectDbStatsUnsafe(dbStatsList);
             }
+
+            // Global pool stats
+            DbStats poolStats = new DbStats(mConfiguration.path, 0, 0, 0,
+                    mTotalPrepareStatements - mTotalPrepareStatementCacheMiss,
+                    mTotalPrepareStatementCacheMiss, mTotalPrepareStatements, true);
+            dbStatsList.add(poolStats);
         }
     }
 
@@ -1203,6 +1214,16 @@
         }
     }
 
+    /** @hide */
+    @NeverCompile
+    public double getStatementCacheMissRate() {
+        if (mTotalPrepareStatements == 0) {
+            // no statements executed thus no miss rate.
+            return 0;
+        }
+        return (double) mTotalPrepareStatementCacheMiss / (double) mTotalPrepareStatements;
+    }
+
     public long getTotalStatementsTime() {
         return mTotalStatementsTime.get();
     }
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 0d0615a..c08294f 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -45,6 +45,8 @@
 import android.util.Pair;
 import android.util.Printer;
 import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.NeverCompile;
 import dalvik.system.CloseGuard;
 import java.io.File;
 import java.io.FileFilter;
@@ -2233,6 +2235,16 @@
         }
     }
 
+    /** @hide */
+    @NeverCompile
+    public double getStatementCacheMissRate() {
+        synchronized (mLock) {
+            throwIfNotOpenLocked();
+
+            return mConnectionPoolLocked.getStatementCacheMissRate();
+        }
+    }
+
     /**
      * Sets whether foreign key constraints are enabled for the database.
      * <p>
@@ -2489,6 +2501,22 @@
         return connectionPools;
     }
 
+    /** @hide */
+    @NeverCompile
+    public int getTotalPreparedStatements() {
+        throwIfNotOpenLocked();
+
+        return mConnectionPoolLocked.mTotalPrepareStatements;
+    }
+
+    /** @hide */
+    @NeverCompile
+    public int getTotalStatementCacheMisses() {
+        throwIfNotOpenLocked();
+
+        return mConnectionPoolLocked.mTotalPrepareStatementCacheMiss;
+    }
+
     /**
      * Dump detailed information about all open databases in the current process.
      * Used by bug report.
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index b84a8d2..93d74b1 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -16,6 +16,7 @@
 
 package android.database.sqlite;
 
+import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -23,7 +24,6 @@
 import android.os.SystemProperties;
 import android.util.Log;
 import android.util.Printer;
-
 import java.util.ArrayList;
 
 /**
@@ -173,16 +173,26 @@
         @UnsupportedAppUsage
         public int lookaside;
 
-        /** statement cache stats: hits/misses/cachesize */
-        public String cache;
+        /** @hide */
+        final public int cacheHits;
+        /** @hide */
+        final public int cacheMisses;
+        /** @hide */
+        final public int cacheSize;
 
-        public DbStats(String dbName, long pageCount, long pageSize, int lookaside,
-            int hits, int misses, int cachesize) {
+        /** true if connection specific stats or whole connection pool if false */
+        public final boolean arePoolStats;
+
+        public DbStats(@NonNull String dbName, long pageCount, long pageSize, int lookaside,
+                int hits, int misses, int cachesize, boolean arePoolStats) {
             this.dbName = dbName;
             this.pageSize = pageSize / 1024;
             dbSize = (pageCount * pageSize) / 1024;
             this.lookaside = lookaside;
-            this.cache = hits + "/" + misses + "/" + cachesize;
+            this.cacheHits = hits;
+            this.cacheMisses = misses;
+            this.cacheSize = cachesize;
+            this.arePoolStats = arePoolStats;
         }
     }
 
diff --git a/core/java/android/hardware/camera2/params/Face.java b/core/java/android/hardware/camera2/params/Face.java
index fa1202b..1d9a5a3a 100644
--- a/core/java/android/hardware/camera2/params/Face.java
+++ b/core/java/android/hardware/camera2/params/Face.java
@@ -69,10 +69,6 @@
      * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
      * rightEyePosition, and mouthPosition may be independently null or not-null.</p>
      *
-     * <p>This constructor is public to allow for easier application testing by
-     * creating custom object instances. It's not necessary to construct these
-     * objects during normal use of the camera API.</p>
-     *
      * @param bounds Bounds of the face.
      * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
      * @param id A unique ID per face visible to the tracker.
@@ -87,6 +83,8 @@
      *             or if id is {@value #ID_UNSUPPORTED} and
      *               leftEyePosition/rightEyePosition/mouthPosition aren't all null,
      *             or else if id is negative.
+     *
+     * @hide
      */
     public Face(@NonNull Rect bounds, int score, int id,
             @NonNull Point leftEyePosition, @NonNull Point rightEyePosition,
@@ -106,10 +104,6 @@
      * the face id of each face is expected to be {@value #ID_UNSUPPORTED}, the leftEyePosition,
      * rightEyePosition, and mouthPositions are expected to be {@code null} for each face.</p>
      *
-     * <p>This constructor is public to allow for easier application testing by
-     * creating custom object instances. It's not necessary to construct these
-     * objects during normal use of the camera API.</p>
-     *
      * @param bounds Bounds of the face.
      * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
      *
@@ -117,6 +111,8 @@
      *             if bounds is {@code null},
      *             or if the confidence is not in the range of
      *             {@value #SCORE_MIN}-{@value #SCORE_MAX}.
+     *
+     * @hide
      */
     public Face(@NonNull Rect bounds, int score) {
         init(bounds, score, ID_UNSUPPORTED,
@@ -130,16 +126,14 @@
             @Nullable Point leftEyePosition, @Nullable Point rightEyePosition,
             @Nullable Point mouthPosition) {
         checkNotNull("bounds", bounds);
-        if (score < SCORE_MIN || score > SCORE_MAX) {
-            throw new IllegalArgumentException("Confidence out of range");
-        } else if (id < 0 && id != ID_UNSUPPORTED) {
-            throw new IllegalArgumentException("Id out of range");
-        }
+        checkScore(score);
+        checkId(id);
         if (id == ID_UNSUPPORTED) {
             checkNull("leftEyePosition", leftEyePosition);
             checkNull("rightEyePosition", rightEyePosition);
             checkNull("mouthPosition", mouthPosition);
         }
+        checkFace(leftEyePosition, rightEyePosition, mouthPosition);
 
         mBounds = bounds;
         mScore = score;
@@ -156,7 +150,7 @@
      * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0)
      * representing the top-left corner of the active array rectangle.</p>
      *
-     * <p>There is no constraints on the the Rectangle value other than it
+     * <p>There is no constraints on the Rectangle value other than it
      * is not-{@code null}.</p>
      */
     public Rect getBounds() {
@@ -190,7 +184,7 @@
      * If the face leaves the field-of-view and comes back, it will get a new
      * id.</p>
      *
-     * <p>This is an optional field, may not be supported on all devices.
+     * <p>This is an optional field and may not be supported on all devices.
      * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
      * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
      * rightEyePosition, and mouthPosition may be independently null or not-null. When devices
@@ -212,7 +206,7 @@
      *
      * <p>The coordinates are in
      * the same space as the ones for {@link #getBounds}. This is an
-     * optional field, may not be supported on all devices. If not
+     * optional field and may not be supported on all devices. If not
      * supported, the value will always be set to null.
      * This value will  always be null only if {@link #getId()} returns
      * {@value #ID_UNSUPPORTED}.</p>
@@ -228,7 +222,7 @@
      *
      * <p>The coordinates are
      * in the same space as the ones for {@link #getBounds}.This is an
-     * optional field, may not be supported on all devices. If not
+     * optional field and may not be supported on all devices. If not
      * supported, the value will always be set to null.
      * This value will  always be null only if {@link #getId()} returns
      * {@value #ID_UNSUPPORTED}.</p>
@@ -244,7 +238,7 @@
      *
      * <p>The coordinates are in
      * the same space as the ones for {@link #getBounds}. This is an optional
-     * field, may not be supported on all devices. If not
+     * field and may not be supported on all devices. If not
      * supported, the value will always be set to null.
      * This value will  always be null only if {@link #getId()} returns
      * {@value #ID_UNSUPPORTED}.</p>
@@ -277,4 +271,253 @@
             throw new IllegalArgumentException(name + " was required to be null, but it wasn't");
         }
     }
+
+    private static void checkScore(int score) {
+        if (score < SCORE_MIN || score > SCORE_MAX) {
+            throw new IllegalArgumentException("Confidence out of range");
+        }
+    }
+
+    private static void checkId(int id) {
+        if (id < 0 && id != ID_UNSUPPORTED) {
+            throw new IllegalArgumentException("Id out of range");
+        }
+    }
+
+    private static void checkFace(@Nullable Point leftEyePosition,
+            @Nullable Point rightEyePosition, @Nullable Point mouthPosition) {
+        if (leftEyePosition != null || rightEyePosition != null || mouthPosition != null) {
+            if (leftEyePosition == null || rightEyePosition == null || mouthPosition == null) {
+                throw new IllegalArgumentException("If any of leftEyePosition, rightEyePosition, "
+                        + "or mouthPosition are non-null, all three must be non-null.");
+            }
+        }
+    }
+
+    /**
+     * Builds a Face object.
+     *
+     * <p>This builder is public to allow for easier application testing by
+     * creating custom object instances. It's not necessary to construct these
+     * objects during normal use of the camera API.</p>
+     */
+    public static final class Builder {
+        private long mBuilderFieldsSet = 0L;
+
+        private static final long FIELD_BOUNDS = 1 << 1;
+        private static final long FIELD_SCORE = 1 << 2;
+        private static final long FIELD_ID = 1 << 3;
+        private static final long FIELD_LEFT_EYE = 1 << 4;
+        private static final long FIELD_RIGHT_EYE = 1 << 5;
+        private static final long FIELD_MOUTH = 1 << 6;
+        private static final long FIELD_BUILT = 1 << 0;
+
+        private static final String FIELD_NAME_BOUNDS = "bounds";
+        private static final String FIELD_NAME_SCORE = "score";
+        private static final String FIELD_NAME_LEFT_EYE = "left eye";
+        private static final String FIELD_NAME_RIGHT_EYE = "right eye";
+        private static final String FIELD_NAME_MOUTH = "mouth";
+
+        private Rect mBounds = null;
+        private int mScore = 0;
+        private int mId = ID_UNSUPPORTED;
+        private Point mLeftEye = null;
+        private Point mRightEye = null;
+        private Point mMouth = null;
+
+        public Builder() {
+            // Empty
+        }
+
+        public Builder(@NonNull Face current) {
+            mBounds = current.mBounds;
+            mScore = current.mScore;
+            mId = current.mId;
+            mLeftEye = current.mLeftEye;
+            mRightEye = current.mRightEye;
+            mMouth = current.mMouth;
+        }
+
+        /**
+         * Bounds of the face.
+         *
+         * <p>A rectangle relative to the sensor's
+         * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0)
+         * representing the top-left corner of the active array rectangle.</p>
+         *
+         * <p>There is no constraints on the Rectangle value other than it
+         * is not-{@code null}.</p>
+         *
+         * @param bounds Bounds of the face.
+         * @return This builder.
+         */
+        public @NonNull Builder setBounds(@NonNull Rect bounds) {
+            checkNotUsed();
+            mBuilderFieldsSet |= FIELD_BOUNDS;
+            mBounds = bounds;
+            return this;
+        }
+
+        /**
+         * The confidence level for the detection of the face.
+         *
+         * <p>The range is {@value #SCORE_MIN} to {@value #SCORE_MAX}.
+         * {@value #SCORE_MAX} is the highest confidence.</p>
+         *
+         * <p>Depending on the device, even very low-confidence faces may be
+         * listed, so applications should filter out faces with low confidence,
+         * depending on the use case. For a typical point-and-shoot camera
+         * application that wishes to display rectangles around detected faces,
+         * filtering out faces with confidence less than half of {@value #SCORE_MAX}
+         * is recommended.</p>
+         *
+         * @see #SCORE_MAX
+         * @see #SCORE_MIN
+         *
+         * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
+         * @return This builder.
+         */
+        public @NonNull Builder setScore(int score) {
+            checkNotUsed();
+            checkScore(score);
+            mBuilderFieldsSet |= FIELD_SCORE;
+            mScore = score;
+            return this;
+        }
+
+        /**
+         * An unique id per face while the face is visible to the tracker.
+         *
+         * <p>
+         * If the face leaves the field-of-view and comes back, it will get a new
+         * id.</p>
+         *
+         * <p>This is an optional field and may not be supported on all devices.
+         * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
+         * mouthPositions should be {@code null}. Otherwise, each of leftEyePosition,
+         * rightEyePosition, and mouthPosition may be independently null or not-null. When devices
+         * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as
+         * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult},
+         * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p>
+         *
+         * <p>This value should either be {@value #ID_UNSUPPORTED} or
+         * otherwise greater than {@code 0}.</p>
+         *
+         * @see #ID_UNSUPPORTED
+         *
+         * @param id A unique ID per face visible to the tracker.
+         * @return This builder.
+         */
+        public @NonNull Builder setId(int id) {
+            checkNotUsed();
+            checkId(id);
+            mBuilderFieldsSet |= FIELD_ID;
+            mId = id;
+            return this;
+        }
+
+        /**
+         * The coordinates of the center of the left eye.
+         *
+         * <p>The coordinates should be
+         * in the same space as the ones for {@link #setBounds}. This is an
+         * optional field and may not be supported on all devices. If not
+         * supported, the value should always be unset or set to null.
+         * This value should always be null if {@link #setId} is called with
+         * {@value #ID_UNSUPPORTED}.</p>
+         *
+         * @param leftEyePosition The position of the left eye.
+         * @return This builder.
+         */
+        public @NonNull Builder setLeftEyePosition(@NonNull Point leftEyePosition) {
+            checkNotUsed();
+            mBuilderFieldsSet |= FIELD_LEFT_EYE;
+            mLeftEye = leftEyePosition;
+            return this;
+        }
+
+        /**
+         * The coordinates of the center of the right eye.
+         *
+         * <p>The coordinates should be
+         * in the same space as the ones for {@link #setBounds}.This is an
+         * optional field and may not be supported on all devices. If not
+         * supported, the value should always be set to null.
+         * This value should always be null if {@link #setId} is called with
+         * {@value #ID_UNSUPPORTED}.</p>
+         *
+         * @param rightEyePosition The position of the right eye.
+         * @return This builder.
+         */
+        public @NonNull Builder setRightEyePosition(@NonNull Point rightEyePosition) {
+            checkNotUsed();
+            mBuilderFieldsSet |= FIELD_RIGHT_EYE;
+            mRightEye = rightEyePosition;
+            return this;
+        }
+
+        /**
+         * The coordinates of the center of the mouth.
+         *
+         * <p>The coordinates should be in
+         * the same space as the ones for {@link #setBounds}. This is an optional
+         * field and may not be supported on all devices. If not
+         * supported, the value should always be set to null.
+         * This value should always be null if {@link #setId} is called with
+         * {@value #ID_UNSUPPORTED}.</p>
+         * </p>
+         *
+         * @param mouthPosition The position of the mouth.
+         * @return This builder.
+         */
+        public @NonNull Builder setMouthPosition(@NonNull Point mouthPosition) {
+            checkNotUsed();
+            mBuilderFieldsSet |= FIELD_MOUTH;
+            mMouth = mouthPosition;
+            return this;
+        }
+
+        /**
+         * Returns an instance of <code>Face</code> created from the fields set
+         * on this builder.
+         *
+         * @return A Face.
+         */
+        public @NonNull Face build() {
+            checkNotUsed();
+            checkFieldSet(FIELD_BOUNDS, FIELD_NAME_BOUNDS);
+            checkFieldSet(FIELD_SCORE, FIELD_NAME_SCORE);
+            if (mId == ID_UNSUPPORTED) {
+                checkIdUnsupportedThenNull(mLeftEye, FIELD_NAME_LEFT_EYE);
+                checkIdUnsupportedThenNull(mRightEye, FIELD_NAME_RIGHT_EYE);
+                checkIdUnsupportedThenNull(mMouth, FIELD_NAME_MOUTH);
+            }
+            checkFace(mLeftEye, mRightEye, mMouth);
+
+            mBuilderFieldsSet |= FIELD_BUILT;
+
+            return new Face(mBounds, mScore, mId, mLeftEye, mRightEye, mMouth);
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & FIELD_BUILT) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+
+        private void checkFieldSet(long field, String fieldName) {
+            if ((mBuilderFieldsSet & field) == 0) {
+                throw new IllegalStateException(
+                        "Field \"" + fieldName + "\" must be set before building.");
+            }
+        }
+
+        private void checkIdUnsupportedThenNull(Object obj, String fieldName) {
+            if (obj != null) {
+                throw new IllegalArgumentException("Field \"" + fieldName
+                        + "\" must be unset or null if id is ID_UNSUPPORTED.");
+            }
+        }
+    }
 }
diff --git a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
index d06bc1d..3e41d63 100644
--- a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
+++ b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
@@ -122,6 +122,9 @@
     }
 
     private IHdmiControlCallback getCallbackWrapper(final OneTouchPlayCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("OneTouchPlayCallback cannot be null.");
+        }
         return new IHdmiControlCallback.Stub() {
             @Override
             public void onComplete(int result) {
@@ -131,6 +134,9 @@
     }
 
     private IHdmiControlCallback getCallbackWrapper(final DisplayStatusCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("DisplayStatusCallback cannot be null.");
+        }
         return new IHdmiControlCallback.Stub() {
             @Override
             public void onComplete(int status) {
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index 3a042a5..e8e4fc9 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -26,7 +26,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -173,38 +172,63 @@
         }
     }
 
-    void apply(@NonNull Chunk chunk) {
+    void apply(Chunk chunk) {
+        List<ProgramSelector.Identifier> removedList = new ArrayList<>();
+        List<ProgramSelector.Identifier> changedList = new ArrayList<>();
+        List<ProgramList.ListCallback> listCallbacksCopied;
+        List<OnCompleteListener> onCompleteListenersCopied = new ArrayList<>();
         synchronized (mLock) {
             if (mIsClosed) return;
 
             mIsComplete = false;
+            listCallbacksCopied = new ArrayList<>(mListCallbacks);
 
             if (chunk.isPurge()) {
-                new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id));
+                for (ProgramSelector.Identifier id : mPrograms.keySet()) {
+                    removeLocked(id, removedList);
+                }
             }
 
-            chunk.getRemoved().stream().forEach(id -> removeLocked(id));
-            chunk.getModified().stream().forEach(info -> putLocked(info));
+            chunk.getRemoved().stream().forEach(id -> removeLocked(id, removedList));
+            chunk.getModified().stream().forEach(info -> putLocked(info, changedList));
 
             if (chunk.isComplete()) {
                 mIsComplete = true;
-                mOnCompleteListeners.forEach(cb -> cb.onComplete());
+                onCompleteListenersCopied = new ArrayList<>(mOnCompleteListeners);
+            }
+        }
+
+        for (int i = 0; i < removedList.size(); i++) {
+            for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+                listCallbacksCopied.get(cbIndex).onItemRemoved(removedList.get(i));
+            }
+        }
+        for (int i = 0; i < changedList.size(); i++) {
+            for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+                listCallbacksCopied.get(cbIndex).onItemChanged(changedList.get(i));
+            }
+        }
+        if (chunk.isComplete()) {
+            for (int cbIndex = 0; cbIndex < onCompleteListenersCopied.size(); cbIndex++) {
+                onCompleteListenersCopied.get(cbIndex).onComplete();
             }
         }
     }
 
-    private void putLocked(@NonNull RadioManager.ProgramInfo value) {
+    private void putLocked(RadioManager.ProgramInfo value,
+            List<ProgramSelector.Identifier> changedIdentifierList) {
         ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
         mPrograms.put(Objects.requireNonNull(key), value);
         ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
-        mListCallbacks.forEach(cb -> cb.onItemChanged(sel));
+        changedIdentifierList.add(sel);
     }
 
-    private void removeLocked(@NonNull ProgramSelector.Identifier key) {
+    private void removeLocked(ProgramSelector.Identifier key,
+            List<ProgramSelector.Identifier> removedIdentifierList) {
         RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
         if (removed == null) return;
         ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
-        mListCallbacks.forEach(cb -> cb.onItemRemoved(sel));
+        removedIdentifierList.add(sel);
     }
 
     /**
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index b47e92d..e960df1 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -31,18 +31,15 @@
 import android.util.Log;
 import android.view.InputChannel;
 import android.view.MotionEvent;
-import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodSession;
 import android.view.inputmethod.InputMethodSubtype;
-import android.window.ImeOnBackInvokedDispatcher;
 
 import com.android.internal.inputmethod.CancellationGroup;
 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.IInputMethod;
-import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
 import com.android.internal.inputmethod.IInputMethodSession;
 import com.android.internal.inputmethod.IInputMethodSessionCallback;
 import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -98,9 +95,9 @@
      *
      * <p>This field must be set and cleared only from the binder thread(s), where the system
      * guarantees that {@link #bindInput(InputBinding)},
-     * {@link #startInput(IBinder, IRemoteInputConnection, int, EditorInfo, boolean, boolean)}, and
-     * {@link #unbindInput()} are called with the same order as the original calls
-     * in {@link com.android.server.inputmethod.InputMethodManagerService}.
+     * {@link #startInput(IInputMethod.StartInputParams)}, and {@link #unbindInput()} are called
+     * with the same order as the original calls in
+     * {@link com.android.server.inputmethod.InputMethodManagerService}.
      * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
      */
     @Nullable
@@ -174,17 +171,9 @@
                 args.recycle();
                 return;
             }
-            case DO_INITIALIZE_INTERNAL: {
-                SomeArgs args = (SomeArgs) msg.obj;
-                try {
-                    inputMethod.initializeInternal((IBinder) args.arg1,
-                            (IInputMethodPrivilegedOperations) args.arg2, msg.arg1,
-                            (boolean) args.arg3, msg.arg2);
-                } finally {
-                    args.recycle();
-                }
+            case DO_INITIALIZE_INTERNAL:
+                inputMethod.initializeInternal((IInputMethod.InitParams) msg.obj);
                 return;
-            }
             case DO_SET_INPUT_CONTEXT: {
                 inputMethod.bindInput((InputBinding)msg.obj);
                 return;
@@ -194,21 +183,10 @@
                 return;
             case DO_START_INPUT: {
                 final SomeArgs args = (SomeArgs) msg.obj;
-                final IBinder startInputToken = (IBinder) args.arg1;
-                final IRemoteInputConnection remoteIc = (IRemoteInputConnection) args.arg2;
-                final EditorInfo info = (EditorInfo) args.arg3;
-                final ImeOnBackInvokedDispatcher imeDispatcher =
-                        (ImeOnBackInvokedDispatcher) args.arg4;
-                final CancellationGroup cancellationGroup = (CancellationGroup) args.arg5;
-                final boolean restarting = args.argi1 == 1;
-                @InputMethodNavButtonFlags
-                final int navButtonFlags = args.argi2;
-                final InputConnection ic = remoteIc != null
-                        ? new RemoteInputConnection(mTarget, remoteIc, cancellationGroup)
-                        : null;
-                info.makeCompatible(mTargetSdkVersion);
-                inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken,
-                        navButtonFlags, imeDispatcher);
+                final InputConnection inputConnection = (InputConnection) args.arg1;
+                final IInputMethod.StartInputParams params =
+                        (IInputMethod.StartInputParams) args.arg2;
+                inputMethod.dispatchStartInput(inputConnection, params);
                 args.recycle();
                 return;
             }
@@ -306,11 +284,8 @@
 
     @BinderThread
     @Override
-    public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
-            int configChanges, boolean stylusHwSupported,
-            @InputMethodNavButtonFlags int navButtonFlags) {
-        mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_INITIALIZE_INTERNAL,
-                configChanges, navButtonFlags, token, privOps, stylusHwSupported));
+    public void initializeInternal(@NonNull IInputMethod.InitParams params) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_INITIALIZE_INTERNAL, params));
     }
 
     @BinderThread
@@ -350,23 +325,19 @@
 
     @BinderThread
     @Override
-    public void startInput(IBinder startInputToken, IRemoteInputConnection inputConnection,
-            EditorInfo editorInfo, boolean restarting,
-            @InputMethodNavButtonFlags int navButtonFlags,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+    public void startInput(@NonNull IInputMethod.StartInputParams params) {
         if (mCancellationGroup == null) {
             Log.e(TAG, "startInput must be called after bindInput.");
             mCancellationGroup = new CancellationGroup();
         }
-        final SomeArgs args = SomeArgs.obtain();
-        args.arg1 = startInputToken;
-        args.arg2 = inputConnection;
-        args.arg3 = editorInfo;
-        args.argi1 = restarting ? 1 : 0;
-        args.argi2 = navButtonFlags;
-        args.arg4 = imeDispatcher;
-        args.arg5 = mCancellationGroup;
-        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_INPUT, args));
+
+        params.editorInfo.makeCompatible(mTargetSdkVersion);
+
+        final InputConnection ic = params.remoteInputConnection == null ? null
+                : new RemoteInputConnection(mTarget, params.remoteInputConnection,
+                        mCancellationGroup);
+
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT, ic, params));
     }
 
     @BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index e442e6d..c02f870 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -144,7 +144,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.IInputContentUriToken;
-import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.inputmethod.IInputMethod;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 import com.android.internal.inputmethod.ImeTracing;
 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
@@ -698,23 +698,21 @@
          */
         @MainThread
         @Override
-        public final void initializeInternal(@NonNull IBinder token,
-                IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
-                boolean stylusHwSupported, @InputMethodNavButtonFlags int navButtonFlags) {
+        public final void initializeInternal(@NonNull IInputMethod.InitParams params) {
             if (mDestroyed) {
                 Log.i(TAG, "The InputMethodService has already onDestroyed()."
                     + "Ignore the initialization.");
                 return;
             }
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
-            mConfigTracker.onInitialize(configChanges);
-            mPrivOps.set(privilegedOperations);
-            InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps);
-            if (stylusHwSupported) {
+            mConfigTracker.onInitialize(params.configChanges);
+            mPrivOps.set(params.privilegedOperations);
+            InputMethodPrivilegedOperationsRegistry.put(params.token, mPrivOps);
+            if (params.stylusHandWritingSupported) {
                 mInkWindow = new InkWindow(mWindow.getContext());
             }
-            mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags);
-            attachToken(token);
+            mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags);
+            attachToken(params.token);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
@@ -821,25 +819,23 @@
          */
         @MainThread
         @Override
-        public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
-                @NonNull EditorInfo editorInfo, boolean restarting,
-                @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags,
-                @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
-            mPrivOps.reportStartInputAsync(startInputToken);
-            mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags);
-            if (restarting) {
-                restartInput(inputConnection, editorInfo);
+        public final void dispatchStartInput(@Nullable InputConnection inputConnection,
+                @NonNull IInputMethod.StartInputParams params) {
+            mPrivOps.reportStartInputAsync(params.startInputToken);
+            mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags);
+            if (params.restarting) {
+                restartInput(inputConnection, params.editorInfo);
             } else {
-                startInput(inputConnection, editorInfo);
+                startInput(inputConnection, params.editorInfo);
             }
             // Update the IME dispatcher last, so that the previously registered back callback
             // (if any) can be unregistered using the old dispatcher if {@link #doFinishInput()}
             // is called from {@link #startInput(InputConnection, EditorInfo)} or
             // {@link #restartInput(InputConnection, EditorInfo)}.
-            mImeDispatcher = imeDispatcher;
+            mImeDispatcher = params.imeDispatcher;
             if (mWindow != null) {
                 mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher(
-                        imeDispatcher);
+                        params.imeDispatcher);
             }
         }
 
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index e02c45d..3a56662 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -58,6 +58,18 @@
     },
     {
       "file_patterns": [
+        "Parcel\\.java",
+        "[^/]*Bundle[^/]*\\.java"
+      ],
+      "name": "FrameworksMockingCoreTests",
+      "options": [
+        { "include-filter":  "android.os.BundleRecyclingTest"},
+        { "exclude-annotation": "androidx.test.filters.FlakyTest" },
+        { "exclude-annotation": "org.junit.Ignore" }
+      ]
+    },
+    {
+      "file_patterns": [
         "BatteryUsageStats[^/]*\\.java",
         "PowerComponents\\.java",
         "[^/]*BatteryConsumer[^/]*\\.java"
diff --git a/core/java/android/service/appprediction/OWNERS b/core/java/android/service/appprediction/OWNERS
index fe012da..6efb975 100644
--- a/core/java/android/service/appprediction/OWNERS
+++ b/core/java/android/service/appprediction/OWNERS
@@ -1,2 +1,4 @@
 adamcohen@google.com
+hyunyoungs@google.com
+pinyaoting@google.com
 sunnygoyal@google.com
diff --git a/core/java/android/service/smartspace/OWNERS b/core/java/android/service/smartspace/OWNERS
index 19ef9d7..d3acd3d3 100644
--- a/core/java/android/service/smartspace/OWNERS
+++ b/core/java/android/service/smartspace/OWNERS
@@ -1,2 +1,4 @@
+hyunyoungs@google.com
+awickham@google.com
 srazdan@google.com
-alexmang@google.com
\ No newline at end of file
+sunnygoyal@google.com
diff --git a/core/java/android/speech/AlternativeSpan.java b/core/java/android/speech/AlternativeSpan.java
index 9f6ad99..f21624d 100644
--- a/core/java/android/speech/AlternativeSpan.java
+++ b/core/java/android/speech/AlternativeSpan.java
@@ -38,14 +38,10 @@
  * other ways manipulating the SpeechRecognizer results before powering dictation features.
  */
 @DataClass(
-        genBuilder = true,
-        genConstructor = false,
         genEqualsHashCode = true,
         genParcelable = true,
         genToString = true
 )
-@DataClass.Suppress(
-        {"Builder.setStartPosition", "Builder.setEndPosition", "Builder.addAlternative"})
 public final class AlternativeSpan implements Parcelable {
     /**
      * The start position of the span of the originally recognized string.
@@ -64,17 +60,14 @@
     /**
      * All the alternatives for the [mStart, mEnd) span.
      *
-     * <p> Must not be empty. If the recognizer does not produce an alternative, this list will
-     * contain a single empty string.
+     * <p> Must not be empty. The object will only be created
+     * if there are some alternatives for the given span.
      *
      * <p> The alternatives may be strings of different lengths than the span they can replace.
      */
     @NonNull
     @DataClass.PluralOf("alternative")
     private final List<String> mAlternatives;
-    private static List<String> defaultAlternatives() {
-        return new ArrayList<>();
-    }
 
     private void onConstructed() {
         Preconditions.checkArgumentNonnegative(mStartPosition,
@@ -100,8 +93,27 @@
     //@formatter:off
 
 
+    /**
+     * Creates a new AlternativeSpan.
+     *
+     * @param startPosition
+     *   The start position of the span of the originally recognized string.
+     *
+     *   <p> Must be set to a non-negative value before building.
+     * @param endPosition
+     *   The exclusive end position of the span of the originally recognized string.
+     *
+     *   <p> Must be set to a value greater than the start of the span before building.
+     * @param alternatives
+     *   All the alternatives for the [mStart, mEnd) span.
+     *
+     *   <p> Must not be empty. The object will only be created
+     *   if there are some alternatives for the given span.
+     *
+     *   <p> The alternatives may be strings of different lengths than the span they can replace.
+     */
     @DataClass.Generated.Member
-    /* package-private */ AlternativeSpan(
+    public AlternativeSpan(
             int startPosition,
             int endPosition,
             @NonNull List<String> alternatives) {
@@ -137,8 +149,8 @@
     /**
      * All the alternatives for the [mStart, mEnd) span.
      *
-     * <p> Must not be empty. If the recognizer does not produce an alternative, this list will
-     * contain a single empty string.
+     * <p> Must not be empty. The object will only be created
+     * if there are some alternatives for the given span.
      *
      * <p> The alternatives may be strings of different lengths than the span they can replace.
      */
@@ -241,82 +253,11 @@
         }
     };
 
-    /**
-     * A builder for {@link AlternativeSpan}
-     */
-    @SuppressWarnings("WeakerAccess")
-    @DataClass.Generated.Member
-    public static final class Builder {
-
-        private int mStartPosition;
-        private int mEndPosition;
-        private @NonNull List<String> mAlternatives;
-
-        private long mBuilderFieldsSet = 0L;
-
-        /**
-         * Creates a new Builder.
-         *
-         * @param startPosition
-         *   The start position of the span of the originally recognized string.
-         *
-         *   <p> Must be set to a non-negative value before building.
-         * @param endPosition
-         *   The exclusive end position of the span of the originally recognized string.
-         *
-         *   <p> Must be set to a value greater than the start of the span before building.
-         */
-        public Builder(
-                int startPosition,
-                int endPosition) {
-            mStartPosition = startPosition;
-            mEndPosition = endPosition;
-        }
-
-        /**
-         * All the alternatives for the [mStart, mEnd) span.
-         *
-         * <p> Must not be empty. If the recognizer does not produce an alternative, this list will
-         * contain a single empty string.
-         *
-         * <p> The alternatives may be strings of different lengths than the span they can replace.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setAlternatives(@NonNull List<String> value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x4;
-            mAlternatives = value;
-            return this;
-        }
-
-        /** Builds the instance. This builder should not be touched after calling this! */
-        public @NonNull AlternativeSpan build() {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x8; // Mark builder used
-
-            if ((mBuilderFieldsSet & 0x4) == 0) {
-                mAlternatives = defaultAlternatives();
-            }
-            AlternativeSpan o = new AlternativeSpan(
-                    mStartPosition,
-                    mEndPosition,
-                    mAlternatives);
-            return o;
-        }
-
-        private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x8) != 0) {
-                throw new IllegalStateException(
-                        "This Builder should not be reused. Use a new Builder instance instead");
-            }
-        }
-    }
-
     @DataClass.Generated(
-            time = 1655225556488L,
+            time = 1656603431902L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/speech/AlternativeSpan.java",
-            inputSignatures = "private final  int mStartPosition\nprivate final  int mEndPosition\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"alternative\") java.util.List<java.lang.String> mAlternatives\nprivate static  java.util.List<java.lang.String> defaultAlternatives()\nprivate  void onConstructed()\nclass AlternativeSpan extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genEqualsHashCode=true, genParcelable=true, genToString=true)")
+            inputSignatures = "private final  int mStartPosition\nprivate final  int mEndPosition\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"alternative\") java.util.List<java.lang.String> mAlternatives\nprivate  void onConstructed()\nclass AlternativeSpan extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/speech/AlternativeSpans.java b/core/java/android/speech/AlternativeSpans.java
index 3393790..0de8c3e 100644
--- a/core/java/android/speech/AlternativeSpans.java
+++ b/core/java/android/speech/AlternativeSpans.java
@@ -32,21 +32,15 @@
  * specific span (substring) of the originally recognized string.
  */
 @DataClass(
-        genBuilder = true,
-        genConstructor = false,
         genEqualsHashCode = true,
         genParcelable = true,
         genToString = true
 )
-@DataClass.Suppress({"Builder.addSpan"})
 public final class AlternativeSpans implements Parcelable {
     /** List of {@link AlternativeSpan} for a specific speech recognition result. */
     @NonNull
     @DataClass.PluralOf("span")
     private final List<AlternativeSpan> mSpans;
-    private static List<AlternativeSpan> defaultSpans() {
-        return new ArrayList<>();
-    }
 
 
 
@@ -63,8 +57,14 @@
     //@formatter:off
 
 
+    /**
+     * Creates a new AlternativeSpans.
+     *
+     * @param spans
+     *   List of {@link AlternativeSpan} for a specific speech recognition result.
+     */
     @DataClass.Generated.Member
-    /* package-private */ AlternativeSpans(
+    public AlternativeSpans(
             @NonNull List<AlternativeSpan> spans) {
         this.mSpans = spans;
         com.android.internal.util.AnnotationValidations.validate(
@@ -163,57 +163,11 @@
         }
     };
 
-    /**
-     * A builder for {@link AlternativeSpans}
-     */
-    @SuppressWarnings("WeakerAccess")
-    @DataClass.Generated.Member
-    public static final class Builder {
-
-        private @NonNull List<AlternativeSpan> mSpans;
-
-        private long mBuilderFieldsSet = 0L;
-
-        public Builder() {
-        }
-
-        /**
-         * List of {@link AlternativeSpan} for a specific speech recognition result.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setSpans(@NonNull List<AlternativeSpan> value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x1;
-            mSpans = value;
-            return this;
-        }
-
-        /** Builds the instance. This builder should not be touched after calling this! */
-        public @NonNull AlternativeSpans build() {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x2; // Mark builder used
-
-            if ((mBuilderFieldsSet & 0x1) == 0) {
-                mSpans = defaultSpans();
-            }
-            AlternativeSpans o = new AlternativeSpans(
-                    mSpans);
-            return o;
-        }
-
-        private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x2) != 0) {
-                throw new IllegalStateException(
-                        "This Builder should not be reused. Use a new Builder instance instead");
-            }
-        }
-    }
-
     @DataClass.Generated(
-            time = 1655151024975L,
+            time = 1656603476918L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/speech/AlternativeSpans.java",
-            inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"span\") java.util.List<android.speech.AlternativeSpan> mSpans\nprivate static  java.util.List<android.speech.AlternativeSpan> defaultSpans()\nclass AlternativeSpans extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genEqualsHashCode=true, genParcelable=true, genToString=true)")
+            inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"span\") java.util.List<android.speech.AlternativeSpan> mSpans\nclass AlternativeSpans extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/text/style/AbsoluteSizeSpan.java b/core/java/android/text/style/AbsoluteSizeSpan.java
index 3b4eea7..6d4f05a 100644
--- a/core/java/android/text/style/AbsoluteSizeSpan.java
+++ b/core/java/android/text/style/AbsoluteSizeSpan.java
@@ -130,4 +130,10 @@
             ds.setTextSize(mSize);
         }
     }
+
+
+    @Override
+    public String toString() {
+        return "AbsoluteSizeSpan{size=" + getSize() + ", isDip=" + getDip() + '}';
+    }
 }
diff --git a/core/java/android/text/style/AlignmentSpan.java b/core/java/android/text/style/AlignmentSpan.java
index 18c3e16..31db78a 100644
--- a/core/java/android/text/style/AlignmentSpan.java
+++ b/core/java/android/text/style/AlignmentSpan.java
@@ -101,5 +101,10 @@
         public Layout.Alignment getAlignment() {
             return mAlignment;
         }
+
+        @Override
+        public String toString() {
+            return "AlignmentSpan.Standard{alignment=" + getAlignment() + '}';
+        }
     }
 }
diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java
index 44e35615..13647d9 100644
--- a/core/java/android/text/style/BackgroundColorSpan.java
+++ b/core/java/android/text/style/BackgroundColorSpan.java
@@ -100,4 +100,9 @@
     public void updateDrawState(@NonNull TextPaint textPaint) {
         textPaint.bgColor = mColor;
     }
+
+    @Override
+    public String toString() {
+        return "BackgroundColorSpan{color=#" + String.format("%08X", getBackgroundColor()) + '}';
+    }
 }
diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java
index ad61788..b3e7bda 100644
--- a/core/java/android/text/style/BulletSpan.java
+++ b/core/java/android/text/style/BulletSpan.java
@@ -235,4 +235,13 @@
             paint.setStyle(style);
         }
     }
+
+    @Override
+    public String toString() {
+        return "BulletSpan{"
+                + "gapWidth=" + getGapWidth()
+                + ", bulletRadius=" + getBulletRadius()
+                + ", color=" + String.format("%08X", getColor())
+                + '}';
+    }
 }
diff --git a/core/java/android/text/style/ClickableSpan.java b/core/java/android/text/style/ClickableSpan.java
index 60aed2a..238da55 100644
--- a/core/java/android/text/style/ClickableSpan.java
+++ b/core/java/android/text/style/ClickableSpan.java
@@ -64,4 +64,9 @@
     public int getId() {
         return mId;
     }
+
+    @Override
+    public String toString() {
+        return "ClickableSpan{}";
+    }
 }
diff --git a/core/java/android/text/style/DrawableMarginSpan.java b/core/java/android/text/style/DrawableMarginSpan.java
index cd199b3..1f3446e 100644
--- a/core/java/android/text/style/DrawableMarginSpan.java
+++ b/core/java/android/text/style/DrawableMarginSpan.java
@@ -112,4 +112,25 @@
             }
         }
     }
+
+    @Override
+    public String toString() {
+        return "DrawableMarginSpan{drawable=" + mDrawable + ", padding=" + mPad + '}';
+    }
+
+    /**
+     * Returns the drawable used.
+     * @return a drawable
+     */
+    @NonNull public Drawable getDrawable() {
+        return mDrawable;
+    }
+
+    /**
+     * Returns a distance between the drawable and text in pixel.
+     * @return a distance pixel from the text
+     */
+    @Px public int getPadding() {
+        return mPad;
+    }
 }
diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java
index d6d99f8..38897ed 100644
--- a/core/java/android/text/style/DynamicDrawableSpan.java
+++ b/core/java/android/text/style/DynamicDrawableSpan.java
@@ -189,5 +189,13 @@
 
         return d;
     }
+
+    @Override
+    public String toString() {
+        return "DynamicDrawableSpan{"
+                + "verticalAlignment=" + getVerticalAlignment()
+                + ", drawable=" + getDrawable()
+                + '}';
+    }
 }
 
diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java
index f7706745..5c97426 100644
--- a/core/java/android/text/style/ForegroundColorSpan.java
+++ b/core/java/android/text/style/ForegroundColorSpan.java
@@ -101,4 +101,9 @@
     public void updateDrawState(@NonNull TextPaint textPaint) {
         textPaint.setColor(mColor);
     }
+
+    @Override
+    public String toString() {
+        return "ForegroundColorSpan{color=#" + String.format("%08X", getForegroundColor()) + '}';
+    }
 }
diff --git a/core/java/android/text/style/IconMarginSpan.java b/core/java/android/text/style/IconMarginSpan.java
index ad78bd5..a6c5139 100644
--- a/core/java/android/text/style/IconMarginSpan.java
+++ b/core/java/android/text/style/IconMarginSpan.java
@@ -110,4 +110,24 @@
         }
     }
 
+    @Override
+    public String toString() {
+        return "IconMarginSpan{bitmap=" + getBitmap() + ", padding=" + getPadding() + '}';
+    }
+
+    /**
+     * Returns the bitmap to be used at the beginning of the text
+     * @return a bitmap
+     */
+    @NonNull public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    /**
+     * Returns the padding width between the bitmap and the text.
+     * @return a padding width in pixels
+     */
+    @Px public int getPadding() {
+        return mPad;
+    }
 }
diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
index ec55aeb..fc85590 100644
--- a/core/java/android/text/style/ImageSpan.java
+++ b/core/java/android/text/style/ImageSpan.java
@@ -266,4 +266,13 @@
     public String getSource() {
         return mSource;
     }
+
+    @Override
+    public String toString() {
+        return "ImageSpan{"
+                + "drawable=" + getDrawable()
+                + ", source='" + getSource() + '\''
+                + ", verticalAlignment=" + getVerticalAlignment()
+                + '}';
+    }
 }
diff --git a/core/java/android/text/style/LocaleSpan.java b/core/java/android/text/style/LocaleSpan.java
index a3a4bf8..489ceea 100644
--- a/core/java/android/text/style/LocaleSpan.java
+++ b/core/java/android/text/style/LocaleSpan.java
@@ -126,4 +126,9 @@
     private static void apply(@NonNull Paint paint, @NonNull LocaleList locales) {
         paint.setTextLocales(locales);
     }
+
+    @Override
+    public String toString() {
+        return "LocaleSpan{locales=" + getLocales() + '}';
+    }
 }
diff --git a/core/java/android/text/style/MaskFilterSpan.java b/core/java/android/text/style/MaskFilterSpan.java
index d76ef94..587d1b4 100644
--- a/core/java/android/text/style/MaskFilterSpan.java
+++ b/core/java/android/text/style/MaskFilterSpan.java
@@ -56,4 +56,9 @@
     public void updateDrawState(TextPaint ds) {
         ds.setMaskFilter(mFilter);
     }
+
+    @Override
+    public String toString() {
+        return "MaskFilterSpan{filter=" + getMaskFilter() + '}';
+    }
 }
diff --git a/core/java/android/text/style/QuoteSpan.java b/core/java/android/text/style/QuoteSpan.java
index a1c12c2..393ede6 100644
--- a/core/java/android/text/style/QuoteSpan.java
+++ b/core/java/android/text/style/QuoteSpan.java
@@ -203,4 +203,13 @@
         p.setStyle(style);
         p.setColor(color);
     }
+
+    @Override
+    public String toString() {
+        return "QuoteSpan{"
+                + "color=" + String.format("#%08X", getColor())
+                + ", stripeWidth=" + getStripeWidth()
+                + ", gapWidth=" + getGapWidth()
+                + '}';
+    }
 }
diff --git a/core/java/android/text/style/RelativeSizeSpan.java b/core/java/android/text/style/RelativeSizeSpan.java
index 3094f27..5c91b20 100644
--- a/core/java/android/text/style/RelativeSizeSpan.java
+++ b/core/java/android/text/style/RelativeSizeSpan.java
@@ -97,4 +97,9 @@
     public void updateMeasureState(@NonNull TextPaint ds) {
         ds.setTextSize(ds.getTextSize() * mProportion);
     }
+
+    @Override
+    public String toString() {
+        return "RelativeSizeSpan{proportion=" + getSizeChange() + '}';
+    }
 }
diff --git a/core/java/android/text/style/ScaleXSpan.java b/core/java/android/text/style/ScaleXSpan.java
index 6ef4cec..d022b07 100644
--- a/core/java/android/text/style/ScaleXSpan.java
+++ b/core/java/android/text/style/ScaleXSpan.java
@@ -102,4 +102,9 @@
     public void updateMeasureState(TextPaint ds) {
         ds.setTextScaleX(ds.getTextScaleX() * mProportion);
     }
+
+    @Override
+    public String toString() {
+        return "ScaleXSpan{scaleX=" + getScaleX() + '}';
+    }
 }
diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java
index a630505..65ee347 100644
--- a/core/java/android/text/style/StrikethroughSpan.java
+++ b/core/java/android/text/style/StrikethroughSpan.java
@@ -77,4 +77,9 @@
     public void updateDrawState(@NonNull TextPaint ds) {
         ds.setStrikeThruText(true);
     }
+
+    @Override
+    public String toString() {
+        return "StrikethroughSpan{}";
+    }
 }
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java
index 176a068..378682b 100644
--- a/core/java/android/text/style/StyleSpan.java
+++ b/core/java/android/text/style/StyleSpan.java
@@ -185,4 +185,12 @@
 
         paint.setTypeface(tf);
     }
+
+    @Override
+    public String toString() {
+        return "StyleSpan{"
+                + "style=" + getStyle()
+                + ", fontWeightAdjustment=" + getFontWeightAdjustment()
+                + '}';
+    }
 }
diff --git a/core/java/android/text/style/SubscriptSpan.java b/core/java/android/text/style/SubscriptSpan.java
index 3d15aad..729a9ad 100644
--- a/core/java/android/text/style/SubscriptSpan.java
+++ b/core/java/android/text/style/SubscriptSpan.java
@@ -86,4 +86,9 @@
     public void updateMeasureState(@NonNull TextPaint textPaint) {
         textPaint.baselineShift -= (int) (textPaint.ascent() / 2);
     }
+
+    @Override
+    public String toString() {
+        return "SubscriptSpan{}";
+    }
 }
diff --git a/core/java/android/text/style/SuperscriptSpan.java b/core/java/android/text/style/SuperscriptSpan.java
index 3dc9d3f..5610223 100644
--- a/core/java/android/text/style/SuperscriptSpan.java
+++ b/core/java/android/text/style/SuperscriptSpan.java
@@ -83,4 +83,9 @@
     public void updateMeasureState(@NonNull TextPaint textPaint) {
         textPaint.baselineShift += (int) (textPaint.ascent() / 2);
     }
+
+    @Override
+    public String toString() {
+        return "SuperscriptSpan{}";
+    }
 }
diff --git a/core/java/android/text/style/TabStopSpan.java b/core/java/android/text/style/TabStopSpan.java
index 2cceb2c..8128475 100644
--- a/core/java/android/text/style/TabStopSpan.java
+++ b/core/java/android/text/style/TabStopSpan.java
@@ -65,5 +65,10 @@
         public int getTabStop() {
             return mTabOffset;
         }
+
+        @Override
+        public String toString() {
+            return "TabStopSpan.Standard{tabOffset=" + getTabStop() + '}';
+        }
     }
 }
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index adb379a..85b7ae9 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -442,6 +442,14 @@
         return mElegantTextHeight;
     }
 
+    /**
+     * Returns the value of letter spacing to be added in em unit.
+     * @return a letter spacing amount
+     */
+    public float getLetterSpacing() {
+        return mLetterSpacing;
+    }
+
     @Override
     public void updateDrawState(TextPaint ds) {
         updateMeasureState(ds);
@@ -534,4 +542,26 @@
             ds.setFontVariationSettings(mFontVariationSettings);
         }
     }
+
+    @Override
+    public String toString() {
+        return "TextAppearanceSpan{"
+                + "familyName='" + getFamily() + '\''
+                + ", style=" + getTextStyle()
+                + ", textSize=" + getTextSize()
+                + ", textColor=" + getTextColor()
+                + ", textColorLink=" + getLinkTextColor()
+                + ", typeface=" + getTypeface()
+                + ", textFontWeight=" + getTextFontWeight()
+                + ", textLocales=" + getTextLocales()
+                + ", shadowRadius=" + getShadowRadius()
+                + ", shadowDx=" + getShadowDx()
+                + ", shadowDy=" + getShadowDy()
+                + ", shadowColor=" + String.format("#%08X", getShadowColor())
+                + ", elegantTextHeight=" + isElegantTextHeight()
+                + ", letterSpacing=" + getLetterSpacing()
+                + ", fontFeatureSettings='" + getFontFeatureSettings() + '\''
+                + ", fontVariationSettings='" + getFontVariationSettings() + '\''
+                + '}';
+    }
 }
diff --git a/core/java/android/text/style/TypefaceSpan.java b/core/java/android/text/style/TypefaceSpan.java
index 908de29..bdfc772 100644
--- a/core/java/android/text/style/TypefaceSpan.java
+++ b/core/java/android/text/style/TypefaceSpan.java
@@ -180,4 +180,12 @@
         }
         paint.setTypeface(styledTypeface);
     }
+
+    @Override
+    public String toString() {
+        return "TypefaceSpan{"
+                + "family='" + getFamily() + '\''
+                + ", typeface=" + getTypeface()
+                + '}';
+    }
 }
diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java
index eab1ef4..e811390 100644
--- a/core/java/android/text/style/URLSpan.java
+++ b/core/java/android/text/style/URLSpan.java
@@ -109,4 +109,9 @@
             Log.w("URLSpan", "Actvity was not found for intent, " + intent.toString());
         }
     }
+
+    @Override
+    public String toString() {
+        return "URLSpan{" + "URL='" + getURL() + '\'' + '}';
+    }
 }
diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java
index 800838e..075e70b 100644
--- a/core/java/android/text/style/UnderlineSpan.java
+++ b/core/java/android/text/style/UnderlineSpan.java
@@ -77,4 +77,9 @@
     public void updateDrawState(@NonNull TextPaint ds) {
         ds.setUnderlineText(true);
     }
+
+    @Override
+    public String toString() {
+        return "UnderlineSpan{}";
+    }
 }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 1a37d11..ff1817a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2351,47 +2351,6 @@
     }
 
     /**
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public static void setDisplayProjection(IBinder displayToken,
-            int orientation, Rect layerStackRect, Rect displayRect) {
-        synchronized (SurfaceControl.class) {
-            sGlobalTransaction.setDisplayProjection(displayToken, orientation,
-                    layerStackRect, displayRect);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
-        synchronized (SurfaceControl.class) {
-            sGlobalTransaction.setDisplayLayerStack(displayToken, layerStack);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public static void setDisplaySurface(IBinder displayToken, Surface surface) {
-        synchronized (SurfaceControl.class) {
-            sGlobalTransaction.setDisplaySurface(displayToken, surface);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public static void setDisplaySize(IBinder displayToken, int width, int height) {
-        synchronized (SurfaceControl.class) {
-            sGlobalTransaction.setDisplaySize(displayToken, width, height);
-        }
-    }
-
-    /**
      * Overrides HDR modes for a display device.
      *
      * If the caller does not have ACCESS_SURFACE_FLINGER permission, this will throw a Security
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 657c0b7..8907420 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -604,6 +604,8 @@
      */
     private boolean mCheckIfCanDraw = false;
 
+    private boolean mDrewOnceForSync = false;
+
     int mSyncSeqId = 0;
     int mLastSyncSeqId = 0;
 
@@ -2820,10 +2822,6 @@
         // Execute enqueued actions on every traversal in case a detached view enqueued an action
         getRunQueue().executeActions(mAttachInfo.mHandler);
 
-        if (mApplyInsetsRequested) {
-            dispatchApplyInsets(host);
-        }
-
         if (mFirst) {
             // make sure touch mode code executes by setting cached value
             // to opposite of the added touch mode.
@@ -2887,6 +2885,18 @@
             }
         }
 
+        if (mApplyInsetsRequested) {
+            dispatchApplyInsets(host);
+            if (mLayoutRequested) {
+                // Short-circuit catching a new layout request here, so
+                // we don't need to go through two layout passes when things
+                // change due to fitting system windows, which can happen a lot.
+                windowSizeMayChange |= measureHierarchy(host, lp,
+                        mView.getContext().getResources(),
+                        desiredWindowWidth, desiredWindowHeight);
+            }
+        }
+
         if (layoutRequested) {
             // Clear this now, so that if anything requests a layout in the
             // rest of this function we will catch it and re-run a full
@@ -2995,6 +3005,9 @@
                     reportNextDraw();
                     mSyncBuffer = true;
                     isSyncRequest = true;
+                    if (!cancelDraw) {
+                        mDrewOnceForSync = false;
+                    }
                 }
 
                 final boolean surfaceControlChanged =
@@ -3516,9 +3529,11 @@
 
         mCheckIfCanDraw = isSyncRequest || cancelDraw;
 
-        boolean cancelAndRedraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || cancelDraw;
+        boolean cancelAndRedraw =
+                mAttachInfo.mTreeObserver.dispatchOnPreDraw() || (cancelDraw && mDrewOnceForSync);
         if (!cancelAndRedraw) {
             createSyncIfNeeded();
+            mDrewOnceForSync = true;
         }
 
         if (!isViewVisible) {
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 418132a..4fc9a07 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -63,9 +63,6 @@
 
     void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
 
-    void temporaryEnableAccessibilityStateUntilKeyguardRemoved(in ComponentName service,
-            boolean touchExplorationEnabled);
-
     // Used by UiAutomation
     IBinder getWindowToken(int windowId, int userId);
 
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index ae145de..313dc43 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -74,6 +74,10 @@
 
     /** @hide */
     @NonNull
+    public static final AutofillId NO_AUTOFILL_ID = new AutofillId(0);
+
+    /** @hide */
+    @NonNull
     @TestApi
     public static AutofillId withoutSession(@NonNull AutofillId id) {
         final int flags = id.mFlags & ~FLAG_HAS_SESSION;
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 6b7a6e9..9492375 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -1016,6 +1016,7 @@
         newEditorInfo.mInitialSurroundingText = mInitialSurroundingText;
         newEditorInfo.hintLocales = hintLocales;
         newEditorInfo.contentMimeTypes = ArrayUtils.cloneOrNull(contentMimeTypes);
+        newEditorInfo.targetInputMethodUser = targetInputMethodUser;
         return newEditorInfo;
     }
 
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
new file mode 100644
index 0000000..4b472ed
--- /dev/null
+++ b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.view.IInputMethodManager;
+
+import java.util.List;
+
+/**
+ * A wrapper class to invoke IPCs defined in {@link IInputMethodManager}.
+ */
+final class IInputMethodManagerInvoker {
+    @NonNull
+    private final IInputMethodManager mTarget;
+
+    private IInputMethodManagerInvoker(@NonNull IInputMethodManager target) {
+        mTarget = target;
+    }
+
+    @AnyThread
+    @NonNull
+    static IInputMethodManagerInvoker create(@NonNull IInputMethodManager imm) {
+        return new IInputMethodManagerInvoker(imm);
+    }
+
+    @AnyThread
+    void addClient(IInputMethodClient client, IRemoteInputConnection fallbackInputConnection,
+            int untrustedDisplayId) {
+        try {
+            mTarget.addClient(client, fallbackInputConnection, untrustedDisplayId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    List<InputMethodInfo> getInputMethodList(@UserIdInt int userId) {
+        try {
+            return mTarget.getInputMethodList(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    List<InputMethodInfo> getAwareLockedInputMethodList(@UserIdInt int userId,
+            @DirectBootAwareness int directBootAwareness) {
+        try {
+            return mTarget.getAwareLockedInputMethodList(userId, directBootAwareness);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
+        try {
+            return mTarget.getEnabledInputMethodList(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+            boolean allowsImplicitlySelectedSubtypes) {
+        try {
+            return mTarget.getEnabledInputMethodSubtypeList(imiId,
+                    allowsImplicitlySelectedSubtypes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    InputMethodSubtype getLastInputMethodSubtype() {
+        try {
+            return mTarget.getLastInputMethodSubtype();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        try {
+            return mTarget.showSoftInput(client, windowToken, flags, resultReceiver, reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        try {
+            return mTarget.hideSoftInput(client, windowToken, flags, resultReceiver, reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    InputBindResult startInputOrWindowGainedFocus(@StartInputReason int startInputReason,
+            IInputMethodClient client, IBinder windowToken,
+            @StartInputFlags int startInputFlags,
+            @android.view.WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+            int windowFlags, EditorInfo editorInfo, IRemoteInputConnection remoteInputConnection,
+            IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+            int unverifiedTargetSdkVersion, ImeOnBackInvokedDispatcher imeDispatcher) {
+        try {
+            return mTarget.startInputOrWindowGainedFocus(startInputReason, client, windowToken,
+                    startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
+                    remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, imeDispatcher);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    void showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode) {
+        try {
+            mTarget.showInputMethodPickerFromClient(client, auxiliarySubtypeMode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    void showInputMethodPickerFromSystem(IInputMethodClient client, int auxiliarySubtypeMode,
+            int displayId) {
+        try {
+            mTarget.showInputMethodPickerFromSystem(client, auxiliarySubtypeMode, displayId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    void showInputMethodAndSubtypeEnablerFromClient(IInputMethodClient client, String imeId) {
+        try {
+            mTarget.showInputMethodAndSubtypeEnablerFromClient(client, imeId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    boolean isInputMethodPickerShownForTest() {
+        try {
+            return mTarget.isInputMethodPickerShownForTest();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    InputMethodSubtype getCurrentInputMethodSubtype() {
+        try {
+            return mTarget.getCurrentInputMethodSubtype();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    void setAdditionalInputMethodSubtypes(String imeId, InputMethodSubtype[] subtypes) {
+        try {
+            mTarget.setAdditionalInputMethodSubtypes(imeId, subtypes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    int getInputMethodWindowVisibleHeight(IInputMethodClient client) {
+        try {
+            return mTarget.getInputMethodWindowVisibleHeight(client);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    void reportVirtualDisplayGeometryAsync(IInputMethodClient client, int childDisplayId,
+            float[] matrixValues) {
+        try {
+            mTarget.reportVirtualDisplayGeometryAsync(client, childDisplayId, matrixValues);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
+        try {
+            mTarget.reportPerceptibleAsync(windowToken, perceptible);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
+        try {
+            mTarget.removeImeSurfaceFromWindowAsync(windowToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    void startStylusHandwriting(IInputMethodClient client) {
+        try {
+            mTarget.startStylusHandwriting(client);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+        try {
+            return mTarget.isStylusHandwritingAvailableAsUser(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 54ff11c..95add29 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -29,10 +29,9 @@
 import android.view.InputChannel;
 import android.view.MotionEvent;
 import android.view.View;
-import android.window.ImeOnBackInvokedDispatcher;
 
 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
-import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.inputmethod.IInputMethod;
 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
 
@@ -100,21 +99,12 @@
      * unique token for the session it has with the system service as well as
      * IPC endpoint to do some other privileged operations.
      *
-     * @param token special token for the system to identify
-     *              {@link InputMethodService}
-     * @param privilegedOperations IPC endpoint to do some privileged
-     *                             operations that are allowed only to the
-     *                             current IME.
-     * @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME.
-     * @param stylusHwSupported {@link InputMethodInfo#supportsStylusHandwriting()} declared by IME.
-     * @param navButtonFlags The initial state of {@link InputMethodNavButtonFlags}.
+     * @param params Contains parameters to initialize the {@link InputMethodService}.
      * @hide
      */
     @MainThread
-    default void initializeInternal(IBinder token,
-            IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
-            boolean stylusHwSupported, @InputMethodNavButtonFlags int navButtonFlags) {
-        attachToken(token);
+    default void initializeInternal(@NonNull IInputMethod.InitParams params) {
+        attachToken(params.token);
     }
 
     /**
@@ -210,47 +200,30 @@
     public void restartInput(InputConnection inputConnection, EditorInfo editorInfo);
 
     /**
-     * This method is called when {@code {@link #startInput(InputConnection, EditorInfo)} or
-     * {@code {@link #restartInput(InputConnection, EditorInfo)} needs to be dispatched.
+     * This method is called when {@link #startInput(InputConnection, EditorInfo)} or
+     * {@link #restartInput(InputConnection, EditorInfo)} needs to be dispatched.
      *
-     * <p>Note: This method is hidden because the {@code startInputToken} that this method is
-     * dealing with is one of internal details, which should not be exposed to the IME developers.
-     * If you override this method, you are responsible for not breaking existing IMEs that expect
+     * <p>Note: This method is hidden because {@link IInputMethod.StartInputParams} is an internal
+     * details, which should not be exposed to the IME developers. If you override this method, you
+     * are responsible for not breaking existing IMEs that expect
      * {@link #startInput(InputConnection, EditorInfo)} to be still called back.</p>
      *
      * @param inputConnection optional specific input connection for communicating with the text
      *                        box; if {@code null}, you should use the generic bound input
      *                        connection
-     * @param editorInfo information about the text box (typically, an EditText) that requests input
-     * @param restarting {@code false} if this corresponds to
-     *                   {@link #startInput(InputConnection, EditorInfo)}. Otherwise this
-     *                   corresponds to {@link #restartInput(InputConnection, EditorInfo)}.
-     * @param startInputToken a token that identifies a logical session that starts with this method
-     *                        call. Some internal IPCs such as {@link
-     *                        InputMethodManager#setImeWindowStatus(IBinder, IBinder, int, int)}
-     *                        require this token to work, and you have to keep the token alive until
-     *                        the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as
-     *                        long as your implementation of {@link InputMethod} relies on such
-     *                        IPCs
-     * @param navButtonFlags {@link InputMethodNavButtonFlags} in the initial state of this session.
-     * @param imeDispatcher The {@link ImeOnBackInvokedDispatcher} to be set on the
-     *                      IME's {@link android.window.WindowOnBackInvokedDispatcher}, so that IME
-     *                      {@link android.window.OnBackInvokedCallback}s can be forwarded to
-     *                      the client requesting to start input.
+     * @param params Raw object of {@link IInputMethod.StartInputParams}.
      * @see #startInput(InputConnection, EditorInfo)
      * @see #restartInput(InputConnection, EditorInfo)
      * @see EditorInfo
      * @hide
      */
     @MainThread
-    default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
-            @NonNull EditorInfo editorInfo, boolean restarting,
-            @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
-        if (restarting) {
-            restartInput(inputConnection, editorInfo);
+    default void dispatchStartInput(@Nullable InputConnection inputConnection,
+            @NonNull IInputMethod.StartInputParams params) {
+        if (params.restarting) {
+            restartInput(inputConnection, params.editorInfo);
         } else {
-            startInput(inputConnection, editorInfo);
+            startInput(inputConnection, params.editorInfo);
         }
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 587fcef..59d3314 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -65,7 +65,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
@@ -90,6 +89,7 @@
 import android.view.View;
 import android.view.ViewRootImpl;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.window.ImeOnBackInvokedDispatcher;
 import android.window.WindowOnBackInvokedDispatcher;
@@ -394,10 +394,17 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // This is a bug id.
 
+    /**
+     * @deprecated Use {@link #mServiceInvoker} instead.
+     */
+    @Deprecated
     @UnsupportedAppUsage
     final IInputMethodManager mService;
     final Looper mMainLooper;
 
+    @NonNull
+    private final IInputMethodManagerInvoker mServiceInvoker;
+
     // For scheduling work on the main thread.  This also serves as our
     // global lock.
     // Remark on @UnsupportedAppUsage: there were context leaks on old versions
@@ -490,29 +497,26 @@
     @GuardedBy("mH")
     private Matrix mVirtualDisplayToScreenMatrix = null;
 
-    /**
-     * As reported by {@link InputBindResult}. This value is determined by
-     * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}.
-     */
-    @GuardedBy("mH")
-    private boolean mIsInputMethodSuppressingSpellChecker = false;
-
     // -----------------------------------------------------------
 
     /**
      * Sequence number of this binding, as returned by the server.
      */
     int mBindSequence = -1;
+
     /**
      * ID of the method we are bound to.
+     *
+     * @deprecated New code should use {@code mCurBindState.mImeId}.
      */
+    @Deprecated
     @UnsupportedAppUsage
     String mCurId;
 
     /**
      * Kept for {@link UnsupportedAppUsage}.  Not officially maintained.
      *
-     * @deprecated New code should use {@link #mCurrentInputMethodSession}.
+     * @deprecated New code should use {@code mCurBindState.mImeSession}.
      */
     @Deprecated
     @GuardedBy("mH")
@@ -521,11 +525,12 @@
     IInputMethodSession mCurMethod;
 
     /**
-     * Encapsulates IPCs to the currently connected InputMethodService.
+     * Encapsulates per-binding state from {@link InputBindResult}.
      */
-    @Nullable
     @GuardedBy("mH")
-    private IInputMethodSessionInvoker mCurrentInputMethodSession = null;
+    @Nullable
+    private BindState mCurBindState;
+
     /**
      * Encapsulates IPCs to the currently connected AccessibilityServices.
      */
@@ -638,11 +643,7 @@
      * @hide
      */
     public void reportPerceptible(IBinder windowToken, boolean perceptible) {
-        try {
-            mService.reportPerceptibleAsync(windowToken, perceptible);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        mServiceInvoker.reportPerceptibleAsync(windowToken, perceptible);
     }
 
     private final class DelegateImpl implements
@@ -692,8 +693,8 @@
         public void finishInputAndReportToIme() {
             synchronized (mH) {
                 finishInputLocked();
-                if (mCurrentInputMethodSession != null) {
-                    mCurrentInputMethodSession.finishInput();
+                if (isImeSessionAvailableLocked()) {
+                    mCurBindState.mImeSession.finishInput();
                 }
                 forAccessibilitySessionsLocked(
                         IAccessibilityInputMethodSessionInvoker::finishInput);
@@ -739,30 +740,26 @@
             synchronized (mH) {
                 // For some reason we didn't do a startInput + windowFocusGain, so
                 // we'll just do a window focus gain and call it a day.
-                try {
-                    View servedView = controller.getServedView();
-                    boolean nextFocusHasConnection = servedView != null && servedView == focusedView
-                            && hasActiveConnection(focusedView);
-                    if (DEBUG) {
-                        Log.v(TAG, "Reporting focus gain, without startInput"
-                                + ", nextFocusIsServedView=" + nextFocusHasConnection);
-                    }
-
-                    final int startInputReason = nextFocusHasConnection
-                            ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
-                            : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
-                    // ignore the result
-                    mService.startInputOrWindowGainedFocus(
-                            startInputReason, mClient,
-                            focusedView.getWindowToken(), startInputFlags, softInputMode,
-                            windowFlags,
-                            null,
-                            null, null,
-                            mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
-                            mImeDispatcher);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
+                View servedView = controller.getServedView();
+                boolean nextFocusHasConnection = servedView != null && servedView == focusedView
+                        && hasActiveConnection(focusedView);
+                if (DEBUG) {
+                    Log.v(TAG, "Reporting focus gain, without startInput"
+                            + ", nextFocusIsServedView=" + nextFocusHasConnection);
                 }
+
+                final int startInputReason = nextFocusHasConnection
+                        ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
+                        : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+                // ignore the result
+                mServiceInvoker.startInputOrWindowGainedFocus(
+                        startInputReason, mClient,
+                        focusedView.getWindowToken(), startInputFlags, softInputMode,
+                        windowFlags,
+                        null,
+                        null, null,
+                        mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
+                        mImeDispatcher);
             }
         }
 
@@ -822,8 +819,7 @@
         @Override
         public boolean hasActiveConnection(View view) {
             synchronized (mH) {
-                if (!hasServedByInputMethodLocked(view)
-                        || mCurrentInputMethodSession == null) {
+                if (!hasServedByInputMethodLocked(view) || !isImeSessionAvailableLocked()) {
                     return false;
                 }
 
@@ -931,13 +927,10 @@
 
                         setInputChannelLocked(res.channel);
                         mCurMethod = res.method; // for @UnsupportedAppUsage
-                        mCurrentInputMethodSession =
-                                IInputMethodSessionInvoker.createOrNull(res.method);
-                        mCurId = res.id;
+                        mCurBindState = new BindState(res);
+                        mCurId = res.id; // for @UnsupportedAppUsage
                         mBindSequence = res.sequence;
                         mVirtualDisplayToScreenMatrix = res.getVirtualDisplayToScreenMatrix();
-                        mIsInputMethodSuppressingSpellChecker =
-                                res.isInputMethodSuppressingSpellChecker;
                     }
                     startInputInner(StartInputReason.BOUND_TO_IMMS, null, 0, 0, 0);
                     return;
@@ -1127,7 +1120,7 @@
                         }
                         mVirtualDisplayToScreenMatrix.setValues(matrixValues);
 
-                        if (mCursorAnchorInfo == null || mCurrentInputMethodSession == null
+                        if (mCursorAnchorInfo == null || !isImeSessionAvailableLocked()
                                 || mServedInputConnection == null) {
                             return;
                         }
@@ -1136,7 +1129,7 @@
                         }
                         // Since the host VirtualDisplay is moved, we need to issue
                         // IMS#updateCursorAnchorInfo() again.
-                        mCurrentInputMethodSession.updateCursorAnchorInfo(
+                        mCurBindState.mImeSession.updateCursorAnchorInfo(
                                 CursorAnchorInfo.createForAdditionalParentMatrix(
                                         mCursorAnchorInfo, mVirtualDisplayToScreenMatrix));
                     }
@@ -1279,9 +1272,7 @@
         // 1) doing so has no effect for A and 2) doing so is sufficient for B.
         final long identity = Binder.clearCallingIdentity();
         try {
-            service.addClient(imm.mClient, imm.mFallbackInputConnection, displayId);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            imm.mServiceInvoker.addClient(imm.mClient, imm.mFallbackInputConnection, displayId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -1319,8 +1310,9 @@
         return new InputMethodManager(stubInterface, displayId, looper);
     }
 
-    private InputMethodManager(IInputMethodManager service, int displayId, Looper looper) {
-        mService = service;
+    private InputMethodManager(@NonNull IInputMethodManager service, int displayId, Looper looper) {
+        mService = service;  // For @UnsupportedAppUsage
+        mServiceInvoker = IInputMethodManagerInvoker.create(service);
         mMainLooper = looper;
         mH = new H(looper);
         mDisplayId = displayId;
@@ -1410,28 +1402,44 @@
      * @return {@link List} of {@link InputMethodInfo}.
      */
     public List<InputMethodInfo> getInputMethodList() {
-        try {
-            // We intentionally do not use UserHandle.getCallingUserId() here because for system
-            // services InputMethodManagerInternal.getInputMethodListAsUser() should be used
-            // instead.
-            return mService.getInputMethodList(UserHandle.myUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        // We intentionally do not use UserHandle.getCallingUserId() here because for system
+        // services InputMethodManagerInternal.getInputMethodListAsUser() should be used
+        // instead.
+        return mServiceInvoker.getInputMethodList(UserHandle.myUserId());
     }
 
     /**
-     * Returns {@code true} if currently selected IME supports Stylus handwriting.
+     * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled.
      * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
      * called and Stylus touch should continue as normal touch input.
      * @see #startStylusHandwriting(View)
      */
     public boolean isStylusHandwritingAvailable() {
-        try {
-            return mService.isStylusHandwritingAvailable();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        return isStylusHandwritingAvailableAsUser(UserHandle.myUserId());
+    }
+
+    /**
+     * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled for
+     * the given userId.
+     * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
+     * called and Stylus touch should continue as normal touch input.
+     * @see #startStylusHandwriting(View)
+     * @param userId user ID to query.
+     * @hide
+     */
+    public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+        final Context fallbackContext = ActivityThread.currentApplication();
+        if (fallbackContext == null) {
+            return false;
         }
+        if (Settings.Global.getInt(fallbackContext.getContentResolver(),
+                Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 0) {
+            if (DEBUG) {
+                Log.d(TAG, "Stylus handwriting is not enabled in settings.");
+            }
+            return false;
+        }
+        return mServiceInvoker.isStylusHandwritingAvailableAsUser(userId);
     }
 
     /**
@@ -1445,11 +1453,7 @@
     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
     @NonNull
     public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
-        try {
-            return mService.getInputMethodList(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return mServiceInvoker.getInputMethodList(userId);
     }
 
     /**
@@ -1465,11 +1469,7 @@
     @NonNull
     public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId,
             @DirectBootAwareness int directBootAwareness) {
-        try {
-            return mService.getAwareLockedInputMethodList(userId, directBootAwareness);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return mServiceInvoker.getAwareLockedInputMethodList(userId, directBootAwareness);
     }
 
     /**
@@ -1480,14 +1480,10 @@
      * @return {@link List} of {@link InputMethodInfo}.
      */
     public List<InputMethodInfo> getEnabledInputMethodList() {
-        try {
-            // We intentionally do not use UserHandle.getCallingUserId() here because for system
-            // services InputMethodManagerInternal.getEnabledInputMethodListAsUser() should be used
-            // instead.
-            return mService.getEnabledInputMethodList(UserHandle.myUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        // We intentionally do not use UserHandle.getCallingUserId() here because for system
+        // services InputMethodManagerInternal.getEnabledInputMethodListAsUser() should be used
+        // instead.
+        return mServiceInvoker.getEnabledInputMethodList(UserHandle.myUserId());
     }
 
     /**
@@ -1499,11 +1495,7 @@
      */
     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
     public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
-        try {
-            return mService.getEnabledInputMethodList(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return mServiceInvoker.getEnabledInputMethodList(userId);
     }
 
     /**
@@ -1518,13 +1510,9 @@
      */
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
             boolean allowsImplicitlySelectedSubtypes) {
-        try {
-            return mService.getEnabledInputMethodSubtypeList(
-                    imi == null ? null : imi.getId(),
-                    allowsImplicitlySelectedSubtypes);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return mServiceInvoker.getEnabledInputMethodSubtypeList(
+                imi == null ? null : imi.getId(),
+                allowsImplicitlySelectedSubtypes);
     }
 
     /**
@@ -1628,7 +1616,8 @@
      */
     public boolean isInputMethodSuppressingSpellChecker() {
         synchronized (mH) {
-            return mIsInputMethodSuppressingSpellChecker;
+            return mCurBindState != null
+                    && mCurBindState.mIsInputMethodSuppressingSpellChecker;
         }
     }
 
@@ -1642,10 +1631,9 @@
         setInputChannelLocked(null);
         // We only reset sequence number for input method, but not accessibility.
         mBindSequence = -1;
-        mCurId = null;
+        mCurId = null; // for @UnsupportedAppUsage
         mCurMethod = null; // for @UnsupportedAppUsage
-        mCurrentInputMethodSession = null;
-        mIsInputMethodSuppressingSpellChecker = false;
+        mCurBindState = null;
     }
 
     /**
@@ -1738,8 +1726,8 @@
             }
 
             mCompletions = completions;
-            if (mCurrentInputMethodSession != null) {
-                mCurrentInputMethodSession.displayCompletions(mCompletions);
+            if (isImeSessionAvailableLocked()) {
+                mCurBindState.mImeSession.displayCompletions(mCompletions);
             }
         }
     }
@@ -1758,8 +1746,8 @@
                 return;
             }
 
-            if (mCurrentInputMethodSession != null) {
-                mCurrentInputMethodSession.updateExtractedText(token, text);
+            if (isImeSessionAvailableLocked()) {
+                mCurBindState.mImeSession.updateExtractedText(token, text);
             }
         }
     }
@@ -1887,18 +1875,14 @@
             // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
             // TODO(b/229426865): call WindowInsetsController#show instead.
             mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
-            try {
-                Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason="
-                        + InputMethodDebug.softInputDisplayReasonToString(reason));
-                return mService.showSoftInput(
-                        mClient,
-                        view.getWindowToken(),
-                        flags,
-                        resultReceiver,
-                        reason);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+            Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason="
+                    + InputMethodDebug.softInputDisplayReasonToString(reason));
+            return mServiceInvoker.showSoftInput(
+                    mClient,
+                    view.getWindowToken(),
+                    flags,
+                    resultReceiver,
+                    reason);
         }
     }
 
@@ -1914,26 +1898,22 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
     public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
         synchronized (mH) {
-            try {
-                Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
-                        + " removed soon. If you are using androidx.appcompat.widget.SearchView,"
-                        + " please update to version 26.0 or newer version.");
-                if (mCurRootView == null || mCurRootView.getView() == null) {
-                    Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
-                    return;
-                }
-                // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
-                // TODO(b/229426865): call WindowInsetsController#show instead.
-                mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
-                mService.showSoftInput(
-                        mClient,
-                        mCurRootView.getView().getWindowToken(),
-                        flags,
-                        resultReceiver,
-                        SoftInputShowHideReason.SHOW_SOFT_INPUT);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
+            Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
+                    + " removed soon. If you are using androidx.appcompat.widget.SearchView,"
+                    + " please update to version 26.0 or newer version.");
+            if (mCurRootView == null || mCurRootView.getView() == null) {
+                Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
+                return;
             }
+            // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
+            // TODO(b/229426865): call WindowInsetsController#show instead.
+            mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
+            mServiceInvoker.showSoftInput(
+                    mClient,
+                    mCurRootView.getView().getWindowToken(),
+                    flags,
+                    resultReceiver,
+                    SoftInputShowHideReason.SHOW_SOFT_INPUT);
         }
     }
 
@@ -2008,11 +1988,8 @@
                 return false;
             }
 
-            try {
-                return mService.hideSoftInput(mClient, windowToken, flags, resultReceiver, reason);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+            return mServiceInvoker.hideSoftInput(mClient, windowToken, flags, resultReceiver,
+                    reason);
         }
     }
 
@@ -2069,13 +2046,9 @@
                         mDisplayId);
             }
 
-            try {
-                mService.startStylusHandwriting(mClient);
-                // TODO(b/210039666): do we need any extra work for supporting non-native
-                //   UI toolkits?
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+            mServiceInvoker.startStylusHandwriting(mClient);
+            // TODO(b/210039666): do we need any extra work for supporting non-native
+            //   UI toolkits?
         }
     }
 
@@ -2194,7 +2167,7 @@
                 // OK to ignore because the calling InputConnection is already abandoned.
                 return true;
             }
-            if (mCurrentInputMethodSession == null) {
+            if (!isImeSessionAvailableLocked()) {
                 // IME is not yet bound to the client.  Need to fall back to the restartInput().
                 return false;
             }
@@ -2205,7 +2178,7 @@
             mCursorCandEnd = textSnapshot.getCompositionEnd();
             editorInfo.initialCapsMode = textSnapshot.getCursorCapsMode();
             editorInfo.setInitialSurroundingTextInternal(textSnapshot.getSurroundingText());
-            mCurrentInputMethodSession.invalidateInput(editorInfo, mServedInputConnection,
+            mCurBindState.mImeSession.invalidateInput(editorInfo, mServedInputConnection,
                     sessionId);
             final IRemoteAccessibilityInputConnection accessibilityInputConnection =
                     mServedInputConnection.asIRemoteAccessibilityInputConnection();
@@ -2321,6 +2294,14 @@
         InputConnection ic = view.onCreateInputConnection(tba);
         if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
 
+        // Clear autofill and field ids if a connection could not be established.
+        // This ensures that even disconnected EditorInfos have well-defined attributes,
+        // making them consistently and straightforwardly comparable.
+        if (ic == null) {
+            tba.autofillId = AutofillId.NO_AUTOFILL_ID;
+            tba.fieldId = 0;
+        }
+
         final Handler icHandler;
         InputBindResult res = null;
         synchronized (mH) {
@@ -2358,7 +2339,7 @@
                 mServedInputConnection = null;
                 mServedInputConnectionHandler = null;
             }
-            RemoteInputConnectionImpl servedInputConnection;
+            final RemoteInputConnectionImpl servedInputConnection;
             if (ic != null) {
                 mCursorSelStart = tba.initialSelStart;
                 mCursorSelEnd = tba.initialSelEnd;
@@ -2390,17 +2371,13 @@
                         + ic + " tba=" + tba + " startInputFlags="
                         + InputMethodDebug.startInputFlagsToString(startInputFlags));
             }
-            try {
-                res = mService.startInputOrWindowGainedFocus(
-                        startInputReason, mClient, windowGainingFocus, startInputFlags,
-                        softInputMode, windowFlags, tba, servedInputConnection,
-                        servedInputConnection == null ? null
-                                : servedInputConnection.asIRemoteAccessibilityInputConnection(),
-                        view.getContext().getApplicationInfo().targetSdkVersion,
-                        mImeDispatcher);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+            res = mServiceInvoker.startInputOrWindowGainedFocus(
+                    startInputReason, mClient, windowGainingFocus, startInputFlags,
+                    softInputMode, windowFlags, tba, servedInputConnection,
+                    servedInputConnection == null ? null
+                            : servedInputConnection.asIRemoteAccessibilityInputConnection(),
+                    view.getContext().getApplicationInfo().targetSdkVersion,
+                    mImeDispatcher);
             if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
             if (res == null) {
                 Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
@@ -2412,12 +2389,11 @@
                 return false;
             }
             mVirtualDisplayToScreenMatrix = res.getVirtualDisplayToScreenMatrix();
-            mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker;
             if (res.id != null) {
                 setInputChannelLocked(res.channel);
                 mBindSequence = res.sequence;
                 mCurMethod = res.method; // for @UnsupportedAppUsage
-                mCurrentInputMethodSession = IInputMethodSessionInvoker.createOrNull(res.method);
+                mCurBindState = new BindState(res);
                 mAccessibilityInputMethodSession.clear();
                 if (res.accessibilitySessions != null) {
                     for (int i = 0; i < res.accessibilitySessions.size(); i++) {
@@ -2430,7 +2406,7 @@
                         }
                     }
                 }
-                mCurId = res.id;
+                mCurId = res.id; // for @UnsupportedAppUsage
             } else if (res.channel != null && res.channel != mCurChannel) {
                 res.channel.dispose();
             }
@@ -2440,8 +2416,8 @@
                     break;
             }
             if (mCompletions != null) {
-                if (mCurrentInputMethodSession != null) {
-                    mCurrentInputMethodSession.displayCompletions(mCompletions);
+                if (isImeSessionAvailableLocked()) {
+                    mCurBindState.mImeSession.displayCompletions(mCompletions);
                 }
             }
         }
@@ -2528,16 +2504,12 @@
                 Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
                 return;
             }
-            try {
-                mService.hideSoftInput(
-                        mClient,
-                        mCurRootView.getView().getWindowToken(),
-                        HIDE_NOT_ALWAYS,
-                        null,
-                        SoftInputShowHideReason.HIDE_SOFT_INPUT);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+            mServiceInvoker.hideSoftInput(
+                    mClient,
+                    mCurRootView.getView().getWindowToken(),
+                    HIDE_NOT_ALWAYS,
+                    null,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT);
         }
     }
 
@@ -2606,15 +2578,11 @@
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
                 null /* icProto */);
         synchronized (mH) {
-            if (mCurrentInputMethodSession != null && mCurRootView != null
+            if (isImeSessionAvailableLocked() && mCurRootView != null
                     && mCurRootView.getWindowToken() == windowToken) {
-                try {
-                    mService.hideSoftInput(mClient, windowToken, 0 /* flags */,
-                            null /* resultReceiver */,
-                            SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
+                mServiceInvoker.hideSoftInput(mClient, windowToken, 0 /* flags */,
+                        null /* resultReceiver */,
+                        SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
             }
         }
     }
@@ -2626,11 +2594,7 @@
      */
     public void removeImeSurface(IBinder windowToken) {
         synchronized (mH) {
-            try {
-                mService.removeImeSurfaceFromWindowAsync(windowToken);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+            mServiceInvoker.removeImeSurfaceFromWindowAsync(windowToken);
         }
     }
 
@@ -2657,7 +2621,7 @@
         checkFocus();
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
-                    || mCurrentInputMethodSession == null) {
+                    || !isImeSessionAvailableLocked()) {
                 return;
             }
 
@@ -2671,9 +2635,9 @@
                 if (DEBUG) Log.d(TAG, "updateSelection");
 
                 if (DEBUG) {
-                    Log.v(TAG, "SELECTION CHANGE: " + mCurrentInputMethodSession);
+                    Log.v(TAG, "SELECTION CHANGE: " + mCurBindState.mImeSession);
                 }
-                mCurrentInputMethodSession.updateSelection(mCursorSelStart, mCursorSelEnd, selStart,
+                mCurBindState.mImeSession.updateSelection(mCursorSelStart, mCursorSelEnd, selStart,
                         selEnd, candidatesStart, candidatesEnd);
                 forAccessibilitySessionsLocked(wrapper -> wrapper.updateSelection(mCursorSelStart,
                         mCursorSelEnd, selStart, selEnd, candidatesStart, candidatesEnd));
@@ -2714,11 +2678,11 @@
         checkFocus();
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
-                    || mCurrentInputMethodSession == null) {
+                    || !isImeSessionAvailableLocked()) {
                 return;
             }
             if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
-            mCurrentInputMethodSession.viewClicked(focusChanged);
+            mCurBindState.mImeSession.viewClicked(focusChanged);
         }
     }
 
@@ -2783,15 +2747,15 @@
         checkFocus();
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
-                    || mCurrentInputMethodSession == null) {
+                    || !isImeSessionAvailableLocked()) {
                 return;
             }
 
             mTmpCursorRect.set(left, top, right, bottom);
             if (!mCursorRect.equals(mTmpCursorRect)) {
-                if (DEBUG) Log.d(TAG, "updateCursor: " + mCurrentInputMethodSession);
+                if (DEBUG) Log.d(TAG, "updateCursor: " + mCurBindState.mImeSession);
 
-                mCurrentInputMethodSession.updateCursor(mTmpCursorRect);
+                mCurBindState.mImeSession.updateCursor(mTmpCursorRect);
                 mCursorRect.set(mTmpCursorRect);
             }
         }
@@ -2815,7 +2779,7 @@
         checkFocus();
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
-                    || mCurrentInputMethodSession == null) {
+                    || !isImeSessionAvailableLocked()) {
                 return;
             }
             // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
@@ -2833,11 +2797,11 @@
             }
             if (DEBUG) Log.v(TAG, "updateCursorAnchorInfo: " + cursorAnchorInfo);
             if (mVirtualDisplayToScreenMatrix != null) {
-                mCurrentInputMethodSession.updateCursorAnchorInfo(
+                mCurBindState.mImeSession.updateCursorAnchorInfo(
                         CursorAnchorInfo.createForAdditionalParentMatrix(
                                 cursorAnchorInfo, mVirtualDisplayToScreenMatrix));
             } else {
-                mCurrentInputMethodSession.updateCursorAnchorInfo(cursorAnchorInfo);
+                mCurBindState.mImeSession.updateCursorAnchorInfo(cursorAnchorInfo);
             }
             mCursorAnchorInfo = cursorAnchorInfo;
         }
@@ -2865,11 +2829,11 @@
         checkFocus();
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
-                    || mCurrentInputMethodSession == null) {
+                    || !isImeSessionAvailableLocked()) {
                 return;
             }
             if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data);
-            mCurrentInputMethodSession.appPrivateCommand(action, data);
+            mCurBindState.mImeSession.appPrivateCommand(action, data);
         }
     }
 
@@ -3028,7 +2992,7 @@
     public int dispatchInputEvent(InputEvent event, Object token,
             FinishedInputEventCallback callback, Handler handler) {
         synchronized (mH) {
-            if (mCurrentInputMethodSession != null) {
+            if (isImeSessionAvailableLocked()) {
                 if (event instanceof KeyEvent) {
                     KeyEvent keyEvent = (KeyEvent)event;
                     if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
@@ -3040,11 +3004,11 @@
                 }
 
                 if (DEBUG) {
-                    Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurrentInputMethodSession);
+                    Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurBindState.mImeSession);
                 }
 
                 PendingEvent p = obtainPendingEventLocked(
-                        event, token, mCurId, callback, handler);
+                        event, token, mCurBindState.mImeId, callback, handler);
                 if (mMainLooper.isCurrentThread()) {
                     // Already running on the IMM thread so we can send the event immediately.
                     return sendInputEventOnMainLooperLocked(p);
@@ -3131,7 +3095,8 @@
                 return DISPATCH_IN_PROGRESS;
             }
 
-            Log.w(TAG, "Unable to send input event to IME: " + mCurId + " dropping: " + event);
+            Log.w(TAG, "Unable to send input event to IME: " + getImeIdLocked()
+                    + " dropping: " + event);
         }
         return DISPATCH_NOT_HANDLED;
     }
@@ -3230,19 +3195,11 @@
         final int mode = showAuxiliarySubtypes
                 ? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES
                 : SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES;
-        try {
-            mService.showInputMethodPickerFromSystem(mClient, mode, displayId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        mServiceInvoker.showInputMethodPickerFromSystem(mClient, mode, displayId);
     }
 
     private void showInputMethodPickerLocked() {
-        try {
-            mService.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        mServiceInvoker.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO);
     }
 
     /**
@@ -3258,11 +3215,7 @@
      */
     @TestApi
     public boolean isInputMethodPickerShown() {
-        try {
-            return mService.isInputMethodPickerShownForTest();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return mServiceInvoker.isInputMethodPickerShownForTest();
     }
 
     /**
@@ -3272,11 +3225,7 @@
      * subtypes of all input methods will be shown.
      */
     public void showInputMethodAndSubtypeEnabler(String imiId) {
-        try {
-            mService.showInputMethodAndSubtypeEnablerFromClient(mClient, imiId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        mServiceInvoker.showInputMethodAndSubtypeEnablerFromClient(mClient, imiId);
     }
 
     /**
@@ -3285,11 +3234,7 @@
      * have any input method subtype.
      */
     public InputMethodSubtype getCurrentInputMethodSubtype() {
-        try {
-            return mService.getCurrentInputMethodSubtype();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return mServiceInvoker.getCurrentInputMethodSubtype();
     }
 
     /**
@@ -3333,12 +3278,8 @@
             // Null or invalid IME ID format.
             return false;
         }
-        final List<InputMethodSubtype> enabledSubtypes;
-        try {
-            enabledSubtypes = mService.getEnabledInputMethodSubtypeList(imeId, true);
-        } catch (RemoteException e) {
-            return false;
-        }
+        final List<InputMethodSubtype> enabledSubtypes =
+                mServiceInvoker.getEnabledInputMethodSubtypeList(imeId, true);
         final int numSubtypes = enabledSubtypes.size();
         for (int i = 0; i < numSubtypes; ++i) {
             final InputMethodSubtype enabledSubtype = enabledSubtypes.get(i);
@@ -3403,11 +3344,7 @@
     @UnsupportedAppUsage(trackingBug = 204906124, maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
             publicAlternatives = "Use {@link android.view.WindowInsets} instead")
     public int getInputMethodWindowVisibleHeight() {
-        try {
-            return mService.getInputMethodWindowVisibleHeight(mClient);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return mServiceInvoker.getInputMethodWindowVisibleHeight(mClient);
     }
 
     /**
@@ -3420,18 +3357,14 @@
      * @hide
      */
     public void reportVirtualDisplayGeometry(int childDisplayId, @Nullable Matrix matrix) {
-        try {
-            final float[] matrixValues;
-            if (matrix == null) {
-                matrixValues = null;
-            } else {
-                matrixValues = new float[9];
-                matrix.getValues(matrixValues);
-            }
-            mService.reportVirtualDisplayGeometryAsync(mClient, childDisplayId, matrixValues);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        final float[] matrixValues;
+        if (matrix == null) {
+            matrixValues = null;
+        } else {
+            matrixValues = new float[9];
+            matrix.getValues(matrixValues);
         }
+        mServiceInvoker.reportVirtualDisplayGeometryAsync(mClient, childDisplayId, matrixValues);
     }
 
     /**
@@ -3535,19 +3468,11 @@
      */
     @Deprecated
     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
-        try {
-            mService.setAdditionalInputMethodSubtypes(imiId, subtypes);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        mServiceInvoker.setAdditionalInputMethodSubtypes(imiId, subtypes);
     }
 
     public InputMethodSubtype getLastInputMethodSubtype() {
-        try {
-            return mService.getLastInputMethodSubtype();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return mServiceInvoker.getLastInputMethodSubtype();
     }
 
     /**
@@ -3567,17 +3492,14 @@
 
         final Printer p = new PrintWriterPrinter(fout);
         p.println("Input method client state for " + this + ":");
-
-        p.println("  mService=" + mService);
-        p.println("  mMainLooper=" + mMainLooper);
         p.println("  mFallbackInputConnection=" + mFallbackInputConnection);
         p.println("  mActive=" + mActive
                 + " mRestartOnNextWindowFocus=" + mRestartOnNextWindowFocus
                 + " mBindSequence=" + mBindSequence
-                + " mCurId=" + mCurId);
+                + " mCurImeId=" + getImeIdLocked());
         p.println("  mFullscreenMode=" + mFullscreenMode);
-        if (mCurrentInputMethodSession != null) {
-            p.println("  mCurMethod=" + mCurrentInputMethodSession);
+        if (isImeSessionAvailableLocked()) {
+            p.println("  mCurMethod=" + mCurBindState.mImeSession);
         } else {
             p.println("  mCurMethod= null");
         }
@@ -3653,6 +3575,43 @@
         }
     }
 
+    private static final class BindState {
+        /**
+         * Encapsulates IPCs to the currently connected InputMethodService.
+         */
+        @Nullable
+        final IInputMethodSessionInvoker mImeSession;
+
+        /**
+         * As reported by {@link InputBindResult}. This value is determined by
+         * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}.
+         */
+        final boolean mIsInputMethodSuppressingSpellChecker;
+
+        /**
+         * As reported by {@link InputBindResult}. This value indicates the bound input method ID.
+         */
+        @Nullable
+        final String mImeId;
+
+        BindState(@NonNull InputBindResult inputBindResult) {
+            mImeSession = IInputMethodSessionInvoker.createOrNull(inputBindResult.method);
+            mIsInputMethodSuppressingSpellChecker =
+                    inputBindResult.isInputMethodSuppressingSpellChecker;
+            mImeId = inputBindResult.id;
+        }
+    }
+
+    @GuardedBy("mH")
+    private boolean isImeSessionAvailableLocked() {
+        return mCurBindState != null && mCurBindState.mImeSession != null;
+    }
+
+    @GuardedBy("mH")
+    private String getImeIdLocked() {
+        return mCurBindState != null ? mCurBindState.mImeId : null;
+    }
+
     private static String dumpViewInfo(@Nullable final View view) {
         if (view == null) {
             return "null";
@@ -3702,14 +3661,14 @@
      */
     @GuardedBy("mH")
     public void dumpDebug(ProtoOutputStream proto, @Nullable byte[] icProto) {
-        if (mCurrentInputMethodSession == null) {
+        if (!isImeSessionAvailableLocked()) {
             return;
         }
 
         proto.write(DISPLAY_ID, mDisplayId);
         final long token = proto.start(INPUT_METHOD_MANAGER);
         synchronized (mH) {
-            proto.write(CUR_ID, mCurId);
+            proto.write(CUR_ID, mCurBindState.mImeId);
             proto.write(FULLSCREEN_MODE, mFullscreenMode);
             proto.write(ACTIVE, mActive);
             proto.write(SERVED_CONNECTING, mServedConnecting);
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index cbaac5f..44997b4 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -548,6 +548,12 @@
      */
     public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
 
+    /**
+     * (boolean) Whether the task manager should show a stop button if the app is allowlisted
+     * by the user.
+     */
+    public static final String TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS =
+            "show_stop_button_for_user_allowlisted_apps";
 
     /**
      * (boolean) Whether the clipboard overlay is enabled.
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index b819615..8bab5c3 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -35,8 +35,16 @@
  * Top-level interface to an input method component (implemented in a Service).
  */
 oneway interface IInputMethod {
-    void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
-             int configChanges, boolean stylusHwSupported, int navigationBarFlags);
+
+    parcelable InitParams {
+        IBinder token;
+        IInputMethodPrivilegedOperations privilegedOperations;
+        int configChanges;
+        boolean stylusHandWritingSupported;
+        int navigationBarFlags;
+    }
+
+    void initializeInternal(in InitParams params);
 
     void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo,
             in IInlineSuggestionsRequestCallback cb);
@@ -45,9 +53,16 @@
 
     void unbindInput();
 
-    void startInput(in IBinder startInputToken, in IRemoteInputConnection inputConnection,
-            in EditorInfo editorInfo, boolean restarting, int navigationBarFlags,
-            in ImeOnBackInvokedDispatcher imeDispatcher);
+    parcelable StartInputParams {
+        IBinder startInputToken;
+        IRemoteInputConnection remoteInputConnection;
+        EditorInfo editorInfo;
+        boolean restarting;
+        int navigationBarFlags;
+        ImeOnBackInvokedDispatcher imeDispatcher;
+    }
+
+    void startInput(in StartInputParams params);
 
     void onNavButtonFlagsChanged(int navButtonFlags);
 
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 508e445..4bed47c 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -36,10 +36,19 @@
             int untrustedDisplayId);
 
     // TODO: Use ParceledListSlice instead
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
     List<InputMethodInfo> getInputMethodList(int userId);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
     List<InputMethodInfo> getAwareLockedInputMethodList(int userId, int directBootAwareness);
+
     // TODO: Use ParceledListSlice instead
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
     List<InputMethodInfo> getEnabledInputMethodList(int userId);
+
     List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId,
             boolean allowsImplicitlySelectedSubtypes);
     InputMethodSubtype getLastInputMethodSubtype();
@@ -48,9 +57,12 @@
             in ResultReceiver resultReceiver, int reason);
     boolean hideSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
             in ResultReceiver resultReceiver, int reason);
+
     // If windowToken is null, this just does startInput().  Otherwise this reports that a window
     // has gained focus, and if 'editorInfo' is non-null then also does startInput.
     // @NonNull
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
     InputBindResult startInputOrWindowGainedFocus(
             /* @StartInputReason */ int startInputReason,
             in IInputMethodClient client, in IBinder windowToken,
@@ -62,8 +74,12 @@
 
     void showInputMethodPickerFromClient(in IInputMethodClient client,
             int auxiliarySubtypeMode);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
     void showInputMethodPickerFromSystem(in IInputMethodClient client,
             int auxiliarySubtypeMode, int displayId);
+
     void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
     boolean isInputMethodPickerShownForTest();
     InputMethodSubtype getCurrentInputMethodSubtype();
@@ -76,8 +92,11 @@
             int childDisplayId, in float[] matrixValues);
 
     oneway void reportPerceptibleAsync(in IBinder windowToken, boolean perceptible);
-    /** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
     void removeImeSurface();
+
     /** Remove the IME surface. Requires passing the currently focused window. */
     oneway void removeImeSurfaceFromWindowAsync(in IBinder windowToken);
     void startProtoDump(in byte[] protoDump, int source, String where);
@@ -90,6 +109,9 @@
 
     /** Start Stylus handwriting session **/
     void startStylusHandwriting(in IInputMethodClient client);
+
     /** Returns {@code true} if currently selected IME supports Stylus handwriting. */
-    boolean isStylusHandwritingAvailable();
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
+    boolean isStylusHandwritingAvailableAsUser(int userId);
 }
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 6ec71ba..5099dd2 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -266,6 +266,9 @@
                 optional int32 lookaside_b = 4;
                 // Statement cache stats: hits/misses/cachesize
                 optional string cache = 5;
+                optional int32 cache_hits = 6;
+                optional int32 cache_misses = 7;
+                optional int32 cache_size = 8;
             }
             repeated Database databases = 4;
         }
diff --git a/core/res/res/values-mcc440-mnc20/config.xml b/core/res/res/values-mcc440-mnc20/config.xml
index 62001d9..7a48342 100644
--- a/core/res/res/values-mcc440-mnc20/config.xml
+++ b/core/res/res/values-mcc440-mnc20/config.xml
@@ -23,6 +23,6 @@
 
     <!-- Configure mobile network MTU. Carrier specific value is set here.
     -->
-    <integer name="config_mobile_mtu">1422</integer>
+    <integer name="config_mobile_mtu">1358</integer>
 
 </resources>
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
index be04b6c..6bd498c 100644
--- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -16,12 +16,15 @@
 
 package android.companion;
 
+import android.content.Context;
 import android.os.SystemClock;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
 import com.android.internal.util.HexDump;
 
+import libcore.util.EmptyArray;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.FilterInputStream;
@@ -31,69 +34,81 @@
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.util.List;
 import java.util.Random;
 
 public class SystemDataTransportTest extends InstrumentationTestCase {
     private static final String TAG = "SystemDataTransportTest";
 
-    private static final int COMMAND_INVALID = 0xF00DCAFE;
-    private static final int COMMAND_PING_V0 = 0x50490000;
-    private static final int COMMAND_PONG_V0 = 0x504F0000;
+    private static final int MESSAGE_INVALID = 0xF00DCAFE;
 
+    private static final int MESSAGE_REQUEST_INVALID = 0x63636363; // ????
+    private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
+
+    private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!!
+    private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
+    private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
+
+    private Context mContext;
     private CompanionDeviceManager mCdm;
+    private int mAssociationId;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
-        mCdm = getInstrumentation().getTargetContext()
-                .getSystemService(CompanionDeviceManager.class);
+        mContext = getInstrumentation().getTargetContext();
+        mCdm = mContext.getSystemService(CompanionDeviceManager.class);
+        mAssociationId = createAssociation();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        mCdm.disassociate(mAssociationId);
     }
 
     public void testPingHandRolled() {
         // NOTE: These packets are explicitly hand-rolled to verify wire format;
         // the remainder of the tests are fine using generated packets
 
-        // PING v0 with payload "HELLO WORLD!"
+        // MESSAGE_REQUEST_PING with payload "HELLO WORLD!"
         final byte[] input = new byte[] {
-                0x50, 0x49, 0x00, 0x00,
-                0x00, 0x00, 0x00, 0x0C,
+                0x63, (byte) 0x80, 0x73, 0x78, // message: MESSAGE_REQUEST_PING
+                0x00, 0x00, 0x00, 0x2A, // sequence: 42
+                0x00, 0x00, 0x00, 0x0C, // length: 12
                 0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x21,
         };
-        // PONG v0 with payload "HELLO WORLD!"
+        // MESSAGE_RESPONSE_SUCCESS with payload "HELLO WORLD!"
         final byte[] expected = new byte[] {
-                0x50, 0x4F, 0x00, 0x00,
-                0x00, 0x00, 0x00, 0x0C,
+                0x33, (byte) 0x83, (byte) 0x85, 0x67, // message: MESSAGE_RESPONSE_SUCCESS
+                0x00, 0x00, 0x00, 0x2A, // sequence: 42
+                0x00, 0x00, 0x00, 0x0C, // length: 12
                 0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x21,
         };
-
-        final ByteArrayInputStream in = new ByteArrayInputStream(input);
-        final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        mCdm.attachSystemDataTransport(42, in, out);
-
-        final byte[] actual = waitForByteArray(out, expected.length);
-        assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+        assertTransportBehavior(input, expected);
     }
 
     public void testPingTrickle() {
-        final byte[] input = generatePacket(COMMAND_PING_V0, TAG);
-        final byte[] expected = generatePacket(COMMAND_PONG_V0, TAG);
+        final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, TAG);
+        final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, TAG);
 
         final ByteArrayInputStream in = new ByteArrayInputStream(input);
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        mCdm.attachSystemDataTransport(42, new TrickleInputStream(in), out);
+        mCdm.attachSystemDataTransport(mAssociationId, new TrickleInputStream(in), out);
 
         final byte[] actual = waitForByteArray(out, expected.length);
         assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
     }
 
     public void testPingDelay() {
-        final byte[] input = generatePacket(COMMAND_PING_V0, TAG);
-        final byte[] expected = generatePacket(COMMAND_PONG_V0, TAG);
+        final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, TAG);
+        final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, TAG);
 
         final ByteArrayInputStream in = new ByteArrayInputStream(input);
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        mCdm.attachSystemDataTransport(42, new DelayingInputStream(in, 1000),
+        mCdm.attachSystemDataTransport(mAssociationId, new DelayingInputStream(in, 1000),
                 new DelayingOutputStream(out, 1000));
 
         final byte[] actual = waitForByteArray(out, expected.length);
@@ -104,49 +119,54 @@
         final byte[] blob = new byte[500_000];
         new Random().nextBytes(blob);
 
-        final byte[] input = generatePacket(COMMAND_PING_V0, blob);
-        final byte[] expected = generatePacket(COMMAND_PONG_V0, blob);
-
-        final ByteArrayInputStream in = new ByteArrayInputStream(input);
-        final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        mCdm.attachSystemDataTransport(42, in, out);
-
-        final byte[] actual = waitForByteArray(out, expected.length);
-        assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+        final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, blob);
+        final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, blob);
+        assertTransportBehavior(input, expected);
     }
 
     public void testMutiplePingPing() {
-        final byte[] input = concat(generatePacket(COMMAND_PING_V0, "red"),
-                generatePacket(COMMAND_PING_V0, "green"));
-        final byte[] expected = concat(generatePacket(COMMAND_PONG_V0, "red"),
-                generatePacket(COMMAND_PONG_V0, "green"));
-
-        final ByteArrayInputStream in = new ByteArrayInputStream(input);
-        final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        mCdm.attachSystemDataTransport(42, in, out);
-
-        final byte[] actual = waitForByteArray(out, expected.length);
-        assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+        final byte[] input = concat(
+                generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, "red"),
+                generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+        final byte[] expected = concat(
+                generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, "red"),
+                generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green"));
+        assertTransportBehavior(input, expected);
     }
 
     public void testMultipleInvalidPing() {
-        final byte[] input = concat(generatePacket(COMMAND_INVALID, "red"),
-                generatePacket(COMMAND_PING_V0, "green"));
-        final byte[] expected = generatePacket(COMMAND_PONG_V0, "green");
+        final byte[] input = concat(
+                generatePacket(MESSAGE_INVALID, /* sequence */ 1, "red"),
+                generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+        final byte[] expected =
+                generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green");
+        assertTransportBehavior(input, expected);
+    }
 
-        final ByteArrayInputStream in = new ByteArrayInputStream(input);
-        final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        mCdm.attachSystemDataTransport(42, in, out);
+    public void testMultipleInvalidRequestPing() {
+        final byte[] input = concat(
+                generatePacket(MESSAGE_REQUEST_INVALID, /* sequence */ 1, "red"),
+                generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+        final byte[] expected = concat(
+                generatePacket(MESSAGE_RESPONSE_FAILURE, /* sequence */ 1),
+                generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green"));
+        assertTransportBehavior(input, expected);
+    }
 
-        final byte[] actual = waitForByteArray(out, expected.length);
-        assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+    public void testMultipleInvalidResponsePing() {
+        final byte[] input = concat(
+                generatePacket(MESSAGE_RESPONSE_INVALID, /* sequence */ 1, "red"),
+                generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+        final byte[] expected =
+                generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green");
+        assertTransportBehavior(input, expected);
     }
 
     public void testDoubleAttach() {
         // Connect an empty connection that is stalled out
         final InputStream in = new EmptyInputStream();
         final OutputStream out = new ByteArrayOutputStream();
-        mCdm.attachSystemDataTransport(42, in, out);
+        mCdm.attachSystemDataTransport(mAssociationId, in, out);
         SystemClock.sleep(1000);
 
         // Attach a second transport that has some packets; it should disconnect
@@ -154,17 +174,57 @@
         testPingHandRolled();
     }
 
-    public static byte[] concat(byte[] a, byte[] b) {
-        return ByteBuffer.allocate(a.length + b.length).put(a).put(b).array();
+    public static byte[] concat(byte[]... blobs) {
+        int length = 0;
+        for (byte[] blob : blobs) {
+            length += blob.length;
+        }
+        final ByteBuffer buf = ByteBuffer.allocate(length);
+        for (byte[] blob : blobs) {
+            buf.put(blob);
+        }
+        return buf.array();
     }
 
-    public static byte[] generatePacket(int command, String data) {
-        return generatePacket(command, data.getBytes(StandardCharsets.UTF_8));
+    public static byte[] generatePacket(int message, int sequence) {
+        return generatePacket(message, sequence, EmptyArray.BYTE);
     }
 
-    public static byte[] generatePacket(int command, byte[] data) {
-        return ByteBuffer.allocate(data.length + 8)
-                .putInt(command).putInt(data.length).put(data).array();
+    public static byte[] generatePacket(int message, int sequence, String data) {
+        return generatePacket(message, sequence, data.getBytes(StandardCharsets.UTF_8));
+    }
+
+    public static byte[] generatePacket(int message, int sequence, byte[] data) {
+        return ByteBuffer.allocate(data.length + 12)
+                .putInt(message)
+                .putInt(sequence)
+                .putInt(data.length)
+                .put(data)
+                .array();
+    }
+
+    private int createAssociation() {
+        getInstrumentation().getUiAutomation().executeShellCommand("cmd companiondevice associate "
+                + mContext.getUserId() + " " + mContext.getPackageName() + " AA:BB:CC:DD:EE:FF");
+        List<AssociationInfo> infos;
+        for (int i = 0; i < 100; i++) {
+            infos = mCdm.getMyAssociations();
+            if (!infos.isEmpty()) {
+                return infos.get(0).getId();
+            } else {
+                SystemClock.sleep(100);
+            }
+        }
+        throw new AssertionError();
+    }
+
+    private void assertTransportBehavior(byte[] input, byte[] expected) {
+        final ByteArrayInputStream in = new ByteArrayInputStream(input);
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        mCdm.attachSystemDataTransport(mAssociationId, in, out);
+
+        final byte[] actual = waitForByteArray(out, expected.length);
+        assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
     }
 
     private static byte[] waitForByteArray(ByteArrayOutputStream out, int size) {
diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java
index 6defe91..a528c19 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java
@@ -41,6 +41,7 @@
 import org.junit.runner.RunWith;
 
 import java.nio.ByteOrder;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Random;
 
@@ -218,6 +219,10 @@
     @SmallTest
     @Test
     public void testSetSystemFontMap() throws Exception {
+
+        // Typeface.setSystemFontMap mutate the returned map. So copying for the backup.
+        HashMap<String, Typeface> backup = new HashMap<>(Typeface.getSystemFontMap());
+
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
         Resources res = context.getResources();
         Map<String, Typeface> fontMap = Map.of(
@@ -226,27 +231,38 @@
                 "monospace", Typeface.create(res.getFont(R.font.samplefont3), Typeface.NORMAL),
                 "sample", Typeface.create(res.getFont(R.font.samplefont4), Typeface.NORMAL),
                 "sample-italic", Typeface.create(res.getFont(R.font.samplefont4), Typeface.ITALIC));
-        Typeface.setSystemFontMap(fontMap);
 
-        // Test public static final fields
-        assertEquals(fontMap.get("sans-serif"), Typeface.DEFAULT);
-        assertEquals(Typeface.BOLD, Typeface.DEFAULT_BOLD.getStyle());
-        assertEquals(fontMap.get("sans-serif"), Typeface.SANS_SERIF);
-        assertEquals(fontMap.get("serif"), Typeface.SERIF);
-        assertEquals(fontMap.get("monospace"), Typeface.MONOSPACE);
+        try {
+            Typeface.setSystemFontMap(fontMap);
 
-        // Test defaults
-        assertEquals(fontMap.get("sans-serif"), Typeface.defaultFromStyle(Typeface.NORMAL));
-        for (int style : STYLES) {
-            String msg = "style = " + style;
-            assertNotNull(msg, Typeface.defaultFromStyle(style));
-            assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
+            // Test public static final fields
+            assertEquals(fontMap.get("sans-serif"), Typeface.DEFAULT);
+            assertEquals(Typeface.BOLD, Typeface.DEFAULT_BOLD.getStyle());
+            assertEquals(fontMap.get("sans-serif"), Typeface.SANS_SERIF);
+            assertEquals(fontMap.get("serif"), Typeface.SERIF);
+            assertEquals(fontMap.get("monospace"), Typeface.MONOSPACE);
+
+            // Test defaults
+            assertEquals(fontMap.get("sans-serif"), Typeface.defaultFromStyle(Typeface.NORMAL));
+            for (int style : STYLES) {
+                String msg = "style = " + style;
+                assertNotNull(msg, Typeface.defaultFromStyle(style));
+                assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
+            }
+
+            // Test create()
+            assertEquals(fontMap.get("sample"), Typeface.create("sample", Typeface.NORMAL));
+            assertEquals(
+                    fontMap.get("sample-italic"),
+                    Typeface.create("sample-italic", Typeface.ITALIC));
+        } finally {
+            // This tests breaks many default font configuration and break the assumption of the
+            // subsequent test cases. To recover the original configuration, call the
+            // setSystemFontMap function with the original data even if it is a test target.
+            // Ideally, this test should be isolated and app should be restart after this test
+            // been executed.
+            Typeface.setSystemFontMap(backup);
         }
-
-        // Test create()
-        assertEquals(fontMap.get("sample"), Typeface.create("sample", Typeface.NORMAL));
-        assertEquals(
-                fontMap.get("sample-italic"), Typeface.create("sample-italic", Typeface.ITALIC));
     }
 
     private static float measureText(Typeface typeface, String text) {
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 8d3ee2a..18da1a4 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -16,6 +16,7 @@
 
 package android.view.stylus;
 
+import static android.provider.Settings.Global.STYLUS_HANDWRITING_ENABLED;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
@@ -32,6 +33,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
 import android.view.HandwritingInitiator;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -67,6 +69,8 @@
     private static final int HW_BOUNDS_OFFSETS_TOP_PX = 20;
     private static final int HW_BOUNDS_OFFSETS_RIGHT_PX = 30;
     private static final int HW_BOUNDS_OFFSETS_BOTTOM_PX = 40;
+    private static final int SETTING_VALUE_ON = 1;
+    private static final int SETTING_VALUE_OFF = 0;
     private int mHandwritingSlop = 4;
 
     private static final Rect sHwArea = new Rect(100, 200, 500, 500);
@@ -74,12 +78,21 @@
     private HandwritingInitiator mHandwritingInitiator;
     private View mTestView;
     private Context mContext;
+    private int mHwInitialState;
+    private boolean mShouldRestoreInitialHwState;
 
     @Before
     public void setup() throws Exception {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         mContext = instrumentation.getTargetContext();
 
+        mHwInitialState = Settings.Global.getInt(mContext.getContentResolver(),
+                STYLUS_HANDWRITING_ENABLED, SETTING_VALUE_OFF);
+        if (mHwInitialState != SETTING_VALUE_ON) {
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    STYLUS_HANDWRITING_ENABLED, SETTING_VALUE_ON);
+            mShouldRestoreInitialHwState = true;
+        }
         String imeId = HandwritingImeService.getImeId();
         instrumentation.getUiAutomation().executeShellCommand("ime enable " + imeId);
         instrumentation.getUiAutomation().executeShellCommand("ime set " + imeId);
@@ -105,6 +118,11 @@
 
     @After
     public void tearDown() throws Exception {
+        if (mShouldRestoreInitialHwState) {
+            mShouldRestoreInitialHwState = false;
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    STYLUS_HANDWRITING_ENABLED, mHwInitialState);
+        }
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .executeShellCommand("ime reset");
     }
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 47f70dd..8d3751e 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -207,8 +207,8 @@
         final Configuration currentConfig = new Configuration();
 
         assertFalse("Must not report change if no public diff",
-                shouldReportChange(currentConfig, newConfig, null /* sizeBuckets */,
-                        0 /* handledConfigChanges */));
+                shouldReportChange(0 /* publicDiff */, currentConfig, newConfig,
+                null /* sizeBuckets */, 0 /* handledConfigChanges */));
 
         final int[] verticalThresholds = {100, 400};
         final SizeConfigurationBuckets buckets = new SizeConfigurationBuckets(
@@ -221,25 +221,25 @@
         newConfig.screenHeightDp = 300;
 
         assertFalse("Must not report changes if the diff is small and not handled",
-                shouldReportChange(currentConfig, newConfig, buckets,
-                        CONFIG_FONT_SCALE /* handledConfigChanges */));
+                shouldReportChange(CONFIG_SCREEN_SIZE /* publicDiff */, currentConfig,
+                newConfig, buckets, CONFIG_FONT_SCALE /* handledConfigChanges */));
 
         assertTrue("Must report changes if the small diff is handled",
-                shouldReportChange(currentConfig, newConfig, buckets,
-                        CONFIG_SCREEN_SIZE /* handledConfigChanges */));
+                shouldReportChange(CONFIG_SCREEN_SIZE /* publicDiff */, currentConfig, newConfig,
+                buckets, CONFIG_SCREEN_SIZE /* handledConfigChanges */));
 
         currentConfig.fontScale = 0.8f;
         newConfig.fontScale = 1.2f;
 
         assertTrue("Must report handled changes regardless of small unhandled change",
-                shouldReportChange(currentConfig, newConfig, buckets,
-                        CONFIG_FONT_SCALE /* handledConfigChanges */));
+                shouldReportChange(CONFIG_SCREEN_SIZE | CONFIG_FONT_SCALE /* publicDiff */,
+                currentConfig, newConfig, buckets, CONFIG_FONT_SCALE /* handledConfigChanges */));
 
         newConfig.screenHeightDp = 500;
 
         assertFalse("Must not report changes if there's unhandled big changes",
-                shouldReportChange(currentConfig, newConfig, buckets,
-                        CONFIG_FONT_SCALE /* handledConfigChanges */));
+                shouldReportChange(CONFIG_SCREEN_SIZE | CONFIG_FONT_SCALE /* publicDiff */,
+                currentConfig, newConfig, buckets, CONFIG_FONT_SCALE /* handledConfigChanges */));
     }
 
     private void recreateAndVerifyNoRelaunch(ActivityThread activityThread, TestActivity activity) {
diff --git a/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java b/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java
new file mode 100644
index 0000000..7c76498
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test for verifying {@link android.os.Bundle} recycles the underlying parcel where needed.
+ *
+ * <p>Build/Install/Run:
+ *  atest FrameworksMockingCoreTests:android.os.BundleRecyclingTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class BundleRecyclingTest {
+    private Parcel mParcelSpy;
+    private Bundle mBundle;
+
+    @Before
+    public void setUp() throws Exception {
+        setUpBundle(/* hasLazy */ true);
+    }
+
+    @Test
+    public void bundleClear_whenUnparcelledWithoutLazy_recyclesParcelOnce() {
+        setUpBundle(/* hasLazy */ false);
+        // Will unparcel and immediately recycle parcel
+        assertNotNull(mBundle.getString("key"));
+        verify(mParcelSpy, times(1)).recycle();
+        assertFalse(mBundle.isDefinitelyEmpty());
+
+        // Should not recycle again
+        mBundle.clear();
+        verify(mParcelSpy, times(1)).recycle();
+        assertTrue(mBundle.isDefinitelyEmpty());
+    }
+
+    @Test
+    public void bundleClear_whenParcelled_recyclesParcel() {
+        assertTrue(mBundle.isParcelled());
+        verify(mParcelSpy, times(0)).recycle();
+
+        mBundle.clear();
+        verify(mParcelSpy, times(1)).recycle();
+        assertTrue(mBundle.isDefinitelyEmpty());
+
+        // Should not recycle again
+        mBundle.clear();
+        verify(mParcelSpy, times(1)).recycle();
+    }
+
+    @Test
+    public void bundleClear_whenUnparcelledWithLazyValueUnwrapped_recyclesParcel() {
+        // Will unparcel with a lazy value, and immediately unwrap the lazy value,
+        // with no lazy values left at the end of getParcelable
+        assertNotNull(mBundle.getParcelable("key", CustomParcelable.class));
+        verify(mParcelSpy, times(0)).recycle();
+
+        mBundle.clear();
+        verify(mParcelSpy, times(1)).recycle();
+        assertTrue(mBundle.isDefinitelyEmpty());
+
+        // Should not recycle again
+        mBundle.clear();
+        verify(mParcelSpy, times(1)).recycle();
+    }
+
+    @Test
+    public void bundleClear_whenUnparcelledWithLazy_recyclesParcel() {
+        // Will unparcel but keep the CustomParcelable lazy
+        assertFalse(mBundle.isEmpty());
+        verify(mParcelSpy, times(0)).recycle();
+
+        mBundle.clear();
+        verify(mParcelSpy, times(1)).recycle();
+        assertTrue(mBundle.isDefinitelyEmpty());
+
+        // Should not recycle again
+        mBundle.clear();
+        verify(mParcelSpy, times(1)).recycle();
+    }
+
+    @Test
+    public void bundleClear_whenClearedWithSharedParcel_doesNotRecycleParcel() {
+        Bundle copy = new Bundle();
+        copy.putAll(mBundle);
+
+        mBundle.clear();
+        assertTrue(mBundle.isDefinitelyEmpty());
+
+        copy.clear();
+        assertTrue(copy.isDefinitelyEmpty());
+
+        verify(mParcelSpy, never()).recycle();
+    }
+
+    @Test
+    public void bundleClear_whenClearedWithCopiedParcel_doesNotRecycleParcel() {
+        // Will unparcel but keep the CustomParcelable lazy
+        assertFalse(mBundle.isEmpty());
+
+        Bundle copy = mBundle.deepCopy();
+        copy.putAll(mBundle);
+
+        mBundle.clear();
+        assertTrue(mBundle.isDefinitelyEmpty());
+
+        copy.clear();
+        assertTrue(copy.isDefinitelyEmpty());
+
+        verify(mParcelSpy, never()).recycle();
+    }
+
+    private void setUpBundle(boolean hasLazy) {
+        AtomicReference<Parcel> parcel = new AtomicReference<>();
+        StaticMockitoSession session = mockitoSession()
+                .strictness(Strictness.STRICT_STUBS)
+                .spyStatic(Parcel.class)
+                .startMocking();
+        doAnswer((Answer<Parcel>) invocationOnSpy -> {
+            Parcel spy = (Parcel) invocationOnSpy.callRealMethod();
+            spyOn(spy);
+            parcel.set(spy);
+            return spy;
+        }).when(() -> Parcel.obtain());
+
+        Bundle bundle = new Bundle();
+        bundle.setClassLoader(getClass().getClassLoader());
+        Parcel p = createBundle(hasLazy);
+        bundle.readFromParcel(p);
+        p.recycle();
+
+        session.finishMocking();
+
+        mParcelSpy = parcel.get();
+        mBundle = bundle;
+    }
+
+    /**
+     * Create a test bundle, parcel it and return the parcel.
+     */
+    private Parcel createBundle(boolean hasLazy) {
+        final Bundle source = new Bundle();
+        if (hasLazy) {
+            source.putParcelable("key", new CustomParcelable(13, "Tiramisu"));
+        } else {
+            source.putString("key", "tiramisu");
+        }
+        return getParcelledBundle(source);
+    }
+
+    /**
+     * Take a bundle, write it to a parcel and return the parcel.
+     */
+    private Parcel getParcelledBundle(Bundle bundle) {
+        final Parcel p = Parcel.obtain();
+        // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
+        bundle.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        return p;
+    }
+
+    private static class CustomParcelable implements Parcelable {
+        public final int integer;
+        public final String string;
+
+        CustomParcelable(int integer, String string) {
+            this.integer = integer;
+            this.string = string;
+        }
+
+        protected CustomParcelable(Parcel in) {
+            integer = in.readInt();
+            string = in.readString();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(integer);
+            out.writeString(string);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof CustomParcelable)) {
+                return false;
+            }
+            CustomParcelable that = (CustomParcelable) other;
+            return integer == that.integer && string.equals(that.string);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(integer, string);
+        }
+
+        public static final Creator<CustomParcelable> CREATOR = new Creator<CustomParcelable>() {
+            @Override
+            public CustomParcelable createFromParcel(Parcel in) {
+                return new CustomParcelable(in);
+            }
+            @Override
+            public CustomParcelable[] newArray(int size) {
+                return new CustomParcelable[size];
+            }
+        };
+    }
+}
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 6abe34b..9c36fc3 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -214,7 +214,7 @@
  * uniform shader myShader;
  * vec4 main(vec2 canvas_coordinates) {
  *     // swap the red and blue color channels when sampling from myShader
- *     return myShader.sample(canvas_coordinates).bgra;
+ *     return myShader.eval(canvas_coordinates).bgra;
  * }</pre>
  *
  * <p>After creating a {@link RuntimeShader} with that program the shader uniform can
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 41791af..2f79cae 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -43,6 +43,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.SystemProperties;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
@@ -70,6 +71,8 @@
 public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
         ActivityEmbeddingComponent {
     static final String TAG = "SplitController";
+    static final boolean ENABLE_SHELL_TRANSITIONS =
+            SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
 
     @VisibleForTesting
     @GuardedBy("mLock")
@@ -332,6 +335,11 @@
      * bounds is large enough for at least one split rule.
      */
     private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
+        if (ENABLE_SHELL_TRANSITIONS) {
+            // TODO(b/207070762): cleanup with legacy app transition
+            // Animation will be handled by WM Shell with Shell transition enabled.
+            return;
+        }
         if (!taskContainer.isTaskBoundsInitialized()
                 || !taskContainer.isWindowingModeInitialized()) {
             // We don't know about the Task bounds/windowingMode yet.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
index dc60bcf..29434f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
@@ -116,7 +116,7 @@
         if (taskId <= 0) return null;
         try {
             return ActivityTaskManager.getService().getTaskSnapshot(
-                    taskId, isLowResolution);
+                    taskId, isLowResolution, false /* takeSnapshotIfNeeded */);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to get task snapshot, taskId=" + taskId, e);
             return null;
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index d4298b8..49eca63 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -25,11 +25,13 @@
 import androidx.test.uiautomator.BySelector
 import androidx.test.uiautomator.UiDevice
 import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
 import com.android.wm.shell.flicker.testapp.Components
+import org.junit.Assert
 
 class SplitScreenHelper(
     instrumentation: Instrumentation,
@@ -187,5 +189,23 @@
                 SystemClock.sleep(GESTURE_STEP_MS)
             }
         }
+
+        fun createShortcutOnHotseatIfNotExist(
+            taplInstrumentation: LauncherInstrumentation,
+            appName: String
+        ) {
+            taplInstrumentation.workspace
+                .deleteAppIcon(taplInstrumentation.workspace.getHotseatAppIcon(0))
+            val allApps = taplInstrumentation.workspace.switchToAllApps()
+            allApps.freeze()
+            try {
+                val appIconSrc = allApps.getAppIcon(appName)
+                Assert.assertNotNull("Unable to find app icon", appIconSrc)
+                val appIconDest = appIconSrc.dragToHotseat(0)
+                Assert.assertNotNull("Unable to drag app icon on hotseat", appIconDest)
+            } finally {
+                allApps.unfreeze()
+            }
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index c18c8ce..19c573b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -77,11 +77,6 @@
             }
         }
 
-    /** {@inheritDoc}  */
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
     /**
      * Checks [pipApp] window remains visible throughout the animation
      */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 0333577..c2ed5a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -28,7 +28,6 @@
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
@@ -116,14 +115,6 @@
     override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
 
     /**
-     * Checks that the [FlickerComponentName.STATUS_BAR] has the correct position at
-     * the start and end of the transition
-     */
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
-    /**
      * Checks that all parts of the screen are covered at the start and end of the transition
      *
      * TODO b/197726599 Prevents all states from being checked
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 32022ad..4e7a9ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -81,11 +81,6 @@
         }
 
     /** {@inheritDoc}  */
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
-    /** {@inheritDoc}  */
     @FlakyTest(bugId = 197726610)
     @Test
     override fun pipLayerExpands() = super.pipLayerExpands()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index f28c1e9..77dcbe0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -18,7 +18,6 @@
 
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
-import android.platform.test.annotations.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
@@ -64,11 +63,6 @@
             }
         }
 
-    /** {@inheritDoc}  */
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
     /**
      * Checks that the focus changes between the pip menu window and the launcher when clicking the
      * dismiss button on pip menu to close the pip window.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 210b196..b9e72a4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -25,7 +25,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -81,10 +80,6 @@
     @Test
     override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible()
 
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
     /**
      * Checks that the focus doesn't change between windows during the transition
      */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index dc1fdde..32900b1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -168,10 +168,6 @@
         }
     }
 
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
     companion object {
         /**
          * Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index 4935fb0..768c090 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.pip
 
 import android.view.Surface
-import android.platform.test.annotations.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
@@ -26,7 +25,6 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.traces.region.RegionSubject
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -80,11 +78,6 @@
         current.isHigherOrEqual(previous.region)
     }
 
-    /** {@inheritDoc}  */
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
     companion object {
         /**
          * Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 42af1bb..2c158b8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -29,7 +29,6 @@
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -96,13 +95,6 @@
     override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
 
     /**
-     * Checks the position of the status bar at the start and end of the transition
-     */
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
-    /**
      * Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition
      */
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 83d3fe2..1f078b4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -116,10 +116,6 @@
     @Test
     override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
 
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
     @Presubmit
     @Test
     fun pipWindowInsideDisplay() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
new file mode 100644
index 0000000..60b0f8e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen by dragging app icon from taskbar.
+ * This test is only for large screen devices.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class EnterSplitScreenByDragFromTaskbar(
+    testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(taplInstrumentation.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    taplInstrumentation.goHome()
+                    SplitScreenHelper.createShortcutOnHotseatIfNotExist(
+                        taplInstrumentation, secondaryApp.appName
+                    )
+                    primaryApp.launchViaIntent(wmHelper)
+                }
+            }
+            transitions {
+                taplInstrumentation.launchedAppState.taskbar
+                    .getAppIcon(secondaryApp.appName)
+                    .dragToSplitscreen(
+                        secondaryApp.`package`,
+                        primaryApp.`package`
+                    )
+            }
+        }
+
+    @Presubmit
+    @Test
+    fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)
+
+    @Presubmit
+    @Test
+    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        testSpec.endRotation, primaryApp.component, false /* splitLeftTop */
+    )
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+        testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */
+    )
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)
+
+    @Presubmit
+    @Test
+    fun secondaryAppWindowBecomesVisible() =
+        testSpec.appWindowBecomesVisible(secondaryApp.component)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedNavigationModes =
+                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+            )
+        }
+    }
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index c2be2ef..d7f3338 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2320,6 +2320,10 @@
      */
     public final void start() {
         native_start();
+        synchronized(mBufferLock) {
+            cacheBuffers(true /* input */);
+            cacheBuffers(false /* input */);
+        }
     }
     private native final void native_start();
 
@@ -3951,9 +3955,6 @@
                         + "objects and attach to QueueRequest objects.");
             }
             if (mCachedInputBuffers == null) {
-                cacheBuffers(true /* input */);
-            }
-            if (mCachedInputBuffers == null) {
                 throw new IllegalStateException();
             }
             // FIXME: check codec status
@@ -3992,9 +3993,6 @@
                         + "Please use getOutputFrame to get output frames.");
             }
             if (mCachedOutputBuffers == null) {
-                cacheBuffers(false /* input */);
-            }
-            if (mCachedOutputBuffers == null) {
                 throw new IllegalStateException();
             }
             // FIXME: check codec status
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 9a7ce75..861d84d7 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -125,9 +125,9 @@
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"‏صدای HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"‏صدای HD"</string>
     <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سمعک"</string>
-    <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"‏صدای LE"</string>
+    <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"صدای کم‌مصرف"</string>
     <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"به سمعک متصل شد"</string>
-    <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"‏متصل به صدای LE"</string>
+    <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"به «صدای کم‌مصرف» وصل است"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"به رسانه صوتی متصل شد"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"به تلفن صوتی متصل شد"</string>
     <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"به سرور انتقال فایل متصل شد"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index a3af304..8d9a619 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -184,7 +184,7 @@
     <string name="launch_defaults_some" msgid="3631650616557252926">"Algumas predefinições definidas"</string>
     <string name="launch_defaults_none" msgid="8049374306261262709">"Nenhuma predefinição definida"</string>
     <string name="tts_settings" msgid="8130616705989351312">"Definições de texto para voz"</string>
-    <string name="tts_settings_title" msgid="7602210956640483039">"Saída de síntese de voz"</string>
+    <string name="tts_settings_title" msgid="7602210956640483039">"Saída de conversão de texto em voz"</string>
     <string name="tts_default_rate_title" msgid="3964187817364304022">"Taxa de voz"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"Velocidade a que o texto é falado"</string>
     <string name="tts_default_pitch_title" msgid="6988592215554485479">"Tonalidade"</string>
@@ -194,12 +194,12 @@
     <string name="tts_lang_not_selected" msgid="7927823081096056147">"Idioma não selecionado"</string>
     <string name="tts_default_lang_summary" msgid="9042620014800063470">"Define a voz do idioma específico para o texto lido"</string>
     <string name="tts_play_example_title" msgid="1599468547216481684">"Ouvir um exemplo"</string>
-    <string name="tts_play_example_summary" msgid="634044730710636383">"Reproduzir uma breve demonstração de síntese de voz"</string>
+    <string name="tts_play_example_summary" msgid="634044730710636383">"Reproduzir uma breve demonstração de conversão de texto em voz"</string>
     <string name="tts_install_data_title" msgid="1829942496472751703">"Instalar dados de voz"</string>
-    <string name="tts_install_data_summary" msgid="3608874324992243851">"Instalar os dados de voz necessários para a síntese de voz"</string>
+    <string name="tts_install_data_summary" msgid="3608874324992243851">"Instalar os dados de voz necessários para a conversão de texto em voz"</string>
     <string name="tts_engine_security_warning" msgid="3372432853837988146">"Este motor de síntese de discurso pode permitir a recolha de todo o texto que será falado, incluindo dados pessoais, como palavras-passe e números de cartão de crédito. O serviço é fornecido com o motor <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g>. Permitir a utilização deste motor de síntese de discurso?"</string>
-    <string name="tts_engine_network_required" msgid="8722087649733906851">"Este idioma requer uma ligação de rede ativa para uma saída de síntese de voz."</string>
-    <string name="tts_default_sample_string" msgid="6388016028292967973">"Exemplo de síntese de voz."</string>
+    <string name="tts_engine_network_required" msgid="8722087649733906851">"Este idioma requer uma ligação de rede ativa para uma saída de conversão de texto em voz."</string>
+    <string name="tts_default_sample_string" msgid="6388016028292967973">"Exemplo de conversão de texto em voz."</string>
     <string name="tts_status_title" msgid="8190784181389278640">"Estado do idioma predefinido"</string>
     <string name="tts_status_ok" msgid="8583076006537547379">"<xliff:g id="LOCALE">%1$s</xliff:g> é totalmente suportado"</string>
     <string name="tts_status_requires_network" msgid="8327617638884678896">"<xliff:g id="LOCALE">%1$s</xliff:g> necessita de ligação de rede"</string>
diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml
index 52554e2..a5b9f79 100644
--- a/packages/SettingsLib/res/values-te/arrays.xml
+++ b/packages/SettingsLib/res/values-te/arrays.xml
@@ -151,9 +151,9 @@
   </string-array>
   <string-array name="bluetooth_audio_active_device_summaries">
     <item msgid="8019740759207729126"></item>
-    <item msgid="204248102837117183">", సక్రియంగా ఉంది"</item>
-    <item msgid="253388653486517049">", (మీడియా) సక్రియంగా ఉంది"</item>
-    <item msgid="5001852592115448348">", (ఫోన్) సక్రియంగా ఉంది"</item>
+    <item msgid="204248102837117183">", యాక్టివ్‌గా ఉంది"</item>
+    <item msgid="253388653486517049">", (మీడియా) యాక్టివ్‌గా ఉంది"</item>
+    <item msgid="5001852592115448348">", (ఫోన్) యాక్టివ్‌గా ఉంది"</item>
   </string-array>
   <string-array name="select_logd_size_titles">
     <item msgid="1191094707770726722">"ఆఫ్"</item>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index da0054b..f89775f 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -291,14 +291,14 @@
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"బ్లూటూత్ MAP వెర్షన్‌"</string>
     <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"బ్లూటూత్ MAP వెర్షన్‌ను ఎంచుకోండి"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"బ్లూటూత్ ఆడియో కోడెక్"</string>
-    <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"బ్లూటూత్ ఆడియో కోడెక్‌ని సక్రియం చేయండి\nఎంపిక"</string>
+    <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"బ్లూటూత్ ఆడియో కోడెక్‌ని యాక్టివేట్ చేయండి\nఎంపిక"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"బ్లూటూత్ ఆడియో శాంపిల్ రేట్"</string>
-    <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"బ్లూటూత్ ఆడియో కోడెక్‌ని సక్రియం చేయండి\nఎంపిక: నమూనా రేట్"</string>
+    <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"బ్లూటూత్ ఆడియో కోడెక్‌ని యాక్టివేట్ చేయండి\nఎంపిక: నమూనా రేట్"</string>
     <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"గ్రే-అవుట్ అంటే ఫోన్ లేదా హెడ్‌సెట్ మద్దతు లేదు అని అర్ధం"</string>
     <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"ఒక్కో శాంపిల్‌కు బ్లూటూత్ ఆడియో బిట్‌లు"</string>
-    <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"బ్లూటూత్ ఆడియో కోడెక్‌ని సక్రియం చేయండి\nఎంపిక: ఒక్కో నమూనాలో బిట్‌లు"</string>
+    <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"బ్లూటూత్ ఆడియో కోడెక్‌ని యాక్టివేట్ చేయండి\nఎంపిక: ఒక్కో నమూనాలో బిట్‌లు"</string>
     <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"బ్లూటూత్ ఆడియో ఛానెల్ మోడ్"</string>
-    <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"బ్లూటూత్ ఆడియో కోడెక్‌ని సక్రియం చేయండి\nఎంపిక: ఛానెల్ మోడ్"</string>
+    <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"బ్లూటూత్ ఆడియో కోడెక్‌ని యాక్టివేట్ చేయండి\nఎంపిక: ఛానెల్ మోడ్"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"బ్లూటూత్ ఆడియో LDAC కోడెక్: ప్లేబ్యాక్ క్వాలిటీ"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"బ్లూటూత్ ఆడియో LDAC యాక్టివ్ చేయండి\nకోడెక్ ఎంపిక: ప్లేబ్యాక్ క్వాలిటీ"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"ప్రసారం చేస్తోంది: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
@@ -423,11 +423,11 @@
   <string-array name="color_mode_descriptions">
     <item msgid="6828141153199944847">"మెరుగైన రంగులు"</item>
     <item msgid="4548987861791236754">"కంటికి కనిపించే విధంగా సహజమైన రంగులు"</item>
-    <item msgid="1282170165150762976">"డిజిటల్ కంటెంట్ కోసం అనుకూలీకరించిన రంగులు"</item>
+    <item msgid="1282170165150762976">"డిజిటల్ కంటెంట్ కోసం అనుకూలంగా మార్చిన రంగులు"</item>
   </string-array>
     <string name="inactive_apps_title" msgid="5372523625297212320">"స్టాండ్‌బై యాప్‌లు"</string>
     <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"నిష్క్రియంగా ఉంది. టోగుల్ చేయడానికి నొక్కండి."</string>
-    <string name="inactive_app_active_summary" msgid="8047630990208722344">"సక్రియంగా ఉంది. టోగుల్ చేయడానికి నొక్కండి."</string>
+    <string name="inactive_app_active_summary" msgid="8047630990208722344">"యాక్టివ్‌గా ఉంది. టోగుల్ చేయడానికి నొక్కండి."</string>
     <string name="standby_bucket_summary" msgid="5128193447550429600">"యాప్ స్టాండ్‌బై స్థితి:<xliff:g id="BUCKET"> %s</xliff:g>"</string>
     <string name="transcode_settings_title" msgid="2581975870429850549">"మీడియా ట్రాన్స్‌కోడింగ్ సెట్టింగ్‌లు"</string>
     <string name="transcode_user_control" msgid="6176368544817731314">"ట్రాన్స్‌కోడింగ్ ఆటోమేటిక్ సెట్టింగ్‌లను ఓవర్‌రైడ్ చేయండి"</string>
@@ -570,7 +570,7 @@
     <string name="user_add_user_item_title" msgid="2394272381086965029">"యూజర్"</string>
     <string name="user_add_profile_item_title" msgid="3111051717414643029">"పరిమితం చేయబడిన ప్రొఫైల్"</string>
     <string name="user_add_user_title" msgid="5457079143694924885">"కొత్త యూజర్‌ను జోడించాలా?"</string>
-    <string name="user_add_user_message_long" msgid="1527434966294733380">"అదనపు యూజర్‌లను క్రియేట్ చేయడం ద్వారా మీరు ఈ పరికరాన్ని ఇతరులతో షేర్ చేయవచ్చు. ప్రతి యూజర్‌కు‌ వారికంటూ ప్రత్యేక స్థలం ఉంటుంది, వారు ఆ స్థలాన్ని యాప్‌లు, వాల్‌పేపర్ మొదలైనవాటితో అనుకూలీకరించవచ్చు. యూజర్‌లు ప్రతి ఒక్కరిపై ప్రభావం చూపే Wi‑Fi వంటి పరికర సెట్టింగ్‌లను కూడా సర్దుబాటు చేయవచ్చు.\n\nమీరు కొత్త యూజర్‌ను జోడించినప్పుడు, ఆ వ్యక్తి వారికంటూ స్వంత స్థలం సెట్ చేసుకోవాలి.\n\nఏ యూజర్ అయినా మిగిలిన యూజర్‌లందరి కోసం యాప్‌లను అప్‌డేట్ చేయవచ్చు. యాక్సెసిబిలిటీ సెట్టింగ్‌లు, సర్వీస్‌లు కొత్త యూజర్‌కి బదిలీ కాకపోవచ్చు."</string>
+    <string name="user_add_user_message_long" msgid="1527434966294733380">"అదనపు యూజర్‌లను క్రియేట్ చేయడం ద్వారా మీరు ఈ పరికరాన్ని ఇతరులతో షేర్ చేయవచ్చు. ప్రతి యూజర్‌కు‌ వారికంటూ ప్రత్యేక స్థలం ఉంటుంది, వారు ఆ స్థలాన్ని యాప్‌లు, వాల్‌పేపర్ మొదలైనవాటితో అనుకూలంగా మార్చవచ్చు. యూజర్‌లు ప్రతి ఒక్కరిపై ప్రభావం చూపే Wi‑Fi వంటి పరికర సెట్టింగ్‌లను కూడా సర్దుబాటు చేయవచ్చు.\n\nమీరు కొత్త యూజర్‌ను జోడించినప్పుడు, ఆ వ్యక్తి వారికంటూ స్వంత స్థలం సెట్ చేసుకోవాలి.\n\nఏ యూజర్ అయినా మిగిలిన యూజర్‌లందరి కోసం యాప్‌లను అప్‌డేట్ చేయవచ్చు. యాక్సెసిబిలిటీ సెట్టింగ్‌లు, సర్వీస్‌లు కొత్త యూజర్‌కి బదిలీ కాకపోవచ్చు."</string>
     <string name="user_add_user_message_short" msgid="3295959985795716166">"మీరు కొత్త యూజర్‌ను జోడించినప్పుడు, ఆ వ్యక్తి తన స్పేస్‌ను సెటప్ చేసుకోవాలి.\n\nఏ యూజర్ అయినా మిగతా యూజర్ల కోసం యాప్‌లను అప్‌డేట్‌ చేయగలరు."</string>
     <string name="user_setup_dialog_title" msgid="8037342066381939995">"యూజర్‌ను ఇప్పుడే సెటప్ చేయాలా?"</string>
     <string name="user_setup_dialog_message" msgid="269931619868102841">"పరికరాన్ని తీసుకోవడానికి వ్యక్తి అందుబాటులో ఉన్నారని నిర్ధారించుకొని, ఆపై వారికి నిల్వ స్థలాన్ని సెటప్ చేయండి"</string>
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 3bd6d51..f9a9ef6 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -52,6 +52,17 @@
       ]
     },
     {
+      "name": "SystemUIGoogleScreenshotTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
       // Permission indicators
       "name": "CtsPermission4TestCases",
       "options": [
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
index 1ad5484..c58c162 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
@@ -17,8 +17,12 @@
 package com.android.systemui
 
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.BoxWithConstraints
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
@@ -29,14 +33,37 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import kotlin.math.roundToInt
 
 /**
  * This is an example Compose feature, which shows a text and a count that is incremented when
- * clicked.
+ * clicked. We also show the max width available to this component, which is displayed either next
+ * to or below the text depending on that max width.
  */
 @Composable
 fun ExampleFeature(text: String, modifier: Modifier = Modifier) {
+    BoxWithConstraints(modifier) {
+        val maxWidth = maxWidth
+        if (maxWidth < 600.dp) {
+            Column {
+                CounterTile(text)
+                Spacer(Modifier.size(16.dp))
+                MaxWidthTile(maxWidth)
+            }
+        } else {
+            Row {
+                CounterTile(text)
+                Spacer(Modifier.size(16.dp))
+                MaxWidthTile(maxWidth)
+            }
+        }
+    }
+}
+
+@Composable
+private fun CounterTile(text: String, modifier: Modifier = Modifier) {
     Surface(
         modifier,
         color = MaterialTheme.colorScheme.primaryContainer,
@@ -51,3 +78,17 @@
         }
     }
 }
+
+@Composable
+private fun MaxWidthTile(maxWidth: Dp, modifier: Modifier = Modifier) {
+    Surface(
+        modifier,
+        color = MaterialTheme.colorScheme.tertiaryContainer,
+        shape = RoundedCornerShape(28.dp),
+    ) {
+        Text(
+            "The max available width to me is: ${maxWidth.value.roundToInt()}dp",
+            Modifier.padding(16.dp)
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp
index 030ad85..bddc1e6 100644
--- a/packages/SystemUI/compose/gallery/Android.bp
+++ b/packages/SystemUI/compose/gallery/Android.bp
@@ -52,6 +52,7 @@
 android_app {
     name: "SystemUIComposeGallery",
     defaults: ["platform_app_defaults"],
+    manifest: "app/AndroidManifest.xml",
 
     static_libs: [
         "SystemUIComposeGalleryLib",
diff --git a/packages/SystemUI/compose/gallery/AndroidManifest.xml b/packages/SystemUI/compose/gallery/AndroidManifest.xml
index 8113263..562e1cc 100644
--- a/packages/SystemUI/compose/gallery/AndroidManifest.xml
+++ b/packages/SystemUI/compose/gallery/AndroidManifest.xml
@@ -17,21 +17,5 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.systemui.compose.gallery">
-    <application
-        android:allowBackup="true"
-        android:icon="@mipmap/ic_launcher"
-        android:label="@string/app_name"
-        android:roundIcon="@mipmap/ic_launcher_round"
-        android:supportsRtl="true"
-        android:theme="@style/Theme.SystemUIGallery">
-        <activity
-            android:name=".GalleryActivity"
-            android:exported="true"
-            android:label="@string/app_name">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
+
 </manifest>
diff --git a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
new file mode 100644
index 0000000..8113263
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.compose.gallery">
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.SystemUIGallery">
+        <activity
+            android:name=".GalleryActivity"
+            android:exported="true"
+            android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
index 652c403..6e17214 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
@@ -18,10 +18,11 @@
 
 import androidx.compose.foundation.layout.Column
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
 import com.android.systemui.ExampleFeature
 
 /** The screen that shows ExampleFeature. */
 @Composable
-fun ExampleFeatureScreen() {
-    Column { ExampleFeature("This is an example feature!") }
+fun ExampleFeatureScreen(modifier: Modifier = Modifier) {
+    Column(modifier) { ExampleFeature("This is an example feature!") }
 }
diff --git a/packages/SystemUI/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp
new file mode 100644
index 0000000..293e51f
--- /dev/null
+++ b/packages/SystemUI/compose/testing/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+    name: "SystemUIComposeTesting",
+    manifest: "AndroidManifest.xml",
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "SystemUIComposeCore",
+        "SystemUIScreenshotLib",
+
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.material3_material3",
+        "androidx.compose.ui_ui-test-junit4",
+        "androidx.compose.ui_ui-test-manifest",
+    ],
+
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/compose/testing/AndroidManifest.xml b/packages/SystemUI/compose/testing/AndroidManifest.xml
new file mode 100644
index 0000000..b1f7c3b
--- /dev/null
+++ b/packages/SystemUI/compose/testing/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.android.systemui.testing.compose">
+    <application
+        android:appComponentFactory="androidx.core.app.AppComponentFactory"
+        tools:replace="android:appComponentFactory">
+    </application>
+</manifest>
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
new file mode 100644
index 0000000..7a0b1f1
--- /dev/null
+++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.compose
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onRoot
+import com.android.systemui.compose.theme.SystemUITheme
+import com.android.systemui.testing.screenshot.ScreenshotActivity
+import com.android.systemui.testing.screenshot.ScreenshotTestRule
+import com.android.systemui.testing.screenshot.ScreenshotTestSpec
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/** A rule for Compose screenshot diff tests. */
+class ComposeScreenshotTestRule(testSpec: ScreenshotTestSpec) : TestRule {
+    private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
+    private val screenshotRule = ScreenshotTestRule(testSpec)
+
+    private val delegate = RuleChain.outerRule(screenshotRule).around(composeRule)
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return delegate.apply(base, description)
+    }
+
+    /**
+     * Compare [content] with the golden image identified by [goldenIdentifier] in the context of
+     * [testSpec].
+     */
+    fun screenshotTest(
+        goldenIdentifier: String,
+        content: @Composable () -> Unit,
+    ) {
+        // Make sure that the activity draws full screen and fits the whole display instead of the
+        // system bars.
+        val activity = composeRule.activity
+        activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
+
+        // Set the content using the AndroidComposeRule to make sure that the Activity is set up
+        // correctly.
+        composeRule.setContent {
+            SystemUITheme {
+                Surface(
+                    color = MaterialTheme.colorScheme.background,
+                ) {
+                    content()
+                }
+            }
+        }
+        composeRule.waitForIdle()
+
+        val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
+        screenshotRule.screenshotTest(goldenIdentifier, view)
+    }
+}
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 0e20fa3..9c49607 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -42,7 +42,6 @@
         android:adjustViewBounds="true"
         android:clipToOutline="true"
         android:background="@drawable/qs_media_outline_album_bg"
-        android:foreground="@drawable/qs_media_scrim"
         />
 
     <!-- Guideline for output switcher -->
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 5e8b892..e079fd3 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -14,20 +14,25 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<!-- TODO(b/203800646): layout_marginTop doesn't seem to work on some large screens. -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/media_ttt_receiver_chip"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:background="@drawable/media_ttt_chip_background_receiver"
     >
 
+    <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
+        android:id="@+id/ripple"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        />
+
     <com.android.internal.widget.CachingIconView
         android:id="@+id/app_icon"
         android:layout_width="@dimen/media_ttt_icon_size_receiver"
         android:layout_height="@dimen/media_ttt_icon_size_receiver"
-        android:layout_gravity="center"
+        android:layout_gravity="center|bottom"
+        android:alpha="0.0"
         />
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
index 1312b41..887e3e7 100644
--- a/packages/SystemUI/res/layout/wireless_charging_layout.xml
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -22,7 +22,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <com.android.systemui.statusbar.charging.ChargingRippleView
+    <com.android.systemui.ripple.RippleView
         android:id="@+id/wireless_charging_ripple"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index bde1262..13250c8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1048,6 +1048,7 @@
     <!-- Since the generic icon isn't circular, we need to scale it down so it still fits within
          the circular chip. -->
     <dimen name="media_ttt_generic_icon_size_receiver">70dp</dimen>
+    <dimen name="media_ttt_receiver_vert_translation">20dp</dimen>
 
     <!-- Window magnification -->
     <dimen name="magnification_border_drag_size">35dp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index be3dfdc..c5beaa7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -137,12 +137,14 @@
     }
 
     /**
-     * @return the task snapshot for the given {@param taskId}.
+     * @return a {@link ThumbnailData} with {@link TaskSnapshot} for the given {@param taskId}.
+     *         The snapshot will be triggered if no cached {@link TaskSnapshot} exists.
      */
     public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean isLowResolution) {
         TaskSnapshot snapshot = null;
         try {
-            snapshot = getService().getTaskSnapshot(taskId, isLowResolution);
+            snapshot = getService().getTaskSnapshot(taskId, isLowResolution,
+                    true /* takeSnapshotIfNeeded */);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to retrieve task snapshot", e);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
deleted file mode 100644
index e0b11d8..0000000
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ /dev/null
@@ -1,239 +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.keyguard;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.icu.text.NumberFormat;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settingslib.Utils;
-import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.AnimatableClockView;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.util.ViewController;
-
-import java.io.PrintWriter;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.TimeZone;
-
-/**
- * Controller for an AnimatableClockView on the keyguard. Instantiated by
- * {@link KeyguardClockSwitchController}.
- */
-public class AnimatableClockController extends ViewController<AnimatableClockView> {
-    private static final String TAG = "AnimatableClockCtrl";
-    private static final int FORMAT_NUMBER = 1234567890;
-
-    private final StatusBarStateController mStatusBarStateController;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final BatteryController mBatteryController;
-    private final int mDozingColor = Color.WHITE;
-    private int mLockScreenColor;
-
-    private boolean mIsDozing;
-    private boolean mIsCharging;
-    private float mDozeAmount;
-    boolean mKeyguardShowing;
-    private Locale mLocale;
-
-    private final NumberFormat mBurmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"));
-    private final String mBurmeseNumerals;
-    private final float mBurmeseLineSpacing;
-    private final float mDefaultLineSpacing;
-
-    public AnimatableClockController(
-            AnimatableClockView view,
-            StatusBarStateController statusBarStateController,
-            BroadcastDispatcher broadcastDispatcher,
-            BatteryController batteryController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            @Main Resources resources
-    ) {
-        super(view);
-        mStatusBarStateController = statusBarStateController;
-        mBroadcastDispatcher = broadcastDispatcher;
-        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mBatteryController = batteryController;
-
-        mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
-        mBurmeseLineSpacing = resources.getFloat(
-                R.dimen.keyguard_clock_line_spacing_scale_burmese);
-        mDefaultLineSpacing = resources.getFloat(
-                R.dimen.keyguard_clock_line_spacing_scale);
-    }
-
-    private void reset() {
-        mView.animateDoze(mIsDozing, false);
-    }
-
-    private final BatteryController.BatteryStateChangeCallback mBatteryCallback =
-            new BatteryController.BatteryStateChangeCallback() {
-        @Override
-        public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
-            if (mKeyguardShowing && !mIsCharging && charging) {
-                mView.animateCharge(mStatusBarStateController::isDozing);
-            }
-            mIsCharging = charging;
-        }
-    };
-
-    private final BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            updateLocale();
-        }
-    };
-
-    private final StatusBarStateController.StateListener mStatusBarStateListener =
-            new StatusBarStateController.StateListener() {
-                @Override
-                public void onDozeAmountChanged(float linear, float eased) {
-                    boolean noAnimation = (mDozeAmount == 0f && linear == 1f)
-                            || (mDozeAmount == 1f && linear == 0f);
-                    boolean isDozing = linear > mDozeAmount;
-                    mDozeAmount = linear;
-                    if (mIsDozing != isDozing) {
-                        mIsDozing = isDozing;
-                        mView.animateDoze(mIsDozing, !noAnimation);
-                    }
-                }
-            };
-
-    private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
-            new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            mKeyguardShowing = showing;
-            if (!mKeyguardShowing) {
-                // reset state (ie: after weight animations)
-                reset();
-            }
-        }
-
-        @Override
-        public void onTimeFormatChanged(String timeFormat) {
-            mView.refreshFormat();
-        }
-
-        @Override
-        public void onTimeZoneChanged(TimeZone timeZone) {
-            mView.onTimeZoneChanged(timeZone);
-        }
-
-        @Override
-        public void onUserSwitchComplete(int userId) {
-            mView.refreshFormat();
-        }
-    };
-
-    @Override
-    protected void onInit() {
-        mIsDozing = mStatusBarStateController.isDozing();
-    }
-
-    @Override
-    protected void onViewAttached() {
-        updateLocale();
-        mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
-                new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
-        mDozeAmount = mStatusBarStateController.getDozeAmount();
-        mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0;
-        mBatteryController.addCallback(mBatteryCallback);
-        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
-
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
-
-        refreshTime();
-        initColors();
-        mView.animateDoze(mIsDozing, false);
-    }
-
-    @Override
-    protected void onViewDetached() {
-        mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
-        mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
-        mBatteryController.removeCallback(mBatteryCallback);
-        mStatusBarStateController.removeCallback(mStatusBarStateListener);
-    }
-
-    /** Animate the clock appearance */
-    public void animateAppear() {
-        if (!mIsDozing) mView.animateAppearOnLockscreen();
-    }
-
-    /** Animate the clock appearance when a foldable device goes from fully-open/half-open state to
-     * fully folded state and it goes to sleep (always on display screen) */
-    public void animateFoldAppear() {
-        mView.animateFoldAppear(true);
-    }
-
-    /**
-     * Updates the time for the view.
-     */
-    public void refreshTime() {
-        mView.refreshTime();
-    }
-
-    /**
-     * Return locallly stored dozing state.
-     */
-    @VisibleForTesting
-    public boolean isDozing() {
-        return mIsDozing;
-    }
-
-    private void updateLocale() {
-        Locale currLocale = Locale.getDefault();
-        if (!Objects.equals(currLocale, mLocale)) {
-            mLocale = currLocale;
-            NumberFormat nf = NumberFormat.getInstance(mLocale);
-            if (nf.format(FORMAT_NUMBER).equals(mBurmeseNumerals)) {
-                mView.setLineSpacingScale(mBurmeseLineSpacing);
-            } else {
-                mView.setLineSpacingScale(mDefaultLineSpacing);
-            }
-            mView.refreshFormat();
-        }
-    }
-
-    private void initColors() {
-        mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
-                com.android.systemui.R.attr.wallpaperTextColorAccent);
-        mView.setColors(mDozingColor, mLockScreenColor);
-        mView.animateDoze(mIsDozing, false);
-    }
-
-    /**
-     * Dump information for debugging
-     */
-    public void dump(@NonNull PrintWriter pw) {
-        pw.println(this);
-        mView.dump(pw);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index a783e1c..de5de32 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -3627,6 +3627,10 @@
         mHandler.sendEmptyMessage(MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED);
     }
 
+    /**
+     * @return true when the screen is on (including when a screensaver is showing),
+     * false when the screen is OFF or DOZE (including showing AOD UI)
+     */
     public boolean isDeviceInteractive() {
         return mDeviceInteractive;
     }
@@ -3769,6 +3773,8 @@
             pw.println("    mFingerprintLockedOut=" + mFingerprintLockedOut);
             pw.println("    mFingerprintLockedOutPermanent=" + mFingerprintLockedOutPermanent);
             pw.println("    enabledByUser=" + mBiometricEnabledForUser.get(userId));
+            pw.println("    mKeyguardOccluded=" + mKeyguardOccluded);
+            pw.println("    mIsDreaming=" + mIsDreaming);
             if (isUdfpsSupported()) {
                 pw.println("        udfpsEnrolled=" + isUdfpsEnrolled());
                 pw.println("        shouldListenForUdfps=" + shouldListenForFingerprint(true));
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index d2c229b..a3351e1 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -47,7 +47,8 @@
 open class DisplayCutoutBaseView : View, RegionInterceptableView {
 
     private var shouldDrawCutout: Boolean = DisplayCutout.getFillBuiltInDisplayCutout(
-            context.resources, context.display?.uniqueId)
+        context.resources, context.display?.uniqueId
+    )
     private var displayUniqueId: String? = null
     private var displayMode: Display.Mode? = null
     protected val location = IntArray(2)
@@ -74,8 +75,8 @@
 
     constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
 
-    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
-            : super(context, attrs, defStyleAttr)
+    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
+        super(context, attrs, defStyleAttr)
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
@@ -85,7 +86,7 @@
         onUpdate()
     }
 
-    fun onDisplayChanged(displayId: Int) {
+    fun onDisplayChanged(newDisplayUniqueId: String?) {
         val oldMode: Display.Mode? = displayMode
         val display: Display? = context.display
         displayMode = display?.mode
@@ -93,7 +94,8 @@
         if (displayUniqueId != display?.uniqueId) {
             displayUniqueId = display?.uniqueId
             shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout(
-                    context.resources, displayUniqueId)
+                context.resources, displayUniqueId
+            )
         }
 
         // Skip if display mode or cutout hasn't changed.
@@ -101,7 +103,7 @@
                 display?.cutout == displayInfo.displayCutout) {
             return
         }
-        if (displayId == display?.displayId) {
+        if (newDisplayUniqueId == display?.uniqueId) {
             updateCutout()
             updateProtectionBoundingPath()
             onUpdate()
@@ -147,8 +149,9 @@
         cutoutBounds.translate(-location[0], -location[1])
 
         // Intersect with window's frame
-        cutoutBounds.op(rootView.left, rootView.top, rootView.right, rootView.bottom,
-                Region.Op.INTERSECT)
+        cutoutBounds.op(
+            rootView.left, rootView.top, rootView.right, rootView.bottom, Region.Op.INTERSECT
+        )
         return cutoutBounds
     }
 
@@ -171,9 +174,12 @@
 
     protected open fun drawCutoutProtection(canvas: Canvas) {
         if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE &&
-                !protectionRect.isEmpty) {
-            canvas.scale(cameraProtectionProgress, cameraProtectionProgress,
-                    protectionRect.centerX(), protectionRect.centerY())
+            !protectionRect.isEmpty
+        ) {
+            canvas.scale(
+                cameraProtectionProgress, cameraProtectionProgress, protectionRect.centerX(),
+                protectionRect.centerY()
+            )
             canvas.drawPath(protectionPath, paint)
         }
     }
@@ -205,14 +211,17 @@
             requestLayout()
         }
         cameraProtectionAnimator?.cancel()
-        cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress,
-                if (showProtection) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE).setDuration(750)
+        cameraProtectionAnimator = ValueAnimator.ofFloat(
+            cameraProtectionProgress,
+            if (showProtection) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE
+        ).setDuration(750)
         cameraProtectionAnimator?.interpolator = Interpolators.DECELERATE_QUINT
-        cameraProtectionAnimator?.addUpdateListener(ValueAnimator.AnimatorUpdateListener {
-            animation: ValueAnimator ->
-            cameraProtectionProgress = animation.animatedValue as Float
-            invalidate()
-        })
+        cameraProtectionAnimator?.addUpdateListener(
+            ValueAnimator.AnimatorUpdateListener { animation: ValueAnimator ->
+                cameraProtectionProgress = animation.animatedValue as Float
+                invalidate()
+            }
+        )
         cameraProtectionAnimator?.addListener(object : AnimatorListenerAdapter() {
             override fun onAnimationEnd(animation: Animator) {
                 cameraProtectionAnimator = null
@@ -245,8 +254,10 @@
         // Apply rotation.
         val lw: Int = displayInfo.logicalWidth
         val lh: Int = displayInfo.logicalHeight
-        val flipped = (displayInfo.rotation == Surface.ROTATION_90 ||
-                displayInfo.rotation == Surface.ROTATION_270)
+        val flipped = (
+            displayInfo.rotation == Surface.ROTATION_90 ||
+                displayInfo.rotation == Surface.ROTATION_270
+            )
         val dw = if (flipped) lh else lw
         val dh = if (flipped) lw else lh
         transformPhysicalToLogicalCoordinates(displayInfo.rotation, dw, dh, m)
@@ -275,7 +286,7 @@
         // We purposely ignore refresh rate and id changes here, because we don't need to
         // invalidate for those, and they can trigger the refresh rate to increase
         return oldMode?.physicalHeight != newMode?.physicalHeight ||
-                oldMode?.physicalWidth != newMode?.physicalWidth
+            oldMode?.physicalWidth != newMode?.physicalWidth
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index f99293a..aaaa3f7 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -32,7 +32,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
-import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
@@ -92,10 +91,8 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -448,6 +445,7 @@
                     }
                 }
 
+                boolean needToUpdateProviderViews = false;
                 final String newUniqueId = mDisplayInfo.uniqueId;
                 if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
                     mDisplayUniqueId = newUniqueId;
@@ -470,8 +468,7 @@
                         updateHwLayerRoundedCornerDrawable();
                         updateHwLayerRoundedCornerExistAndSize();
                     }
-
-                    updateOverlayProviderViews();
+                    needToUpdateProviderViews = true;
                 }
 
                 final float newRatio = getPhysicalPixelDisplaySizeRatio();
@@ -480,7 +477,13 @@
                     if (mScreenDecorHwcLayer != null) {
                         updateHwLayerRoundedCornerExistAndSize();
                     }
-                    updateOverlayProviderViews();
+                    needToUpdateProviderViews = true;
+                }
+
+                if (needToUpdateProviderViews) {
+                    updateOverlayProviderViews(null);
+                } else {
+                    updateOverlayProviderViews(new Integer[] { mFaceScanningViewId });
                 }
 
                 if (mCutoutViews != null) {
@@ -490,18 +493,12 @@
                         if (cutoutView == null) {
                             continue;
                         }
-                        cutoutView.onDisplayChanged(displayId);
+                        cutoutView.onDisplayChanged(newUniqueId);
                     }
                 }
 
-                DisplayCutoutView overlay = (DisplayCutoutView) getOverlayView(mFaceScanningViewId);
-                if (overlay != null) {
-                    // handle display resolution changes
-                    overlay.onDisplayChanged(displayId);
-                }
-
                 if (mScreenDecorHwcLayer != null) {
-                    mScreenDecorHwcLayer.onDisplayChanged(displayId);
+                    mScreenDecorHwcLayer.onDisplayChanged(newUniqueId);
                 }
             }
         };
@@ -804,7 +801,7 @@
                     return;
                 }
                 removeOverlayView(provider.getViewId());
-                overlay.addDecorProvider(provider, mRotation);
+                overlay.addDecorProvider(provider, mRotation, mTintColor);
             });
         }
         // Use visibility of privacy dot views & face scanning view to determine the overlay's
@@ -954,24 +951,6 @@
             return;
         }
 
-        // When the hwc supports screen decorations, the layer will use the A8 color mode which
-        // won't be affected by the color inversion. If the composition goes the client composition
-        // route, the color inversion will be handled by the RenderEngine.
-        final Set<Integer> viewsMayNeedColorUpdate = new HashSet<>();
-        if (mHwcScreenDecorationSupport == null) {
-            ColorStateList tintList = ColorStateList.valueOf(mTintColor);
-            mRoundedCornerResDelegate.setColorTintList(tintList);
-            viewsMayNeedColorUpdate.add(R.id.rounded_corner_top_left);
-            viewsMayNeedColorUpdate.add(R.id.rounded_corner_top_right);
-            viewsMayNeedColorUpdate.add(R.id.rounded_corner_bottom_left);
-            viewsMayNeedColorUpdate.add(R.id.rounded_corner_bottom_right);
-            viewsMayNeedColorUpdate.add(R.id.display_cutout);
-        }
-        if (getOverlayView(mFaceScanningViewId) != null) {
-            viewsMayNeedColorUpdate.add(mFaceScanningViewId);
-        }
-        final Integer[] views = new Integer[viewsMayNeedColorUpdate.size()];
-        viewsMayNeedColorUpdate.toArray(views);
         for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
             if (mOverlays[i] == null) {
                 continue;
@@ -981,14 +960,19 @@
             View child;
             for (int j = 0; j < size; j++) {
                 child = overlayView.getChildAt(j);
-                if (viewsMayNeedColorUpdate.contains(child.getId())
-                        && child instanceof DisplayCutoutView) {
+                if (child instanceof DisplayCutoutView && child.getId() == R.id.display_cutout) {
                     ((DisplayCutoutView) child).setColor(mTintColor);
                 }
             }
-            mOverlays[i].onReloadResAndMeasure(views, mProviderRefreshToken,
-                    mRotation, mDisplayUniqueId);
         }
+
+        updateOverlayProviderViews(new Integer[] {
+                mFaceScanningViewId,
+                R.id.rounded_corner_top_left,
+                R.id.rounded_corner_top_right,
+                R.id.rounded_corner_bottom_left,
+                R.id.rounded_corner_bottom_right
+        });
     }
 
     @VisibleForTesting
@@ -1119,7 +1103,7 @@
             }
 
             // update all provider views inside overlay
-            updateOverlayProviderViews();
+            updateOverlayProviderViews(null);
         }
 
         FaceScanningOverlay faceScanningOverlay =
@@ -1191,7 +1175,7 @@
                 context.getResources(), context.getDisplay().getUniqueId());
     }
 
-    private void updateOverlayProviderViews() {
+    private void updateOverlayProviderViews(@Nullable Integer[] filterIds) {
         if (mOverlays == null) {
             return;
         }
@@ -1200,7 +1184,8 @@
             if (overlay == null) {
                 continue;
             }
-            overlay.onReloadResAndMeasure(null, mProviderRefreshToken, mRotation, mDisplayUniqueId);
+            overlay.onReloadResAndMeasure(filterIds, mProviderRefreshToken, mRotation, mTintColor,
+                    mDisplayUniqueId);
         }
     }
 
@@ -1239,19 +1224,12 @@
             } catch (NumberFormatException e) {
                 mRoundedCornerResDelegate.setTuningSizeFactor(null);
             }
-            Integer[] filterIds = {
+            updateOverlayProviderViews(new Integer[] {
                     R.id.rounded_corner_top_left,
                     R.id.rounded_corner_top_right,
                     R.id.rounded_corner_bottom_left,
                     R.id.rounded_corner_bottom_right
-            };
-            for (final OverlayWindow overlay: mOverlays) {
-                if (overlay == null) {
-                    continue;
-                }
-                overlay.onReloadResAndMeasure(filterIds, mProviderRefreshToken, mRotation,
-                        mDisplayUniqueId);
-            }
+            });
             updateHwLayerRoundedCornerExistAndSize();
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 378ae14..fef7383 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -29,8 +29,7 @@
 import android.view.animation.PathInterpolator
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.animation.Interpolators
-import com.android.systemui.statusbar.charging.DwellRippleShader
-import com.android.systemui.statusbar.charging.RippleShader
+import com.android.systemui.ripple.RippleShader
 
 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
 
@@ -298,7 +297,7 @@
             addListener(object : AnimatorListenerAdapter() {
                 override fun onAnimationStart(animation: Animator?) {
                     unlockedRippleInProgress = true
-                    rippleShader.shouldFadeOutRipple = true
+                    rippleShader.rippleFill = false
                     drawRipple = true
                     visibility = VISIBLE
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt b/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt
rename to packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
index 236129f..979fe33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.charging
+package com.android.systemui.biometrics
 
 import android.graphics.PointF
 import android.graphics.RuntimeShader
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
new file mode 100644
index 0000000..4986fe8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.camera
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityOptions
+import android.app.ActivityTaskManager
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.os.AsyncTask
+import android.os.RemoteException
+import android.os.UserHandle
+import android.util.Log
+import android.view.WindowManager
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.camera.CameraIntents.Companion.isSecureCameraIntent
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.PanelViewController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+
+/**
+ * Helps with handling camera-related gestures (for example, double-tap the power button to launch
+ * the camera).
+ */
+class CameraGestureHelper @Inject constructor(
+    private val context: Context,
+    private val centralSurfaces: CentralSurfaces,
+    private val keyguardStateController: KeyguardStateController,
+    private val packageManager: PackageManager,
+    private val activityManager: ActivityManager,
+    private val activityStarter: ActivityStarter,
+    private val activityIntentHelper: ActivityIntentHelper,
+    private val cameraIntents: CameraIntentsWrapper,
+    private val contentResolver: ContentResolver,
+) {
+    /**
+     * Whether the camera application can be launched for the camera launch gesture.
+     */
+    fun canCameraGestureBeLaunched(statusBarState: Int): Boolean {
+        if (!centralSurfaces.isCameraAllowedByAdmin) {
+            return false
+        }
+
+        val resolveInfo: ResolveInfo = packageManager.resolveActivityAsUser(
+            getStartCameraIntent(),
+            PackageManager.MATCH_DEFAULT_ONLY,
+            KeyguardUpdateMonitor.getCurrentUser()
+        )
+        val resolvedPackage = resolveInfo.activityInfo?.packageName
+        return (resolvedPackage != null &&
+                (statusBarState != StatusBarState.SHADE ||
+                !isForegroundApp(resolvedPackage)))
+    }
+
+    /**
+     * Launches the camera.
+     *
+     * @param source The source of the camera launch, to be passed to the camera app via [Intent]
+     */
+    fun launchCamera(source: Int) {
+        val intent: Intent = getStartCameraIntent()
+        intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source)
+        val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
+            intent, KeyguardUpdateMonitor.getCurrentUser()
+        )
+        if (isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
+            AsyncTask.execute {
+                // Normally an activity will set its requested rotation animation on its window.
+                // However when launching an activity causes the orientation to change this is too
+                // late. In these cases, the default animation is used. This doesn't look good for
+                // the camera (as it rotates the camera contents out of sync with physical reality).
+                // Therefore, we ask the WindowManager to force the cross-fade animation if an
+                // orientation change happens to occur during the launch.
+                val activityOptions = ActivityOptions.makeBasic()
+                activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true)
+                activityOptions.rotationAnimationHint =
+                    WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+                try {
+                    ActivityTaskManager.getService().startActivityAsUser(
+                        null,
+                        context.basePackageName,
+                        context.attributionTag,
+                        intent,
+                        intent.resolveTypeIfNeeded(contentResolver),
+                        null,
+                        null,
+                        0,
+                        Intent.FLAG_ACTIVITY_NEW_TASK,
+                        null,
+                        activityOptions.toBundle(),
+                        UserHandle.CURRENT.identifier,
+                    )
+                } catch (e: RemoteException) {
+                    Log.w(
+                        PanelViewController.TAG,
+                        "Unable to start camera activity",
+                        e
+                    )
+                }
+            }
+        } else {
+            // We need to delay starting the activity because ResolverActivity finishes itself if
+            // launched from behind the lock-screen.
+            activityStarter.startActivity(intent, false /* dismissShade */)
+        }
+    }
+
+    /**
+     * Returns an [Intent] that can be used to start the camera app such that it occludes the
+     * lock-screen, if needed.
+     */
+    private fun getStartCameraIntent(): Intent {
+        val isLockScreenDismissible = keyguardStateController.canDismissLockScreen()
+        val isSecure = keyguardStateController.isMethodSecure
+        return if (isSecure && !isLockScreenDismissible) {
+            cameraIntents.getSecureCameraIntent()
+        } else {
+            cameraIntents.getInsecureCameraIntent()
+        }
+    }
+
+    /**
+     * Returns `true` if the application with the given package name is running in the foreground;
+     * `false` otherwise
+     */
+    private fun isForegroundApp(packageName: String): Boolean {
+        val tasks: List<RunningTaskInfo> = activityManager.getRunningTasks(1)
+        return tasks.isNotEmpty() && packageName == tasks[0].topActivity.packageName
+    }
+
+    companion object {
+        private const val EXTRA_CAMERA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
new file mode 100644
index 0000000..cf02f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.camera
+
+import android.content.Context
+import android.content.Intent
+import javax.inject.Inject
+
+/** Injectable wrapper around [CameraIntents]. */
+class CameraIntentsWrapper @Inject constructor(
+    private val context: Context,
+) {
+
+    /**
+     * Returns an [Intent] that can be used to start the camera, suitable for when the device is
+     * already unlocked
+     */
+    fun getSecureCameraIntent(): Intent {
+        return CameraIntents.getSecureCameraIntent(context)
+    }
+
+    /**
+     * Returns an [Intent] that can be used to start the camera, suitable for when the device is not
+     * already unlocked
+     */
+    fun getInsecureCameraIntent(): Intent {
+        return CameraIntents.getInsecureCameraIntent(context)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
rename to packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 558bcac..8292e52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.charging
+package com.android.systemui.charging
 
 import android.content.Context
 import android.content.res.Configuration
@@ -32,6 +32,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.ripple.RippleView
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.policy.BatteryController
@@ -84,7 +85,7 @@
     private var debounceLevel = 0
 
     @VisibleForTesting
-    var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null)
+    var rippleView: RippleView = RippleView(context, attrs = null)
 
     init {
         pluggedIn = batteryController.isPluggedIn
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 0d3e2ae..f6368ee 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -34,7 +34,7 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
-import com.android.systemui.statusbar.charging.ChargingRippleView;
+import com.android.systemui.ripple.RippleView;
 
 import java.text.NumberFormat;
 
@@ -46,7 +46,7 @@
     private static final long RIPPLE_ANIMATION_DURATION = 1500;
     private static final int SCRIM_COLOR = 0x4C000000;
     private static final int SCRIM_FADE_DURATION = 300;
-    private ChargingRippleView mRippleView;
+    private RippleView mRippleView;
 
     public WirelessChargingLayout(Context context) {
         super(context);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 366ef26..ba1e057 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -55,8 +55,6 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
@@ -220,11 +218,9 @@
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
             NotificationGroupManagerLegacy groupManager,
-            NotificationEntryManager entryManager,
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
-            NotifPipelineFlags notifPipelineFlags,
             DumpManager dumpManager,
             @Main Executor sysuiMainExecutor) {
         return Optional.ofNullable(BubblesManager.create(context,
@@ -240,11 +236,9 @@
                 zenModeController,
                 notifUserManager,
                 groupManager,
-                entryManager,
                 notifCollection,
                 notifPipeline,
                 sysUiState,
-                notifPipelineFlags,
                 dumpManager,
                 sysuiMainExecutor));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
index 169b50e..de6d727 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
@@ -33,7 +33,7 @@
 
     /** The number of total aligned bounds */
     val numOfAlignedEdge: Int
-    get() = alignedBounds.size
+        get() = alignedBounds.size
 
     /** The aligned bounds for the view which is created through inflateView() */
     abstract val alignedBounds: List<Int>
@@ -46,14 +46,16 @@
         view: View,
         reloadToken: Int,
         @Surface.Rotation rotation: Int,
-        displayUniqueId: String? = null
+        tintColor: Int,
+        displayUniqueId: String?
     )
 
     /** Inflate view into parent as current rotation */
     abstract fun inflateView(
         context: Context,
         parent: ViewGroup,
-        @Surface.Rotation rotation: Int
+        @Surface.Rotation rotation: Int,
+        tintColor: Int
     ): View
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 81d3d6c..5925c57 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -115,19 +115,25 @@
     override fun onReloadResAndMeasure(
         view: View,
         reloadToken: Int,
-        rotation: Int,
+        @Surface.Rotation rotation: Int,
+        tintColor: Int,
         displayUniqueId: String?
     ) {
         (view.layoutParams as FrameLayout.LayoutParams).let {
             updateLayoutParams(it, rotation)
             view.layoutParams = it
+            (view as? FaceScanningOverlay)?.let { overlay ->
+                overlay.setColor(tintColor)
+                overlay.onDisplayChanged(displayUniqueId)
+            }
         }
     }
 
     override fun inflateView(
         context: Context,
         parent: ViewGroup,
-        @Surface.Rotation rotation: Int
+        @Surface.Rotation rotation: Int,
+        tintColor: Int
     ): View {
         val view = FaceScanningOverlay(
                 context,
@@ -137,6 +143,7 @@
                 mainExecutor
         )
         view.id = viewId
+        view.setColor(tintColor)
         FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT).let {
             updateLayoutParams(it, rotation)
diff --git a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
index 3c0748e..dfb0b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
@@ -34,9 +34,10 @@
 
     fun addDecorProvider(
         decorProvider: DecorProvider,
-        @Surface.Rotation rotation: Int
+        @Surface.Rotation rotation: Int,
+        tintColor: Int
     ) {
-        val view = decorProvider.inflateView(context, rootView, rotation)
+        val view = decorProvider.inflateView(context, rootView, rotation, tintColor)
         viewProviderMap[decorProvider.viewId] = Pair(view, decorProvider)
     }
 
@@ -69,7 +70,7 @@
      */
     fun hasSameProviders(newProviders: List<DecorProvider>): Boolean {
         return (newProviders.size == viewProviderMap.size) &&
-                newProviders.all { getView(it.viewId) != null }
+            newProviders.all { getView(it.viewId) != null }
     }
 
     /**
@@ -82,23 +83,28 @@
         filterIds: Array<Int>? = null,
         reloadToken: Int,
         @Surface.Rotation rotation: Int,
+        tintColor: Int,
         displayUniqueId: String? = null
     ) {
         filterIds?.forEach { id ->
             viewProviderMap[id]?.let {
                 it.second.onReloadResAndMeasure(
-                        view = it.first,
-                        reloadToken = reloadToken,
-                        displayUniqueId = displayUniqueId,
-                        rotation = rotation)
+                    view = it.first,
+                    reloadToken = reloadToken,
+                    rotation = rotation,
+                    tintColor = tintColor,
+                    displayUniqueId = displayUniqueId
+                )
             }
         } ?: run {
             viewProviderMap.values.forEach {
                 it.second.onReloadResAndMeasure(
-                        view = it.first,
-                        reloadToken = reloadToken,
-                        displayUniqueId = displayUniqueId,
-                        rotation = rotation)
+                    view = it.first,
+                    reloadToken = reloadToken,
+                    rotation = rotation,
+                    tintColor = tintColor,
+                    displayUniqueId = displayUniqueId
+                )
             }
         }
     }
@@ -111,4 +117,4 @@
             pw.println("    child[$i]=$child")
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
index 9f624b3..e18c0e1 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
@@ -20,9 +20,9 @@
 import android.content.res.Resources
 import android.view.DisplayCutout
 import android.view.LayoutInflater
+import android.view.Surface
 import android.view.View
 import android.view.ViewGroup
-import android.view.Surface
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
@@ -85,6 +85,7 @@
         view: View,
         reloadToken: Int,
         rotation: Int,
+        tintColor: Int,
         displayUniqueId: String?
     ) {
         // Do nothing here because it is handled inside PrivacyDotViewController
@@ -93,7 +94,8 @@
     override fun inflateView(
         context: Context,
         parent: ViewGroup,
-        @Surface.Rotation rotation: Int
+        @Surface.Rotation rotation: Int,
+        tintColor: Int
     ): View {
         LayoutInflater.from(context).inflate(layoutId, parent, true)
         return parent.getChildAt(parent.childCount - 1 /* latest new added child */)
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
index e316722..8156797 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.decor
 
 import android.content.Context
+import android.content.res.ColorStateList
 import android.view.DisplayCutout
 import android.view.Gravity
 import android.view.Surface
@@ -38,12 +39,13 @@
     override fun inflateView(
         context: Context,
         parent: ViewGroup,
-        @Surface.Rotation rotation: Int
+        @Surface.Rotation rotation: Int,
+        tintColor: Int
     ): View {
         return ImageView(context).also { view ->
             // View
             view.id = viewId
-            initView(view, rotation)
+            initView(view, rotation, tintColor)
 
             // LayoutParams
             val layoutSize = if (isTop) {
@@ -52,31 +54,36 @@
                 roundedCornerResDelegate.bottomRoundedSize
             }
             val params = FrameLayout.LayoutParams(
-                    layoutSize.width,
-                    layoutSize.height,
-                    alignedBound1.toLayoutGravity(rotation) or
-                            alignedBound2.toLayoutGravity(rotation))
+                layoutSize.width,
+                layoutSize.height,
+                alignedBound1.toLayoutGravity(rotation) or alignedBound2.toLayoutGravity(rotation)
+            )
 
             // AddView
             parent.addView(view, params)
         }
     }
 
-    private fun initView(view: ImageView, @Surface.Rotation rotation: Int) {
+    private fun initView(
+        view: ImageView,
+        @Surface.Rotation rotation: Int,
+        tintColor: Int
+    ) {
         view.setRoundedCornerImage(roundedCornerResDelegate, isTop)
         view.adjustRotation(alignedBounds, rotation)
-        view.imageTintList = roundedCornerResDelegate.colorTintList
+        view.imageTintList = ColorStateList.valueOf(tintColor)
     }
 
     override fun onReloadResAndMeasure(
         view: View,
         reloadToken: Int,
         @Surface.Rotation rotation: Int,
+        tintColor: Int,
         displayUniqueId: String?
     ) {
         roundedCornerResDelegate.updateDisplayUniqueId(displayUniqueId, reloadToken)
 
-        initView((view as ImageView), rotation)
+        initView((view as ImageView), rotation, tintColor)
 
         val layoutSize = if (isTop) {
             roundedCornerResDelegate.topRoundedSize
@@ -87,7 +94,7 @@
             it.width = layoutSize.width
             it.height = layoutSize.height
             it.gravity = alignedBound1.toLayoutGravity(rotation) or
-                    alignedBound2.toLayoutGravity(rotation)
+                alignedBound2.toLayoutGravity(rotation)
             view.setLayoutParams(it)
         }
     }
@@ -134,10 +141,10 @@
         setImageDrawable(drawable)
     } else {
         setImageResource(
-                if (isTop)
-                    R.drawable.rounded_corner_top
-                else
-                    R.drawable.rounded_corner_bottom
+            if (isTop)
+                R.drawable.rounded_corner_top
+            else
+                R.drawable.rounded_corner_bottom
         )
     }
 }
@@ -187,4 +194,4 @@
     this.rotation = newRotation
     this.scaleX = newScaleX
     this.scaleY = newScaleY
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index b5a0cfc..a252864 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -18,9 +18,7 @@
 
 import android.annotation.ArrayRes
 import android.annotation.DrawableRes
-import android.content.res.ColorStateList
 import android.content.res.Resources
-import android.graphics.Color
 import android.graphics.drawable.Drawable
 import android.util.DisplayUtils
 import android.util.Size
@@ -57,8 +55,6 @@
     var bottomRoundedSize = Size(0, 0)
         private set
 
-    var colorTintList = ColorStateList.valueOf(Color.BLACK)
-
     var tuningSizeFactor: Int? = null
         set(value) {
             if (field == value) {
@@ -107,19 +103,19 @@
 
         val hasDefaultRadius = RoundedCorners.getRoundedCornerRadius(res, displayUniqueId) > 0
         hasTop = hasDefaultRadius ||
-                (RoundedCorners.getRoundedCornerTopRadius(res, displayUniqueId) > 0)
+            (RoundedCorners.getRoundedCornerTopRadius(res, displayUniqueId) > 0)
         hasBottom = hasDefaultRadius ||
-                (RoundedCorners.getRoundedCornerBottomRadius(res, displayUniqueId) > 0)
+            (RoundedCorners.getRoundedCornerBottomRadius(res, displayUniqueId) > 0)
 
         topRoundedDrawable = getDrawable(
-                displayConfigIndex = configIdx,
-                arrayResId = R.array.config_roundedCornerTopDrawableArray,
-                backupDrawableId = R.drawable.rounded_corner_top
+            displayConfigIndex = configIdx,
+            arrayResId = R.array.config_roundedCornerTopDrawableArray,
+            backupDrawableId = R.drawable.rounded_corner_top
         )
         bottomRoundedDrawable = getDrawable(
-                displayConfigIndex = configIdx,
-                arrayResId = R.array.config_roundedCornerBottomDrawableArray,
-                backupDrawableId = R.drawable.rounded_corner_bottom
+            displayConfigIndex = configIdx,
+            arrayResId = R.array.config_roundedCornerBottomDrawableArray,
+            backupDrawableId = R.drawable.rounded_corner_bottom
         )
     }
 
@@ -147,13 +143,15 @@
         if (physicalPixelDisplaySizeRatio != 1f) {
             if (topRoundedSize.width != 0) {
                 topRoundedSize = Size(
-                        (physicalPixelDisplaySizeRatio * topRoundedSize.width + 0.5f).toInt(),
-                        (physicalPixelDisplaySizeRatio * topRoundedSize.height + 0.5f).toInt())
+                    (physicalPixelDisplaySizeRatio * topRoundedSize.width + 0.5f).toInt(),
+                    (physicalPixelDisplaySizeRatio * topRoundedSize.height + 0.5f).toInt()
+                )
             }
             if (bottomRoundedSize.width != 0) {
                 bottomRoundedSize = Size(
-                        (physicalPixelDisplaySizeRatio * bottomRoundedSize.width + 0.5f).toInt(),
-                        (physicalPixelDisplaySizeRatio * bottomRoundedSize.height + 0.5f).toInt())
+                    (physicalPixelDisplaySizeRatio * bottomRoundedSize.width + 0.5f).toInt(),
+                    (physicalPixelDisplaySizeRatio * bottomRoundedSize.height + 0.5f).toInt()
+                )
             }
         }
     }
@@ -180,8 +178,9 @@
         pw.println("  hasTop=$hasTop")
         pw.println("  hasBottom=$hasBottom")
         pw.println("  topRoundedSize(w,h)=(${topRoundedSize.width},${topRoundedSize.height})")
-        pw.println("  bottomRoundedSize(w,h)=(${bottomRoundedSize.width}," +
-                "${bottomRoundedSize.height})")
+        pw.println(
+            "  bottomRoundedSize(w,h)=(${bottomRoundedSize.width},${bottomRoundedSize.height})"
+        )
         pw.println("  physicalPixelDisplaySizeRatio=$physicalPixelDisplaySizeRatio")
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 064388f..2fa104a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -162,6 +162,11 @@
             new ResourceBooleanFlag(800, R.bool.flag_monet);
 
     /***************************************/
+    // 801 - region sampling
+    public static final BooleanFlag REGION_SAMPLING =
+            new BooleanFlag(801, false);
+
+    /***************************************/
     // 900 - media
     public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
     public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 95b3b3f..3eb3c80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -453,6 +453,8 @@
 
         @Override // Binder interface
         public void setOccluded(boolean isOccluded, boolean animate) {
+            Log.d(TAG, "setOccluded(" + isOccluded + ")");
+
             Trace.beginSection("KeyguardService.mBinder#setOccluded");
             checkPermission();
             mKeyguardViewMediator.setOccluded(isOccluded, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4ee8985..c6da7d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -837,13 +837,10 @@
     private final ActivityLaunchAnimator.Controller mOccludeAnimationController =
             new ActivityLaunchAnimator.Controller() {
                 @Override
-                public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
-                    setOccluded(true /* occluded */, false /* animate */);
-                }
+                public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {}
 
                 @Override
                 public void onLaunchAnimationCancelled() {
-                    setOccluded(true /* occluded */, false /* animate */);
                     Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
                             + mOccluded);
                 }
@@ -916,7 +913,7 @@
                         mUnoccludeAnimator.cancel();
                     }
 
-                    setOccluded(isKeyguardOccluded, false /* animate */);
+                    setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
                     Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
                             + mOccluded);
                 }
@@ -926,6 +923,7 @@
                         RemoteAnimationTarget[] wallpapers,
                         RemoteAnimationTarget[] nonApps,
                         IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+                    Log.d(TAG, "UnoccludeAnimator#onAnimationStart. Set occluded = false.");
                     setOccluded(false /* isOccluded */, true /* animate */);
 
                     if (apps == null || apps.length == 0 || apps[0] == null) {
@@ -1677,6 +1675,8 @@
      * Notify us when the keyguard is occluded by another window
      */
     public void setOccluded(boolean isOccluded, boolean animate) {
+        Log.d(TAG, "setOccluded(" + isOccluded + ")");
+
         Trace.beginSection("KeyguardViewMediator#setOccluded");
         if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded);
         mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
@@ -1707,6 +1707,7 @@
      */
     private void handleSetOccluded(boolean isOccluded, boolean animate) {
         Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
+        Log.d(TAG, "handleSetOccluded(" + isOccluded + ")");
         synchronized (KeyguardViewMediator.this) {
             if (mHiding && isOccluded) {
                 // We're in the process of going away but WindowManager wants to show a
@@ -3208,13 +3209,18 @@
             // internal state to reflect that immediately, vs. waiting for the launch animator to
             // begin. Otherwise, calls to setShowingLocked, etc. will not know that we're about to
             // be occluded and might re-show the keyguard.
+            Log.d(TAG, "OccludeAnimator#onAnimationStart. Set occluded = true.");
             setOccluded(true /* isOccluded */, false /* animate */);
         }
 
         @Override
         public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
             super.onAnimationCancelled(isKeyguardOccluded);
-            Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded);
+
+            Log.d(TAG, "Occlude animation cancelled by WM. "
+                    + "Setting occluded state to: " + isKeyguardOccluded);
+            setOccluded(isKeyguardOccluded /* occluded */, false /* animate */);
+
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 90cca15..d0da18a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -27,6 +27,8 @@
 import com.android.systemui.log.LogcatEchoTracker;
 import com.android.systemui.log.LogcatEchoTrackerDebug;
 import com.android.systemui.log.LogcatEchoTrackerProd;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.util.Compile;
 
 import dagger.Module;
 import dagger.Provides;
@@ -48,8 +50,14 @@
     @Provides
     @SysUISingleton
     @NotificationLog
-    public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) {
-        return factory.create("NotifLog", 1000 /* maxSize */, false /* systrace */);
+    public static LogBuffer provideNotificationsLogBuffer(
+            LogBufferFactory factory,
+            NotifPipelineFlags notifPipelineFlags) {
+        int maxSize = 1000;
+        if (Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()) {
+            maxSize *= 10;
+        }
+        return factory.create("NotifLog", maxSize, false /* systrace */);
     }
 
     /** Provides a logging buffer for logs related to heads up presentation of notifications. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
index f1d5e94..d082655 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
@@ -17,20 +17,17 @@
 package com.android.systemui.media
 
 import android.animation.ArgbEvaluator
-import android.animation.ValueAnimator.AnimatorUpdateListener
 import android.animation.ValueAnimator
+import android.animation.ValueAnimator.AnimatorUpdateListener
 import android.content.Context
 import android.content.res.ColorStateList
-import android.graphics.Color
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.RippleDrawable
 import android.content.res.Configuration
 import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.graphics.drawable.RippleDrawable
 import com.android.internal.R
 import com.android.internal.annotations.VisibleForTesting
 import com.android.settingslib.Utils
 import com.android.systemui.monet.ColorScheme
-import com.android.systemui.util.getColorWithAlpha
 
 /**
  * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
@@ -106,7 +103,6 @@
     constructor(context: Context, mediaViewHolder: MediaViewHolder) :
         this(context, mediaViewHolder, ::AnimatingColorTransition)
 
-    private var isGradientEnabled = true
     val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
     val surfaceColor = animatingColorTransitionFactory(
         bgColor,
@@ -187,16 +183,6 @@
         mediaViewHolder.seekBar.progressBackgroundTintList = ColorStateList.valueOf(textTertiary)
     }
 
-    val bgGradientStart = animatingColorTransitionFactory(
-        bgColor,
-        albumGradientPicker(::backgroundStartFromScheme, 0.25f)
-    ) { _ -> updateAlbumGradient() }
-
-    val bgGradientEnd = animatingColorTransitionFactory(
-        bgColor,
-        albumGradientPicker(::backgroundEndFromScheme, 0.9f)
-    ) { _ -> updateAlbumGradient() }
-
     val colorTransitions = arrayOf(
         surfaceColor,
         colorSeamless,
@@ -206,37 +192,13 @@
         textPrimaryInverse,
         textSecondary,
         textTertiary,
-        bgGradientStart,
-        bgGradientEnd
     )
 
-    private fun updateAlbumGradient() {
-        val gradient = mediaViewHolder.albumView.foreground?.mutate()
-        if (gradient is GradientDrawable) {
-            gradient.colors = intArrayOf(
-                bgGradientStart?.currentColor ?: 0,
-                bgGradientEnd?.currentColor ?: 0)
-        }
-    }
-
-    private fun albumGradientPicker(
-        inner: (ColorScheme) -> Int,
-        targetAlpha: Float
-    ): (ColorScheme) -> Int {
-        return { scheme ->
-            if (isGradientEnabled)
-                getColorWithAlpha(inner(scheme), targetAlpha)
-            else
-                Color.TRANSPARENT
-        }
-    }
-
     private fun loadDefaultColor(id: Int): Int {
         return Utils.getColorAttr(context, id).defaultColor
     }
 
-    fun updateColorScheme(colorScheme: ColorScheme?, enableGradient: Boolean) {
-        isGradientEnabled = enableGradient
+    fun updateColorScheme(colorScheme: ColorScheme?) {
         colorTransitions.forEach { it.updateColorScheme(colorScheme) }
         colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 68aba62..1ed65b3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -38,7 +38,9 @@
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
+import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.TransitionDrawable;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
@@ -82,6 +84,7 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ColorUtilKt;
 import com.android.systemui.util.animation.TransitionLayout;
 import com.android.systemui.util.time.SystemClock;
 
@@ -683,7 +686,18 @@
                 WallpaperColors wallpaperColors = WallpaperColors
                         .fromBitmap(artworkIcon.getBitmap());
                 mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
-                artwork = getScaledBackground(artworkIcon, width, height);
+                Drawable albumArt = getScaledBackground(artworkIcon, width, height);
+                GradientDrawable gradient = (GradientDrawable) mContext
+                        .getDrawable(R.drawable.qs_media_scrim);
+                gradient.setColors(new int[] {
+                        ColorUtilKt.getColorWithAlpha(
+                                MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
+                                0.25f),
+                        ColorUtilKt.getColorWithAlpha(
+                                MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
+                                0.9f),
+                });
+                artwork = new LayerDrawable(new Drawable[] { albumArt, gradient });
                 isArtworkBound = true;
             } else {
                 // If there's no artwork, use colors from the app icon
@@ -735,7 +749,7 @@
                 }
 
                 // Transition Colors to current color scheme
-                mColorSchemeTransition.updateColorScheme(colorScheme, mIsArtworkBound);
+                mColorSchemeTransition.updateColorScheme(colorScheme);
 
                 // App icon - use notification icon
                 ImageView appIconView = mMediaViewHolder.getAppIcon();
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index fe1ac80..c9fce79 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -25,17 +25,14 @@
 import android.os.PowerManager
 import android.os.SystemClock
 import android.util.Log
-import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.MotionEvent
-import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
-import android.widget.LinearLayout
 import com.android.internal.widget.CachingIconView
 import com.android.settingslib.Utils
 import com.android.systemui.R
@@ -57,7 +54,7 @@
 abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
     internal val context: Context,
     internal val logger: MediaTttLogger,
-    private val windowManager: WindowManager,
+    internal val windowManager: WindowManager,
     private val viewUtil: ViewUtil,
     @Main private val mainExecutor: DelayableExecutor,
     private val accessibilityManager: AccessibilityManager,
@@ -65,12 +62,15 @@
     private val powerManager: PowerManager,
     @LayoutRes private val chipLayoutRes: Int
 ) {
-    /** The window layout parameters we'll use when attaching the view to a window. */
+
+    /**
+     * Window layout params that will be used as a starting point for the [windowLayoutParams] of
+     * all subclasses.
+     */
     @SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
-    private val windowLayoutParams = WindowManager.LayoutParams().apply {
+    internal val commonWindowLayoutParams = WindowManager.LayoutParams().apply {
         width = WindowManager.LayoutParams.WRAP_CONTENT
         height = WindowManager.LayoutParams.WRAP_CONTENT
-        gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
         type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
         flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
         title = WINDOW_TITLE
@@ -78,6 +78,14 @@
         setTrustedOverlay()
     }
 
+    /**
+     * The window layout parameters we'll use when attaching the view to a window.
+     *
+     * Subclasses must override this to provide their specific layout params, and they should use
+     * [commonWindowLayoutParams] as part of their layout params.
+     */
+    internal abstract val windowLayoutParams: WindowManager.LayoutParams
+
     /** The chip view currently being displayed. Null if the chip is not being displayed. */
     private var chipView: ViewGroup? = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index a5d763c..99a5b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -16,17 +16,23 @@
 
 package com.android.systemui.media.taptotransfer.receiver
 
+import android.annotation.SuppressLint
 import android.app.StatusBarManager
 import android.content.Context
+import android.graphics.PointF
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.media.MediaRoute2Info
 import android.os.Handler
 import android.os.PowerManager
 import android.util.Log
+import android.view.Gravity
+import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
+import androidx.core.graphics.ColorUtils
+import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
@@ -36,6 +42,7 @@
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.util.animation.AnimationUtil.Companion.frames
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.view.ViewUtil
 import javax.inject.Inject
@@ -69,6 +76,17 @@
     powerManager,
     R.layout.media_ttt_chip_receiver
 ) {
+    @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+    override val windowLayoutParams = commonWindowLayoutParams.apply {
+        gravity = Gravity.BOTTOM.or(Gravity.CENTER_HORIZONTAL)
+        // Params below are needed for the ripple to work correctly
+        width = WindowManager.LayoutParams.MATCH_PARENT
+        height = WindowManager.LayoutParams.MATCH_PARENT
+        layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+        fitInsetsTypes = 0 // Ignore insets from all system bars
+        flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+    }
+
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
         override fun updateMediaTapToTransferReceiverDisplay(
             @StatusBarManager.MediaTransferReceiverState displayState: Int,
@@ -131,6 +149,19 @@
         )
     }
 
+    override fun animateChipIn(chipView: ViewGroup) {
+        val appIconView = chipView.requireViewById<View>(R.id.app_icon)
+        appIconView.animate()
+                .translationYBy(-1 * getTranslationAmount().toFloat())
+                .setDuration(30.frames)
+                .start()
+        appIconView.animate()
+                .alpha(1f)
+                .setDuration(5.frames)
+                .start()
+        startRipple(chipView.requireViewById(R.id.ripple))
+    }
+
     override fun getIconSize(isAppIcon: Boolean): Int? =
         context.resources.getDimensionPixelSize(
             if (isAppIcon) {
@@ -139,6 +170,45 @@
                 R.dimen.media_ttt_generic_icon_size_receiver
             }
         )
+
+    /** Returns the amount that the chip will be translated by in its intro animation. */
+    private fun getTranslationAmount(): Int {
+        return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
+    }
+
+    private fun startRipple(rippleView: ReceiverChipRippleView) {
+        if (rippleView.rippleInProgress) {
+            // Skip if ripple is still playing
+            return
+        }
+        rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+            override fun onViewDetachedFromWindow(view: View?) {}
+
+            override fun onViewAttachedToWindow(view: View?) {
+                if (view == null) {
+                    return
+                }
+                val attachedRippleView = view as ReceiverChipRippleView
+                layoutRipple(attachedRippleView)
+                attachedRippleView.startRipple()
+                attachedRippleView.removeOnAttachStateChangeListener(this)
+            }
+        })
+    }
+
+    private fun layoutRipple(rippleView: ReceiverChipRippleView) {
+        val windowBounds = windowManager.currentWindowMetrics.bounds
+        val height = windowBounds.height()
+        val width = windowBounds.width()
+
+        rippleView.radius = height / 5f
+        // Center the ripple on the bottom of the screen in the middle.
+        rippleView.origin = PointF(/* x= */ width / 2f, /* y= */ height.toFloat())
+
+        val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+        val colorWithAlpha = ColorUtils.setAlphaComponent(color, 70)
+        rippleView.setColor(colorWithAlpha)
+    }
 }
 
 data class ChipReceiverInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
new file mode 100644
index 0000000..fed546bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.receiver
+
+import android.content.Context
+import android.util.AttributeSet
+import com.android.systemui.ripple.RippleView
+
+/**
+ * An expanding ripple effect for the media tap-to-transfer receiver chip.
+ */
+class ReceiverChipRippleView(
+        context: Context?, attrs: AttributeSet?
+) : RippleView(context, attrs) {
+    init {
+        setRippleFill(true)
+        duration = 3000L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 943604c..797a770 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -21,6 +21,7 @@
 import android.media.MediaRoute2Info
 import android.os.PowerManager
 import android.util.Log
+import android.view.Gravity
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
@@ -69,6 +70,10 @@
     powerManager,
     R.layout.media_ttt_chip
 ) {
+    override val windowLayoutParams = commonWindowLayoutParams.apply {
+        gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
+    }
+
     private var currentlyDisplayedChipState: ChipStateSender? = null
 
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 0288c9f..8f6c373 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -40,11 +40,13 @@
 import android.widget.ImageView
 import android.widget.TextView
 import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.Dumpable
 import com.android.systemui.R
@@ -87,6 +89,7 @@
         private val LOG_TAG = FgsManagerController::class.java.simpleName
         private const val DEFAULT_TASK_MANAGER_ENABLED = true
         private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
+        private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
     }
 
     var changesSinceDialog = false
@@ -96,6 +99,8 @@
         private set
     var showFooterDot = false
         private set
+    var showStopBtnForUserAllowlistedApps = false
+        private set
 
     private val lock = Any()
 
@@ -163,6 +168,9 @@
                 isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable)
                 showFooterDot =
                     it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, showFooterDot)
+                showStopBtnForUserAllowlistedApps = it.getBoolean(
+                    TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+                    showStopBtnForUserAllowlistedApps)
             }
 
             isAvailable = deviceConfigProxy.getBoolean(
@@ -173,6 +181,10 @@
                 NAMESPACE_SYSTEMUI,
                 TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT
             )
+            showStopBtnForUserAllowlistedApps = deviceConfigProxy.getBoolean(
+                NAMESPACE_SYSTEMUI,
+                TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+                DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS)
 
             dumpManager.registerDumpable(this)
 
@@ -275,6 +287,20 @@
         }
     }
 
+    @VisibleForTesting
+    @JvmName("getNumVisibleButtons")
+    internal fun getNumVisibleButtons(): Int {
+        synchronized(lock) {
+            return getNumVisibleButtonsLocked()
+        }
+    }
+
+    private fun getNumVisibleButtonsLocked(): Int {
+        return runningServiceTokens.keys.count {
+            it.uiControl != UIControl.HIDE_BUTTON && currentProfileIds.contains(it.userId)
+        }
+    }
+
     fun shouldUpdateFooterVisibility() = dialog == null
 
     fun showDialog(viewLaunchedFrom: View?) {
@@ -505,6 +531,13 @@
                 PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI,
                 PowerExemptionManager.REASON_ROLE_DIALER,
                 PowerExemptionManager.REASON_SYSTEM_MODULE -> UIControl.HIDE_BUTTON
+
+                PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE ->
+                    if (showStopBtnForUserAllowlistedApps) {
+                        UIControl.NORMAL
+                    } else {
+                        UIControl.HIDE_BUTTON
+                    }
                 else -> UIControl.NORMAL
             }
             uiControlInitialized = true
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 75bf189..260a371 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -95,7 +95,9 @@
                         getButton(BUTTON_NEUTRAL)
                     )
 
-                    dismiss()
+                    if (controller == null) {
+                        dismiss()
+                    }
 
                     activityStarter.postStartActivityDismissingKeyguard(
                         USER_SETTINGS_INTENT, 0, controller
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
rename to packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index bdad36c..93a2efc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.statusbar.charging
+package com.android.systemui.ripple
 
 import android.graphics.PointF
 import android.graphics.RuntimeShader
@@ -148,7 +148,7 @@
             val fadeOutNoise = subProgress(0.4f, 1f, value)
             var fadeOutRipple = 0f
             var fadeCircle = 0f
-            if (shouldFadeOutRipple) {
+            if (!rippleFill) {
                 fadeCircle = subProgress(0f, 0.2f, value)
                 fadeOutRipple = subProgress(0.3f, 1f, value)
             }
@@ -202,5 +202,9 @@
             setFloatUniform("in_pixelDensity", value)
         }
 
-    var shouldFadeOutRipple: Boolean = true
+    /**
+     * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
+     * False for a ring effect.
+     */
+    var rippleFill: Boolean = false
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
rename to packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index 10e90fe..fc52464 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.charging
+package com.android.systemui.ripple
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
@@ -30,9 +30,10 @@
 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
 
 /**
- * Expanding ripple effect that shows when charging begins.
+ * A generic expanding ripple effect. To trigger the ripple expansion, set [radius] and [origin],
+ * then call [startRipple].
  */
-class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
     private val rippleShader = RippleShader()
     private val defaultColor: Int = 0xffffffff.toInt()
     private val ripplePaint = Paint()
@@ -74,9 +75,9 @@
         }
         val animator = ValueAnimator.ofFloat(0f, 1f)
         animator.duration = duration
-        animator.addUpdateListener { animator ->
-            val now = animator.currentPlayTime
-            val progress = animator.animatedValue as Float
+        animator.addUpdateListener { updateListener ->
+            val now = updateListener.currentPlayTime
+            val progress = updateListener.animatedValue as Float
             rippleShader.progress = progress
             rippleShader.distortionStrength = 1 - progress
             rippleShader.time = now.toFloat()
@@ -92,10 +93,20 @@
         rippleInProgress = true
     }
 
+    /** Set the color to be used for the ripple. */
     fun setColor(color: Int) {
         rippleShader.color = color
     }
 
+    /**
+     * Set whether the ripple should remain filled as the ripple expands.
+     *
+     * See [RippleShader.rippleFill].
+     */
+    fun setRippleFill(rippleFill: Boolean) {
+        rippleShader.rippleFill = rippleFill
+    }
+
     override fun onDraw(canvas: Canvas?) {
         if (canvas == null || !canvas.isHardwareAccelerated) {
             // Drawing with the ripple shader requires hardware acceleration, so skip
@@ -107,6 +118,6 @@
         // animation implementation in the ripple shader.
         val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
                 (1 - rippleShader.progress)) * radius * 2
-        canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
+        canvas.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 1ce05ec..c900c5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -36,7 +36,6 @@
 import android.media.session.PlaybackState;
 import android.os.AsyncTask;
 import android.os.Trace;
-import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
@@ -44,7 +43,6 @@
 import android.view.View;
 import android.widget.ImageView;
 
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.Interpolators;
@@ -56,9 +54,6 @@
 import com.android.systemui.media.SmartspaceMediaData;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -112,11 +107,9 @@
     }
 
     private final NotificationVisibilityProvider mVisibilityProvider;
-    private final NotificationEntryManager mEntryManager;
     private final MediaDataManager mMediaDataManager;
     private final NotifPipeline mNotifPipeline;
     private final NotifCollection mNotifCollection;
-    private final boolean mUsingNotifPipeline;
 
     @Nullable
     private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
@@ -180,12 +173,10 @@
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             NotificationVisibilityProvider visibilityProvider,
-            NotificationEntryManager notificationEntryManager,
             MediaArtworkProcessor mediaArtworkProcessor,
             KeyguardBypassController keyguardBypassController,
             NotifPipeline notifPipeline,
             NotifCollection notifCollection,
-            NotifPipelineFlags notifPipelineFlags,
             @Main DelayableExecutor mainExecutor,
             MediaDataManager mediaDataManager,
             DumpManager dumpManager) {
@@ -197,19 +188,12 @@
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mVisibilityProvider = visibilityProvider;
-        mEntryManager = notificationEntryManager;
         mMainExecutor = mainExecutor;
         mMediaDataManager = mediaDataManager;
         mNotifPipeline = notifPipeline;
         mNotifCollection = notifCollection;
 
-        if (!notifPipelineFlags.isNewPipelineEnabled()) {
-            setupNEM();
-            mUsingNotifPipeline = false;
-        } else {
-            setupNotifPipeline();
-            mUsingNotifPipeline = true;
-        }
+        setupNotifPipeline();
 
         dumpManager.registerDumpable(this);
     }
@@ -273,79 +257,6 @@
         });
     }
 
-    private void setupNEM() {
-        mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
-
-            @Override
-            public void onPendingEntryAdded(NotificationEntry entry) {
-                mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
-            }
-
-            @Override
-            public void onPreEntryUpdated(NotificationEntry entry) {
-                mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
-            }
-
-            @Override
-            public void onEntryInflated(NotificationEntry entry) {
-                findAndUpdateMediaNotifications();
-            }
-
-            @Override
-            public void onEntryReinflated(NotificationEntry entry) {
-                findAndUpdateMediaNotifications();
-            }
-
-            @Override
-            public void onEntryRemoved(
-                    @NonNull NotificationEntry entry,
-                    @Nullable NotificationVisibility visibility,
-                    boolean removedByUser,
-                    int reason) {
-                removeEntry(entry);
-            }
-        });
-
-        // Pending entries are never inflated, and will never generate a call to onEntryRemoved().
-        // This can happen when notifications are added and canceled before inflation. Add this
-        // separate listener for cleanup, since media inflation occurs onPendingEntryAdded().
-        mEntryManager.addCollectionListener(new NotifCollectionListener() {
-            @Override
-            public void onEntryCleanUp(@NonNull NotificationEntry entry) {
-                removeEntry(entry);
-            }
-        });
-
-        mMediaDataManager.addListener(new MediaDataManager.Listener() {
-            @Override
-            public void onMediaDataLoaded(@NonNull String key,
-                    @Nullable String oldKey, @NonNull MediaData data, boolean immediately,
-                    int receivedSmartspaceCardLatency, boolean isSsReactivated) {
-            }
-
-            @Override
-            public void onSmartspaceMediaDataLoaded(@NonNull String key,
-                    @NonNull SmartspaceMediaData data, boolean shouldPrioritize) {
-
-            }
-
-            @Override
-            public void onMediaDataRemoved(@NonNull String key) {
-                NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key);
-                if (entry != null) {
-                    // TODO(b/160713608): "removing" this notification won't happen and
-                    //  won't send the 'deleteIntent' if the notification is ongoing.
-                    mEntryManager.performRemoveNotification(entry.getSbn(),
-                            getDismissedByUserStats(entry),
-                            NotificationListenerService.REASON_CANCEL);
-                }
-            }
-
-            @Override
-            public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {}
-        });
-    }
-
     private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
         return new DismissedByUserStats(
                 NotificationStats.DISMISSAL_SHADE, // Add DISMISSAL_MEDIA?
@@ -401,22 +312,10 @@
         if (mMediaNotificationKey == null) {
             return null;
         }
-        if (mUsingNotifPipeline) {
-            return Optional.ofNullable(mNotifPipeline.getEntry(mMediaNotificationKey))
-                .map(entry -> entry.getIcons().getShelfIcon())
-                .map(StatusBarIconView::getSourceIcon)
-                .orElse(null);
-        } else {
-            synchronized (mEntryManager) {
-                NotificationEntry entry = mEntryManager
-                    .getActiveNotificationUnfiltered(mMediaNotificationKey);
-                if (entry == null || entry.getIcons().getShelfIcon() == null) {
-                    return null;
-                }
-
-                return entry.getIcons().getShelfIcon().getSourceIcon();
-            }
-        }
+        return Optional.ofNullable(mNotifPipeline.getEntry(mMediaNotificationKey))
+            .map(entry -> entry.getIcons().getShelfIcon())
+            .map(StatusBarIconView::getSourceIcon)
+            .orElse(null);
     }
 
     public void addCallback(MediaListener callback) {
@@ -431,21 +330,9 @@
 
     public void findAndUpdateMediaNotifications() {
         boolean metaDataChanged;
-        if (mUsingNotifPipeline) {
-            // TODO(b/169655907): get the semi-filtered notifications for current user
-            Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
-            metaDataChanged = findPlayingMediaNotification(allNotifications);
-        } else {
-            synchronized (mEntryManager) {
-                Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs();
-                metaDataChanged = findPlayingMediaNotification(allNotifications);
-            }
-
-            if (metaDataChanged) {
-                mEntryManager.updateNotifications("NotificationMediaManager - metaDataChanged");
-            }
-
-        }
+        // TODO(b/169655907): get the semi-filtered notifications for current user
+        Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
+        metaDataChanged = findPlayingMediaNotification(allNotifications);
         dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 5cdd01f..78b3b0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -286,10 +286,6 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mRebuilder = rebuilder;
-        if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
-            mRemoteInputListener = createLegacyRemoteInputLifetimeExtender(mainHandler,
-                    notificationEntryManager, smartReplyController);
-        }
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
         mStatusBarStateController = statusBarStateController;
         mRemoteInputUriController = remoteInputUriController;
@@ -313,34 +309,19 @@
                     int reason) {
                 // We're removing the notification, the smart controller can forget about it.
                 mSmartReplyController.stopSending(entry);
-
-                if (removedByUser && entry != null) {
-                    onPerformRemoveNotification(entry, entry.getKey());
-                }
             }
         });
     }
 
     /** Add a listener for various remote input events.  Works with NEW pipeline only. */
     public void setRemoteInputListener(@NonNull RemoteInputListener remoteInputListener) {
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            if (mRemoteInputListener != null) {
-                throw new IllegalStateException("mRemoteInputListener is already set");
-            }
-            mRemoteInputListener = remoteInputListener;
-            if (mRemoteInputController != null) {
-                mRemoteInputListener.setRemoteInputController(mRemoteInputController);
-            }
+        if (mRemoteInputListener != null) {
+            throw new IllegalStateException("mRemoteInputListener is already set");
         }
-    }
-
-    @NonNull
-    @VisibleForTesting
-    protected LegacyRemoteInputLifetimeExtender createLegacyRemoteInputLifetimeExtender(
-            Handler mainHandler,
-            NotificationEntryManager notificationEntryManager,
-            SmartReplyController smartReplyController) {
-        return new LegacyRemoteInputLifetimeExtender();
+        mRemoteInputListener = remoteInputListener;
+        if (mRemoteInputController != null) {
+            mRemoteInputListener.setRemoteInputController(mRemoteInputController);
+        }
     }
 
     /** Initializes this component with the provided dependencies. */
@@ -381,12 +362,6 @@
                 }
             }
         });
-        if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
-            mSmartReplyController.setCallback((entry, reply) -> {
-                StatusBarNotification newSbn = mRebuilder.rebuildForSendingSmartReply(entry, reply);
-                mEntryManager.updateNotification(newSbn, null /* ranking */);
-            });
-        }
     }
 
     public void addControllerCallback(RemoteInputController.Callback callback) {
@@ -588,19 +563,6 @@
         return v.findViewWithTag(RemoteInputView.VIEW_TAG);
     }
 
-    public ArrayList<NotificationLifetimeExtender> getLifetimeExtenders() {
-        // OLD pipeline code ONLY; can assume implementation
-        return ((LegacyRemoteInputLifetimeExtender) mRemoteInputListener).mLifetimeExtenders;
-    }
-
-    @VisibleForTesting
-    void onPerformRemoveNotification(NotificationEntry entry, final String key) {
-        // OLD pipeline code ONLY; can assume implementation
-        ((LegacyRemoteInputLifetimeExtender) mRemoteInputListener)
-                .mKeysKeptForRemoteInputHistory.remove(key);
-        cleanUpRemoteInputForUserRemoval(entry);
-    }
-
     /**
      * Disable remote input on the entry and remove the remote input view.
      * This should be called when a user dismisses a notification that won't be lifetime extended.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 03d66ee..587c23b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -139,12 +139,10 @@
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             NotificationVisibilityProvider visibilityProvider,
-            NotificationEntryManager notificationEntryManager,
             MediaArtworkProcessor mediaArtworkProcessor,
             KeyguardBypassController keyguardBypassController,
             NotifPipeline notifPipeline,
             NotifCollection notifCollection,
-            NotifPipelineFlags notifPipelineFlags,
             @Main DelayableExecutor mainExecutor,
             MediaDataManager mediaDataManager,
             DumpManager dumpManager) {
@@ -153,12 +151,10 @@
                 centralSurfacesOptionalLazy,
                 notificationShadeWindowController,
                 visibilityProvider,
-                notificationEntryManager,
                 mediaArtworkProcessor,
                 keyguardBypassController,
                 notifPipeline,
                 notifCollection,
-                notifPipelineFlags,
                 mainExecutor,
                 mediaDataManager,
                 dumpManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 6a01df6..1fdf18f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -310,7 +310,7 @@
         }
 
         locallyDismissNotifications(entriesToLocallyDismiss);
-        dispatchEventsAndRebuildList();
+        dispatchEventsAndRebuildList("dismissNotifications");
     }
 
     /**
@@ -354,7 +354,7 @@
         }
 
         locallyDismissNotifications(entries);
-        dispatchEventsAndRebuildList();
+        dispatchEventsAndRebuildList("dismissAllNotifications");
     }
 
     /**
@@ -401,7 +401,7 @@
 
         postNotification(sbn, requireRanking(rankingMap, sbn.getKey()));
         applyRanking(rankingMap);
-        dispatchEventsAndRebuildList();
+        dispatchEventsAndRebuildList("onNotificationPosted");
     }
 
     private void onNotificationGroupPosted(List<CoalescedEvent> batch) {
@@ -412,7 +412,7 @@
         for (CoalescedEvent event : batch) {
             postNotification(event.getSbn(), event.getRanking());
         }
-        dispatchEventsAndRebuildList();
+        dispatchEventsAndRebuildList("onNotificationGroupPosted");
     }
 
     private void onNotificationRemoved(
@@ -433,14 +433,14 @@
         entry.mCancellationReason = reason;
         tryRemoveNotification(entry);
         applyRanking(rankingMap);
-        dispatchEventsAndRebuildList();
+        dispatchEventsAndRebuildList("onNotificationRemoved");
     }
 
     private void onNotificationRankingUpdate(RankingMap rankingMap) {
         Assert.isMainThread();
         mEventQueue.add(new RankingUpdatedEvent(rankingMap));
         applyRanking(rankingMap);
-        dispatchEventsAndRebuildList();
+        dispatchEventsAndRebuildList("onNotificationRankingUpdate");
     }
 
     private void onNotificationChannelModified(
@@ -450,7 +450,7 @@
             int modificationType) {
         Assert.isMainThread();
         mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType));
-        dispatchEventsAndRebuildList();
+        dispatchEventsAndRebuildList("onNotificationChannelModified");
     }
 
     private void onNotificationsInitialized() {
@@ -578,12 +578,10 @@
                     // TODO: (b/145659174) update the sbn's overrideGroupKey in
                     //  NotificationEntry.setRanking instead of here once we fully migrate to the
                     //  NewNotifPipeline
-                    if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-                        final String newOverrideGroupKey = ranking.getOverrideGroupKey();
-                        if (!Objects.equals(entry.getSbn().getOverrideGroupKey(),
-                                newOverrideGroupKey)) {
-                            entry.getSbn().setOverrideGroupKey(newOverrideGroupKey);
-                        }
+                    final String newOverrideGroupKey = ranking.getOverrideGroupKey();
+                    if (!Objects.equals(entry.getSbn().getOverrideGroupKey(),
+                            newOverrideGroupKey)) {
+                        entry.getSbn().setOverrideGroupKey(newOverrideGroupKey);
                     }
                 } else {
                     if (currentEntriesWithoutRankings == null) {
@@ -610,7 +608,7 @@
         mEventQueue.add(new RankingAppliedEvent());
     }
 
-    private void dispatchEventsAndRebuildList() {
+    private void dispatchEventsAndRebuildList(String reason) {
         Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList");
         mAmDispatchingToOtherCode = true;
         while (!mEventQueue.isEmpty()) {
@@ -619,7 +617,7 @@
         mAmDispatchingToOtherCode = false;
 
         if (mBuildListener != null) {
-            mBuildListener.onBuildList(mReadOnlyNotificationSet);
+            mBuildListener.onBuildList(mReadOnlyNotificationSet, reason);
         }
         Trace.endSection();
     }
@@ -654,7 +652,7 @@
 
         if (!isLifetimeExtended(entry)) {
             if (tryRemoveNotification(entry)) {
-                dispatchEventsAndRebuildList();
+                dispatchEventsAndRebuildList("onEndLifetimeExtension");
             }
         }
     }
@@ -963,7 +961,7 @@
         mEventQueue.add(new EntryUpdatedEvent(entry, false /* fromSystem */));
 
         // Skip the applyRanking step and go straight to dispatching the events
-        dispatchEventsAndRebuildList();
+        dispatchEventsAndRebuildList("updateNotificationInternally");
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
index e3ee813..a57440c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
@@ -76,7 +76,6 @@
  */
 @SysUISingleton
 class NotifPipeline @Inject constructor(
-    notifPipelineFlags: NotifPipelineFlags,
     private val mNotifCollection: NotifCollection,
     private val mShadeListBuilder: ShadeListBuilder,
     private val mRenderStageManager: RenderStageManager
@@ -107,8 +106,6 @@
         return mNotifCollection.getEntry(key)
     }
 
-    val isNewPipelineEnabled: Boolean = notifPipelineFlags.isNewPipelineEnabled()
-
     /**
      * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
      * dismissed or retracted by system server to be temporarily retained in the collection.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 26d2ee3..8a18d31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -304,11 +304,11 @@
     private final CollectionReadyForBuildListener mReadyForBuildListener =
             new CollectionReadyForBuildListener() {
                 @Override
-                public void onBuildList(Collection<NotificationEntry> entries) {
+                public void onBuildList(Collection<NotificationEntry> entries, String reason) {
                     Assert.isMainThread();
                     mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
 
-                    mLogger.logOnBuildList();
+                    mLogger.logOnBuildList(reason);
                     mAllEntries = entries;
                     mChoreographer.schedule();
                 }
@@ -456,7 +456,8 @@
         mLogger.logEndBuildList(
                 mIterationCount,
                 mReadOnlyNotifList.size(),
-                countChildren(mReadOnlyNotifList));
+                countChildren(mReadOnlyNotifList),
+                /* enforcedVisualStability */ !mNotifStabilityManager.isEveryChangeAllowed());
         if (mAlwaysLogList || mIterationCount % 10 == 0) {
             Trace.beginSection("ShadeListBuilder.logFinalList");
             mLogger.logFinalList(mNotifList);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index b923fdf..7293953 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -127,14 +127,6 @@
                 DismissedByUserStats dismissedByUserStats,
                 int reason
         ) {
-            if (!mNotifPipeline.isNewPipelineEnabled()) {
-                // The `entry` will be from whichever pipeline is active, so if the old pipeline is
-                // running, make sure that we use the new pipeline's entry (if it still exists).
-                NotificationEntry newPipelineEntry = mNotifPipeline.getEntry(entry.getKey());
-                if (newPipelineEntry != null) {
-                    entry = newPipelineEntry;
-                }
-            }
             if (isInterceptingDismissal(entry)) {
                 mInterceptedDismissalEntries.remove(entry.getKey());
                 mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index f9b8644..1399385 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -68,9 +68,7 @@
         //  pipeline, such as this DataStoreCoordinator which cannot be removed, as it's a critical
         //  glue between the pipeline and parts of SystemUI which depend on pipeline output via the
         //  NotifLiveDataStore.
-        if (notifPipelineFlags.isNewPipelineEnabled()) {
-            mCoordinators.add(dataStoreCoordinator)
-        }
+        mCoordinators.add(dataStoreCoordinator)
 
         // Attach normal coordinators.
         mCoordinators.add(hideLocallyDismissedNotifsCoordinator)
@@ -93,18 +91,14 @@
         if (notifPipelineFlags.isSmartspaceDedupingEnabled()) {
             mCoordinators.add(smartspaceDedupingCoordinator)
         }
-        if (notifPipelineFlags.isNewPipelineEnabled()) {
-            mCoordinators.add(headsUpCoordinator)
-            mCoordinators.add(gutsCoordinator)
-            mCoordinators.add(preparationCoordinator)
-            mCoordinators.add(remoteInputCoordinator)
-        }
+        mCoordinators.add(headsUpCoordinator)
+        mCoordinators.add(gutsCoordinator)
+        mCoordinators.add(preparationCoordinator)
+        mCoordinators.add(remoteInputCoordinator)
 
         // Manually add Ordered Sections
         // HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default
-        if (notifPipelineFlags.isNewPipelineEnabled()) {
-            mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
-        }
+        mOrderedSections.add(headsUpCoordinator.sectioner)
         mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
         mOrderedSections.add(conversationCoordinator.sectioner) // People
         mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
index 48f00ac..ce361ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
@@ -65,13 +65,6 @@
         statusBarStateController.addCallback(statusBarStateListener)
         smartspaceController.addListener(this::onNewSmartspaceTargets)
 
-        if (!pipeline.isNewPipelineEnabled) {
-            // TODO (b/173126564): Remove this once the old pipeline is no longer necessary
-            notificationLockscreenUserManager.addKeyguardNotificationSuppressor { entry ->
-                isDupedWithSmartspaceContent(entry)
-            }
-        }
-
         recordStatusBarState(statusBarStateController.state)
     }
 
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
index bae16fe..5a281b1 100644
--- 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
@@ -70,11 +70,9 @@
 
     override fun attach(pipeline: NotifPipeline) {
         mPipeline = pipeline
-        if (pipeline.isNewPipelineEnabled) {
-            mLockscreenUserManager.addUserChangedListener(mUserChangedListener)
-            mConfigurationController.addCallback(this)
-            mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback)
-        }
+        mLockscreenUserManager.addUserChangedListener(mUserChangedListener)
+        mConfigurationController.addCallback(this)
+        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback)
     }
 
     override fun onDensityOrFontScaleChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index d1ab62c..a34d033 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -24,7 +24,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -58,7 +57,6 @@
     private final NotifInflaterImpl mNotifInflater;
     private final DumpManager mDumpManager;
     private final ShadeViewManagerFactory mShadeViewManagerFactory;
-    private final NotifPipelineFlags mNotifPipelineFlags;
 
     /* These are saved just for dumping. */
     private ShadeViewManager mShadeViewManager;
@@ -74,8 +72,7 @@
             NotifCoordinators notifCoordinators,
             NotifInflaterImpl notifInflater,
             DumpManager dumpManager,
-            ShadeViewManagerFactory shadeViewManagerFactory,
-            NotifPipelineFlags notifPipelineFlags
+            ShadeViewManagerFactory shadeViewManagerFactory
     ) {
         mPipelineWrapper = pipelineWrapper;
         mGroupCoalescer = groupCoalescer;
@@ -86,7 +83,6 @@
         mDumpManager = dumpManager;
         mNotifInflater = notifInflater;
         mShadeViewManagerFactory = shadeViewManagerFactory;
-        mNotifPipelineFlags = notifPipelineFlags;
     }
 
     /** Hooks the new pipeline up to NotificationManager */
@@ -100,25 +96,21 @@
         mNotificationService = notificationService;
 
         // Setup inflation
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            mNotifInflater.setRowBinder(rowBinder);
-        }
+        mNotifInflater.setRowBinder(rowBinder);
 
         // Wire up coordinators
         mNotifPluggableCoordinators.attach(mPipelineWrapper);
 
         // Wire up pipeline
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            mShadeViewManager = mShadeViewManagerFactory.create(listContainer, stackController);
-            mShadeViewManager.attach(mRenderStageManager);
-        }
+        mShadeViewManager = mShadeViewManagerFactory.create(listContainer, stackController);
+        mShadeViewManager.attach(mRenderStageManager);
         mRenderStageManager.attach(mListBuilder);
         mListBuilder.attach(mNotifCollection);
         mNotifCollection.attach(mGroupCoalescer);
         mGroupCoalescer.attach(mNotificationService);
 
         Log.d(TAG, "Notif pipeline initialized."
-                + " rendering=" + mNotifPipelineFlags.isNewPipelineEnabled());
+                + " rendering=" + true);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 8d1759b..10a627d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -21,31 +21,42 @@
 import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
 import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.util.Compile
 import javax.inject.Inject
 
 class ShadeListBuilderLogger @Inject constructor(
+    notifPipelineFlags: NotifPipelineFlags,
     @NotificationLog private val buffer: LogBuffer
 ) {
-    fun logOnBuildList() {
+    fun logOnBuildList(reason: String?) {
         buffer.log(TAG, INFO, {
+            str1 = reason
         }, {
-            "Request received from NotifCollection"
+            "Request received from NotifCollection for $str1"
         })
     }
 
-    fun logEndBuildList(buildId: Int, topLevelEntries: Int, numChildren: Int) {
+    fun logEndBuildList(
+        buildId: Int,
+        topLevelEntries: Int,
+        numChildren: Int,
+        enforcedVisualStability: Boolean
+    ) {
         buffer.log(TAG, INFO, {
             long1 = buildId.toLong()
             int1 = topLevelEntries
             int2 = numChildren
+            bool1 = enforcedVisualStability
         }, {
-            "(Build $long1) Build complete ($int1 top-level entries, $int2 children)"
+            "(Build $long1) Build complete ($int1 top-level entries, $int2 children)" +
+                    " enforcedVisualStability=$bool1"
         })
     }
 
@@ -280,6 +291,8 @@
         })
     }
 
+    val logRankInFinalList = Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()
+
     fun logFinalList(entries: List<ListEntry>) {
         if (entries.isEmpty()) {
             buffer.log(TAG, DEBUG, {}, { "(empty list)" })
@@ -289,16 +302,20 @@
             buffer.log(TAG, DEBUG, {
                 int1 = i
                 str1 = entry.logKey
+                bool1 = logRankInFinalList
+                int2 = entry.representativeEntry!!.ranking.rank
             }, {
-                "[$int1] $str1"
+                "[$int1] $str1".let { if (bool1) "$it rank=$int2" else it }
             })
 
             if (entry is GroupEntry) {
                 entry.summary?.let {
                     buffer.log(TAG, DEBUG, {
                         str1 = it.logKey
+                        bool1 = logRankInFinalList
+                        int2 = it.ranking.rank
                     }, {
-                        "  [*] $str1 (summary)"
+                        "  [*] $str1 (summary)".let { if (bool1) "$it rank=$int2" else it }
                     })
                 }
                 for (j in entry.children.indices) {
@@ -306,8 +323,10 @@
                     buffer.log(TAG, DEBUG, {
                         int1 = j
                         str1 = child.logKey
+                        bool1 = logRankInFinalList
+                        int2 = child.ranking.rank
                     }, {
-                        "  [$int1] $str1"
+                        "  [$int1] $str1".let { if (bool1) "$it rank=$int2" else it }
                     })
                 }
             }
@@ -318,4 +337,4 @@
         buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
 }
 
-private const val TAG = "ShadeListBuilder"
\ No newline at end of file
+private const val TAG = "ShadeListBuilder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
index 4023474..941b2ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
@@ -29,5 +29,5 @@
      * Called by the NotifCollection to indicate that something in the collection has changed and
      * that the list builder should regenerate the list.
      */
-    void onBuildList(Collection<NotificationEntry> entries);
+    void onBuildList(Collection<NotificationEntry> entries, String reason);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 8b1bed7..385fbb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -24,32 +24,23 @@
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.AnimatedImageNotificationManager
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.NotificationClicker
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.NotificationListController
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
 import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
-import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
-import com.android.systemui.statusbar.notification.interruption.HeadsUpController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.RemoteInputUriController
 import com.android.wm.shell.bubbles.Bubbles
 import dagger.Lazy
 import java.io.PrintWriter
@@ -65,32 +56,23 @@
  */
 @SysUISingleton
 class NotificationsControllerImpl @Inject constructor(
-    private val notifPipelineFlags: NotifPipelineFlags,
     private val notificationListener: NotificationListener,
     private val entryManager: NotificationEntryManager,
-    private val debugModeFilterProvider: DebugModeFilterProvider,
-    private val legacyRanker: NotificationRankingManager,
     private val commonNotifCollection: Lazy<CommonNotifCollection>,
     private val notifPipeline: Lazy<NotifPipeline>,
     private val notifLiveDataStore: NotifLiveDataStore,
     private val targetSdkResolver: TargetSdkResolver,
-    private val newNotifPipelineInitializer: Lazy<NotifPipelineInitializer>,
+    private val notifPipelineInitializer: Lazy<NotifPipelineInitializer>,
     private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
     private val notificationLogger: NotificationLogger,
     private val deviceProvisionedController: DeviceProvisionedController,
     private val notificationRowBinder: NotificationRowBinderImpl,
-    private val bindEventManagerImpl: BindEventManagerImpl,
-    private val remoteInputUriController: RemoteInputUriController,
-    private val groupManagerLegacy: Lazy<NotificationGroupManagerLegacy>,
-    private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper,
     private val notificationsMediaManager: NotificationMediaManager,
-    private val headsUpManager: HeadsUpManager,
-    private val headsUpController: HeadsUpController,
     private val headsUpViewBinder: HeadsUpViewBinder,
     private val clickerBuilder: NotificationClicker.Builder,
     private val animatedImageNotificationManager: AnimatedImageNotificationManager,
     private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
-    private val bubblesOptional: Optional<Bubbles>
+    private val bubblesOptional: Optional<Bubbles>,
 ) : NotificationsController {
 
     override fun initialize(
@@ -122,7 +104,7 @@
         notifBindPipelineInitializer.initialize()
         animatedImageNotificationManager.bind()
 
-        newNotifPipelineInitializer.get().initialize(
+        notifPipelineInitializer.get().initialize(
                 notificationListener,
                 notificationRowBinder,
                 listContainer,
@@ -170,9 +152,4 @@
 
     override fun getActiveNotificationsCount(): Int =
         notifLiveDataStore.activeNotifCount.value
-
-    companion object {
-        // NOTE: The new pipeline is always active, even if the old pipeline is *rendering*.
-        private const val INITIALIZE_NEW_PIPELINE = true
-    }
 }
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 4013254..6d513d0da 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
@@ -24,8 +24,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.annotation.VisibleForTesting;
-
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.R;
@@ -65,6 +64,9 @@
     private int mPinnedZTranslationExtra;
     private float mNotificationScrimPadding;
     private int mMarginBottom;
+    private float mQuickQsOffsetHeight;
+    private float mSmallCornerRadius;
+    private float mLargeCornerRadius;
 
     public StackScrollAlgorithm(
             Context context,
@@ -74,10 +76,10 @@
     }
 
     public void initView(Context context) {
-        initConstants(context);
+        updateResources(context);
     }
 
-    private void initConstants(Context context) {
+    private void updateResources(Context context) {
         Resources res = context.getResources();
         mPaddingBetweenElements = res.getDimensionPixelSize(
                 R.dimen.notification_divider_height);
@@ -93,6 +95,9 @@
                 R.dimen.notification_section_divider_height_lockscreen);
         mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
         mMarginBottom = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+        mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(context);
+        mSmallCornerRadius = res.getDimension(R.dimen.notification_corner_radius_small);
+        mLargeCornerRadius = res.getDimension(R.dimen.notification_corner_radius);
     }
 
     /**
@@ -441,6 +446,15 @@
         return false;
     }
 
+    @VisibleForTesting
+    void maybeUpdateHeadsUpIsVisible(ExpandableViewState viewState, boolean isShadeExpanded,
+            boolean mustStayOnScreen, boolean topVisible, float viewEnd, float hunMax) {
+
+        if (isShadeExpanded && mustStayOnScreen && topVisible) {
+            viewState.headsUpIsVisible = viewEnd < hunMax;
+        }
+    }
+
     // TODO(b/172289889) polish shade open from HUN
     /**
      * Populates the {@link ExpandableViewState} for a single child.
@@ -474,14 +488,6 @@
                     : ShadeInterpolation.getContentAlpha(expansion);
         }
 
-        if (ambientState.isShadeExpanded() && view.mustStayOnScreen()
-                && viewState.yTranslation >= 0) {
-            // Even if we're not scrolled away we're in view and we're also not in the
-            // shelf. We can relax the constraints and let us scroll off the top!
-            float end = viewState.yTranslation + viewState.height + ambientState.getStackY();
-            viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
-        }
-
         final float expansionFraction = getExpansionFractionWithoutShelf(
                 algorithmState, ambientState);
 
@@ -497,8 +503,15 @@
             algorithmState.mCurrentExpandedYPosition += gap;
         }
 
+        // Must set viewState.yTranslation _before_ use.
+        // Incoming views have yTranslation=0 by default.
         viewState.yTranslation = algorithmState.mCurrentYPosition;
 
+        maybeUpdateHeadsUpIsVisible(viewState, ambientState.isShadeExpanded(),
+                view.mustStayOnScreen(), /* topVisible */ viewState.yTranslation >= 0,
+                /* viewEnd */ viewState.yTranslation + viewState.height + ambientState.getStackY(),
+                /* hunMax */ ambientState.getMaxHeadsUpTranslation()
+        );
         if (view instanceof FooterView) {
             final boolean shadeClosed = !ambientState.isShadeExpanded();
             final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
@@ -682,7 +695,8 @@
                 if (row.mustStayOnScreen() && !childState.headsUpIsVisible
                         && !row.showingPulsing()) {
                     // Ensure that the heads up is always visible even when scrolled off
-                    clampHunToTop(ambientState, row, childState);
+                    clampHunToTop(mQuickQsOffsetHeight, ambientState.getStackTranslation(),
+                            row.getCollapsedHeight(), childState);
                     if (isTopEntry && row.isAboveShelf()) {
                         // the first hun can't get off screen.
                         clampHunToMaxTranslation(ambientState, row, childState);
@@ -719,27 +733,62 @@
         }
     }
 
-    private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
-            ExpandableViewState childState) {
-        float newTranslation = Math.max(ambientState.getTopPadding()
-                + ambientState.getStackTranslation(), childState.yTranslation);
-        childState.height = (int) Math.max(childState.height - (newTranslation
-                - childState.yTranslation), row.getCollapsedHeight());
-        childState.yTranslation = newTranslation;
+    /**
+     * When shade is open and we are scrolled to the bottom of notifications,
+     * clamp incoming HUN in its collapsed form, right below qs offset.
+     * Transition pinned collapsed HUN to full height when scrolling back up.
+     */
+    @VisibleForTesting
+    void clampHunToTop(float quickQsOffsetHeight, float stackTranslation, float collapsedHeight,
+            ExpandableViewState viewState) {
+
+        final float newTranslation = Math.max(quickQsOffsetHeight + stackTranslation,
+                viewState.yTranslation);
+
+        // Transition from collapsed pinned state to fully expanded state
+        // when the pinned HUN approaches its actual location (when scrolling back to top).
+        final float distToRealY = newTranslation - viewState.yTranslation;
+        viewState.height = (int) Math.max(viewState.height - distToRealY, collapsedHeight);
+        viewState.yTranslation = newTranslation;
     }
 
+    // Pin HUN to bottom of expanded QS
+    // while the rest of notifications are scrolled offscreen.
     private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
             ExpandableViewState childState) {
-        float newTranslation;
         float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
-        float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
+        final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
                 + ambientState.getStackTranslation();
         maxHeadsUpTranslation = Math.min(maxHeadsUpTranslation, maxShelfPosition);
-        float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
-        newTranslation = Math.min(childState.yTranslation, bottomPosition);
+
+        final float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
+        final float newTranslation = Math.min(childState.yTranslation, bottomPosition);
         childState.height = (int) Math.min(childState.height, maxHeadsUpTranslation
                 - newTranslation);
         childState.yTranslation = newTranslation;
+
+        // Animate pinned HUN bottom corners to and from original roundness.
+        final float originalCornerRadius =
+                row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
+        final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
+                ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
+        row.setBottomRoundness(roundness, /* animate= */ false);
+    }
+
+    @VisibleForTesting
+    float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
+            float viewMaxHeight, float originalCornerRadius) {
+
+        // Compute y where corner roundness should be in its original unpinned state.
+        // We use view max height because the pinned collapsed HUN expands to max height
+        // when it becomes unpinned.
+        final float originalRoundnessY = hostViewHeight - viewMaxHeight;
+
+        final float distToOriginalRoundness = Math.max(0f, stackY - originalRoundnessY);
+        final float progressToPinnedRoundness = Math.min(1f,
+                distToOriginalRoundness / viewMaxHeight);
+
+        return MathUtils.lerp(originalCornerRadius, 1f, progressToPinnedRoundness);
     }
 
     protected int getMaxAllowedChildHeight(View child) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 39620ac..a0f386f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -448,7 +448,6 @@
         }
         // During wake and unlock, we need to draw black before waking up to avoid abrupt
         // brightness changes due to display state transitions.
-        boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
         Runnable wakeUp = ()-> {
             if (!wasDeviceInteractive) {
                 if (DEBUG_BIO_WAKELOCK) {
@@ -659,7 +658,10 @@
             mLatencyTracker.onActionCancel(action);
         }
 
-        if (biometricSourceType == BiometricSourceType.FINGERPRINT
+        if (!mVibratorHelper.hasVibrator()
+                && (!mUpdateMonitor.isDeviceInteractive() || mUpdateMonitor.isDreaming())) {
+            startWakeAndUnlock(MODE_SHOW_BOUNCER);
+        } else if (biometricSourceType == BiometricSourceType.FINGERPRINT
                 && mUpdateMonitor.isUdfpsSupported()) {
             long currUptimeMillis = SystemClock.uptimeMillis();
             if (currUptimeMillis - mLastFpFailureUptimeMillis < mConsecutiveFpFailureThreshold) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 745b7d9..aea5360 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -138,6 +138,7 @@
 import com.android.systemui.biometrics.AuthRippleController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.camera.CameraIntents;
+import com.android.systemui.charging.WiredChargingRippleController;
 import com.android.systemui.charging.WirelessChargingAnimation;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -196,7 +197,6 @@
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.charging.WiredChargingRippleController;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.core.StatusBarInitializer;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -1742,6 +1742,7 @@
                     // activity is exited.
                     if (mKeyguardStateController.isShowing()
                             && !mKeyguardStateController.isKeyguardGoingAway()) {
+                        Log.d(TAG, "Setting occluded = true in #startActivity.");
                         mKeyguardViewMediator.setOccluded(true /* isOccluded */,
                                 true /* animate */);
                     }
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 1fcaeae..5810d64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -115,6 +115,7 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.camera.CameraGestureHelper;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.controls.dagger.ControlsComponent;
@@ -691,6 +692,8 @@
         }
     };
 
+    private final CameraGestureHelper mCameraGestureHelper;
+
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
             @Main Resources resources,
@@ -762,7 +765,8 @@
             NotificationStackSizeCalculator notificationStackSizeCalculator,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             ShadeTransitionController shadeTransitionController,
-            SystemClock systemClock) {
+            SystemClock systemClock,
+            CameraGestureHelper cameraGestureHelper) {
         super(view,
                 falsingManager,
                 dozeLog,
@@ -947,6 +951,7 @@
                         }
                     }
                 });
+        mCameraGestureHelper = cameraGestureHelper;
     }
 
     @VisibleForTesting
@@ -3630,6 +3635,11 @@
 
     /** Launches the camera. */
     public void launchCamera(int source) {
+        if (!isFullyCollapsed()) {
+            setLaunchingAffordance(true);
+        }
+
+        mCameraGestureHelper.launchCamera(source);
     }
 
     public void onAffordanceLaunchEnded() {
@@ -3656,17 +3666,7 @@
      * Whether the camera application can be launched for the camera launch gesture.
      */
     public boolean canCameraGestureBeLaunched() {
-        return false;
-    }
-
-    /**
-     * Return true if the applications with the package name is running in foreground.
-     *
-     * @param pkgName application package name.
-     */
-    private boolean isForegroundApp(String pkgName) {
-        List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
-        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
+        return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState);
     }
 
     public boolean hideStatusBarIconsWhenExpanded() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
index 911e750..a6160aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import androidx.annotation.FloatRange
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.Compile
 import javax.inject.Inject
 
 /**
@@ -109,8 +110,8 @@
 
         debugLog(
             "panelExpansionChanged:" +
-                    "start state=${oldState.stateToString()} " +
-                    "end state=${state.stateToString()} " +
+                    "start state=${oldState.panelStateToString()} " +
+                    "end state=${state.panelStateToString()} " +
                     "f=$fraction " +
                     "expanded=$expanded " +
                     "tracking=$tracking" +
@@ -126,14 +127,15 @@
 
     /** Updates the panel state if necessary. */
     fun updateState(@PanelState state: Int) {
-        debugLog("update state: ${this.state.stateToString()} -> ${state.stateToString()}")
+        debugLog(
+            "update state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}")
         if (this.state != state) {
             updateStateInternal(state)
         }
     }
 
     private fun updateStateInternal(@PanelState state: Int) {
-        debugLog("go state: ${this.state.stateToString()} -> ${state.stateToString()}")
+        debugLog("go state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}")
         this.state = state
         stateListeners.forEach { it.onPanelStateChanged(state) }
     }
@@ -154,7 +156,7 @@
 const val STATE_OPEN = 2
 
 @PanelState
-private fun Int.stateToString(): String {
+fun Int.panelStateToString(): String {
     return when (this) {
         STATE_CLOSED -> "CLOSED"
         STATE_OPENING -> "OPENING"
@@ -163,5 +165,5 @@
     }
 }
 
-private const val DEBUG = false
 private val TAG = PanelExpansionStateManager::class.simpleName
+private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt
index 16f28e7..1b8afb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt
@@ -11,6 +11,8 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
+import com.android.systemui.statusbar.phone.panelstate.PanelState
+import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.LargeScreenUtils
 import java.io.PrintWriter
@@ -30,8 +32,9 @@
 
     private var inSplitShade = false
     private var splitShadeScrimTransitionDistance = 0
-    private var lastExpansionFraction: Float = 0f
+    private var lastExpansionFraction: Float? = null
     private var lastExpansionEvent: PanelExpansionChangeEvent? = null
+    private var currentPanelState: Int? = null
 
     init {
         updateResources()
@@ -41,8 +44,8 @@
                     updateResources()
                 }
             })
-        dumpManager
-            .registerDumpable(ScrimShadeTransitionController::class.java.simpleName, this::dump)
+        dumpManager.registerDumpable(
+            ScrimShadeTransitionController::class.java.simpleName, this::dump)
     }
 
     private fun updateResources() {
@@ -51,21 +54,38 @@
             resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
     }
 
-    fun onPanelExpansionChanged(panelExpansionChangeEvent: PanelExpansionChangeEvent) {
-        val expansionFraction = calculateScrimExpansionFraction(panelExpansionChangeEvent)
-        scrimController.setRawPanelExpansionFraction(expansionFraction)
-        lastExpansionFraction = expansionFraction
-        lastExpansionEvent = panelExpansionChangeEvent
+    fun onPanelStateChanged(@PanelState state: Int) {
+        currentPanelState = state
+        onStateChanged()
     }
 
-    private fun calculateScrimExpansionFraction(expansionEvent: PanelExpansionChangeEvent): Float {
-        return if (inSplitShade && isScreenUnlocked()) {
+    fun onPanelExpansionChanged(panelExpansionChangeEvent: PanelExpansionChangeEvent) {
+        lastExpansionEvent = panelExpansionChangeEvent
+        onStateChanged()
+    }
+
+    private fun onStateChanged() {
+        val expansionEvent = lastExpansionEvent ?: return
+        val panelState = currentPanelState
+        val expansionFraction = calculateScrimExpansionFraction(expansionEvent, panelState)
+        scrimController.setRawPanelExpansionFraction(expansionFraction)
+        lastExpansionFraction = expansionFraction
+    }
+
+    private fun calculateScrimExpansionFraction(
+        expansionEvent: PanelExpansionChangeEvent,
+        @PanelState panelState: Int?
+    ): Float {
+        return if (canUseCustomFraction(panelState)) {
             constrain(expansionEvent.dragDownPxAmount / splitShadeScrimTransitionDistance, 0f, 1f)
         } else {
             expansionEvent.fraction
         }
     }
 
+    private fun canUseCustomFraction(panelState: Int?) =
+        inSplitShade && isScreenUnlocked() && panelState == STATE_OPENING
+
     private fun isScreenUnlocked() =
         statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
 
@@ -78,9 +98,9 @@
                     isScreenUnlocked: ${isScreenUnlocked()}
                     splitShadeScrimTransitionDistance: $splitShadeScrimTransitionDistance
                   State:
+                    currentPanelState: $currentPanelState
                     lastExpansionFraction: $lastExpansionFraction
                     lastExpansionEvent: $lastExpansionEvent
-            """.trimIndent()
-        )
+            """.trimIndent())
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt
index e967d4a..71c6159 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt
@@ -11,6 +11,7 @@
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
 import com.android.systemui.statusbar.phone.panelstate.PanelState
+import com.android.systemui.statusbar.phone.panelstate.panelStateToString
 import com.android.systemui.statusbar.policy.ConfigurationController
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -34,6 +35,8 @@
     lateinit var qs: QS
 
     private var inSplitShade = false
+    private var currentPanelState: Int? = null
+    private var lastPanelExpansionChangeEvent: PanelExpansionChangeEvent? = null
 
     private val splitShadeOverScroller by lazy {
         splitShadeOverScrollerFactory.create({ qs }, { notificationStackScrollLayoutController })
@@ -66,10 +69,13 @@
     }
 
     private fun onPanelStateChanged(@PanelState state: Int) {
+        currentPanelState = state
         shadeOverScroller.onPanelStateChanged(state)
+        scrimShadeTransitionController.onPanelStateChanged(state)
     }
 
     private fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
+        lastPanelExpansionChangeEvent = event
         shadeOverScroller.onDragDownAmountChanged(event.dragDownPxAmount)
         scrimShadeTransitionController.onPanelExpansionChanged(event)
     }
@@ -84,6 +90,8 @@
             """
             ShadeTransitionController:
                 inSplitShade: $inSplitShade
+                currentPanelState: ${currentPanelState?.panelStateToString()}
+                lastPanelExpansionChangeEvent: $lastPanelExpansionChangeEvent
                 qs.isInitialized: ${this::qs.isInitialized}
                 npvc.isInitialized: ${this::notificationPanelViewController.isInitialized}
                 nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized}
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index cc6bf6a..f017126 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -31,6 +31,7 @@
 
 import com.android.settingslib.users.EditUserInfoController;
 import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
 
 import javax.inject.Inject;
 
@@ -55,15 +56,18 @@
     private final UserCreator mUserCreator;
     private final EditUserInfoController mEditUserInfoController;
     private final IActivityManager mActivityManager;
+    private final ActivityStarter mActivityStarter;
 
     private Dialog mSetupUserDialog;
 
     @Inject
     public CreateUserActivity(UserCreator userCreator,
-            EditUserInfoController editUserInfoController, IActivityManager activityManager) {
+            EditUserInfoController editUserInfoController, IActivityManager activityManager,
+            ActivityStarter activityStarter) {
         mUserCreator = userCreator;
         mEditUserInfoController = editUserInfoController;
         mActivityManager = activityManager;
+        mActivityStarter = activityStarter;
     }
 
     @Override
@@ -104,10 +108,7 @@
 
         return mEditUserInfoController.createDialog(
                 this,
-                (intent, requestCode) -> {
-                    mEditUserInfoController.startingActivityForResult();
-                    startActivityForResult(intent, requestCode);
-                },
+                this::startActivity,
                 null,
                 defaultUserName,
                 getString(com.android.settingslib.R.string.user_add_user),
@@ -160,4 +161,17 @@
             Log.e(TAG, "Couldn't switch user.", e);
         }
     }
+
+    /**
+     * Lambda to start activity from an intent. Ensures that device is unlocked first.
+     * @param intent
+     * @param requestCode
+     */
+    private void startActivity(Intent intent, int requestCode) {
+        mActivityStarter.dismissKeyguardThenExecute(() -> {
+            mEditUserInfoController.startingActivityForResult();
+            startActivityForResult(intent, requestCode);
+            return true;
+        }, /* cancel= */ null, /* afterKeyguardGone= */ true);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index 4e9030f..dac8a0b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -92,7 +92,15 @@
     }
 
     private void updateConditionMetState(Condition condition) {
-        mConditions.get(condition).stream().forEach(token -> mSubscriptions.get(token).update());
+        final ArraySet<Subscription.Token> subscriptions = mConditions.get(condition);
+
+        // It's possible the condition was removed between the time the callback occurred and
+        // update was executed on the main thread.
+        if (subscriptions == null) {
+            return;
+        }
+
+        subscriptions.stream().forEach(token -> mSubscriptions.get(token).update());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index e90775d..bca2a24 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -21,14 +21,10 @@
 import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CLICK;
 import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
 import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
-import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
@@ -55,7 +51,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
@@ -63,9 +58,7 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationChannelHelper;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -114,7 +107,6 @@
     private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final NotificationLockscreenUserManager mNotifUserManager;
     private final NotificationGroupManagerLegacy mNotificationGroupManager;
-    private final NotificationEntryManager mNotificationEntryManager;
     private final CommonNotifCollection mCommonNotifCollection;
     private final NotifPipeline mNotifPipeline;
     private final Executor mSysuiMainExecutor;
@@ -142,11 +134,9 @@
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
             NotificationGroupManagerLegacy groupManager,
-            NotificationEntryManager entryManager,
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
-            NotifPipelineFlags notifPipelineFlags,
             DumpManager dumpManager,
             Executor sysuiMainExecutor) {
         if (bubblesOptional.isPresent()) {
@@ -163,11 +153,9 @@
                     zenModeController,
                     notifUserManager,
                     groupManager,
-                    entryManager,
                     notifCollection,
                     notifPipeline,
                     sysUiState,
-                    notifPipelineFlags,
                     dumpManager,
                     sysuiMainExecutor);
         } else {
@@ -189,11 +177,9 @@
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
             NotificationGroupManagerLegacy groupManager,
-            NotificationEntryManager entryManager,
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
-            NotifPipelineFlags notifPipelineFlags,
             DumpManager dumpManager,
             Executor sysuiMainExecutor) {
         mContext = context;
@@ -205,7 +191,6 @@
         mNotificationInterruptStateProvider = interruptionStateProvider;
         mNotifUserManager = notifUserManager;
         mNotificationGroupManager = groupManager;
-        mNotificationEntryManager = entryManager;
         mCommonNotifCollection = notifCollection;
         mNotifPipeline = notifPipeline;
         mSysuiMainExecutor = sysuiMainExecutor;
@@ -215,11 +200,7 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE))
                 : statusBarService;
 
-        if (notifPipelineFlags.isNewPipelineEnabled()) {
-            setupNotifPipeline();
-        } else {
-            setupNEM();
-        }
+        setupNotifPipeline();
 
         dumpManager.registerDumpable(TAG, this);
 
@@ -438,141 +419,6 @@
         mBubbles.setSysuiProxy(mSysuiProxy);
     }
 
-    private void setupNEM() {
-        mNotificationEntryManager.addNotificationEntryListener(
-                new NotificationEntryListener() {
-                    @Override
-                    public void onPendingEntryAdded(NotificationEntry entry) {
-                        BubblesManager.this.onEntryAdded(entry);
-                    }
-
-                    @Override
-                    public void onPreEntryUpdated(NotificationEntry entry) {
-                        BubblesManager.this.onEntryUpdated(entry);
-                    }
-
-                    @Override
-                    public void onEntryRemoved(
-                            NotificationEntry entry,
-                            @Nullable NotificationVisibility visibility,
-                            boolean removedByUser,
-                            int reason) {
-                        BubblesManager.this.onEntryRemoved(entry);
-                    }
-
-                    @Override
-                    public void onNotificationRankingUpdated(RankingMap rankingMap) {
-                        BubblesManager.this.onRankingUpdate(rankingMap);
-                    }
-
-                    @Override
-                    public void onNotificationChannelModified(
-                            String pkgName,
-                            UserHandle user,
-                            NotificationChannel channel,
-                            int modificationType) {
-                        BubblesManager.this.onNotificationChannelModified(pkgName,
-                                user,
-                                channel,
-                                modificationType);
-                    }
-                });
-
-        // The new pipeline takes care of this as a NotifDismissInterceptor BubbleCoordinator
-        mNotificationEntryManager.addNotificationRemoveInterceptor(
-                (key, entry, dismissReason) -> {
-                    final boolean isClearAll = dismissReason == REASON_CANCEL_ALL;
-                    final boolean isUserDismiss = dismissReason == REASON_CANCEL
-                            || dismissReason == REASON_CLICK;
-                    final boolean isAppCancel = dismissReason == REASON_APP_CANCEL
-                            || dismissReason == REASON_APP_CANCEL_ALL;
-                    final boolean isSummaryCancel =
-                            dismissReason == REASON_GROUP_SUMMARY_CANCELED;
-
-                    // Need to check for !appCancel here because the notification may have
-                    // previously been dismissed & entry.isRowDismissed would still be true
-                    boolean userRemovedNotif =
-                            (entry != null && entry.isRowDismissed() && !isAppCancel)
-                                    || isClearAll || isUserDismiss || isSummaryCancel;
-
-                    if (userRemovedNotif) {
-                        return handleDismissalInterception(entry);
-                    }
-                    return false;
-                });
-
-        mNotificationGroupManager.registerGroupChangeListener(
-                new NotificationGroupManagerLegacy.OnGroupChangeListener() {
-                    @Override
-                    public void onGroupSuppressionChanged(
-                            NotificationGroupManagerLegacy.NotificationGroup group,
-                            boolean suppressed) {
-                        // More notifications could be added causing summary to no longer
-                        // be suppressed -- in this case need to remove the key.
-                        final String groupKey = group.summary != null
-                                ? group.summary.getSbn().getGroupKey()
-                                : null;
-                        if (!suppressed && groupKey != null) {
-                            mBubbles.removeSuppressedSummaryIfNecessary(groupKey, null, null);
-                        }
-                    }
-                });
-
-        addNotifCallback(new NotifCallback() {
-            @Override
-            public void removeNotification(NotificationEntry entry,
-                    DismissedByUserStats dismissedByUserStats, int reason) {
-                mNotificationEntryManager.performRemoveNotification(entry.getSbn(),
-                        dismissedByUserStats, reason);
-            }
-
-            @Override
-            public void invalidateNotifications(String reason) {
-                mNotificationEntryManager.updateNotifications(reason);
-            }
-
-            @Override
-            public void maybeCancelSummary(NotificationEntry entry) {
-                // Check if removed bubble has an associated suppressed group summary that needs
-                // to be removed now.
-                final String groupKey = entry.getSbn().getGroupKey();
-                mBubbles.removeSuppressedSummaryIfNecessary(groupKey, (summaryKey) -> {
-                    final NotificationEntry summary =
-                            mNotificationEntryManager.getActiveNotificationUnfiltered(summaryKey);
-                    if (summary != null) {
-                        mNotificationEntryManager.performRemoveNotification(
-                                summary.getSbn(),
-                                getDismissedByUserStats(summary, false),
-                                UNDEFINED_DISMISS_REASON);
-                    }
-                }, mSysuiMainExecutor);
-
-                // Check if we still need to remove the summary from NoManGroup because the summary
-                // may not be in the mBubbleData.mSuppressedGroupKeys list and removed above.
-                // For example:
-                // 1. Bubbled notifications (group) is posted to shade and are visible bubbles
-                // 2. User expands bubbles so now their respective notifications in the shade are
-                // hidden, including the group summary
-                // 3. User removes all bubbles
-                // 4. We expect all the removed bubbles AND the summary (note: the summary was
-                // never added to the suppressedSummary list in BubbleData, so we add this check)
-                NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(entry);
-                if (summary != null) {
-                    ArrayList<NotificationEntry> summaryChildren =
-                            mNotificationGroupManager.getLogicalChildren(summary.getSbn());
-                    boolean isSummaryThisNotif = summary.getKey().equals(entry.getKey());
-                    if (!isSummaryThisNotif && (summaryChildren == null
-                            || summaryChildren.isEmpty())) {
-                        mNotificationEntryManager.performRemoveNotification(
-                                summary.getSbn(),
-                                getDismissedByUserStats(summary, false),
-                                UNDEFINED_DISMISS_REASON);
-                    }
-                }
-            }
-        });
-    }
-
     private void setupNotifPipeline() {
         mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 90609fa..64a7986 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -1383,7 +1383,7 @@
 
         mScreenDecorations.mDisplayListener.onDisplayChanged(1);
 
-        verify(hwcLayer, times(1)).onDisplayChanged(1);
+        verify(hwcLayer, times(1)).onDisplayChanged(any());
     }
 
     @Test
@@ -1407,7 +1407,7 @@
 
         mScreenDecorations.mDisplayListener.onDisplayChanged(1);
 
-        verify(cutoutView, times(1)).onDisplayChanged(1);
+        verify(cutoutView, times(1)).onDisplayChanged(any());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index d0cf792..6978490 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.charging
+package com.android.systemui.charging
 
 import android.testing.AndroidTestingRunner
 import android.view.View
@@ -24,6 +24,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.ripple.RippleView
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -50,7 +51,7 @@
     @Mock private lateinit var batteryController: BatteryController
     @Mock private lateinit var featureFlags: FeatureFlags
     @Mock private lateinit var configurationController: ConfigurationController
-    @Mock private lateinit var rippleView: ChargingRippleView
+    @Mock private lateinit var rippleView: RippleView
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var uiEventLogger: UiEventLogger
     private val systemClock = FakeSystemClock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
index 69366fa..8bf17d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.decor
 
+import android.graphics.Color
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.DisplayCutout
@@ -51,35 +52,45 @@
 
     @Before
     fun setUp() {
-        decorProvider1 = spy(PrivacyDotCornerDecorProviderImpl(
+        decorProvider1 = spy(
+            PrivacyDotCornerDecorProviderImpl(
                 viewId = TEST_DECOR_VIEW_ID_1,
                 alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
                 alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
-                layoutId = R.layout.privacy_dot_top_left))
-        decorProvider2 = spy(PrivacyDotCornerDecorProviderImpl(
+                layoutId = R.layout.privacy_dot_top_left
+            )
+        )
+        decorProvider2 = spy(
+            PrivacyDotCornerDecorProviderImpl(
                 viewId = TEST_DECOR_VIEW_ID_2,
                 alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
                 alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
-                layoutId = R.layout.privacy_dot_bottom_left))
-        decorProvider3 = spy(PrivacyDotCornerDecorProviderImpl(
+                layoutId = R.layout.privacy_dot_bottom_left
+            )
+        )
+        decorProvider3 = spy(
+            PrivacyDotCornerDecorProviderImpl(
                 viewId = TEST_DECOR_VIEW_ID_3,
                 alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
                 alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
-                layoutId = R.layout.privacy_dot_bottom_right))
-
+                layoutId = R.layout.privacy_dot_bottom_right
+            )
+        )
         overlay = OverlayWindow(mContext)
     }
 
     @Test
     fun testAddProvider() {
         @Surface.Rotation val rotation = Surface.ROTATION_270
-        overlay.addDecorProvider(decorProvider1, rotation)
-        overlay.addDecorProvider(decorProvider2, rotation)
+        overlay.addDecorProvider(decorProvider1, rotation, Color.BLACK)
+        overlay.addDecorProvider(decorProvider2, rotation, Color.YELLOW)
 
         verify(decorProvider1, times(1)).inflateView(
-                mContext, overlay.rootView, rotation)
+                mContext, overlay.rootView, rotation, Color.BLACK
+        )
         verify(decorProvider2, times(1)).inflateView(
-                mContext, overlay.rootView, rotation)
+                mContext, overlay.rootView, rotation, Color.YELLOW
+        )
 
         val view1FoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1)
         Assert.assertNotNull(view1FoundFromRootView)
@@ -91,8 +102,8 @@
 
     @Test
     fun testRemoveView() {
-        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270)
-        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270)
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270, Color.BLACK)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270, Color.BLACK)
         overlay.removeView(TEST_DECOR_VIEW_ID_1)
 
         val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1)
@@ -102,39 +113,47 @@
 
     @Test
     fun testOnReloadResAndMeasureWithoutIds() {
-        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0)
-        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0)
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0, Color.BLACK)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0, Color.BLACK)
 
         overlay.onReloadResAndMeasure(
-                reloadToken = 1,
-                rotation = Surface.ROTATION_90,
-                displayUniqueId = null)
+            reloadToken = 1,
+            rotation = Surface.ROTATION_90,
+            tintColor = Color.BLACK,
+            displayUniqueId = null
+        )
         verify(decorProvider1, times(1)).onReloadResAndMeasure(
-                overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null)
+                overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, Color.BLACK, null
+        )
         verify(decorProvider2, times(1)).onReloadResAndMeasure(
-                overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null)
+                overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, Color.BLACK, null
+        )
     }
 
     @Test
     fun testOnReloadResAndMeasureWithIds() {
-        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0)
-        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0)
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0, Color.BLACK)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0, Color.BLACK)
 
         overlay.onReloadResAndMeasure(
-                filterIds = arrayOf(TEST_DECOR_VIEW_ID_2),
-                reloadToken = 1,
-                rotation = Surface.ROTATION_90,
-                displayUniqueId = null)
+            filterIds = arrayOf(TEST_DECOR_VIEW_ID_2),
+            reloadToken = 1,
+            rotation = Surface.ROTATION_90,
+            tintColor = Color.BLACK,
+            displayUniqueId = null
+        )
         verify(decorProvider1, never()).onReloadResAndMeasure(
-                overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null)
+                overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, Color.BLACK, null
+        )
         verify(decorProvider2, times(1)).onReloadResAndMeasure(
-                overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null)
+                overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, Color.BLACK, null
+        )
     }
 
     @Test
     fun testRemoveRedundantViewsWithNullParameter() {
-        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270)
-        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270)
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270, Color.BLACK)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270, Color.BLACK)
 
         overlay.removeRedundantViews(null)
 
@@ -146,13 +165,15 @@
 
     @Test
     fun testRemoveRedundantViewsWith2Providers() {
-        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270)
-        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270)
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270, Color.BLACK)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270, Color.BLACK)
 
-        overlay.removeRedundantViews(IntArray(2).apply {
-            this[0] = TEST_DECOR_VIEW_ID_3
-            this[1] = TEST_DECOR_VIEW_ID_1
-        })
+        overlay.removeRedundantViews(
+            IntArray(2).apply {
+                this[0] = TEST_DECOR_VIEW_ID_3
+                this[1] = TEST_DECOR_VIEW_ID_1
+            }
+        )
 
         Assert.assertNotNull(overlay.getView(TEST_DECOR_VIEW_ID_1))
         Assert.assertNotNull(overlay.rootView.findViewById(TEST_DECOR_VIEW_ID_1))
@@ -167,16 +188,16 @@
         Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2)))
         Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1)))
 
-        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0)
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0, Color.BLACK)
         Assert.assertFalse(overlay.hasSameProviders(emptyList()))
         Assert.assertTrue(overlay.hasSameProviders(listOf(decorProvider1)))
         Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2)))
         Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1)))
 
-        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0, Color.BLACK)
         Assert.assertFalse(overlay.hasSameProviders(emptyList()))
         Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider1)))
         Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2)))
         Assert.assertTrue(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1)))
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
deleted file mode 100644
index df506b4..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.AnimatableClockController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.Utils;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.AnimatableClockView;
-import com.android.systemui.statusbar.policy.BatteryController;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AnimatableClockControllerTest extends SysuiTestCase {
-    @Mock
-    private AnimatableClockView mClockView;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    private BatteryController mBatteryController;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private Resources mResources;
-
-    private MockitoSession mStaticMockSession;
-    private AnimatableClockController mAnimatableClockController;
-
-    // Capture listeners so that they can be used to send events
-    @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
-            ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
-    private View.OnAttachStateChangeListener mAttachListener;
-
-    @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor;
-    private StatusBarStateController.StateListener mStatusBarStateCallback;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mStaticMockSession = mockitoSession()
-                .mockStatic(Utils.class)
-                .strictness(Strictness.LENIENT) // it's ok if mocked classes aren't used
-                .startMocking();
-        when(Utils.getColorAttrDefaultColor(anyObject(), anyInt())).thenReturn(0);
-
-        mAnimatableClockController = new AnimatableClockController(
-                mClockView,
-                mStatusBarStateController,
-                mBroadcastDispatcher,
-                mBatteryController,
-                mKeyguardUpdateMonitor,
-                mResources
-        );
-        mAnimatableClockController.init();
-        captureAttachListener();
-    }
-
-    @After
-    public void tearDown() {
-        mStaticMockSession.finishMocking();
-    }
-
-    @Test
-    public void testOnAttachedUpdatesDozeStateToTrue() {
-        // GIVEN dozing
-        when(mStatusBarStateController.isDozing()).thenReturn(true);
-        when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);
-
-        // WHEN the clock view gets attached
-        mAttachListener.onViewAttachedToWindow(mClockView);
-
-        // THEN the clock controller updated its dozing state to true
-        assertTrue(mAnimatableClockController.isDozing());
-    }
-
-    @Test
-    public void testOnAttachedUpdatesDozeStateToFalse() {
-        // GIVEN not dozing
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
-
-        // WHEN the clock view gets attached
-        mAttachListener.onViewAttachedToWindow(mClockView);
-
-        // THEN the clock controller updated its dozing state to false
-        assertFalse(mAnimatableClockController.isDozing());
-    }
-
-    private void captureAttachListener() {
-        verify(mClockView).addOnAttachStateChangeListener(mAttachCaptor.capture());
-        mAttachListener = mAttachCaptor.getValue();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
index b979241..f56d42e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.media
 
-import org.mockito.Mockito.`when` as whenever
 import android.animation.ValueAnimator
 import android.graphics.Color
 import android.testing.AndroidTestingRunner
@@ -34,6 +33,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
 private const val DEFAULT_COLOR = Color.RED
@@ -147,8 +147,8 @@
 
     @Test
     fun testColorSchemeTransition_update() {
-        colorSchemeTransition.updateColorScheme(colorScheme, true)
-        verify(mockAnimatingTransition, times(10)).updateColorScheme(colorScheme)
+        colorSchemeTransition.updateColorScheme(colorScheme)
+        verify(mockAnimatingTransition, times(8)).updateColorScheme(colorScheme)
         verify(gutsViewHolder).colorScheme = colorScheme
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 540f2a5..c13c30b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -21,7 +21,6 @@
 import android.app.PendingIntent
 import android.app.smartspace.SmartspaceAction
 import android.content.Context
-import org.mockito.Mockito.`when` as whenever
 import android.content.Intent
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
@@ -69,8 +68,8 @@
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
-import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.withArgCaptor
@@ -93,6 +92,7 @@
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
 private const val KEY = "TEST_KEY"
@@ -355,6 +355,7 @@
         whenever(viewHolder.player).thenReturn(view)
         whenever(viewHolder.appIcon).thenReturn(appIcon)
         whenever(viewHolder.albumView).thenReturn(albumView)
+        whenever(albumView.foreground).thenReturn(mock(Drawable::class.java))
         whenever(viewHolder.titleText).thenReturn(titleText)
         whenever(viewHolder.artistText).thenReturn(artistText)
         whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 1527f0d..2eb4783 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -371,11 +371,9 @@
         powerManager,
         R.layout.media_ttt_chip
     ) {
-        override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {
-
-        }
-
-        override fun getIconSize(isAppIcon: Boolean): Int? = ICON_SIZE
+        override val windowLayoutParams = commonWindowLayoutParams
+        override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {}
+        override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE
     }
 
     inner class ChipInfo : ChipInfoCommon {
@@ -386,4 +384,4 @@
 private const val PACKAGE_NAME = "com.android.systemui"
 private const val APP_NAME = "Fake App Name"
 private const val TIMEOUT_MS = 10000L
-private const val ICON_SIZE = 47
\ No newline at end of file
+private const val ICON_SIZE = 47
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 2927669..3dc10d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs;
 
+import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
+
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
@@ -34,6 +36,7 @@
 import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -222,7 +225,41 @@
         Assert.assertEquals(2, mFmc.getNumRunningPackages());
     }
 
+    @Test
+    public void testButtonVisibilityOnShowAllowlistButtonFlagChange() throws Exception {
+        setUserProfiles(0);
+        setBackgroundRestrictionExemptionReason("pkg", 12345, REASON_ALLOWLISTED_PACKAGE);
 
+        final Binder binder = new Binder();
+        setShowStopButtonForUserAllowlistedApps(true);
+        mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
+        Assert.assertEquals(1, mFmc.getNumVisibleButtons());
+
+        mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, false);
+        Assert.assertEquals(0, mFmc.getNumVisibleButtons());
+
+        setShowStopButtonForUserAllowlistedApps(false);
+        mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
+        Assert.assertEquals(0, mFmc.getNumVisibleButtons());
+    }
+
+    private void setShowStopButtonForUserAllowlistedApps(boolean enable) {
+        mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+                enable ? "true" : "false", false);
+        mBackgroundExecutor.advanceClockToLast();
+        mBackgroundExecutor.runAllReady();
+    }
+
+    private void setBackgroundRestrictionExemptionReason(String pkgName, int uid, int reason)
+            throws Exception {
+        Mockito.doReturn(uid)
+                .when(mPackageManager)
+                .getPackageUidAsUser(pkgName, UserHandle.getUserId(uid));
+        Mockito.doReturn(reason)
+                .when(mIActivityManager)
+                .getBackgroundRestrictionExemptionReason(uid);
+    }
 
     FgsManagerController createFgsManagerController() throws RemoteException {
         ArgumentCaptor<IForegroundServiceObserver> iForegroundServiceObserverArgumentCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index aad03e2..9d908fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -139,7 +139,6 @@
         `when`(dialog.getButton(DialogInterface.BUTTON_NEUTRAL)).thenReturn(neutralButton)
 
         clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
-        verify(dialog).dismiss()
 
         verify(activityStarter)
             .postStartActivityDismissingKeyguard(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 2691ff9..34d13c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -16,12 +16,9 @@
 
 package com.android.systemui.statusbar;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
@@ -30,19 +27,14 @@
 import android.os.Looper;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.service.notification.NotificationListenerService;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
-import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputActiveExtender;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputHistoryExtender;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.SmartReplyHistoryExtender;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -52,8 +44,6 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 
-import com.google.android.collect.Sets;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -76,23 +66,15 @@
     @Mock private NotificationRemoteInputManager.Callback mCallback;
     @Mock private RemoteInputController mController;
     @Mock private SmartReplyController mSmartReplyController;
-    @Mock private NotificationListenerService.RankingMap mRanking;
     @Mock private ExpandableNotificationRow mRow;
     @Mock private StatusBarStateController mStateController;
     @Mock private RemoteInputUriController mRemoteInputUriController;
     @Mock private NotificationClickNotifier mClickNotifier;
-
-    // Dependency mocks:
     @Mock private NotificationEntryManager mEntryManager;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
 
     private TestableNotificationRemoteInputManager mRemoteInputManager;
     private NotificationEntry mEntry;
-    private RemoteInputHistoryExtender mRemoteInputHistoryExtender;
-    private SmartReplyHistoryExtender mSmartReplyHistoryExtender;
-    private RemoteInputActiveExtender mRemoteInputActiveExtender;
-    private TestableNotificationRemoteInputManager.FakeLegacyRemoteInputLifetimeExtender
-            mLegacyRemoteInputLifetimeExtender;
 
     @Before
     public void setUp() {
@@ -121,21 +103,7 @@
                 .build();
         mEntry.setRow(mRow);
 
-        mRemoteInputManager.setUpWithPresenterForTest(mCallback,
-                mDelegate, mController);
-        for (NotificationLifetimeExtender extender : mRemoteInputManager.getLifetimeExtenders()) {
-            extender.setCallback(
-                    mock(NotificationLifetimeExtender.NotificationSafeToRemoveCallback.class));
-        }
-    }
-
-    @Test
-    public void testPerformOnRemoveNotification() {
-        when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
-        mRemoteInputManager.onPerformRemoveNotification(mEntry, mEntry.getKey());
-
-        assertFalse(mEntry.mRemoteEditImeVisible);
-        verify(mController).removeRemoteInput(mEntry, null);
+        mRemoteInputManager.setUpWithPresenterForTest(mCallback, mDelegate, mController);
     }
 
     @Test
@@ -143,7 +111,6 @@
         when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
 
         assertTrue(mRemoteInputManager.isRemoteInputActive(mEntry));
-        assertTrue(mRemoteInputActiveExtender.shouldExtendLifetime(mEntry));
     }
 
     @Test
@@ -152,7 +119,6 @@
         when(mController.isSpinning(mEntry.getKey())).thenReturn(true);
 
         assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry));
-        assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry));
     }
 
     @Test
@@ -161,7 +127,6 @@
         mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime();
 
         assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry));
-        assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry));
     }
 
     @Test
@@ -170,20 +135,6 @@
         when(mSmartReplyController.isSendingSmartReply(mEntry.getKey())).thenReturn(true);
 
         assertTrue(mRemoteInputManager.shouldKeepForSmartReplyHistory(mEntry));
-        assertTrue(mSmartReplyHistoryExtender.shouldExtendLifetime(mEntry));
-    }
-
-    @Test
-    public void testNotificationWithRemoteInputActiveIsRemovedOnCollapse() {
-        mRemoteInputActiveExtender.setShouldManageLifetime(mEntry, true /* shouldManage */);
-
-        assertEquals(mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive(),
-                Sets.newArraySet(mEntry));
-
-        mRemoteInputManager.onPanelCollapsed();
-
-        assertTrue(
-                mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive().isEmpty());
     }
 
     private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
@@ -227,28 +178,5 @@
             mRemoteInputController = controller;
         }
 
-        @NonNull
-        @Override
-        protected LegacyRemoteInputLifetimeExtender createLegacyRemoteInputLifetimeExtender(
-                Handler mainHandler,
-                NotificationEntryManager notificationEntryManager,
-                SmartReplyController smartReplyController) {
-            mLegacyRemoteInputLifetimeExtender = new FakeLegacyRemoteInputLifetimeExtender();
-            return mLegacyRemoteInputLifetimeExtender;
-        }
-
-        class FakeLegacyRemoteInputLifetimeExtender extends LegacyRemoteInputLifetimeExtender {
-
-            @Override
-            protected void addLifetimeExtenders() {
-                mRemoteInputActiveExtender = new RemoteInputActiveExtender();
-                mRemoteInputHistoryExtender = new RemoteInputHistoryExtender();
-                mSmartReplyHistoryExtender = new SmartReplyHistoryExtender();
-                mLifetimeExtenders.add(mRemoteInputHistoryExtender);
-                mLifetimeExtenders.add(mSmartReplyHistoryExtender);
-                mLifetimeExtenders.add(mRemoteInputActiveExtender);
-            }
-        }
-
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 3500e4d..c75aa81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -17,15 +17,15 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
@@ -39,14 +39,12 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+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.coordinator.RemoteInputCoordinator;
+import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -54,8 +52,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Optional;
-
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
@@ -67,57 +63,50 @@
     private static final int TEST_CHOICE_COUNT = 4;
     private static final int TEST_ACTION_COUNT = 3;
 
-    private Notification mNotification;
     private NotificationEntry mEntry;
     private SmartReplyController mSmartReplyController;
-    private NotificationRemoteInputManager mRemoteInputManager;
 
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
-    @Mock private RemoteInputController.Delegate mDelegate;
-    @Mock private NotificationRemoteInputManager.Callback mCallback;
     @Mock private StatusBarNotification mSbn;
-    @Mock private NotificationEntryManager mNotificationEntryManager;
     @Mock private IStatusBarService mIStatusBarService;
-    @Mock private StatusBarStateController mStatusBarStateController;
-    @Mock private RemoteInputUriController mRemoteInputUriController;
     @Mock private NotificationClickNotifier mClickNotifier;
+    @Mock private InternalNotifUpdater mInternalNotifUpdater;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectTestDependency(NotificationEntryManager.class,
-                mNotificationEntryManager);
 
         mSmartReplyController = new SmartReplyController(
                 mock(DumpManager.class),
                 mVisibilityProvider,
                 mIStatusBarService,
                 mClickNotifier);
-        mDependency.injectTestDependency(SmartReplyController.class,
-                mSmartReplyController);
-
-        mRemoteInputManager = new NotificationRemoteInputManager(mContext,
-                mock(NotifPipelineFlags.class),
-                mock(NotificationLockscreenUserManager.class),
-                mSmartReplyController,
-                mVisibilityProvider,
-                mNotificationEntryManager,
+        RemoteInputCoordinator remoteInputCoordinator = new RemoteInputCoordinator(
+                mock(DumpManager.class),
                 new RemoteInputNotificationRebuilder(mContext),
-                () -> Optional.of(mock(CentralSurfaces.class)),
-                mStatusBarStateController,
-                Handler.createAsync(Looper.myLooper()),
-                mRemoteInputUriController,
-                mClickNotifier,
-                mock(ActionClickLogger.class),
-                mock(DumpManager.class));
-        mRemoteInputManager.setUpWithCallback(mCallback, mDelegate);
-        mNotification = new Notification.Builder(mContext, "")
+                mock(NotificationRemoteInputManager.class),
+                mock(Handler.class),
+                mSmartReplyController);
+        remoteInputCoordinator.setRemoteInputController(mock(RemoteInputController.class));
+        NotifPipeline notifPipeline = mock(NotifPipeline.class);
+        when(notifPipeline.getInternalNotifUpdater(anyString())).thenReturn(mInternalNotifUpdater);
+        remoteInputCoordinator.attach(notifPipeline);
+
+        Notification notification = new Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
                 .setContentText("Text").build();
-
-        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
-                0, mNotification, new UserHandle(ActivityManager.getCurrentUser()), null, 0);
+        mSbn = new StatusBarNotification(
+                TEST_PACKAGE_NAME,
+                TEST_PACKAGE_NAME,
+                0,
+                null,
+                TEST_UID,
+                0,
+                notification,
+                new UserHandle(ActivityManager.getCurrentUser()),
+                null,
+                0);
         mEntry = new NotificationEntryBuilder()
                 .setSbn(mSbn)
                 .build();
@@ -128,10 +117,9 @@
         mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT,
                 MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
 
-        // Sending smart reply should make calls to NotificationEntryManager
-        // to update the notification with reply and spinner.
-        verify(mNotificationEntryManager).updateNotification(
-                argThat(sbn -> sbn.getKey().equals(mSbn.getKey())), isNull());
+        // Sending smart reply should update the notification with reply and spinner.
+        verify(mInternalNotifUpdater).onInternalNotificationUpdate(
+                argThat(sbn -> sbn.getKey().equals(mSbn.getKey())), anyString());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index f286349..ef763d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -152,8 +152,6 @@
         MockitoAnnotations.initMocks(this);
         allowTestableLooperAsMainThread();
 
-        when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true);
-
         when(mEulogizer.record(any(Exception.class))).thenAnswer(i -> i.getArguments()[0]);
 
         mListenerInOrder = inOrder(mCollectionListener);
@@ -1684,9 +1682,9 @@
         return new CollectionEvent(rawEvent, requireNonNull(mEntryCaptor.getValue()));
     }
 
-    private void verifyBuiltList(Collection<NotificationEntry> list) {
-        verify(mBuildListener).onBuildList(mBuildListCaptor.capture());
-        assertEquals(new ArraySet<>(list), new ArraySet<>(mBuildListCaptor.getValue()));
+    private void verifyBuiltList(Collection<NotificationEntry> expectedList) {
+        verify(mBuildListener).onBuildList(mBuildListCaptor.capture(), any());
+        assertThat(mBuildListCaptor.getValue()).containsExactly(expectedList.toArray());
     }
 
     private static class RecordingCollectionListener implements NotifCollectionListener {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 9546058..555adfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -570,7 +570,7 @@
         assertTrue(entry.hasFinishedInitialization());
 
         // WHEN the pipeline is kicked off
-        mReadyForBuildListener.onBuildList(singletonList(entry));
+        mReadyForBuildListener.onBuildList(singletonList(entry), "test");
         mPipelineChoreographer.runIfScheduled();
 
         // THEN the entry's initialization time is reset
@@ -2092,7 +2092,7 @@
             mPendingSet.clear();
         }
 
-        mReadyForBuildListener.onBuildList(mEntrySet);
+        mReadyForBuildListener.onBuildList(mEntrySet, "test");
         mPipelineChoreographer.runIfScheduled();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
index 5ba926e..0830191 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
@@ -60,7 +60,6 @@
 
     @Before
     fun setUp() {
-        whenever(pipeline.isNewPipelineEnabled).thenReturn(true)
         whenever(pipeline.allNotifs).thenReturn(listOf(entry))
         whenever(entry.row).thenReturn(row)
         coordinator = ViewConfigCoordinator(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 275dbfd..8fd6842 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -15,6 +15,7 @@
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
+import junit.framework.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mockito.mock
@@ -164,4 +165,178 @@
         stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart)
         assertFalse(expandableViewState.hidden)
     }
+
+    @Test
+    fun maybeUpdateHeadsUpIsVisible_endVisible_true() {
+        val expandableViewState = ExpandableViewState()
+        expandableViewState.headsUpIsVisible = false
+
+        stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+                /* isShadeExpanded= */ true,
+                /* mustStayOnScreen= */ true,
+                /* isViewEndVisible= */ true,
+                /* viewEnd= */ 0f,
+                /* maxHunY= */ 10f)
+
+        assertTrue(expandableViewState.headsUpIsVisible)
+    }
+
+    @Test
+    fun maybeUpdateHeadsUpIsVisible_endHidden_false() {
+        val expandableViewState = ExpandableViewState()
+        expandableViewState.headsUpIsVisible = true
+
+        stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+                /* isShadeExpanded= */ true,
+                /* mustStayOnScreen= */ true,
+                /* isViewEndVisible= */ true,
+                /* viewEnd= */ 10f,
+                /* maxHunY= */ 0f)
+
+        assertFalse(expandableViewState.headsUpIsVisible)
+    }
+
+    @Test
+    fun maybeUpdateHeadsUpIsVisible_shadeClosed_noUpdate() {
+        val expandableViewState = ExpandableViewState()
+        expandableViewState.headsUpIsVisible = true
+
+        stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+                /* isShadeExpanded= */ false,
+                /* mustStayOnScreen= */ true,
+                /* isViewEndVisible= */ true,
+                /* viewEnd= */ 10f,
+                /* maxHunY= */ 1f)
+
+        assertTrue(expandableViewState.headsUpIsVisible)
+    }
+
+    @Test
+    fun maybeUpdateHeadsUpIsVisible_notHUN_noUpdate() {
+        val expandableViewState = ExpandableViewState()
+        expandableViewState.headsUpIsVisible = true
+
+        stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+                /* isShadeExpanded= */ true,
+                /* mustStayOnScreen= */ false,
+                /* isViewEndVisible= */ true,
+                /* viewEnd= */ 10f,
+                /* maxHunY= */ 1f)
+
+        assertTrue(expandableViewState.headsUpIsVisible)
+    }
+
+    @Test
+    fun maybeUpdateHeadsUpIsVisible_topHidden_noUpdate() {
+        val expandableViewState = ExpandableViewState()
+        expandableViewState.headsUpIsVisible = true
+
+        stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+                /* isShadeExpanded= */ true,
+                /* mustStayOnScreen= */ true,
+                /* isViewEndVisible= */ false,
+                /* viewEnd= */ 10f,
+                /* maxHunY= */ 1f)
+
+        assertTrue(expandableViewState.headsUpIsVisible)
+    }
+
+    @Test
+    fun clampHunToTop_viewYGreaterThanQqs_viewYUnchanged() {
+        val expandableViewState = ExpandableViewState()
+        expandableViewState.yTranslation = 50f
+
+        stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+                /* stackTranslation= */ 0f,
+                /* collapsedHeight= */ 1f, expandableViewState)
+
+        // qqs (10 + 0) < viewY (50)
+        assertEquals(50f, expandableViewState.yTranslation)
+    }
+
+    @Test
+    fun clampHunToTop_viewYLessThanQqs_viewYChanged() {
+        val expandableViewState = ExpandableViewState()
+        expandableViewState.yTranslation = -10f
+
+        stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+                /* stackTranslation= */ 0f,
+                /* collapsedHeight= */ 1f, expandableViewState)
+
+        // qqs (10 + 0) > viewY (-10)
+        assertEquals(10f, expandableViewState.yTranslation)
+    }
+
+
+    @Test
+    fun clampHunToTop_viewYFarAboveVisibleStack_heightCollapsed() {
+        val expandableViewState = ExpandableViewState()
+        expandableViewState.height = 20
+        expandableViewState.yTranslation = -100f
+
+        stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+                /* stackTranslation= */ 0f,
+                /* collapsedHeight= */ 10f, expandableViewState)
+
+        // newTranslation = max(10, -100) = 10
+        // distToRealY = 10 - (-100f) = 110
+        // height = max(20 - 110, 10f)
+        assertEquals(10, expandableViewState.height)
+    }
+
+    @Test
+    fun clampHunToTop_viewYNearVisibleStack_heightTallerThanCollapsed() {
+        val expandableViewState = ExpandableViewState()
+        expandableViewState.height = 20
+        expandableViewState.yTranslation = 5f
+
+        stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+                /* stackTranslation= */ 0f,
+                /* collapsedHeight= */ 10f, expandableViewState)
+
+        // newTranslation = max(10, 5) = 10
+        // distToRealY = 10 - 5 = 5
+        // height = max(20 - 5, 10) = 15
+        assertEquals(15, expandableViewState.height)
+    }
+
+    @Test
+    fun computeCornerRoundnessForPinnedHun_stackBelowScreen_round() {
+        val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+                /* hostViewHeight= */ 100f,
+                /* stackY= */ 110f,
+                /* viewMaxHeight= */ 20f,
+                /* originalCornerRoundness= */ 0f)
+        assertEquals(1f, currentRoundness)
+    }
+
+    @Test
+    fun computeCornerRoundnessForPinnedHun_stackAboveScreenBelowPinPoint_halfRound() {
+        val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+                /* hostViewHeight= */ 100f,
+                /* stackY= */ 90f,
+                /* viewMaxHeight= */ 20f,
+                /* originalCornerRoundness= */ 0f)
+        assertEquals(0.5f, currentRoundness)
+    }
+
+    @Test
+    fun computeCornerRoundnessForPinnedHun_stackAbovePinPoint_notRound() {
+        val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+                /* hostViewHeight= */ 100f,
+                /* stackY= */ 0f,
+                /* viewMaxHeight= */ 20f,
+                /* originalCornerRoundness= */ 0f)
+        assertEquals(0f, currentRoundness)
+    }
+
+    @Test
+    fun computeCornerRoundnessForPinnedHun_originallyRoundAndStackAbovePinPoint_round() {
+        val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+                /* hostViewHeight= */ 100f,
+                /* stackY= */ 0f,
+                /* viewMaxHeight= */ 20f,
+                /* originalCornerRoundness= */ 1f)
+        assertEquals(1f, currentRoundness)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index e5b6286..272ef3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -131,6 +131,7 @@
         when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
                 .thenReturn(true);
         when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+        when(mVibratorHelper.hasVibrator()).thenReturn(true);
         mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
         mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController,
                 mKeyguardViewMediator, mScrimController, mShadeController,
@@ -423,4 +424,35 @@
         verify(mHandler).post(captor.capture());
         captor.getValue().run();
     }
+
+    @Test
+    public void onFPFailureNoHaptics_notDeviceInteractive_showBouncer() {
+        // GIVEN no vibrator and the screen is off
+        when(mVibratorHelper.hasVibrator()).thenReturn(false);
+        when(mUpdateMonitor.isDeviceInteractive()).thenReturn(false);
+        when(mUpdateMonitor.isDreaming()).thenReturn(false);
+
+        // WHEN FP fails
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+        // after device is finished waking up
+        mBiometricUnlockController.mWakefulnessObserver.onFinishedWakingUp();
+
+        // THEN show the bouncer
+        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+    }
+
+    @Test
+    public void onFPFailureNoHaptics_dreaming_showBouncer() {
+        // GIVEN no vibrator and device is dreaming
+        when(mVibratorHelper.hasVibrator()).thenReturn(false);
+        when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+        when(mUpdateMonitor.isDreaming()).thenReturn(true);
+
+        // WHEN FP fails
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+        // THEN show the bouncer
+        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 266f0e9..2e26a2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -84,6 +84,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.charging.WiredChargingRippleController;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -117,7 +118,6 @@
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.charging.WiredChargingRippleController;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index cec6a81..7fd61cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -87,6 +87,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.camera.CameraGestureHelper;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.controls.dagger.ControlsComponent;
@@ -546,7 +547,8 @@
                 mNotificationStackSizeCalculator,
                 mUnlockedScreenOffAnimationController,
                 mShadeTransitionController,
-                mSystemClock);
+                mSystemClock,
+                mock(CameraGestureHelper.class));
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 () -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt
index cafe113..304a274 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -9,6 +9,9 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
+import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED
+import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN
+import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import org.junit.Before
 import org.junit.Test
@@ -39,8 +42,9 @@
                 dumpManager,
                 scrimController,
                 context.resources,
-                statusBarStateController
-            )
+                statusBarStateController)
+
+        controller.onPanelStateChanged(STATE_OPENING)
     }
 
     @Test
@@ -54,8 +58,7 @@
 
     @Test
     fun onPanelExpansionChanged_inSplitShade_unlockedShade_setsFractionBasedOnDragDownAmount() {
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.SHADE)
+        whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
         val scrimShadeTransitionDistance =
             context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
         setSplitShadeEnabled(true)
@@ -68,23 +71,20 @@
 
     @Test
     fun onPanelExpansionChanged_inSplitShade_largeDragDownAmount_fractionIsNotGreaterThan1() {
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.SHADE)
+        whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
         val scrimShadeTransitionDistance =
             context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
         setSplitShadeEnabled(true)
 
         controller.onPanelExpansionChanged(
-            EXPANSION_EVENT.copy(dragDownPxAmount = 100f * scrimShadeTransitionDistance)
-        )
+            EXPANSION_EVENT.copy(dragDownPxAmount = 100f * scrimShadeTransitionDistance))
 
         verify(scrimController).setRawPanelExpansionFraction(1f)
     }
 
     @Test
     fun onPanelExpansionChanged_inSplitShade_negativeDragDownAmount_fractionIsNotLessThan0() {
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.SHADE)
+        whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
         setSplitShadeEnabled(true)
 
         controller.onPanelExpansionChanged(EXPANSION_EVENT.copy(dragDownPxAmount = -100f))
@@ -114,6 +114,30 @@
         verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
     }
 
+    @Test
+    fun onPanelExpansionChanged_inSplitShade_panelOpen_setsFractionEqualToEventFraction() {
+        controller.onPanelStateChanged(STATE_OPEN)
+        whenever(statusBarStateController.currentOrUpcomingState)
+            .thenReturn(StatusBarState.KEYGUARD)
+        setSplitShadeEnabled(true)
+
+        controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+    }
+
+    @Test
+    fun onPanelExpansionChanged_inSplitShade_panelClosed_setsFractionEqualToEventFraction() {
+        controller.onPanelStateChanged(STATE_CLOSED)
+        whenever(statusBarStateController.currentOrUpcomingState)
+            .thenReturn(StatusBarState.KEYGUARD)
+        setSplitShadeEnabled(true)
+
+        controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+    }
+
     private fun setSplitShadeEnabled(enabled: Boolean) {
         overrideResource(R.bool.config_use_split_notification_shade, enabled)
         configurationController.notifyConfigurationChanged()
@@ -122,7 +146,6 @@
     companion object {
         val EXPANSION_EVENT =
             PanelExpansionChangeEvent(
-                fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 10f
-            )
+                fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 10f)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt
index 85a8c6b..8b7e04b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt
@@ -8,6 +8,7 @@
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.NotificationPanelViewController
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
 import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
 import com.android.systemui.statusbar.policy.FakeConfigurationController
@@ -49,8 +50,7 @@
                 context,
                 splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
                 noOpOverScroller,
-                scrimShadeTransitionController
-            )
+                scrimShadeTransitionController)
 
         // Resetting as they are notified upon initialization.
         reset(noOpOverScroller, splitShadeOverScroller)
@@ -91,6 +91,16 @@
         verifyZeroInteractions(splitShadeOverScroller)
     }
 
+    @Test
+    fun onPanelStateChanged_forwardsToScrimTransitionController() {
+        initLateProperties()
+
+        startPanelExpansion()
+
+        verify(scrimShadeTransitionController).onPanelStateChanged(STATE_OPENING)
+        verify(scrimShadeTransitionController).onPanelExpansionChanged(DEFAULT_EXPANSION_EVENT)
+    }
+
     private fun initLateProperties() {
         controller.qs = qs
         controller.notificationStackScrollLayoutController = nsslController
@@ -112,14 +122,20 @@
 
     private fun startPanelExpansion() {
         panelExpansionStateManager.onPanelExpansionChanged(
-            fraction = 0.5f,
-            expanded = true,
-            tracking = true,
-            dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT
+            DEFAULT_EXPANSION_EVENT.fraction,
+            DEFAULT_EXPANSION_EVENT.expanded,
+            DEFAULT_EXPANSION_EVENT.tracking,
+            DEFAULT_EXPANSION_EVENT.dragDownPxAmount,
         )
     }
 
     companion object {
         private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
+        private val DEFAULT_EXPANSION_EVENT =
+            PanelExpansionChangeEvent(
+                fraction = 0.5f,
+                expanded = true,
+                tracking = true,
+                dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
index 1e35b0f..125b362 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -159,6 +159,24 @@
         Mockito.clearInvocations(callback);
     }
 
+    // Ensure that updating a callback that is removed doesn't result in an exception due to the
+    // absence of the condition.
+    @Test
+    public void testUpdateRemovedCallback() {
+        final Monitor.Callback callback1 =
+                mock(Monitor.Callback.class);
+        final Monitor.Subscription.Token subscription1 =
+                mConditionMonitor.addSubscription(getDefaultBuilder(callback1).build());
+        ArgumentCaptor<Condition.Callback> monitorCallback =
+                ArgumentCaptor.forClass(Condition.Callback.class);
+        mExecutor.runAllReady();
+        verify(mCondition1).addCallback(monitorCallback.capture());
+        // This will execute first before the handler for onConditionChanged.
+        mConditionMonitor.removeSubscription(subscription1);
+        monitorCallback.getValue().onConditionChanged(mCondition1);
+        mExecutor.runAllReady();
+    }
+
     @Test
     public void addCallback_addFirstCallback_addCallbackToAllConditions() {
         final Monitor.Callback callback1 =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7d4e27f..2e58fc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -21,12 +21,9 @@
 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
 import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NOTIF_CANCEL;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -62,7 +59,6 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.hardware.display.AmbientDisplayConfiguration;
-import android.hardware.face.FaceManager;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.UserHandle;
@@ -90,18 +86,16 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 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.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
@@ -118,7 +112,6 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.TaskViewTransitions;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -140,8 +133,6 @@
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
 
-import com.google.common.collect.ImmutableList;
-
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -155,22 +146,17 @@
 import java.util.List;
 import java.util.Optional;
 
-/**
- * Tests the NotificationEntryManager setup with BubbleController.
- * The {@link NotifPipeline} setup with BubbleController is tested in
- * {@link NewNotifPipelineBubblesTest}.
- */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class BubblesTest extends SysuiTestCase {
     @Mock
-    private NotificationEntryManager mNotificationEntryManager;
-    @Mock
     private CommonNotifCollection mCommonNotifCollection;
     @Mock
     private NotificationGroupManagerLegacy mNotificationGroupManager;
     @Mock
+    private BubblesManager.NotifCallback mNotifCallback;
+    @Mock
     private WindowManager mWindowManager;
     @Mock
     private IActivityManager mActivityManager;
@@ -183,8 +169,6 @@
     @Mock
     private ZenModeConfig mZenModeConfig;
     @Mock
-    private FaceManager mFaceManager;
-    @Mock
     private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock
     private SysuiStatusBarStateController mStatusBarStateController;
@@ -196,15 +180,17 @@
     private FloatingContentCoordinator mFloatingContentCoordinator;
     @Mock
     private BubbleDataRepository mDataRepository;
+    @Mock
+    private NotificationShadeWindowView mNotificationShadeWindowView;
+    @Mock
+    private AuthController mAuthController;
 
     private SysUiState mSysUiState;
     private boolean mSysUiStateBubblesExpanded;
     private boolean mSysUiStateBubblesManageMenuExpanded;
 
     @Captor
-    private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
-    @Captor
-    private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor;
+    private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
     @Captor
     private ArgumentCaptor<List<Bubble>> mBubbleListCaptor;
     @Captor
@@ -212,22 +198,16 @@
     @Captor
     private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
 
-
     private BubblesManager mBubblesManager;
-    // TODO(178618782): Move tests on the controller directly to the shell
     private TestableBubbleController mBubbleController;
     private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
-    private NotificationEntryListener mEntryListener;
-    private NotificationRemoveInterceptor mRemoveInterceptor;
-
+    private NotifCollectionListener mEntryListener;
     private NotificationTestHelper mNotificationTestHelper;
     private NotificationEntry mRow;
     private NotificationEntry mRow2;
-    private NotificationEntry mRow3;
     private ExpandableNotificationRow mNonBubbleNotifRow;
     private BubbleEntry mBubbleEntry;
     private BubbleEntry mBubbleEntry2;
-    private BubbleEntry mBubbleEntry3;
 
     private BubbleEntry mBubbleEntryUser11;
     private BubbleEntry mBubbleEntry2User11;
@@ -245,12 +225,8 @@
     @Mock
     private NotifPipeline mNotifPipeline;
     @Mock
-    private NotifPipelineFlags mNotifPipelineFlags;
-    @Mock
     private DumpManager mDumpManager;
     @Mock
-    private NotificationShadeWindowView mNotificationShadeWindowView;
-    @Mock
     private IStatusBarService mStatusBarService;
     @Mock
     private NotificationVisibilityProvider mVisibilityProvider;
@@ -269,8 +245,6 @@
     @Mock
     private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock
-    private AuthController mAuthController;
-    @Mock
     private TaskViewTransitions mTaskViewTransitions;
     @Mock
     private Optional<OneHandedController> mOneHandedOptional;
@@ -290,7 +264,6 @@
         // For the purposes of this test, just run everything synchronously
         ShellExecutor syncExecutor = new SyncExecutor();
 
-        mContext.addMockSystemService(FaceManager.class, mFaceManager);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
@@ -308,11 +281,9 @@
                 TestableLooper.get(this));
         mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
         mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
-        mRow3 = mNotificationTestHelper.createBubble(mDeleteIntent);
         mNonBubbleNotifRow = mNotificationTestHelper.createRow();
         mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow);
         mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2);
-        mBubbleEntry3 = BubblesManager.notifToBubbleEntry(mRow3);
 
         UserHandle handle = mock(UserHandle.class);
         when(handle.getIdentifier()).thenReturn(11);
@@ -321,9 +292,6 @@
         mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry(
                 mNotificationTestHelper.createBubble(handle));
 
-        // Return non-null notification data from the CommonNotifCollection
-        when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
-
         mZenModeConfig.suppressedVisualEffects = 0;
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
@@ -336,7 +304,6 @@
                     (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
         });
 
-        // TODO: Fix
         mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
         mPositioner.setMaxBubbles(5);
         mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
@@ -355,8 +322,6 @@
                         mock(NotifPipelineFlags.class),
                         mock(KeyguardNotificationVisibilityProvider.class)
                 );
-
-        when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
         when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
         mBubbleController = new TestableBubbleController(
                 mContext,
@@ -396,23 +361,17 @@
                 mZenModeController,
                 mLockscreenUserManager,
                 mNotificationGroupManager,
-                mNotificationEntryManager,
                 mCommonNotifCollection,
                 mNotifPipeline,
                 mSysUiState,
-                mNotifPipelineFlags,
                 mDumpManager,
                 syncExecutor);
+        mBubblesManager.addNotifCallback(mNotifCallback);
 
-        // XXX: Does *this* need to be changed?
         // Get a reference to the BubbleController's entry listener
-        verify(mNotificationEntryManager, atLeastOnce())
-                .addNotificationEntryListener(mEntryListenerCaptor.capture());
-        mEntryListener = mEntryListenerCaptor.getValue();
-        // And the remove interceptor
-        verify(mNotificationEntryManager, atLeastOnce())
-                .addNotificationRemoveInterceptor(mRemoveInterceptorCaptor.capture());
-        mRemoveInterceptor = mRemoveInterceptorCaptor.getValue();
+        verify(mNotifPipeline, atLeastOnce())
+                .addCollectionListener(mNotifListenerCaptor.capture());
+        mEntryListener = mNotifListenerCaptor.getValue();
     }
 
     @Test
@@ -433,90 +392,75 @@
     @Test
     public void testRemoveBubble() {
         mBubbleController.updateBubble(mBubbleEntry);
-        assertNotNull(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey()));
+        assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
         assertTrue(mBubbleController.hasBubbles());
-        verify(mNotificationEntryManager).updateNotifications(any());
+        verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
 
         mBubbleController.removeBubble(
                 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
-        verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
+        verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
 
         assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
-    public void testPromoteBubble_autoExpand() throws Exception {
-        mBubbleController.updateBubble(mBubbleEntry2);
+    public void testRemoveBubble_withDismissedNotif_inOverflow() {
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
-        when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
-        when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
-        mBubbleController.removeBubble(
-                mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
 
-        Bubble b = mBubbleData.getOverflowBubbleWithKey(mRow.getKey());
-        assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(b));
-        verify(mNotificationEntryManager, never()).performRemoveNotification(
-                eq(mRow.getSbn()), any(),  anyInt());
-        assertThat(mRow.isBubble()).isFalse();
-
-        Bubble b2 = mBubbleData.getBubbleInStackWithKey(mRow2.getKey());
-        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(b2);
-
-        mBubbleController.promoteBubbleFromOverflow(b);
-
-        assertThat(b.isBubble()).isTrue();
-        assertThat(b.shouldAutoExpand()).isTrue();
-        int flags = Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE
-                | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
-        verify(mStatusBarService, times(1)).onNotificationBubbleChanged(
-                eq(b.getKey()), eq(true), eq(flags));
-    }
-
-    @Test
-    public void testCancelOverflowBubble() {
-        mBubbleController.updateBubble(mBubbleEntry2);
-        mBubbleController.updateBubble(mBubbleEntry, /* suppressFlyout */
-                false, /* showInShade */ true);
-        when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
-        when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
-        mBubbleController.removeBubble(
-                mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
-
-        mBubbleController.removeBubble(
-                mRow.getKey(), DISMISS_NOTIF_CANCEL);
-        verify(mNotificationEntryManager, times(1)).performRemoveNotification(
-                eq(mRow.getSbn()), any(), anyInt());
-        assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
-        assertFalse(mRow.isBubble());
-    }
-
-    @Test
-    public void testUserChange_doesNotRemoveNotif() {
-        mBubbleController.updateBubble(mBubbleEntry);
         assertTrue(mBubbleController.hasBubbles());
+        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
 
+        // Make it look like dismissed notif
+        mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
+
+        // Now remove the bubble
         mBubbleController.removeBubble(
-                mRow.getKey(), Bubbles.DISMISS_USER_CHANGED);
-        verify(mNotificationEntryManager, never()).performRemoveNotification(
-                eq(mRow.getSbn()), any(), anyInt());
+                mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+        assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
+
+        // We don't remove the notification since the bubble is still in overflow.
+        verify(mNotifCallback, never()).removeNotification(eq(mRow), any(), anyInt());
         assertFalse(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-        assertTrue(mRow.isBubble());
+    }
+
+    @Test
+    public void testRemoveBubble_withDismissedNotif_notInOverflow() {
+        mEntryListener.onEntryAdded(mRow);
+        mBubbleController.updateBubble(mBubbleEntry);
+        when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
+
+        assertTrue(mBubbleController.hasBubbles());
+        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
+
+        // Make it look like dismissed notif
+        mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
+
+        // Now remove the bubble
+        mBubbleController.removeBubble(
+                mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
+        assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
+
+        // Since the notif is dismissed and not in overflow, once the bubble is removed,
+        // removeNotification gets called to really remove the notif
+        verify(mNotifCallback, times(1)).removeNotification(eq(mRow),
+                any(), anyInt());
+        assertFalse(mBubbleController.hasBubbles());
     }
 
     @Test
     public void testDismissStack() {
         mBubbleController.updateBubble(mBubbleEntry);
-        verify(mNotificationEntryManager, times(1)).updateNotifications(any());
+        verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
         assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
         mBubbleController.updateBubble(mBubbleEntry2);
-        verify(mNotificationEntryManager, times(2)).updateNotifications(any());
+        verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
         assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
         assertTrue(mBubbleController.hasBubbles());
 
         mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
-        verify(mNotificationEntryManager, times(3)).updateNotifications(any());
+        verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
 
@@ -528,7 +472,7 @@
         assertStackCollapsed();
 
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
 
         // We should have bubbles & their notifs should not be suppressed
@@ -536,7 +480,6 @@
         assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
 
         // Expand the stack
-        BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
@@ -556,8 +499,8 @@
     @Ignore("Currently broken.")
     public void testCollapseAfterChangingExpandedBubble() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow);
-        mEntryListener.onPendingEntryAdded(mRow2);
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
         mBubbleController.updateBubble(mBubbleEntry);
         mBubbleController.updateBubble(mBubbleEntry2);
 
@@ -593,6 +536,7 @@
         verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
                 true, mRow.getKey());
 
+
         // Collapse
         mBubbleController.collapseStack();
         assertStackCollapsed();
@@ -602,7 +546,7 @@
     @Test
     public void testExpansionRemovesShowInShadeAndDot() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
 
         // We should have bubbles & their notifs should not be suppressed
@@ -627,7 +571,7 @@
     @Test
     public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
 
         // We should have bubbles & their notifs should not be suppressed
@@ -649,7 +593,7 @@
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
 
         // Send update
-        mEntryListener.onPreEntryUpdated(mRow);
+        mEntryListener.onEntryUpdated(mRow);
 
         // Nothing should have changed
         // Notif is suppressed after expansion
@@ -661,8 +605,8 @@
     @Test
     public void testRemoveLastExpanded_collapses() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow);
-        mEntryListener.onPendingEntryAdded(mRow2);
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
         mBubbleController.updateBubble(mBubbleEntry);
         mBubbleController.updateBubble(mBubbleEntry2);
 
@@ -707,7 +651,7 @@
     @Test
     public void testRemoveLastExpandedEmptyOverflow_collapses() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
 
         // Expand
@@ -732,6 +676,7 @@
         assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
+
     @Test
     public void testAutoExpand_fails_noFlag() {
         assertStackCollapsed();
@@ -739,7 +684,7 @@
                 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
 
         // Expansion shouldn't change
@@ -755,7 +700,7 @@
                 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
 
         // Expansion should change
@@ -771,7 +716,7 @@
                 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
 
         // Add the suppress notif bubble
-        mEntryListener.onPendingEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
 
         // Notif should be suppressed because we were foreground
@@ -805,22 +750,8 @@
     }
 
     @Test
-    public void testExpandStackAndSelectBubble_removedFirst() {
-        mEntryListener.onPendingEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        // Simulate notification cancellation.
-        mRemoveInterceptor.onNotificationRemoveRequested(
-                mRow.getKey(), mRow, REASON_APP_CANCEL);
-
-        mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
-
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
     public void testMarkNewNotificationAsShowInShade() {
-        mEntryListener.onPendingEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow);
         assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
 
         mTestableLooper.processAllMessages();
@@ -829,8 +760,8 @@
 
     @Test
     public void testAddNotif_notBubble() {
-        mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry());
-        mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry());
+        mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
+        mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
 
         assertThat(mBubbleController.hasBubbles()).isFalse();
     }
@@ -868,48 +799,33 @@
         NotificationListenerService.Ranking ranking = new RankingBuilder(
                 mRow.getRanking()).setCanBubble(false).build();
         mRow.setRanking(ranking);
-        mEntryListener.onPreEntryUpdated(mRow);
+        mEntryListener.onEntryUpdated(mRow);
 
         assertFalse(mBubbleController.hasBubbles());
         verify(mDeleteIntent, never()).send();
     }
 
     @Test
-    public void testRemoveBubble_succeeds_appCancel() {
-        mEntryListener.onPendingEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        assertTrue(mBubbleController.hasBubbles());
-
-        boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
-                mRow.getKey(), mRow, REASON_APP_CANCEL);
-
-        // Cancels always remove so no need to intercept
-        assertFalse(intercepted);
-    }
-
-    @Test
     public void testRemoveBubble_entryListenerRemove() {
-        mEntryListener.onPendingEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
 
         assertTrue(mBubbleController.hasBubbles());
 
         // Removes the notification
-        mEntryListener.onEntryRemoved(mRow, null, false, REASON_APP_CANCEL);
+        mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
         assertFalse(mBubbleController.hasBubbles());
     }
 
     @Test
-    public void removeBubble_clearAllIntercepted()  {
-        mEntryListener.onPendingEntryAdded(mRow);
+    public void removeBubble_intercepted() {
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
 
         assertTrue(mBubbleController.hasBubbles());
         assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
 
-        boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
-                mRow.getKey(), mRow, REASON_CANCEL_ALL);
+        boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
 
         // Intercept!
         assertTrue(intercepted);
@@ -918,99 +834,51 @@
     }
 
     @Test
-    public void removeBubble_userDismissNotifIntercepted() {
-        mEntryListener.onPendingEntryAdded(mRow);
+    public void removeBubble_dismissIntoOverflow_intercepted() {
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
 
         assertTrue(mBubbleController.hasBubbles());
         assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
 
-        boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
-                mRow.getKey(), mRow, REASON_CANCEL);
-
-        // Intercept!
-        assertTrue(intercepted);
-        // Should update show in shade state
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-    }
-
-    @Test
-    public void removeNotif_inOverflow_intercepted() {
-        // Get bubble with notif in shade.
-        mEntryListener.onPendingEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        // Dismiss the bubble into overflow.
-        mBubbleController.removeBubble(
-                mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+        // Dismiss the bubble
+        mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
         assertFalse(mBubbleController.hasBubbles());
 
-        boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
-                mRow.getKey(), mRow, REASON_CANCEL);
+        // Dismiss the notification
+        boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
 
-        // Notif is no longer a bubble, but still in overflow, so we intercept removal.
+        // Intercept dismissal since bubble is going into overflow
         assertTrue(intercepted);
     }
 
     @Test
-    public void removeNotif_notInOverflow_notIntercepted() {
-        // Get bubble with notif in shade.
-        mEntryListener.onPendingEntryAdded(mRow);
+    public void removeBubble_notIntercepted() {
+        mEntryListener.onEntryAdded(mRow);
         mBubbleController.updateBubble(mBubbleEntry);
 
         assertTrue(mBubbleController.hasBubbles());
         assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
 
-        mBubbleController.removeBubble(
-                mRow.getKey(), Bubbles.DISMISS_NO_LONGER_BUBBLE);
+        // Dismiss the bubble
+        mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
         assertFalse(mBubbleController.hasBubbles());
 
-        boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
-                mRow.getKey(), mRow, REASON_CANCEL);
+        // Dismiss the notification
+        boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
 
-        // Notif is no longer a bubble, so we should not intercept removal.
+        // Not a bubble anymore so we don't intercept dismissal.
         assertFalse(intercepted);
     }
 
     @Test
-    public void testOverflowBubble_maxReached_notInShade_bubbleRemoved() {
-        mBubbleController.updateBubble(
-                mBubbleEntry, /* suppressFlyout */ false, /* showInShade */ false);
-        mBubbleController.updateBubble(
-                mBubbleEntry2, /* suppressFlyout */ false, /* showInShade */ false);
-        mBubbleController.updateBubble(
-                mBubbleEntry3, /* suppressFlyout */ false, /* showInShade */ false);
-        when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
-        when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
-        when(mCommonNotifCollection.getEntry(mRow3.getKey())).thenReturn(mRow3);
-        assertEquals(mBubbleData.getBubbles().size(), 3);
-
-        mBubbleData.setMaxOverflowBubbles(1);
-        mBubbleController.removeBubble(
-                mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
-        assertEquals(mBubbleData.getBubbles().size(), 2);
-        assertEquals(mBubbleData.getOverflowBubbles().size(), 1);
-
-        mBubbleController.removeBubble(
-                mRow2.getKey(), Bubbles.DISMISS_USER_GESTURE);
-        // Overflow max of 1 is reached; mRow is oldest, so it gets removed
-        verify(mNotificationEntryManager, times(1)).performRemoveNotification(
-                eq(mRow.getSbn()), any(), eq(REASON_CANCEL));
-        assertEquals(mBubbleData.getBubbles().size(), 1);
-        assertEquals(mBubbleData.getOverflowBubbles().size(), 1);
-    }
-
-    @Test
     public void testNotifyShadeSuppressionChange_notificationDismiss() {
-        mEntryListener.onPendingEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow);
 
         assertTrue(mBubbleController.hasBubbles());
         assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
 
-        mRemoveInterceptor.onNotificationRemoveRequested(
-                mRow.getKey(), mRow, REASON_CANCEL);
+        mBubblesManager.handleDismissalInterception(mRow);
 
         // Should update show in shade state
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -1022,7 +890,7 @@
 
     @Test
     public void testNotifyShadeSuppressionChange_bubbleExpanded() {
-        mEntryListener.onPendingEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow);
 
         assertTrue(mBubbleController.hasBubbles());
         assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
@@ -1042,9 +910,9 @@
         // GIVEN a group summary with a bubble child
         ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
         ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+        mEntryListener.onEntryAdded(groupedBubble.getEntry());
         when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
                 .thenReturn(groupedBubble.getEntry());
-        mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
         groupSummary.addChildNotification(groupedBubble);
         assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
 
@@ -1054,10 +922,10 @@
         // THEN the summary and bubbled child are suppressed from the shade
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
                 groupedBubble.getEntry().getKey(),
-                groupSummary.getEntry().getSbn().getGroupKey()));
+                groupedBubble.getEntry().getSbn().getGroupKey()));
         assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
                 groupedBubble.getEntry().getKey(),
-                groupSummary.getEntry().getSbn().getGroupKey()));
+                groupedBubble.getEntry().getSbn().getGroupKey()));
         assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey()));
     }
 
@@ -1066,7 +934,7 @@
         // GIVEN a group summary with a bubble child
         ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
         ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
-        mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
+        mEntryListener.onEntryAdded(groupedBubble.getEntry());
         when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
                 .thenReturn(groupedBubble.getEntry());
         groupSummary.addChildNotification(groupedBubble);
@@ -1076,7 +944,7 @@
         mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
 
         // WHEN the summary is cancelled by the app
-        mEntryListener.onEntryRemoved(groupSummary.getEntry(), null, false, REASON_APP_CANCEL);
+        mEntryListener.onEntryRemoved(groupSummary.getEntry(), REASON_APP_CANCEL);
 
         // THEN the summary and its children are removed from bubble data
         assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
@@ -1085,14 +953,14 @@
     }
 
     @Test
-    public void testSummaryDismissal_marksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
+    public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
             throws Exception {
         // GIVEN a group summary with two (non-bubble) children and one bubble child
         ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
         ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+        mEntryListener.onEntryAdded(groupedBubble.getEntry());
         when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
                 .thenReturn(groupedBubble.getEntry());
-        mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
         groupSummary.addChildNotification(groupedBubble);
 
         // WHEN the summary is dismissed
@@ -1100,16 +968,15 @@
 
         // THEN only the NON-bubble children are dismissed
         List<ExpandableNotificationRow> childrenRows = groupSummary.getAttachedChildren();
-        verify(mNotificationEntryManager, times(1)).performRemoveNotification(
-                eq(childrenRows.get(0).getEntry().getSbn()), any(),
-                eq(REASON_GROUP_SUMMARY_CANCELED));
-        verify(mNotificationEntryManager, times(1)).performRemoveNotification(
-                eq(childrenRows.get(1).getEntry().getSbn()), any(),
-                eq(REASON_GROUP_SUMMARY_CANCELED));
-        verify(mNotificationEntryManager, never()).performRemoveNotification(
-                eq(groupedBubble.getEntry().getSbn()), any(), anyInt());
+        verify(mNotifCallback, times(1)).removeNotification(
+                eq(childrenRows.get(0).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
+        verify(mNotifCallback, times(1)).removeNotification(
+                eq(childrenRows.get(1).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
+        verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()),
+                any(), anyInt());
 
-        // THEN the bubble child is suppressed from the shade
+        // THEN the bubble child still exists as a bubble and is suppressed from the shade
+        assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
                 groupedBubble.getEntry().getKey(),
                 groupedBubble.getEntry().getSbn().getGroupKey()));
@@ -1117,34 +984,17 @@
                 groupedBubble.getEntry().getKey(),
                 groupedBubble.getEntry().getSbn().getGroupKey()));
 
-        // THEN the summary is removed from GroupManager
-        verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry());
+        // THEN the summary is also suppressed from the shade
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                groupSummary.getEntry().getKey(),
+                groupSummary.getEntry().getSbn().getGroupKey()));
+        assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
+                groupSummary.getEntry().getKey(),
+                groupSummary.getEntry().getSbn().getGroupKey()));
     }
 
 
     /**
-     * Verifies that when a non visually interruptive update occurs for a bubble in the overflow,
-     * the that bubble does not get promoted from the overflow.
-     */
-    @Test
-    public void test_notVisuallyInterruptive_updateOverflowBubble_notAdded() {
-        // Setup
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleController.updateBubble(mBubbleEntry2);
-        assertTrue(mBubbleController.hasBubbles());
-
-        // Overflow it
-        mBubbleData.dismissBubbleWithKey(mRow.getKey(),
-                Bubbles.DISMISS_USER_GESTURE);
-        assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getKey())).isFalse();
-        assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue();
-
-        // Test
-        mBubbleController.updateBubble(mBubbleEntry);
-        assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getKey())).isFalse();
-    }
-
-    /**
      * Verifies that when the user changes, the bubbles in the overflow list is cleared. Doesn't
      * test the loading from the repository which would be a nice thing to add.
      */
@@ -1185,15 +1035,17 @@
      */
     @Test
     public void testOverflowLoadedOnce() {
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleController.updateBubble(mBubbleEntry2);
-        mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
-        assertThat(mBubbleData.getOverflowBubbles().isEmpty()).isFalse();
+        // XXX
+        when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
+        when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
 
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleController.updateBubble(mBubbleEntry2);
-        mBubbleController.removeBubble(mBubbleEntry.getKey(), DISMISS_NOTIF_CANCEL);
-        mBubbleController.removeBubble(mBubbleEntry2.getKey(), DISMISS_NOTIF_CANCEL);
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
+        assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+
+        mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
+        mEntryListener.onEntryRemoved(mRow2, REASON_APP_CANCEL);
         assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
 
         verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
@@ -1376,6 +1228,7 @@
         assertStackCollapsed();
     }
 
+
     @Test
     public void testRegisterUnregisterBroadcastListener() {
         spyOn(mContext);
@@ -1455,7 +1308,7 @@
 
     @Test
     public void testSetShouldAutoExpand_notifiesFlagChanged() {
-        mEntryListener.onPendingEntryAdded(mRow);
+        mBubbleController.updateBubble(mBubbleEntry);
 
         assertTrue(mBubbleController.hasBubbles());
         Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
@@ -1551,7 +1404,7 @@
     }
 
     /**
-     * Sets the bubble metadata flags for this entry. These ]flags are normally set by
+     * Sets the bubble metadata flags for this entry. These flags are normally set by
      * NotificationManagerService when the notification is sent, however, these tests do not
      * go through that path so we set them explicitly when testing.
      */
@@ -1570,12 +1423,15 @@
     private Notification.BubbleMetadata getMetadata() {
         Intent target = new Intent(mContext, BubblesTestActivity.class);
         PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, FLAG_MUTABLE);
-
-        return new Notification.BubbleMetadata.Builder(bubbleIntent,
-                Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble))
+        return new Notification.BubbleMetadata.Builder(
+                bubbleIntent,
+                Icon.createWithResource(
+                        mContext,
+                        com.android.wm.shell.R.drawable.bubble_ic_create_bubble))
                 .build();
     }
 
+
     /**
      * Asserts that the bubble stack is expanded and also validates the cached state is updated.
      */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
deleted file mode 100644
index a6327b9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ /dev/null
@@ -1,1401 +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.wmshell;
-
-import static android.app.Notification.FLAG_BUBBLE;
-import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
-import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.IActivityManager;
-import android.app.INotificationManager;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.LauncherApps;
-import android.content.pm.UserInfo;
-import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.service.dreams.IDreamManager;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.ZenModeConfig;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.View;
-import android.view.WindowManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationFilter;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.bubbles.BubbleData;
-import com.android.wm.shell.bubbles.BubbleDataRepository;
-import com.android.wm.shell.bubbles.BubbleEntry;
-import com.android.wm.shell.bubbles.BubbleLogger;
-import com.android.wm.shell.bubbles.BubbleStackView;
-import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.onehanded.OneHandedController;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Tests the NotifPipeline setup with BubbleController.
- * The NotificationEntryManager setup with BubbleController is tested in
- * {@link BubblesTest}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NewNotifPipelineBubblesTest extends SysuiTestCase {
-    @Mock
-    private NotificationEntryManager mNotificationEntryManager;
-    @Mock
-    private CommonNotifCollection mCommonNotifCollection;
-    @Mock
-    private NotificationGroupManagerLegacy mNotificationGroupManager;
-    @Mock
-    private BubblesManager.NotifCallback mNotifCallback;
-    @Mock
-    private WindowManager mWindowManager;
-    @Mock
-    private IActivityManager mActivityManager;
-    @Mock
-    private DozeParameters mDozeParameters;
-    @Mock
-    private ConfigurationController mConfigurationController;
-    @Mock
-    private ZenModeController mZenModeController;
-    @Mock
-    private ZenModeConfig mZenModeConfig;
-    @Mock
-    private NotificationLockscreenUserManager mLockscreenUserManager;
-    @Mock
-    private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock
-    private KeyguardViewMediator mKeyguardViewMediator;
-    @Mock
-    private KeyguardBypassController mKeyguardBypassController;
-    @Mock
-    private FloatingContentCoordinator mFloatingContentCoordinator;
-    @Mock
-    private BubbleDataRepository mDataRepository;
-    @Mock
-    private NotificationShadeWindowView mNotificationShadeWindowView;
-    @Mock
-    private AuthController mAuthController;
-
-    private SysUiState mSysUiState;
-    private boolean mSysUiStateBubblesExpanded;
-    private boolean mSysUiStateBubblesManageMenuExpanded;
-
-    @Captor
-    private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
-    @Captor
-    private ArgumentCaptor<List<Bubble>> mBubbleListCaptor;
-    @Captor
-    private ArgumentCaptor<IntentFilter> mFilterArgumentCaptor;
-    @Captor
-    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
-
-    private BubblesManager mBubblesManager;
-    private TestableBubbleController mBubbleController;
-    private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
-    private NotifCollectionListener mEntryListener;
-    private NotificationTestHelper mNotificationTestHelper;
-    private NotificationEntry mRow;
-    private NotificationEntry mRow2;
-    private ExpandableNotificationRow mNonBubbleNotifRow;
-    private BubbleEntry mBubbleEntry;
-    private BubbleEntry mBubbleEntry2;
-
-    private BubbleEntry mBubbleEntryUser11;
-    private BubbleEntry mBubbleEntry2User11;
-
-    @Mock
-    private Bubbles.BubbleExpandListener mBubbleExpandListener;
-    @Mock
-    private PendingIntent mDeleteIntent;
-    @Mock
-    private SysuiColorExtractor mColorExtractor;
-    @Mock
-    ColorExtractor.GradientColors mGradientColors;
-    @Mock
-    private ShadeController mShadeController;
-    @Mock
-    private NotifPipeline mNotifPipeline;
-    @Mock
-    private NotifPipelineFlags mNotifPipelineFlags;
-    @Mock
-    private DumpManager mDumpManager;
-    @Mock
-    private IStatusBarService mStatusBarService;
-    @Mock
-    private NotificationVisibilityProvider mVisibilityProvider;
-    @Mock
-    private LauncherApps mLauncherApps;
-    @Mock
-    private WindowManagerShellWrapper mWindowManagerShellWrapper;
-    @Mock
-    private BubbleLogger mBubbleLogger;
-    @Mock
-    private TaskStackListenerImpl mTaskStackListener;
-    @Mock
-    private ShellTaskOrganizer mShellTaskOrganizer;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private ScreenOffAnimationController mScreenOffAnimationController;
-    @Mock
-    private TaskViewTransitions mTaskViewTransitions;
-    @Mock
-    private Optional<OneHandedController> mOneHandedOptional;
-
-    private TestableBubblePositioner mPositioner;
-
-    private BubbleData mBubbleData;
-
-    private TestableLooper mTestableLooper;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mTestableLooper = TestableLooper.get(this);
-
-        // For the purposes of this test, just run everything synchronously
-        ShellExecutor syncExecutor = new SyncExecutor();
-
-        when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
-
-        mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
-                mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
-                mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
-                mColorExtractor, mDumpManager, mKeyguardStateController,
-                mScreenOffAnimationController, mAuthController);
-        mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
-        mNotificationShadeWindowController.attach();
-
-        // Need notifications for bubbles
-        mNotificationTestHelper = new NotificationTestHelper(
-                mContext,
-                mDependency,
-                TestableLooper.get(this));
-        mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
-        mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
-        mNonBubbleNotifRow = mNotificationTestHelper.createRow();
-        mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow);
-        mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2);
-
-        UserHandle handle = mock(UserHandle.class);
-        when(handle.getIdentifier()).thenReturn(11);
-        mBubbleEntryUser11 = BubblesManager.notifToBubbleEntry(
-                mNotificationTestHelper.createBubble(handle));
-        mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry(
-                mNotificationTestHelper.createBubble(handle));
-
-        mZenModeConfig.suppressedVisualEffects = 0;
-        when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
-
-        mSysUiState = new SysUiState();
-        mSysUiState.addCallback(sysUiFlags -> {
-            mSysUiStateBubblesManageMenuExpanded =
-                    (sysUiFlags
-                            & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
-            mSysUiStateBubblesExpanded =
-                    (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
-        });
-
-        mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
-        mPositioner.setMaxBubbles(5);
-        mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
-
-        TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
-                new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
-                        mock(PowerManager.class),
-                        mock(IDreamManager.class),
-                        mock(AmbientDisplayConfiguration.class),
-                        mock(NotificationFilter.class),
-                        mock(StatusBarStateController.class),
-                        mock(BatteryController.class),
-                        mock(HeadsUpManager.class),
-                        mock(NotificationInterruptLogger.class),
-                        mock(Handler.class),
-                        mock(NotifPipelineFlags.class),
-                        mock(KeyguardNotificationVisibilityProvider.class)
-                );
-        when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true);
-        when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
-        mBubbleController = new TestableBubbleController(
-                mContext,
-                mBubbleData,
-                mFloatingContentCoordinator,
-                mDataRepository,
-                mStatusBarService,
-                mWindowManager,
-                mWindowManagerShellWrapper,
-                mock(UserManager.class),
-                mLauncherApps,
-                mBubbleLogger,
-                mTaskStackListener,
-                mShellTaskOrganizer,
-                mPositioner,
-                mock(DisplayController.class),
-                mOneHandedOptional,
-                mock(DragAndDropController.class),
-                syncExecutor,
-                mock(Handler.class),
-                mTaskViewTransitions,
-                mock(SyncTransactionQueue.class));
-        mBubbleController.setExpandListener(mBubbleExpandListener);
-        spyOn(mBubbleController);
-
-        mBubblesManager = new BubblesManager(
-                mContext,
-                mBubbleController.asBubbles(),
-                mNotificationShadeWindowController,
-                mock(KeyguardStateController.class),
-                mShadeController,
-                mConfigurationController,
-                mStatusBarService,
-                mock(INotificationManager.class),
-                mVisibilityProvider,
-                interruptionStateProvider,
-                mZenModeController,
-                mLockscreenUserManager,
-                mNotificationGroupManager,
-                mNotificationEntryManager,
-                mCommonNotifCollection,
-                mNotifPipeline,
-                mSysUiState,
-                mNotifPipelineFlags,
-                mDumpManager,
-                syncExecutor);
-        mBubblesManager.addNotifCallback(mNotifCallback);
-
-        // Get a reference to the BubbleController's entry listener
-        verify(mNotifPipeline, atLeastOnce())
-                .addCollectionListener(mNotifListenerCaptor.capture());
-        mEntryListener = mNotifListenerCaptor.getValue();
-    }
-
-    @Test
-    public void testAddBubble() {
-        mBubbleController.updateBubble(mBubbleEntry);
-        assertTrue(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testHasBubbles() {
-        assertFalse(mBubbleController.hasBubbles());
-        mBubbleController.updateBubble(mBubbleEntry);
-        assertTrue(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testRemoveBubble() {
-        mBubbleController.updateBubble(mBubbleEntry);
-        assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
-        assertTrue(mBubbleController.hasBubbles());
-        verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
-
-        mBubbleController.removeBubble(
-                mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
-        assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
-        verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
-
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testRemoveBubble_withDismissedNotif_inOverflow() {
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        // Make it look like dismissed notif
-        mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
-
-        // Now remove the bubble
-        mBubbleController.removeBubble(
-                mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
-        assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
-
-        // We don't remove the notification since the bubble is still in overflow.
-        verify(mNotifCallback, never()).removeNotification(eq(mRow), any(), anyInt());
-        assertFalse(mBubbleController.hasBubbles());
-    }
-
-    @Test
-    public void testRemoveBubble_withDismissedNotif_notInOverflow() {
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-        when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
-
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        // Make it look like dismissed notif
-        mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
-
-        // Now remove the bubble
-        mBubbleController.removeBubble(
-                mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
-        assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
-
-        // Since the notif is dismissed and not in overflow, once the bubble is removed,
-        // removeNotification gets called to really remove the notif
-        verify(mNotifCallback, times(1)).removeNotification(eq(mRow),
-                any(), anyInt());
-        assertFalse(mBubbleController.hasBubbles());
-    }
-
-    @Test
-    public void testDismissStack() {
-        mBubbleController.updateBubble(mBubbleEntry);
-        verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
-        assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
-        mBubbleController.updateBubble(mBubbleEntry2);
-        verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
-        assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
-        assertTrue(mBubbleController.hasBubbles());
-
-        mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
-        verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
-        assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
-        assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
-
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testExpandCollapseStack() {
-        assertStackCollapsed();
-
-        // Mark it as a bubble and add it explicitly
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        // We should have bubbles & their notifs should not be suppressed
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        // Expand the stack
-        mBubbleData.setExpanded(true);
-        assertStackExpanded();
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
-        // Make sure the notif is suppressed
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
-        // Collapse
-        mBubbleController.collapseStack();
-        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
-        assertStackCollapsed();
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    @Ignore("Currently broken.")
-    public void testCollapseAfterChangingExpandedBubble() {
-        // Mark it as a bubble and add it explicitly
-        mEntryListener.onEntryAdded(mRow);
-        mEntryListener.onEntryAdded(mRow2);
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleController.updateBubble(mBubbleEntry2);
-
-        // We should have bubbles & their notifs should not be suppressed
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry2);
-
-        // Expand
-        BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleData.setExpanded(true);
-        assertStackExpanded();
-        verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
-                true, mRow2.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
-        // Last added is the one that is expanded
-        assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry2);
-
-        // Switch which bubble is expanded
-        mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(
-                mRow.getKey()));
-        mBubbleData.setExpanded(true);
-        assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey(
-                stackView.getExpandedBubble().getKey()).getKey());
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
-        // collapse for previous bubble
-        verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
-                false, mRow2.getKey());
-        // expand for selected bubble
-        verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
-                true, mRow.getKey());
-
-
-        // Collapse
-        mBubbleController.collapseStack();
-        assertStackCollapsed();
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testExpansionRemovesShowInShadeAndDot() {
-        // Mark it as a bubble and add it explicitly
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        // We should have bubbles & their notifs should not be suppressed
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        mTestableLooper.processAllMessages();
-        assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
-        // Expand
-        mBubbleData.setExpanded(true);
-        assertStackExpanded();
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
-        // Notif is suppressed after expansion
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-        // Notif shouldn't show dot after expansion
-        assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-    }
-
-    @Test
-    public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
-        // Mark it as a bubble and add it explicitly
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        // We should have bubbles & their notifs should not be suppressed
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        mTestableLooper.processAllMessages();
-        assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
-        // Expand
-        mBubbleData.setExpanded(true);
-        assertStackExpanded();
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
-        // Notif is suppressed after expansion
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-        // Notif shouldn't show dot after expansion
-        assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
-        // Send update
-        mEntryListener.onEntryUpdated(mRow);
-
-        // Nothing should have changed
-        // Notif is suppressed after expansion
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-        // Notif shouldn't show dot after expansion
-        assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-    }
-
-    @Test
-    public void testRemoveLastExpanded_collapses() {
-        // Mark it as a bubble and add it explicitly
-        mEntryListener.onEntryAdded(mRow);
-        mEntryListener.onEntryAdded(mRow2);
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleController.updateBubble(mBubbleEntry2);
-
-        // Expand
-        BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleData.setExpanded(true);
-
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
-        assertStackExpanded();
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
-
-        // Last added is the one that is expanded
-        assertEquals(mRow2.getKey(), mBubbleData.getBubbleInStackWithKey(
-                stackView.getExpandedBubble().getKey()).getKey());
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry2);
-
-        // Dismiss currently expanded
-        mBubbleController.removeBubble(
-                mBubbleData.getBubbleInStackWithKey(
-                        stackView.getExpandedBubble().getKey()).getKey(),
-                Bubbles.DISMISS_USER_GESTURE);
-        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getKey());
-
-        // Make sure first bubble is selected
-        assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey(
-                stackView.getExpandedBubble().getKey()).getKey());
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
-        // Dismiss that one
-        mBubbleController.removeBubble(
-                mBubbleData.getBubbleInStackWithKey(
-                        stackView.getExpandedBubble().getKey()).getKey(),
-                Bubbles.DISMISS_USER_GESTURE);
-
-        // We should be collapsed
-        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
-        assertFalse(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testRemoveLastExpandedEmptyOverflow_collapses() {
-        // Mark it as a bubble and add it explicitly
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        // Expand
-        BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleData.setExpanded(true);
-
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-        assertStackExpanded();
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
-        // Block the bubble so it won't be in the overflow
-        mBubbleController.removeBubble(
-                mBubbleData.getBubbleInStackWithKey(
-                        stackView.getExpandedBubble().getKey()).getKey(),
-                Bubbles.DISMISS_BLOCKED);
-
-        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
-
-        // We should be collapsed
-        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
-        assertFalse(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-
-    @Test
-    public void testAutoExpand_fails_noFlag() {
-        assertStackCollapsed();
-        setMetadataFlags(mRow,
-                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
-
-        // Add the auto expand bubble
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        // Expansion shouldn't change
-        verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
-                mRow.getKey());
-        assertStackCollapsed();
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testAutoExpand_succeeds_withFlag() {
-        setMetadataFlags(mRow,
-                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
-
-        // Add the auto expand bubble
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        // Expansion should change
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
-                mRow.getKey());
-        assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testSuppressNotif_onInitialNotif() {
-        setMetadataFlags(mRow,
-                Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
-
-        // Add the suppress notif bubble
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        // Notif should be suppressed because we were foreground
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-        // Dot + flyout is hidden because notif is suppressed
-        assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-        assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testSuppressNotif_onUpdateNotif() {
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        // Should not be suppressed
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-        // Should show dot
-        assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
-        // Update to suppress notif
-        setMetadataFlags(mRow,
-                Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        // Notif should be suppressed
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-        // Dot + flyout is hidden because notif is suppressed
-        assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-        assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testMarkNewNotificationAsShowInShade() {
-        mEntryListener.onEntryAdded(mRow);
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        mTestableLooper.processAllMessages();
-        assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-    }
-
-    @Test
-    public void testAddNotif_notBubble() {
-        mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
-        mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
-
-        assertThat(mBubbleController.hasBubbles()).isFalse();
-    }
-
-    @Test
-    public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_AGED);
-        verify(mDeleteIntent, never()).send();
-    }
-
-    @Test
-    public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleController.removeBubble(
-                mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
-        verify(mDeleteIntent, times(1)).send();
-    }
-
-    @Test
-    public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleController.updateBubble(mBubbleEntry2);
-        mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
-        verify(mDeleteIntent, times(2)).send();
-    }
-
-    @Test
-    public void testRemoveBubble_noLongerBubbleAfterUpdate()
-            throws PendingIntent.CanceledException {
-        mBubbleController.updateBubble(mBubbleEntry);
-        assertTrue(mBubbleController.hasBubbles());
-
-        mRow.getSbn().getNotification().flags &= ~FLAG_BUBBLE;
-        NotificationListenerService.Ranking ranking = new RankingBuilder(
-                mRow.getRanking()).setCanBubble(false).build();
-        mRow.setRanking(ranking);
-        mEntryListener.onEntryUpdated(mRow);
-
-        assertFalse(mBubbleController.hasBubbles());
-        verify(mDeleteIntent, never()).send();
-    }
-
-    @Test
-    public void testRemoveBubble_entryListenerRemove() {
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        assertTrue(mBubbleController.hasBubbles());
-
-        // Removes the notification
-        mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
-        assertFalse(mBubbleController.hasBubbles());
-    }
-
-    @Test
-    public void removeBubble_intercepted() {
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
-
-        // Intercept!
-        assertTrue(intercepted);
-        // Should update show in shade state
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-    }
-
-    @Test
-    public void removeBubble_dismissIntoOverflow_intercepted() {
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        // Dismiss the bubble
-        mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
-        assertFalse(mBubbleController.hasBubbles());
-
-        // Dismiss the notification
-        boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
-
-        // Intercept dismissal since bubble is going into overflow
-        assertTrue(intercepted);
-    }
-
-    @Test
-    public void removeBubble_notIntercepted() {
-        mEntryListener.onEntryAdded(mRow);
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        // Dismiss the bubble
-        mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
-        assertFalse(mBubbleController.hasBubbles());
-
-        // Dismiss the notification
-        boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
-
-        // Not a bubble anymore so we don't intercept dismissal.
-        assertFalse(intercepted);
-    }
-
-    @Test
-    public void testNotifyShadeSuppressionChange_notificationDismiss() {
-        mEntryListener.onEntryAdded(mRow);
-
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        mBubblesManager.handleDismissalInterception(mRow);
-
-        // Should update show in shade state
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
-        // Should notify delegate that shade state changed
-        verify(mBubbleController).onBubbleMetadataFlagChanged(
-                mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
-    }
-
-    @Test
-    public void testNotifyShadeSuppressionChange_bubbleExpanded() {
-        mEntryListener.onEntryAdded(mRow);
-
-        assertTrue(mBubbleController.hasBubbles());
-        assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
-        mBubbleData.setExpanded(true);
-
-        // Once a bubble is expanded the notif is suppressed
-        assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
-        // Should notify delegate that shade state changed
-        verify(mBubbleController).onBubbleMetadataFlagChanged(
-                mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
-    }
-
-    @Test
-    public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception {
-        // GIVEN a group summary with a bubble child
-        ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
-        ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
-        mEntryListener.onEntryAdded(groupedBubble.getEntry());
-        when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
-                .thenReturn(groupedBubble.getEntry());
-        groupSummary.addChildNotification(groupedBubble);
-        assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
-
-        // WHEN the summary is dismissed
-        mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
-
-        // THEN the summary and bubbled child are suppressed from the shade
-        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
-                groupedBubble.getEntry().getKey(),
-                groupedBubble.getEntry().getSbn().getGroupKey()));
-        assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
-                groupedBubble.getEntry().getKey(),
-                groupedBubble.getEntry().getSbn().getGroupKey()));
-        assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey()));
-    }
-
-    @Test
-    public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception {
-        // GIVEN a group summary with a bubble child
-        ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
-        ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
-        mEntryListener.onEntryAdded(groupedBubble.getEntry());
-        when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
-                .thenReturn(groupedBubble.getEntry());
-        groupSummary.addChildNotification(groupedBubble);
-        assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
-
-        // GIVEN the summary is dismissed
-        mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
-
-        // WHEN the summary is cancelled by the app
-        mEntryListener.onEntryRemoved(groupSummary.getEntry(), REASON_APP_CANCEL);
-
-        // THEN the summary and its children are removed from bubble data
-        assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
-        assertFalse(mBubbleData.isSummarySuppressed(
-                groupSummary.getEntry().getSbn().getGroupKey()));
-    }
-
-    @Test
-    public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
-            throws Exception {
-        // GIVEN a group summary with two (non-bubble) children and one bubble child
-        ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
-        ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
-        mEntryListener.onEntryAdded(groupedBubble.getEntry());
-        when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
-                .thenReturn(groupedBubble.getEntry());
-        groupSummary.addChildNotification(groupedBubble);
-
-        // WHEN the summary is dismissed
-        mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
-
-        // THEN only the NON-bubble children are dismissed
-        List<ExpandableNotificationRow> childrenRows = groupSummary.getAttachedChildren();
-        verify(mNotifCallback, times(1)).removeNotification(
-                eq(childrenRows.get(0).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
-        verify(mNotifCallback, times(1)).removeNotification(
-                eq(childrenRows.get(1).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
-        verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()),
-                any(), anyInt());
-
-        // THEN the bubble child still exists as a bubble and is suppressed from the shade
-        assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
-        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
-                groupedBubble.getEntry().getKey(),
-                groupedBubble.getEntry().getSbn().getGroupKey()));
-        assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
-                groupedBubble.getEntry().getKey(),
-                groupedBubble.getEntry().getSbn().getGroupKey()));
-
-        // THEN the summary is also suppressed from the shade
-        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
-                groupSummary.getEntry().getKey(),
-                groupSummary.getEntry().getSbn().getGroupKey()));
-        assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
-                groupSummary.getEntry().getKey(),
-                groupSummary.getEntry().getSbn().getGroupKey()));
-    }
-
-
-    /**
-     * Verifies that when the user changes, the bubbles in the overflow list is cleared. Doesn't
-     * test the loading from the repository which would be a nice thing to add.
-     */
-    @Test
-    public void testOnUserChanged_overflowState() {
-        int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier();
-        int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier();
-
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleController.updateBubble(mBubbleEntry2);
-        assertTrue(mBubbleController.hasBubbles());
-        mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
-
-        // Verify these are in the overflow
-        assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey())).isNotNull();
-        assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull();
-
-        // Switch users
-        mBubbleController.onUserChanged(secondUserId);
-        assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
-
-        // Give this user some bubbles
-        mBubbleController.updateBubble(mBubbleEntryUser11);
-        mBubbleController.updateBubble(mBubbleEntry2User11);
-        assertTrue(mBubbleController.hasBubbles());
-        mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
-
-        // Verify these are in the overflow
-        assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntryUser11.getKey())).isNotNull();
-        assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2User11.getKey())).isNotNull();
-
-        // Would have loaded bubbles twice because of user switch
-        verify(mDataRepository, times(2)).loadBubbles(anyInt(), any());
-    }
-
-    /**
-     * Verifies we only load the overflow data once.
-     */
-    @Test
-    public void testOverflowLoadedOnce() {
-        // XXX
-        when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
-        when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
-
-        mEntryListener.onEntryAdded(mRow);
-        mEntryListener.onEntryAdded(mRow2);
-        mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
-        assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
-
-        mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
-        mEntryListener.onEntryRemoved(mRow2, REASON_APP_CANCEL);
-        assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
-
-        verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
-    }
-
-    /**
-     * Verifies that shortcut deletions triggers that bubble being removed from XML.
-     */
-    @Test
-    public void testDeleteShortcutsDeletesXml() throws Exception {
-        ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId");
-        BubbleEntry shortcutBubbleEntry = BubblesManager.notifToBubbleEntry(row.getEntry());
-        mBubbleController.updateBubble(shortcutBubbleEntry);
-
-        mBubbleData.dismissBubbleWithKey(shortcutBubbleEntry.getKey(),
-                Bubbles.DISMISS_SHORTCUT_REMOVED);
-
-        verify(mDataRepository, atLeastOnce()).removeBubbles(anyInt(), mBubbleListCaptor.capture());
-        assertThat(mBubbleListCaptor.getValue().get(0).getKey()).isEqualTo(
-                shortcutBubbleEntry.getKey());
-    }
-
-    @Test
-    public void testShowManageMenuChangesSysuiState() {
-        mBubbleController.updateBubble(mBubbleEntry);
-        assertTrue(mBubbleController.hasBubbles());
-
-        // Expand the stack
-        BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleData.setExpanded(true);
-        assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
-        // Show the menu
-        stackView.showManageMenu(true);
-        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testHideManageMenuChangesSysuiState() {
-        mBubbleController.updateBubble(mBubbleEntry);
-        assertTrue(mBubbleController.hasBubbles());
-
-        // Expand the stack
-        BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleData.setExpanded(true);
-        assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
-        // Show the menu
-        stackView.showManageMenu(true);
-        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
-
-        // Hide the menu
-        stackView.showManageMenu(false);
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testCollapseBubbleManageMenuChangesSysuiState() {
-        mBubbleController.updateBubble(mBubbleEntry);
-        assertTrue(mBubbleController.hasBubbles());
-
-        // Expand the stack
-        BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleData.setExpanded(true);
-        assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
-        // Show the menu
-        stackView.showManageMenu(true);
-        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
-
-        // Collapse the stack
-        mBubbleData.setExpanded(false);
-
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
-    }
-
-    @Test
-    public void testNotificationChannelModified_channelUpdated_removesOverflowBubble()
-            throws Exception {
-        // Setup
-        ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId");
-        NotificationEntry entry = row.getEntry();
-        entry.getChannel().setConversationId(
-                row.getEntry().getChannel().getParentChannelId(),
-                "shortcutId");
-        mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry()));
-        assertTrue(mBubbleController.hasBubbles());
-
-        // Overflow it
-        mBubbleData.dismissBubbleWithKey(entry.getKey(),
-                Bubbles.DISMISS_USER_GESTURE);
-        assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue();
-
-        // Test
-        entry.getChannel().setDeleted(true);
-        mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(),
-                entry.getSbn().getUser(),
-                entry.getChannel(),
-                NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
-        assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse();
-    }
-
-    @Test
-    public void testNotificationChannelModified_channelDeleted_removesOverflowBubble()
-            throws Exception {
-        // Setup
-        ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId");
-        NotificationEntry entry = row.getEntry();
-        entry.getChannel().setConversationId(
-                row.getEntry().getChannel().getParentChannelId(),
-                "shortcutId");
-        mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry()));
-        assertTrue(mBubbleController.hasBubbles());
-
-        // Overflow it
-        mBubbleData.dismissBubbleWithKey(entry.getKey(),
-                Bubbles.DISMISS_USER_GESTURE);
-        assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue();
-
-        // Test
-        entry.getChannel().setDeleted(true);
-        mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(),
-                entry.getSbn().getUser(),
-                entry.getChannel(),
-                NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
-        assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse();
-    }
-
-    @Test
-    public void testStackViewOnBackPressed_updatesBubbleDataExpandState() {
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        // Expand the stack
-        mBubbleData.setExpanded(true);
-        assertStackExpanded();
-
-        // Hit back
-        BubbleStackView stackView = mBubbleController.getStackView();
-        stackView.onBackPressed();
-
-        // Make sure we're collapsed
-        assertStackCollapsed();
-    }
-
-
-    @Test
-    public void testRegisterUnregisterBroadcastListener() {
-        spyOn(mContext);
-        mBubbleController.updateBubble(mBubbleEntry);
-        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
-        assertThat(mFilterArgumentCaptor.getValue().getAction(0)).isEqualTo(
-                Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        assertThat(mFilterArgumentCaptor.getValue().getAction(1)).isEqualTo(
-                Intent.ACTION_SCREEN_OFF);
-
-        mBubbleData.dismissBubbleWithKey(mBubbleEntry.getKey(), REASON_APP_CANCEL);
-        // TODO: not certain why this isn't called normally when tests are run, perhaps because
-        // it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe
-        mBubbleController.onAllBubblesAnimatedOut();
-
-        verify(mContext).unregisterReceiver(eq(mBroadcastReceiverArgumentCaptor.getValue()));
-    }
-
-    @Test
-    public void testBroadcastReceiverCloseDialogs_notGestureNav() {
-        spyOn(mContext);
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleData.setExpanded(true);
-        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
-        Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
-
-        assertStackExpanded();
-    }
-
-    @Test
-    public void testBroadcastReceiverCloseDialogs_reasonGestureNav() {
-        spyOn(mContext);
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleData.setExpanded(true);
-
-        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
-        Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        i.putExtra("reason", "gestureNav");
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
-        assertStackCollapsed();
-    }
-
-    @Test
-    public void testBroadcastReceiver_screenOff() {
-        spyOn(mContext);
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleData.setExpanded(true);
-
-        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
-
-        Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
-        assertStackCollapsed();
-    }
-
-    @Test
-    public void testOnStatusBarStateChanged() {
-        mBubbleController.updateBubble(mBubbleEntry);
-        mBubbleData.setExpanded(true);
-        assertStackExpanded();
-        BubbleStackView stackView = mBubbleController.getStackView();
-        assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
-
-        mBubbleController.onStatusBarStateChanged(false);
-
-        assertStackCollapsed();
-        assertThat(stackView.getVisibility()).isEqualTo(View.INVISIBLE);
-
-        mBubbleController.onStatusBarStateChanged(true);
-        assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void testSetShouldAutoExpand_notifiesFlagChanged() {
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        assertTrue(mBubbleController.hasBubbles());
-        Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
-        assertThat(b.shouldAutoExpand()).isFalse();
-
-        // Set it to the same thing
-        b.setShouldAutoExpand(false);
-
-        // Verify it doesn't notify
-        verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any());
-
-        // Set it to something different
-        b.setShouldAutoExpand(true);
-        verify(mBubbleController).onBubbleMetadataFlagChanged(b);
-    }
-
-    @Test
-    public void testUpdateBubble_skipsDndSuppressListNotifs() {
-        mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
-                mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
-                mRow.shouldSuppressPeek());
-        mBubbleEntry.getBubbleMetadata().setFlags(
-                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
-
-        mBubbleController.updateBubble(mBubbleEntry);
-
-        Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey());
-        assertThat(b.shouldAutoExpand()).isFalse();
-        assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull();
-    }
-
-    @Test
-    public void testOnRankingUpdate_DndSuppressListNotif() {
-        // It's in the stack
-        mBubbleController.updateBubble(mBubbleEntry);
-        assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue();
-
-        // Set current user profile
-        SparseArray<UserInfo> userInfos = new SparseArray<>();
-        userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(),
-                mock(UserInfo.class));
-        mBubbleController.onCurrentProfilesChanged(userInfos);
-
-        // Send ranking update that the notif is suppressed from the list.
-        HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>();
-        mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
-                mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
-                mRow.shouldSuppressPeek());
-        Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true);
-        entryDataByKey.put(mBubbleEntry.getKey(), pair);
-
-        NotificationListenerService.RankingMap rankingMap =
-                mock(NotificationListenerService.RankingMap.class);
-        when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
-        mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
-
-        // Should no longer be in the stack
-        assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
-    }
-
-    /**
-     * Sets the bubble metadata flags for this entry. These flags are normally set by
-     * NotificationManagerService when the notification is sent, however, these tests do not
-     * go through that path so we set them explicitly when testing.
-     */
-    private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) {
-        Notification.BubbleMetadata bubbleMetadata =
-                entry.getSbn().getNotification().getBubbleMetadata();
-        int flags = bubbleMetadata.getFlags();
-        if (enableFlag) {
-            flags |= flag;
-        } else {
-            flags &= ~flag;
-        }
-        bubbleMetadata.setFlags(flags);
-    }
-
-    /**
-     * Asserts that the bubble stack is expanded and also validates the cached state is updated.
-     */
-    private void assertStackExpanded() {
-        assertTrue(mBubbleController.isStackExpanded());
-        assertTrue(mBubbleController.getImplCachedState().isStackExpanded());
-    }
-
-    /**
-     * Asserts that the bubble stack is collapsed and also validates the cached state is updated.
-     */
-    private void assertStackCollapsed() {
-        assertFalse(mBubbleController.isStackExpanded());
-        assertFalse(mBubbleController.getImplCachedState().isStackExpanded());
-    }
-
-    /**
-     * Asserts that a bubble notification is suppressed from the shade and also validates the cached
-     * state is updated.
-     */
-    private void assertBubbleNotificationSuppressedFromShade(BubbleEntry entry) {
-        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
-                entry.getKey(), entry.getGroupKey()));
-        assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
-                entry.getKey(), entry.getGroupKey()));
-    }
-
-    /**
-     * Asserts that a bubble notification is not suppressed from the shade and also validates the
-     * cached state is updated.
-     */
-    private void assertBubbleNotificationNotSuppressedFromShade(BubbleEntry entry) {
-        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
-                entry.getKey(), entry.getGroupKey()));
-        assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
-                entry.getKey(), entry.getGroupKey()));
-    }
-
-    /**
-     * Asserts that the system ui states associated to bubbles are in the correct state.
-     */
-    private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) {
-        assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
-        assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
-    }
-}
diff --git a/services/accessibility/OWNERS b/services/accessibility/OWNERS
index a31cfae..2b9f179 100644
--- a/services/accessibility/OWNERS
+++ b/services/accessibility/OWNERS
@@ -1,4 +1,4 @@
-svetoslavganov@google.com
 pweaver@google.com
-rhedjao@google.com
+sallyyuen@google.com
 ryanlwlin@google.com
+fuego@google.com
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 99c8495..79f590f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -199,9 +199,6 @@
     private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
         "registerUiTestAutomationService";
 
-    private static final String TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED =
-            "temporaryEnableAccessibilityStateUntilKeyguardRemoved";
-
     private static final String GET_WINDOW_TOKEN = "getWindowToken";
 
     private static final String SET_PIP_ACTION_REPLACEMENT =
@@ -1235,46 +1232,6 @@
     }
 
     @Override
-    public void temporaryEnableAccessibilityStateUntilKeyguardRemoved(
-            ComponentName service, boolean touchExplorationEnabled) {
-        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
-            mTraceManager.logTrace(
-                    LOG_TAG + ".temporaryEnableAccessibilityStateUntilKeyguardRemoved",
-                    FLAGS_ACCESSIBILITY_MANAGER,
-                    "service=" + service + ";touchExplorationEnabled=" + touchExplorationEnabled);
-        }
-
-        mSecurityPolicy.enforceCallingPermission(
-                Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY,
-                TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED);
-        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) {
-            mTraceManager.logTrace("WindowManagerInternal.isKeyguardLocked",
-                    FLAGS_WINDOW_MANAGER_INTERNAL);
-        }
-        if (!mWindowManagerService.isKeyguardLocked()) {
-            return;
-        }
-        synchronized (mLock) {
-            // Set the temporary state.
-            AccessibilityUserState userState = getCurrentUserStateLocked();
-
-            userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
-            userState.setDisplayMagnificationEnabledLocked(false);
-            userState.disableShortcutMagnificationLocked();
-            userState.setAutoclickEnabledLocked(false);
-            userState.mEnabledServices.clear();
-            userState.mEnabledServices.add(service);
-            userState.getBindingServicesLocked().clear();
-            userState.getCrashedServicesLocked().clear();
-            userState.mTouchExplorationGrantedServices.clear();
-            userState.mTouchExplorationGrantedServices.add(service);
-
-            // User the current state instead settings.
-            onUserStateChangedLocked(userState);
-        }
-    }
-
-    @Override
     public IBinder getWindowToken(int windowId, int userId) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".getWindowToken",
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 966d887..dc39b01 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -184,7 +184,12 @@
         mPanningScalingState.mScrollGestureDetector.onTouchEvent(event);
         mPanningScalingState.mScaleGestureDetector.onTouchEvent(event);
 
-        stateHandler.onMotionEvent(event, rawEvent, policyFlags);
+        try {
+            stateHandler.onMotionEvent(event, rawEvent, policyFlags);
+        } catch (GestureException e) {
+            Slog.e(mLogTag, "Error processing motion event", e);
+            clearAndTransitionToStateDetecting();
+        }
     }
 
     @Override
@@ -281,7 +286,8 @@
     }
 
     interface State {
-        void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+        void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)
+                throws GestureException;
 
         default void clear() {}
 
@@ -439,7 +445,8 @@
         private boolean mLastMoveOutsideMagnifiedRegion;
 
         @Override
-        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)
+                throws GestureException {
             final int action = event.getActionMasked();
             switch (action) {
                 case ACTION_POINTER_DOWN: {
@@ -449,7 +456,7 @@
                 break;
                 case ACTION_MOVE: {
                     if (event.getPointerCount() != 1) {
-                        throw new IllegalStateException("Should have one pointer down.");
+                        throw new GestureException("Should have one pointer down.");
                     }
                     final float eventX = event.getX();
                     final float eventY = event.getY();
@@ -475,7 +482,7 @@
 
                 case ACTION_DOWN:
                 case ACTION_POINTER_UP: {
-                    throw new IllegalArgumentException(
+                    throw new GestureException(
                             "Unexpected event type: " + MotionEvent.actionToString(action));
                 }
             }
@@ -1087,4 +1094,13 @@
             mGestureHandler.mDetectingState.setShortcutTriggered(false);
         }
     }
+
+    /**
+     * Indicates an error with a gesture handler or state.
+     */
+    private static class GestureException extends Exception {
+        GestureException(String message) {
+            super(message);
+        }
+    }
 }
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index d3ef6dc..cdeb2dc 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -11,7 +11,6 @@
     name: "services.companion-sources",
     srcs: [
         "java/**/*.java",
-        "java/**/*.proto",
     ],
     path: "java",
     visibility: ["//frameworks/base/services"],
@@ -20,9 +19,6 @@
 java_library_static {
     name: "services.companion",
     defaults: ["platform_service_defaults"],
-    proto: {
-        type: "stream",
-    },
     srcs: [":services.companion-sources"],
     libs: [
         "app-compat-annotations",
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 2ab1aa8..2acb65e 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -244,29 +244,6 @@
         primaryServiceConnector.postOnDeviceDisappeared(association);
     }
 
-    /** Pass an encrypted secure message to the companion application for transporting. */
-    public void dispatchMessage(@UserIdInt int userId, @NonNull String packageName,
-            int associationId, int messageId, @NonNull byte[] message) {
-        if (DEBUG) {
-            Log.i(TAG, "dispatchMessage() u" + userId + "/" + packageName
-                    + " associationId=" + associationId);
-        }
-
-        final CompanionDeviceServiceConnector primaryServiceConnector =
-                getPrimaryServiceConnector(userId, packageName);
-        if (primaryServiceConnector == null) {
-            if (DEBUG) {
-                Log.e(TAG, "dispatchMessage(): "
-                        + "u" + userId + "/" + packageName + " is NOT bound.");
-                Log.d(TAG, "Stacktrace", new Throwable());
-            }
-            return;
-        }
-
-        primaryServiceConnector.postOnMessageDispatchedFromSystem(associationId, messageId,
-                message);
-    }
-      
     void dump(@NonNull PrintWriter out) {
         out.append("Companion Device Application Controller: \n");
 
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index c354561..fa043f8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -106,11 +106,10 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.companion.datatransfer.CompanionMessageProcessor;
 import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
 import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
-import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;
+import com.android.server.companion.transport.CompanionTransportManager;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -151,10 +150,9 @@
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     private AssociationRequestsProcessor mAssociationRequestsProcessor;
     private SystemDataTransferProcessor mSystemDataTransferProcessor;
-    private CompanionMessageProcessor mCompanionMessageProcessor;
     private CompanionDevicePresenceMonitor mDevicePresenceMonitor;
     private CompanionApplicationController mCompanionAppController;
-    private CompanionSecureCommunicationsManager mSecureCommsManager;
+    private CompanionTransportManager mTransportManager;
 
     private final ActivityTaskManagerInternal mAtmInternal;
     private final ActivityManagerInternal mAmInternal;
@@ -238,11 +236,9 @@
                 /* cdmService */this, mAssociationStore);
         mCompanionAppController = new CompanionApplicationController(
                 context, mApplicationControllerCallback);
-        mSecureCommsManager = new CompanionSecureCommunicationsManager(
-                mAssociationStore, mCompanionAppController);
-        mCompanionMessageProcessor = new CompanionMessageProcessor(mSecureCommsManager);
+        mTransportManager = new CompanionTransportManager(context);
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore,
-                mSystemDataTransferRequestStore, mCompanionMessageProcessor);
+                mSystemDataTransferRequestStore, mTransportManager);
 
         // Publish "binder" service.
         final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl();
@@ -319,18 +315,32 @@
                 MINUTES.toMillis(10));
     }
 
-    @Nullable
+    @NonNull
     AssociationInfo getAssociationWithCallerChecks(
             @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
-        final AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
+        AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
                 userId, packageName, macAddress);
-        return sanitizeWithCallerChecks(getContext(), association);
+        association = sanitizeWithCallerChecks(getContext(), association);
+        if (association != null) {
+            return association;
+        } else {
+            throw new IllegalArgumentException("Association does not exist "
+                    + "or the caller does not have permissions to manage it "
+                    + "(ie. it belongs to a different package or a different user).");
+        }
     }
 
-    @Nullable
+    @NonNull
     AssociationInfo getAssociationWithCallerChecks(int associationId) {
-        final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
-        return sanitizeWithCallerChecks(getContext(), association);
+        AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+        association = sanitizeWithCallerChecks(getContext(), association);
+        if (association != null) {
+            return association;
+        } else {
+            throw new IllegalArgumentException("Association does not exist "
+                    + "or the caller does not have permissions to manage it "
+                    + "(ie. it belongs to a different package or a different user).");
+        }
     }
 
     private void onDeviceAppearedInternal(int associationId) {
@@ -609,12 +619,6 @@
 
             final AssociationInfo association =
                     getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
-            if (association == null) {
-                throw new IllegalArgumentException("Association does not exist "
-                        + "or the caller does not have permissions to manage it "
-                        + "(ie. it belongs to a different package or a different user).");
-            }
-
             disassociateInternal(association.getId());
         }
 
@@ -622,15 +626,9 @@
         public void disassociate(int associationId) {
             if (DEBUG) Log.i(TAG, "disassociate() associationId=" + associationId);
 
-            final AssociationInfo association = getAssociationWithCallerChecks(associationId);
-            if (association == null) {
-                throw new IllegalArgumentException("Association with ID " + associationId + " "
-                        + "does not exist "
-                        + "or belongs to a different package "
-                        + "or belongs to a different user");
-            }
-
-            disassociateInternal(associationId);
+            final AssociationInfo association =
+                    getAssociationWithCallerChecks(associationId);
+            disassociateInternal(association.getId());
         }
 
         @Override
@@ -698,27 +696,6 @@
         }
 
         @Override
-        public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
-            if (DEBUG) {
-                Log.i(TAG, "dispatchMessage() associationId=" + associationId + "\n"
-                        + " message(Base64)=" + Base64.encodeToString(message, 0));
-            }
-
-            getContext().enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES,
-                    "dispatchMessage");
-
-            AssociationInfo association = getAssociationWithCallerChecks(associationId);
-            if (association == null) {
-                throw new IllegalArgumentException("Association with ID " + associationId + " "
-                        + "does not exist "
-                        + "or belongs to a different package "
-                        + "or belongs to a different user");
-            }
-
-            mSecureCommsManager.receiveSecureMessage(messageId, associationId, message);
-        }
-
-        @Override
         public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
                 int userId, int associationId) {
             return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent(
@@ -735,14 +712,14 @@
         @Override
         public void attachSystemDataTransport(String packageName, int userId, int associationId,
                 ParcelFileDescriptor fd) {
-            mSystemDataTransferProcessor.attachSystemDataTransport(packageName, userId,
-                    associationId, fd);
+            getAssociationWithCallerChecks(associationId);
+            mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd);
         }
 
         @Override
         public void detachSystemDataTransport(String packageName, int userId, int associationId) {
-            mSystemDataTransferProcessor.detachSystemDataTransport(packageName, userId,
-                    associationId);
+            getAssociationWithCallerChecks(associationId);
+            mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
         }
 
         @Override
@@ -750,13 +727,6 @@
             if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId);
 
             AssociationInfo association = getAssociationWithCallerChecks(associationId);
-            if (association == null) {
-                throw new IllegalArgumentException("Association with ID " + associationId + " "
-                        + "does not exist "
-                        + "or belongs to a different package "
-                        + "or belongs to a different user");
-            }
-
             if (!association.isSelfManaged()) {
                 throw new IllegalArgumentException("Association with ID " + associationId
                         + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
@@ -777,13 +747,6 @@
             if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId);
 
             final AssociationInfo association = getAssociationWithCallerChecks(associationId);
-            if (association == null) {
-                throw new IllegalArgumentException("Association with ID " + associationId + " "
-                        + "does not exist "
-                        + "or belongs to a different package "
-                        + "or belongs to a different user");
-            }
-
             if (!association.isSelfManaged()) {
                 throw new IllegalArgumentException("Association with ID " + associationId
                         + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
@@ -892,7 +855,6 @@
             final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand(
                     CompanionDeviceManagerService.this,
                     mAssociationStore,
-                    mSecureCommsManager,
                     mDevicePresenceMonitor);
             cmd.exec(this, in, out, err, args, callback, resultReceiver);
         }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
index 7360f08..a288f7b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -99,12 +99,6 @@
         post(companionService -> companionService.onDeviceDisappeared(associationInfo));
     }
 
-    void postOnMessageDispatchedFromSystem(int associationId, int messageId,
-            @NonNull byte[] message) {
-        post(companionService ->
-                companionService.onMessageDispatchedFromSystem(messageId, associationId, message));
-    }
-
     /**
      * Post "unbind" job, which will run *after* all previously posted jobs complete.
      *
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 0b7bc03..322ad46 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -16,14 +16,9 @@
 
 package com.android.server.companion;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-
 import android.companion.AssociationInfo;
 import android.os.Binder;
 import android.os.ShellCommand;
-import android.util.Base64;
-
-import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;
 
 import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
 
@@ -35,16 +30,13 @@
 
     private final CompanionDeviceManagerService mService;
     private final AssociationStore mAssociationStore;
-    private final CompanionSecureCommunicationsManager mSecureCommsManager;
     private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
 
     CompanionDeviceShellCommand(CompanionDeviceManagerService service,
             AssociationStore associationStore,
-            CompanionSecureCommunicationsManager secureCommsManager,
             CompanionDevicePresenceMonitor devicePresenceMonitor) {
         mService = service;
         mAssociationStore = associationStore;
-        mSecureCommsManager = secureCommsManager;
         mDevicePresenceMonitor = devicePresenceMonitor;
     }
 
@@ -92,32 +84,6 @@
                     mService.loadAssociationsFromDisk();
                     break;
 
-                case "send-secure-message":
-                    associationId = getNextIntArgRequired();
-                    final byte[] message;
-
-                    // The message should be either a UTF-8 String or Base64-encoded data.
-                    final boolean isBase64 = "--base64".equals(getNextOption());
-                    if (isBase64) {
-                        final String base64encodedMessage = getNextArgRequired();
-                        message = Base64.decode(base64encodedMessage, 0);
-                    } else {
-                        // We treat the rest of the command as the message, which should contain at
-                        // least one word (hence getNextArg_Required() below), but there may be
-                        // more.
-                        final StringBuilder sb = new StringBuilder(getNextArgRequired());
-                        // Pick up the rest.
-                        for (String word : peekRemainingArgs()) {
-                            sb.append(" ").append(word);
-                        }
-                        // And now convert to byte[]...
-                        message = sb.toString().getBytes(UTF_8);
-                    }
-
-                    mSecureCommsManager.sendSecureMessage(associationId, /* messageId */ 0,
-                            message);
-                    break;
-
                 case "simulate-device-appeared":
                     associationId = getNextIntArgRequired();
                     mDevicePresenceMonitor.simulateDeviceAppeared(associationId);
@@ -167,8 +133,6 @@
         pw.println("      Create a new Association.");
         pw.println("  disassociate USER_ID PACKAGE MAC_ADDRESS");
         pw.println("      Remove an existing Association.");
-        pw.println("  send-secure-message ASSOCIATION_ID [--base64] MESSAGE");
-        pw.println("      Send a secure message to an associated companion device.");
         pw.println("  clear-association-memory-cache");
         pw.println("      Clear the in-memory association cache and reload all association ");
         pw.println("      information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
diff --git a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java b/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java
deleted file mode 100644
index 91ad93c..0000000
--- a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer;
-
-class CompanionMessageInfo {
-
-    private final long mId;
-    private final int mPage;
-    private final int mTotal;
-    private final int mType;
-    private final byte[] mData;
-
-    CompanionMessageInfo(long id, int page, int total, int type, byte[] data) {
-        mId = id;
-        mPage = page;
-        mTotal = total;
-        mType = type;
-        mData = data;
-    }
-
-    public long getId() {
-        return mId;
-    }
-
-    public int getPage() {
-        return mPage;
-    }
-
-    public int getType() {
-        return mType;
-    }
-
-    public byte[] getData() {
-        return mData;
-    }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java
deleted file mode 100644
index 98a00aa6..0000000
--- a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.Slog;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.server.companion.proto.CompanionMessage;
-import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class builds and reads CompanionMessage. And also paginate and combine messages.
- */
-public class CompanionMessageProcessor {
-
-    private static final String LOG_TAG = CompanionMessageProcessor.class.getSimpleName();
-
-    /** Listener for incoming complete messages. */
-    interface Listener {
-        /** When a complete message is received from the companion app. */
-        void onCompleteMessageReceived(@NonNull CompanionMessageInfo message);
-    }
-
-    // Rough size for each CompanionMessage, each message can exceed 50K for a little, but not
-    // too much. Hard limit is 100K, WCS data processing limit. Closer to 100K, less stable at
-    // the WCS data processing layer. Refer to
-    // https://developers.google.com/android/reference/com/google/android/gms/wearable/MessageClient
-    // #public-abstract-taskinteger-sendmessage-string-nodeid,-string-path,-byte[]-data
-    private static final int MESSAGE_SIZE_IN_BYTES = 50000;
-
-    private final CompanionSecureCommunicationsManager mSecureCommsManager;
-
-    @Nullable
-    private Listener mListener;
-
-    // Association id -> (parent id -> received messages)
-    private final Map<Integer, Map<Integer, List<CompanionMessageInfo>>> mAssociationsMessagesMap =
-            new HashMap<>();
-    // Association id -> next parent id
-    private final Map<Integer, Integer> mNextParentId = new HashMap<>();
-
-    public CompanionMessageProcessor(CompanionSecureCommunicationsManager secureCommsManager) {
-        mSecureCommsManager = secureCommsManager;
-        mSecureCommsManager.setListener(this::onDecryptedMessageReceived);
-    }
-
-    public void setListener(@NonNull Listener listener) {
-        mListener = listener;
-    }
-
-    /**
-     * Paginate the data into multiple messages with size limit. And dispatch the messages to the
-     * companion app.
-     */
-    public void paginateAndDispatchMessagesToApp(byte[] data, int messageType,
-            String packageName, int userId, int associationId) {
-        Slog.i(LOG_TAG, "Paginating " + data.length + " bytes.");
-
-        final int totalMessageCount = (data.length / MESSAGE_SIZE_IN_BYTES)
-                + ((data.length % MESSAGE_SIZE_IN_BYTES == 0) ? 0 : 1);
-        int parentMessageId = findNextParentId(associationId, totalMessageCount);
-
-        for (int i = 0; i < totalMessageCount; i++) {
-            ProtoOutputStream proto = new ProtoOutputStream();
-            int messageId = parentMessageId + i + 1;
-            proto.write(CompanionMessage.ID, messageId);
-
-            long paginationInfoToken = proto.start(CompanionMessage.PAGINATION_INFO);
-            proto.write(CompanionMessage.PaginationInfo.PARENT_ID, parentMessageId);
-            proto.write(CompanionMessage.PaginationInfo.PAGE, i + 1);
-            proto.write(CompanionMessage.PaginationInfo.TOTAL, totalMessageCount);
-            proto.end(paginationInfoToken);
-
-            proto.write(CompanionMessage.TYPE, messageType);
-            byte[] currentData = Arrays.copyOfRange(data, i * MESSAGE_SIZE_IN_BYTES,
-                    Math.min((i + 1) * MESSAGE_SIZE_IN_BYTES, data.length));
-            proto.write(CompanionMessage.DATA, currentData);
-
-            byte[] message = proto.getBytes();
-
-            Slog.i(LOG_TAG, "Sending [" + message.length + "] bytes to " + packageName);
-
-            mSecureCommsManager.sendSecureMessage(associationId, messageId, message);
-        }
-    }
-
-    /**
-     * Process the message and store it. If all the messages with the same parent id have been
-     * received, return the message with combined message data. Otherwise, return null if there's
-     * still data parts missing.
-     */
-    public CompanionMessageInfo onDecryptedMessageReceived(int messageId, int associationId,
-            byte[] message) {
-        Slog.i(LOG_TAG, "Partial message received, size [" + message.length
-                + "], reading from protobuf.");
-
-        ProtoInputStream proto = new ProtoInputStream(message);
-        try {
-            int id = 0;
-            int parentId = 0;
-            int page = 0;
-            int total = 0;
-            int type = CompanionMessage.UNKNOWN;
-            byte[] data = null;
-
-            // Read proto data
-            while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                switch (proto.getFieldNumber()) {
-                    case (int) CompanionMessage.ID:
-                        id = proto.readInt(CompanionMessage.ID);
-                        break;
-                    case (int) CompanionMessage.PAGINATION_INFO:
-                        long paginationToken = proto.start(CompanionMessage.PAGINATION_INFO);
-                        while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                            switch (proto.getFieldNumber()) {
-                                case (int) CompanionMessage.PaginationInfo.PARENT_ID:
-                                    parentId = proto.readInt(
-                                            CompanionMessage.PaginationInfo.PARENT_ID);
-                                    break;
-                                case (int) CompanionMessage.PaginationInfo.PAGE:
-                                    page = proto.readInt(CompanionMessage.PaginationInfo.PAGE);
-                                    break;
-                                case (int) CompanionMessage.PaginationInfo.TOTAL:
-                                    total = proto.readInt(CompanionMessage.PaginationInfo.TOTAL);
-                                    break;
-                                default:
-                                    Slog.e(LOG_TAG, "Unexpected field id "
-                                            + proto.getFieldNumber() + " for PaginationInfo.");
-                                    break;
-                            }
-                        }
-                        proto.end(paginationToken);
-                        break;
-                    case (int) CompanionMessage.TYPE:
-                        type = proto.readInt(CompanionMessage.TYPE);
-                        break;
-                    case (int) CompanionMessage.DATA:
-                        data = proto.readBytes(CompanionMessage.DATA);
-                        break;
-                    default:
-                        Slog.e(LOG_TAG, "Unexpected field id " + proto.getFieldNumber()
-                                + " for CompanionMessage.");
-                        break;
-                }
-            }
-
-            if (id == messageId) {
-                CompanionMessageInfo messageInfo = new CompanionMessageInfo(id, page, total, type,
-                        data);
-                // Add the message into mAssociationsMessagesMap
-                Map<Integer, List<CompanionMessageInfo>> associationMessages =
-                        mAssociationsMessagesMap.getOrDefault(associationId, new HashMap<>());
-                List<CompanionMessageInfo> childMessages = associationMessages.getOrDefault(
-                        parentId, new ArrayList<>());
-                childMessages.add(messageInfo);
-                associationMessages.put(parentId, childMessages);
-                mAssociationsMessagesMap.put(associationId, associationMessages);
-                // Check if all the messages with the same parentId are received.
-                if (childMessages.size() == total) {
-                    Slog.i(LOG_TAG, "All [" + total + "] messages are received for parentId ["
-                            + parentId + "]. Processing.");
-
-                    childMessages.sort(Comparator.comparing(CompanionMessageInfo::getPage));
-                    ByteArrayOutputStream stream = new ByteArrayOutputStream();
-                    for (int i = 0; i < childMessages.size(); i++) {
-                        stream.write(childMessages.get(i).getData());
-                    }
-                    mAssociationsMessagesMap.remove(parentId);
-                    mListener.onCompleteMessageReceived(
-                            new CompanionMessageInfo(parentId, 0, total, type,
-                                    stream.toByteArray()));
-                } else {
-                    Slog.i(LOG_TAG, "[" + childMessages.size() + "/" + total
-                            + "] messages are received for parentId [" + parentId + "]");
-                }
-            } else {
-                Slog.e(LOG_TAG, "Message id mismatch.");
-                return null;
-            }
-        } catch (IOException e) {
-            Slog.e(LOG_TAG, "Can't read proto from the message.");
-            return null;
-        }
-        return null;
-    }
-
-    /**
-     * Find the next parent id from [1, Integer.MAX_VALUE].
-     * The parent and child ids are incremental.
-     */
-    private int findNextParentId(int associationId, int totalMessageCount) {
-        int nextParentId = mNextParentId.getOrDefault(associationId, 1);
-
-        // If the last child message id exceeds the Integer range, start from 1 again.
-        if (nextParentId > Integer.MAX_VALUE - totalMessageCount - 1) {
-            nextParentId = 1;
-        }
-
-        mNextParentId.put(associationId, nextParentId + totalMessageCount + 1);
-
-        return nextParentId;
-    }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 7eede55..88ebb97 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -25,6 +25,7 @@
 import static com.android.server.companion.Utils.prepareForIpc;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.companion.AssociationInfo;
@@ -38,34 +39,27 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.permission.PermissionControllerManager;
 import android.util.Slog;
-import android.util.SparseArray;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.server.companion.AssociationStore;
 import com.android.server.companion.CompanionDeviceManagerService;
 import com.android.server.companion.PermissionsUtils;
-import com.android.server.companion.proto.CompanionMessage;
+import com.android.server.companion.transport.CompanionTransportManager;
 
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 
 /**
- * This processor builds user consent intent for a given SystemDataTransferRequest and processes the
- * request when the system is ready (a secure channel is established between the handhold and the
- * companion device).
+ * This processor builds user consent intent for a
+ * {@link SystemDataTransferRequest} and processes the request once a channel is
+ * established between the local and remote companion device.
  */
 public class SystemDataTransferProcessor {
 
@@ -85,21 +79,19 @@
     private final Context mContext;
     private final AssociationStore mAssociationStore;
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
-    private final CompanionMessageProcessor mCompanionMessageProcessor;
+    private final CompanionTransportManager mTransportManager;
     private final PermissionControllerManager mPermissionControllerManager;
     private final ExecutorService mExecutor;
-    @GuardedBy("mTransports")
-    private final SparseArray<Transport> mTransports = new SparseArray<>();
 
     public SystemDataTransferProcessor(CompanionDeviceManagerService service,
             AssociationStore associationStore,
             SystemDataTransferRequestStore systemDataTransferRequestStore,
-            CompanionMessageProcessor companionMessageProcessor) {
+            CompanionTransportManager transportManager) {
         mContext = service.getContext();
         mAssociationStore = associationStore;
         mSystemDataTransferRequestStore = systemDataTransferRequestStore;
-        mCompanionMessageProcessor = companionMessageProcessor;
-        mCompanionMessageProcessor.setListener(this::onCompleteMessageReceived);
+        mTransportManager = transportManager;
+        mTransportManager.setListener(this::onReceivePermissionRestore);
         mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
         mExecutor = Executors.newSingleThreadExecutor();
     }
@@ -194,77 +186,55 @@
             return;
         }
 
-        // TODO: Establish a secure channel
-
         // Start permission sync
         final long callingIdentityToken = Binder.clearCallingIdentity();
         try {
+            // TODO: refactor to work with streams of data
             mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
-                    mExecutor,
-                    backup -> mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup,
-                        CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId));
+                    mExecutor, backup -> {
+                        Future<?> future = mTransportManager
+                                .requestPermissionRestore(associationId, backup);
+                        translateFutureToCallback(future, callback);
+                    });
         } finally {
             Binder.restoreCallingIdentity(callingIdentityToken);
         }
     }
 
-    public void attachSystemDataTransport(String packageName, int userId, int associationId,
-            ParcelFileDescriptor fd) {
-        synchronized (mTransports) {
-            // TODO: restore once testing has evolved
-            // resolveAssociation(packageName, userId, associationId);
-
-            if (mTransports.contains(associationId)) {
-                detachSystemDataTransport(packageName, userId, associationId);
-            }
-
-            final Transport transport = new Transport(fd);
-            transport.start();
-            mTransports.put(associationId, transport);
-        }
-    }
-
-    public void detachSystemDataTransport(String packageName, int userId, int associationId) {
-        synchronized (mTransports) {
-            // TODO: restore once testing has evolved
-            // resolveAssociation(packageName, userId, associationId);
-
-            final Transport transport = mTransports.get(associationId);
-            if (transport != null) {
-                mTransports.delete(associationId);
-                transport.stop();
-            }
-        }
-    }
-
-    /**
-     * Process a complete decrypted message reported by the companion app.
-     */
-    public void onCompleteMessageReceived(@NonNull CompanionMessageInfo completeMessage) {
-        switch (completeMessage.getType()) {
-            case CompanionMessage.PERMISSION_SYNC:
-                processPermissionSyncMessage(completeMessage);
-                break;
-            default:
-                Slog.e(LOG_TAG, "Unknown message type [" + completeMessage.getType()
-                        + "]. Unable to process.");
-        }
-    }
-
-    private void processPermissionSyncMessage(CompanionMessageInfo messageInfo) {
+    private void onReceivePermissionRestore(byte[] message) {
         Slog.i(LOG_TAG, "Applying permissions.");
         // Start applying permissions
         UserHandle user = mContext.getUser();
         final long callingIdentityToken = Binder.clearCallingIdentity();
         try {
+            // TODO: refactor to work with streams of data
             mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(
-                    messageInfo.getData(), user);
+                    message, user);
         } finally {
-            Slog.i(LOG_TAG, "Permissions applied.");
             Binder.restoreCallingIdentity(callingIdentityToken);
         }
     }
 
+    private static void translateFutureToCallback(@NonNull Future<?> future,
+            @Nullable ISystemDataTransferCallback callback) {
+        try {
+            future.get(15, TimeUnit.SECONDS);
+            try {
+                if (callback != null) {
+                    callback.onResult();
+                }
+            } catch (RemoteException ignored) {
+            }
+        } catch (Exception e) {
+            try {
+                if (callback != null) {
+                    callback.onError(e.getMessage());
+                }
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
     private final ResultReceiver mOnSystemDataTransferRequestConfirmationReceiver =
             new ResultReceiver(Handler.getMain()) {
                 @Override
@@ -291,74 +261,4 @@
                     Slog.e(LOG_TAG, "Unknown result code:" + resultCode);
                 }
             };
-
-    private class Transport {
-        private final InputStream mRemoteIn;
-        private final OutputStream mRemoteOut;
-
-        private volatile boolean mStopped;
-
-        public Transport(ParcelFileDescriptor fd) {
-            mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
-            mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
-        }
-
-        public void start() {
-            new Thread(() -> {
-                try {
-                    while (!mStopped) {
-                        processNextCommand();
-                    }
-                } catch (IOException e) {
-                    if (!mStopped) {
-                        Slog.w(LOG_TAG, "Trouble during transport", e);
-                        stop();
-                    }
-                }
-            }).start();
-        }
-
-        public void stop() {
-            mStopped = true;
-
-            IoUtils.closeQuietly(mRemoteIn);
-            IoUtils.closeQuietly(mRemoteOut);
-        }
-
-        private void processNextCommand() throws IOException {
-            Slog.d(LOG_TAG, "Waiting for next command...");
-
-            // Read message header
-            final byte[] headerBytes = new byte[8];
-            Streams.readFully(mRemoteIn, headerBytes);
-            final ByteBuffer header = ByteBuffer.wrap(headerBytes);
-            final int command = header.getInt();
-            final int length = header.getInt();
-
-            Slog.d(LOG_TAG, "Received command 0x" + Integer.toHexString(command)
-                    + " length " + length);
-            switch (command) {
-                case 0x50490000: // PI(NG) version 0
-                    // Repeat back the given payload, within reason
-                    final int target = Math.min(length, 1_000_000);
-                    final byte[] payload = new byte[target];
-                    Streams.readFully(mRemoteIn, payload);
-                    Streams.skipByReading(mRemoteIn, length - target);
-
-                    // Respond with PO(NG) version 0
-                    header.rewind();
-                    header.putInt(0x504F0000);
-                    header.putInt(target);
-                    mRemoteOut.write(header.array());
-                    mRemoteOut.write(payload);
-                    break;
-
-                default:
-                    // Emit local warning, and skip message to
-                    // handle next one
-                    Slog.w(LOG_TAG, "Unknown command 0x" + Integer.toHexString(command));
-                    mRemoteIn.skip(length);
-            }
-        }
-    }
 }
diff --git a/services/companion/java/com/android/server/companion/proto/companion_message.proto b/services/companion/java/com/android/server/companion/proto/companion_message.proto
deleted file mode 100644
index 893a0db..0000000
--- a/services/companion/java/com/android/server/companion/proto/companion_message.proto
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto3";
-
-option java_multiple_files = true;
-
-package com.android.server.companion.proto;
-
-/* Represents a message between companion devices */
-message CompanionMessage {
-  int32 id = 1;
-
-  PaginationInfo paginationInfo = 2;
-
-  Type type = 3;
-
-  // message body data
-  bytes data = 4;
-
-  /* Message pagination info */
-  message PaginationInfo {
-    // id of the parent message, which should be the same for all the paginated messages
-    int32 parentId = 1;
-
-    // page number of the current message in [1, total]
-    int32 page = 2;
-
-    // total number of messages
-    int32 total = 3;
-  }
-
-  /* Message type */
-  enum Type {
-    // default value for proto3
-    UNKNOWN = 0;
-
-    // handshake message to establish secure channel
-    SECURE_CHANNEL_HANDSHAKE = 1;
-
-    // permission sync
-    PERMISSION_SYNC = 2;
-  }
-}
diff --git a/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java b/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java
deleted file mode 100644
index 16a74ec..0000000
--- a/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.securechannel;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.companion.AssociationInfo;
-import android.util.Base64;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.server.companion.AssociationStore;
-import com.android.server.companion.CompanionApplicationController;
-
-/** Secure Comms Manager */
-@SuppressLint("LongLogTag")
-public class CompanionSecureCommunicationsManager {
-    static final String TAG = "CompanionDevice_SecureComms";
-    static final boolean DEBUG = false;
-
-    /** Listener for incoming decrypted messages. */
-    public interface Listener {
-        /** When an incoming message is decrypted. */
-        void onDecryptedMessageReceived(int messageId, int associationId, byte[] message);
-    }
-
-    private final AssociationStore mAssociationStore;
-    private final CompanionApplicationController mCompanionAppController;
-
-    @Nullable
-    private Listener mListener;
-
-    /** Constructor */
-    public CompanionSecureCommunicationsManager(AssociationStore associationStore,
-            CompanionApplicationController companionApplicationController) {
-        mAssociationStore = associationStore;
-        mCompanionAppController = companionApplicationController;
-    }
-
-    public void setListener(@NonNull Listener listener) {
-        mListener = listener;
-    }
-
-    /**
-     * Send a data to the associated companion device via secure channel (establishing one if
-     * needed).
-     * @param associationId associationId of the "recipient" companion device.
-     * @param messageId id of the message
-     * @param message data to be sent securely.
-     */
-    public void sendSecureMessage(int associationId, int messageId, @NonNull byte[] message) {
-        if (DEBUG) {
-            Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n"
-                    + "   message (Base64)=\"" + Base64.encodeToString(message, 0) + "\"");
-        }
-
-        final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
-        if (association == null) {
-            throw new IllegalArgumentException(
-                    "Association with ID " + associationId + " does not exist");
-        }
-        if (DEBUG) Log.d(TAG, "  association=" + association);
-
-        final int userId = association.getUserId();
-        final String packageName = association.getPackageName();
-
-        // Bind to the app if it hasn't been bound.
-        if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
-            Slog.d(TAG, "userId [" + userId + "] packageName [" + packageName
-                    + "] is not bound. Binding it now to send a secure message.");
-            mCompanionAppController.bindCompanionApplication(userId, packageName,
-                    association.isSelfManaged());
-
-            // TODO(b/202926196): implement: encrypt and pass on the companion application for
-            //  transporting
-            mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId,
-                    message);
-
-            Slog.d(TAG, "Unbinding userId [" + userId + "] packageName [" + packageName
-                    + "]");
-            mCompanionAppController.unbindCompanionApplication(userId, packageName);
-        }
-
-        // TODO(b/202926196): implement: encrypt and pass on the companion application for
-        //  transporting
-        mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId,
-                message);
-    }
-
-    /**
-     * Decrypt and dispatch message received from an associated companion device.
-     * @param associationId associationId of the "sender" companion device.
-     * @param encryptedMessage data.
-     */
-    public void receiveSecureMessage(int messageId, int associationId,
-            @NonNull byte[] encryptedMessage) {
-        if (DEBUG) {
-            Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n"
-                    + "   message (Base64)=\"" + Base64.encodeToString(encryptedMessage, 0) + "\"");
-        }
-
-        // TODO(b/202926196): implement: decrypt and dispatch
-
-        mListener.onDecryptedMessageReceived(messageId, associationId, encryptedMessage);
-    }
-}
diff --git a/services/companion/java/com/android/server/companion/securechannel/OWNERS b/services/companion/java/com/android/server/companion/securechannel/OWNERS
deleted file mode 100644
index ecb97f4..0000000
--- a/services/companion/java/com/android/server/companion/securechannel/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-set noparent
-
-sergeynv@google.com
-ewol@google.com
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
new file mode 100644
index 0000000..77d51ea
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.transport;
+
+import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+import libcore.util.EmptyArray;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@SuppressLint("LongLogTag")
+public class CompanionTransportManager {
+    private static final String TAG = "CompanionTransportManager";
+    // TODO: flip to false
+    private static final boolean DEBUG = true;
+
+    private static final int HEADER_LENGTH = 12;
+    // TODO: refactor message processing to use streams to remove this limit
+    private static final int MAX_PAYLOAD_LENGTH = 1_000_000;
+
+    private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
+    private static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
+
+    private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
+    private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
+
+    private static boolean isRequest(int message) {
+        return (message & 0xFF000000) == 0x63000000;
+    }
+
+    private static boolean isResponse(int message) {
+        return (message & 0xFF000000) == 0x33000000;
+    }
+
+    public interface Listener {
+        void onRequestPermissionRestore(byte[] data);
+    }
+
+    private final Context mContext;
+
+    @GuardedBy("mTransports")
+    private final SparseArray<Transport> mTransports = new SparseArray<>();
+
+    @Nullable
+    private Listener mListener;
+
+    public CompanionTransportManager(Context context) {
+        mContext = context;
+    }
+
+    public void setListener(@NonNull Listener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * For the moment, we only offer transporting of system data to built-in
+     * companion apps; future work will improve the security model to support
+     * third-party companion apps.
+     */
+    private void enforceCallerCanTransportSystemData(String packageName, int userId) {
+        mContext.enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES, TAG);
+
+        try {
+            final ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser(
+                    packageName, 0, userId);
+            final int instrumentationUid = LocalServices.getService(ActivityManagerInternal.class)
+                    .getInstrumentationSourceUid(Binder.getCallingUid());
+            if (!Build.isDebuggable() && !info.isSystemApp()
+                    && instrumentationUid == android.os.Process.INVALID_UID) {
+                throw new SecurityException("Transporting of system data currently only available "
+                        + "to built-in companion apps or tests");
+            }
+        } catch (NameNotFoundException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    public void attachSystemDataTransport(String packageName, int userId, int associationId,
+            ParcelFileDescriptor fd) {
+        enforceCallerCanTransportSystemData(packageName, userId);
+        synchronized (mTransports) {
+            if (mTransports.contains(associationId)) {
+                detachSystemDataTransport(packageName, userId, associationId);
+            }
+
+            final Transport transport = new Transport(associationId, fd);
+            transport.start();
+            mTransports.put(associationId, transport);
+        }
+    }
+
+    public void detachSystemDataTransport(String packageName, int userId, int associationId) {
+        enforceCallerCanTransportSystemData(packageName, userId);
+        synchronized (mTransports) {
+            final Transport transport = mTransports.get(associationId);
+            if (transport != null) {
+                mTransports.delete(associationId);
+                transport.stop();
+            }
+        }
+    }
+
+    public Future<?> requestPermissionRestore(int associationId, byte[] data) {
+        synchronized (mTransports) {
+            final Transport transport = mTransports.get(associationId);
+            if (transport != null) {
+                return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
+            } else {
+                return CompletableFuture.failedFuture(new IOException("Missing transport"));
+            }
+        }
+    }
+
+    private class Transport {
+        private final int mAssociationId;
+
+        private final InputStream mRemoteIn;
+        private final OutputStream mRemoteOut;
+
+        private final AtomicInteger mNextSequence = new AtomicInteger();
+
+        @GuardedBy("mPendingRequests")
+        private final SparseArray<CompletableFuture<byte[]>> mPendingRequests = new SparseArray<>();
+
+        private volatile boolean mStopped;
+
+        public Transport(int associationId, ParcelFileDescriptor fd) {
+            mAssociationId = associationId;
+            mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+            mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+        }
+
+        public void start() {
+            new Thread(() -> {
+                try {
+                    while (!mStopped) {
+                        receiveMessage();
+                    }
+                } catch (IOException e) {
+                    if (!mStopped) {
+                        Slog.w(TAG, "Trouble during transport", e);
+                        stop();
+                    }
+                }
+            }).start();
+        }
+
+        public void stop() {
+            mStopped = true;
+
+            IoUtils.closeQuietly(mRemoteIn);
+            IoUtils.closeQuietly(mRemoteOut);
+        }
+
+        public Future<byte[]> requestForResponse(int message, byte[] data) {
+            final int sequence = mNextSequence.incrementAndGet();
+            final CompletableFuture<byte[]> pending = new CompletableFuture<>();
+            synchronized (mPendingRequests) {
+                mPendingRequests.put(sequence, pending);
+            }
+            try {
+                sendMessage(message, sequence, data);
+            } catch (IOException e) {
+                synchronized (mPendingRequests) {
+                    mPendingRequests.remove(sequence);
+                }
+                pending.completeExceptionally(e);
+            }
+            return pending;
+        }
+
+        private void sendMessage(int message, int sequence, @NonNull byte[] data)
+                throws IOException {
+            if (DEBUG) {
+                Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
+                        + " sequence " + sequence + " length " + data.length
+                        + " to association " + mAssociationId);
+            }
+
+            synchronized (mRemoteOut) {
+                final ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH)
+                        .putInt(message)
+                        .putInt(sequence)
+                        .putInt(data.length);
+                mRemoteOut.write(header.array());
+                mRemoteOut.write(data);
+                mRemoteOut.flush();
+            }
+        }
+
+        private void receiveMessage() throws IOException {
+            if (DEBUG) {
+                Slog.d(TAG, "Waiting for next message...");
+            }
+
+            final byte[] headerBytes = new byte[HEADER_LENGTH];
+            Streams.readFully(mRemoteIn, headerBytes);
+            final ByteBuffer header = ByteBuffer.wrap(headerBytes);
+            final int message = header.getInt();
+            final int sequence = header.getInt();
+            final int length = header.getInt();
+
+            if (DEBUG) {
+                Slog.d(TAG, "Received message 0x" + Integer.toHexString(message)
+                        + " sequence " + sequence + " length " + length
+                        + " from association " + mAssociationId);
+            }
+            if (length > MAX_PAYLOAD_LENGTH) {
+                Slog.w(TAG, "Ignoring message 0x" + Integer.toHexString(message)
+                        + " sequence " + sequence + " length " + length
+                        + " from association " + mAssociationId + " beyond maximum length");
+                Streams.skipByReading(mRemoteIn, length);
+                return;
+            }
+
+            final byte[] data = new byte[length];
+            Streams.readFully(mRemoteIn, data);
+
+            if (isRequest(message)) {
+                processRequest(message, sequence, data);
+            } else if (isResponse(message)) {
+                processResponse(message, sequence, data);
+            } else {
+                Slog.w(TAG, "Unknown message 0x" + Integer.toHexString(message));
+            }
+        }
+
+        private void processRequest(int message, int sequence, byte[] data)
+                throws IOException {
+            switch (message) {
+                case MESSAGE_REQUEST_PING: {
+                    sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
+                    break;
+                }
+                case MESSAGE_REQUEST_PERMISSION_RESTORE: {
+                    if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                            && !Build.isDebuggable()) {
+                        Slog.w(TAG, "Restoring permissions only supported on watches");
+                        sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+                        break;
+                    }
+                    try {
+                        mListener.onRequestPermissionRestore(data);
+                        sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
+                    } catch (Exception e) {
+                        Slog.w(TAG, "Failed to restore permissions");
+                        sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+                    }
+                    break;
+                }
+                default: {
+                    Slog.w(TAG, "Unknown request 0x" + Integer.toHexString(message));
+                    sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+                    break;
+                }
+            }
+        }
+
+        private void processResponse(int message, int sequence, byte[] data) {
+            final CompletableFuture<byte[]> future;
+            synchronized (mPendingRequests) {
+                future = mPendingRequests.removeReturnOld(sequence);
+            }
+            if (future == null) {
+                Slog.w(TAG, "Ignoring unknown sequence " + sequence);
+                return;
+            }
+
+            switch (message) {
+                case MESSAGE_RESPONSE_SUCCESS: {
+                    future.complete(data);
+                    break;
+                }
+                case MESSAGE_RESPONSE_FAILURE: {
+                    future.completeExceptionally(new RuntimeException("Remote failure"));
+                    break;
+                }
+                default: {
+                    Slog.w(TAG, "Ignoring unknown response 0x" + Integer.toHexString(message));
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index d3ef6be..07b6843 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -45,6 +45,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.INetworkManagementService;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.ServiceManager;
@@ -131,6 +132,12 @@
             return INetworkManagementService.Stub.asInterface(
                     ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
         }
+
+        /** Create a VPN. */
+        public Vpn createVpn(Looper looper, Context context, INetworkManagementService nms,
+                INetd netd, int userId) {
+            return new Vpn(looper, context, nms, netd, userId, new VpnProfileStore());
+        }
     }
 
     public VpnManagerService(Context context, Dependencies deps) {
@@ -688,6 +695,7 @@
 
         // Listen to package add and removal events for all users.
         intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
         intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         intentFilter.addDataScheme("package");
@@ -738,6 +746,10 @@
                 final boolean isReplacing = intent.getBooleanExtra(
                         Intent.EXTRA_REPLACING, false);
                 onPackageRemoved(packageName, uid, isReplacing);
+            } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                final boolean isReplacing = intent.getBooleanExtra(
+                        Intent.EXTRA_REPLACING, false);
+                onPackageAdded(packageName, uid, isReplacing);
             } else {
                 Log.wtf(TAG, "received unexpected intent: " + action);
             }
@@ -757,15 +769,15 @@
         }
     };
 
-    private void onUserStarted(int userId) {
+    @VisibleForTesting
+    void onUserStarted(int userId) {
         synchronized (mVpns) {
             Vpn userVpn = mVpns.get(userId);
             if (userVpn != null) {
                 loge("Starting user already has a VPN");
                 return;
             }
-            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId,
-                    new VpnProfileStore());
+            userVpn = mDeps.createVpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId);
             mVpns.put(userId, userVpn);
             if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
                 updateLockdownVpn();
@@ -842,7 +854,8 @@
         }
     }
 
-    private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
+    @VisibleForTesting
+    void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
         if (TextUtils.isEmpty(packageName) || uid < 0) {
             Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
             return;
@@ -851,15 +864,34 @@
         final int userId = UserHandle.getUserId(uid);
         synchronized (mVpns) {
             final Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
+            if (vpn == null || isReplacing) {
                 return;
             }
             // Legacy always-on VPN won't be affected since the package name is not set.
-            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
+            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
                 log("Removing always-on VPN package " + packageName + " for user "
                         + userId);
                 vpn.setAlwaysOnPackage(null, false, null);
             }
+
+            vpn.refreshPlatformVpnAppExclusionList();
+        }
+    }
+
+    @VisibleForTesting
+    void onPackageAdded(String packageName, int uid, boolean isReplacing) {
+        if (TextUtils.isEmpty(packageName) || uid < 0) {
+            Log.wtf(TAG, "Invalid package in onPackageAdded: " + packageName + " | " + uid);
+            return;
+        }
+
+        final int userId = UserHandle.getUserId(uid);
+        synchronized (mVpns) {
+            final Vpn vpn = mVpns.get(userId);
+
+            if (vpn != null && !isReplacing) {
+                vpn.refreshPlatformVpnAppExclusionList();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5c13921..60286be 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8755,7 +8755,7 @@
     private static String processClass(ProcessRecord process) {
         if (process == null || process.getPid() == MY_PID) {
             return "system_server";
-        } else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+        } else if (process.info.isSystemApp() || process.info.isSystemExt()) {
             return "system_app";
         } else {
             return "data_app";
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 2ec744f..a3ae35e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -24,6 +24,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
@@ -175,6 +176,7 @@
     private String mAgent;  // Agent to attach on startup.
     private boolean mAttachAgentDuringBind;  // Whether agent should be attached late.
     private int mDisplayId;
+    private int mTaskDisplayAreaFeatureId;
     private int mWindowingMode;
     private int mActivityType;
     private int mTaskId;
@@ -368,6 +370,7 @@
         mStreaming = false;
         mUserId = defUser;
         mDisplayId = INVALID_DISPLAY;
+        mTaskDisplayAreaFeatureId = FEATURE_UNDEFINED;
         mWindowingMode = WINDOWING_MODE_UNDEFINED;
         mActivityType = ACTIVITY_TYPE_UNDEFINED;
         mTaskId = INVALID_TASK_ID;
@@ -423,6 +426,8 @@
                     mReceiverPermission = getNextArgRequired();
                 } else if (opt.equals("--display")) {
                     mDisplayId = Integer.parseInt(getNextArgRequired());
+                } else if (opt.equals("--task-display-area-feature-id")) {
+                    mTaskDisplayAreaFeatureId = Integer.parseInt(getNextArgRequired());
                 } else if (opt.equals("--windowingMode")) {
                     mWindowingMode = Integer.parseInt(getNextArgRequired());
                 } else if (opt.equals("--activityType")) {
@@ -554,6 +559,12 @@
                 options = ActivityOptions.makeBasic();
                 options.setLaunchDisplayId(mDisplayId);
             }
+            if (mTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+                if (options == null) {
+                    options = ActivityOptions.makeBasic();
+                }
+                options.setLaunchTaskDisplayAreaFeatureId(mTaskDisplayAreaFeatureId);
+            }
             if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {
                 if (options == null) {
                     options = ActivityOptions.makeBasic();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 178b6bb..132309e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -5129,6 +5129,7 @@
         private static final String EXTRA_REQUESTER = "requester";
 
         private static final String DROPBOX_TAG_IMPERCEPTIBLE_KILL = "imperceptible_app_kill";
+        private static final boolean LOG_TO_DROPBOX = false;
 
         // uid -> killing information mapping
         private SparseArray<List<Bundle>> mWorkItems = new SparseArray<List<Bundle>>();
@@ -5234,7 +5235,7 @@
 
         private void handleDeviceIdle() {
             final DropBoxManager dbox = mService.mContext.getSystemService(DropBoxManager.class);
-            final boolean logToDropbox = dbox != null
+            final boolean logToDropbox = LOG_TO_DROPBOX && dbox != null
                     && dbox.isTagEnabled(DROPBOX_TAG_IMPERCEPTIBLE_KILL);
 
             synchronized (mService) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fb4b683..42d8778 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -365,6 +365,8 @@
     private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45;
     private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46;
     private static final int MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR = 47;
+    private static final int MSG_ROTATION_UPDATE = 48;
+    private static final int MSG_FOLD_UPDATE = 49;
 
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -1251,7 +1253,9 @@
 
         intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         if (mMonitorRotation) {
-            RotationHelper.init(mContext, mAudioHandler);
+            RotationHelper.init(mContext, mAudioHandler,
+                    rotationParam -> onRotationUpdate(rotationParam),
+                    foldParam -> onFoldUpdate(foldParam));
         }
 
         intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
@@ -1398,6 +1402,20 @@
     }
 
     //-----------------------------------------------------------------
+    // rotation/fold updates coming from RotationHelper
+    void onRotationUpdate(String rotationParameter) {
+        // use REPLACE as only the last rotation matters
+        sendMsg(mAudioHandler, MSG_ROTATION_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
+                /*obj*/ rotationParameter, /*delay*/ 0);
+    }
+
+    void onFoldUpdate(String foldParameter) {
+        // use REPLACE as only the last fold state matters
+        sendMsg(mAudioHandler, MSG_FOLD_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
+                /*obj*/ foldParameter, /*delay*/ 0);
+    }
+
+    //-----------------------------------------------------------------
     // monitoring requests for volume range initialization
     @Override // AudioSystemAdapter.OnVolRangeInitRequestListener
     public void onVolumeRangeInitRequestFromNative() {
@@ -8327,6 +8345,16 @@
                 case MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR:
                     dispatchDeviceVolumeBehavior((AudioDeviceAttributes) msg.obj, msg.arg1);
                     break;
+
+                case MSG_ROTATION_UPDATE:
+                    // rotation parameter format: "rotation=x" where x is one of 0, 90, 180, 270
+                    mAudioSystem.setParameters((String) msg.obj);
+                    break;
+
+                case MSG_FOLD_UPDATE:
+                    // fold parameter format: "device_folded=x" where x is one of on, off
+                    mAudioSystem.setParameters((String) msg.obj);
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
index eb8387f..5cdf58b 100644
--- a/services/core/java/com/android/server/audio/RotationHelper.java
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -21,13 +21,14 @@
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
-import android.media.AudioSystem;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.util.Log;
 import android.view.Display;
 import android.view.Surface;
 
+import java.util.function.Consumer;
+
 /**
  * Class to handle device rotation events for AudioService, and forward device rotation
  * and folded state to the audio HALs through AudioSystem.
@@ -53,6 +54,10 @@
 
     private static AudioDisplayListener sDisplayListener;
     private static FoldStateListener sFoldStateListener;
+    /** callback to send rotation updates to AudioSystem */
+    private static Consumer<String> sRotationUpdateCb;
+    /** callback to send folded state updates to AudioSystem */
+    private static Consumer<String> sFoldUpdateCb;
 
     private static final Object sRotationLock = new Object();
     private static final Object sFoldStateLock = new Object();
@@ -67,13 +72,16 @@
      * - sDisplayListener != null
      * - sContext != null
      */
-    static void init(Context context, Handler handler) {
+    static void init(Context context, Handler handler,
+            Consumer<String> rotationUpdateCb, Consumer<String> foldUpdateCb) {
         if (context == null) {
             throw new IllegalArgumentException("Invalid null context");
         }
         sContext = context;
         sHandler = handler;
         sDisplayListener = new AudioDisplayListener();
+        sRotationUpdateCb = rotationUpdateCb;
+        sFoldUpdateCb = foldUpdateCb;
         enable();
     }
 
@@ -115,21 +123,26 @@
         if (DEBUG_ROTATION) {
             Log.i(TAG, "publishing device rotation =" + rotation + " (x90deg)");
         }
+        String rotationParam;
         switch (rotation) {
             case Surface.ROTATION_0:
-                AudioSystem.setParameters("rotation=0");
+                rotationParam = "rotation=0";
                 break;
             case Surface.ROTATION_90:
-                AudioSystem.setParameters("rotation=90");
+                rotationParam = "rotation=90";
                 break;
             case Surface.ROTATION_180:
-                AudioSystem.setParameters("rotation=180");
+                rotationParam = "rotation=180";
                 break;
             case Surface.ROTATION_270:
-                AudioSystem.setParameters("rotation=270");
+                rotationParam = "rotation=270";
                 break;
             default:
                 Log.e(TAG, "Unknown device rotation");
+                rotationParam = null;
+        }
+        if (rotationParam != null) {
+            sRotationUpdateCb.accept(rotationParam);
         }
     }
 
@@ -140,11 +153,13 @@
         synchronized (sFoldStateLock) {
             if (sDeviceFold != newFolded) {
                 sDeviceFold = newFolded;
+                String foldParam;
                 if (newFolded) {
-                    AudioSystem.setParameters("device_folded=on");
+                    foldParam = "device_folded=on";
                 } else {
-                    AudioSystem.setParameters("device_folded=off");
+                    foldParam = "device_folded=off";
                 }
+                sFoldUpdateCb.accept(foldParam);
             }
         }
     }
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 5b26672..dd44af1 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -280,18 +280,13 @@
             }
             // for both transaural / binaural, we are not forcing enablement as the init() method
             // could have been called another time after boot in case of audioserver restart
-            if (mTransauralSupported) {
-                // not force-enabling as this device might already be in the device list
-                addCompatibleAudioDevice(
-                        new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
-                                false /*forceEnable*/);
-            }
-            if (mBinauralSupported) {
-                // not force-enabling as this device might already be in the device list
-                addCompatibleAudioDevice(
-                        new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
-                                false /*forceEnable*/);
-            }
+            addCompatibleAudioDevice(
+                    new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+                            false /*forceEnable*/);
+            // not force-enabling as this device might already be in the device list
+            addCompatibleAudioDevice(
+                    new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
+                            false /*forceEnable*/);
         } catch (RemoteException e) {
             resetCapabilities();
         } finally {
@@ -497,10 +492,9 @@
     synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
         // build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices
         ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>();
-        for (SADeviceState dev : mSADevices) {
-            if (dev.mEnabled) {
-                compatList.add(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
-                        dev.mDeviceType, dev.mDeviceAddress == null ? "" : dev.mDeviceAddress));
+        for (SADeviceState deviceState : mSADevices) {
+            if (deviceState.mEnabled) {
+                compatList.add(deviceState.getAudioDeviceAttributes());
             }
         }
         return compatList;
@@ -521,15 +515,15 @@
      */
     private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada,
             boolean forceEnable) {
+        if (!isDeviceCompatibleWithSpatializationModes(ada)) {
+            return;
+        }
         loglogi("addCompatibleAudioDevice: dev=" + ada);
-        final int deviceType = ada.getType();
-        final boolean wireless = isWireless(deviceType);
         boolean isInList = false;
         SADeviceState deviceUpdated = null; // non-null on update.
 
         for (SADeviceState deviceState : mSADevices) {
-            if (deviceType == deviceState.mDeviceType
-                    && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
+            if (deviceState.matchesAudioDeviceAttributes(ada)) {
                 isInList = true;
                 if (forceEnable) {
                     deviceState.mEnabled = true;
@@ -539,11 +533,10 @@
             }
         }
         if (!isInList) {
-            final SADeviceState dev = new SADeviceState(deviceType,
-                    wireless ? ada.getAddress() : "");
-            dev.mEnabled = true;
-            mSADevices.add(dev);
-            deviceUpdated = dev;
+            final SADeviceState deviceState = new SADeviceState(ada.getType(), ada.getAddress());
+            deviceState.mEnabled = true;
+            mSADevices.add(deviceState);
+            deviceUpdated = deviceState;
         }
         if (deviceUpdated != null) {
             onRoutingUpdated();
@@ -574,13 +567,10 @@
 
     synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
         loglogi("removeCompatibleAudioDevice: dev=" + ada);
-        final int deviceType = ada.getType();
-        final boolean wireless = isWireless(deviceType);
         SADeviceState deviceUpdated = null; // non-null on update.
 
         for (SADeviceState deviceState : mSADevices) {
-            if (deviceType == deviceState.mDeviceType
-                    && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
+            if (deviceState.matchesAudioDeviceAttributes(ada)) {
                 deviceState.mEnabled = false;
                 deviceUpdated = deviceState;
                 break;
@@ -602,10 +592,9 @@
         // if not a wireless device, this value will be overwritten to map the type
         // to TYPE_BUILTIN_SPEAKER or TYPE_WIRED_HEADPHONES
         @AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType();
-        final boolean wireless = isWireless(deviceType);
 
         // if not a wireless device: find if media device is in the speaker, wired headphones
-        if (!wireless) {
+        if (!isWireless(deviceType)) {
             // is the device type capable of doing SA?
             if (!mSACapableDeviceTypes.contains(deviceType)) {
                 Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada);
@@ -640,9 +629,7 @@
         boolean enabled = false;
         boolean available = false;
         for (SADeviceState deviceState : mSADevices) {
-            if (deviceType == deviceState.mDeviceType
-                    && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
-                    || !wireless) {
+            if (deviceState.matchesAudioDeviceAttributes(ada)) {
                 available = true;
                 enabled = deviceState.mEnabled;
                 break;
@@ -652,11 +639,12 @@
     }
 
     private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) {
+        if (!isDeviceCompatibleWithSpatializationModes(ada)) {
+            return;
+        }
         boolean knownDevice = false;
         for (SADeviceState deviceState : mSADevices) {
-            // wireless device so always check address
-            if (ada.getType() == deviceState.mDeviceType
-                    && ada.getAddress().equals(deviceState.mDeviceAddress)) {
+            if (deviceState.matchesAudioDeviceAttributes(ada)) {
                 knownDevice = true;
                 break;
             }
@@ -704,13 +692,8 @@
         if (ada.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
             return false;
         }
-
-        final int deviceType = ada.getType();
-        final boolean wireless = isWireless(deviceType);
         for (SADeviceState deviceState : mSADevices) {
-            if (deviceType == deviceState.mDeviceType
-                    && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
-                    || !wireless) {
+            if (deviceState.matchesAudioDeviceAttributes(ada)) {
                 return true;
             }
         }
@@ -719,12 +702,19 @@
 
     private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes,
             @NonNull AudioFormat format, @NonNull AudioDeviceAttributes[] devices) {
-        final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(devices[0].getType(),
+        if (isDeviceCompatibleWithSpatializationModes(devices[0])) {
+            return AudioSystem.canBeSpatialized(attributes, format, devices);
+        }
+        return false;
+    }
+
+    private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) {
+        final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
                 /*default when type not found*/ SpatializationMode.SPATIALIZER_BINAURAL);
         if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
                 || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
                         && mTransauralSupported)) {
-            return AudioSystem.canBeSpatialized(attributes, format, devices);
+            return true;
         }
         return false;
     }
@@ -1089,13 +1079,8 @@
             Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled
                     + " for " + ada);
         }
-        final int deviceType = ada.getType();
-        final boolean wireless = isWireless(deviceType);
-
         for (SADeviceState deviceState : mSADevices) {
-            if (deviceType == deviceState.mDeviceType
-                    && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
-                    || !wireless) {
+            if (deviceState.matchesAudioDeviceAttributes(ada)) {
                 if (!deviceState.mHasHeadTracker) {
                     Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled
                             + " device:" + ada + " on a device without headtracker");
@@ -1109,7 +1094,7 @@
             }
         }
         // check current routing to see if it affects the headtracking mode
-        if (ROUTING_DEVICES[0].getType() == deviceType
+        if (ROUTING_DEVICES[0].getType() == ada.getType()
                 && ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
             setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
                     : Spatializer.HEAD_TRACKING_MODE_DISABLED);
@@ -1121,13 +1106,8 @@
             Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada);
             return false;
         }
-        final int deviceType = ada.getType();
-        final boolean wireless = isWireless(deviceType);
-
         for (SADeviceState deviceState : mSADevices) {
-            if (deviceType == deviceState.mDeviceType
-                    && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
-                    || !wireless) {
+            if (deviceState.matchesAudioDeviceAttributes(ada)) {
                 return deviceState.mHasHeadTracker;
             }
         }
@@ -1144,13 +1124,8 @@
             Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada);
             return false;
         }
-        final int deviceType = ada.getType();
-        final boolean wireless = isWireless(deviceType);
-
         for (SADeviceState deviceState : mSADevices) {
-            if (deviceType == deviceState.mDeviceType
-                    && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
-                    || !wireless) {
+            if (deviceState.matchesAudioDeviceAttributes(ada)) {
                 if (!deviceState.mHasHeadTracker) {
                     deviceState.mHasHeadTracker = true;
                     mAudioService.persistSpatialAudioDeviceSettings();
@@ -1168,13 +1143,8 @@
             Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada);
             return false;
         }
-        final int deviceType = ada.getType();
-        final boolean wireless = isWireless(deviceType);
-
         for (SADeviceState deviceState : mSADevices) {
-            if (deviceType == deviceState.mDeviceType
-                    && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
-                    || !wireless) {
+            if (deviceState.matchesAudioDeviceAttributes(ada)) {
                 if (!deviceState.mHasHeadTracker) {
                     return false;
                 }
@@ -1531,7 +1501,7 @@
 
         SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @NonNull String address) {
             mDeviceType = deviceType;
-            mDeviceAddress = Objects.requireNonNull(address);
+            mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
         }
 
         @Override
@@ -1599,6 +1569,18 @@
                 return null;
             }
         }
+
+        public AudioDeviceAttributes getAudioDeviceAttributes() {
+            return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+                    mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress);
+        }
+
+        public boolean matchesAudioDeviceAttributes(AudioDeviceAttributes ada) {
+            final int deviceType = ada.getType();
+            final boolean wireless = isWireless(deviceType);
+            return (deviceType == mDeviceType)
+                        && (!wireless || ada.getAddress().equals(mDeviceAddress));
+        }
     }
 
     /*package*/ synchronized String getSADeviceSettings() {
@@ -1619,7 +1601,9 @@
         // small list, not worth overhead of Arrays.stream(devSettings)
         for (String setting : devSettings) {
             SADeviceState devState = SADeviceState.fromPersistedString(setting);
-            if (devState != null) {
+            if (devState != null
+                    && isDeviceCompatibleWithSpatializationModes(
+                            devState.getAudioDeviceAttributes())) {
                 mSADevices.add(devState);
                 logDeviceState(devState, "setSADeviceSettings");
             }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 5b282ce..4e5ce8a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -4085,6 +4085,20 @@
             @NonNull List<String> excludedApps) {
         enforceNotRestrictedUser();
         if (!storeAppExclusionList(packageName, excludedApps)) return false;
+
+        updateAppExclusionList(excludedApps);
+
+        return true;
+    }
+
+    /**
+     * Triggers an update of the VPN network's excluded UIDs if a VPN is running.
+     */
+    public synchronized void refreshPlatformVpnAppExclusionList() {
+        updateAppExclusionList(getAppExclusionList(mPackage));
+    }
+
+    private synchronized void updateAppExclusionList(@NonNull List<String> excludedApps) {
         // Re-build and update NetworkCapabilities via NetworkAgent.
         if (mNetworkAgent != null) {
             // Only update the platform VPN
@@ -4097,8 +4111,6 @@
                 mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
             }
         }
-
-        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/dreams/OWNERS b/services/core/java/com/android/server/dreams/OWNERS
index 426f002..7302f6e 100644
--- a/services/core/java/com/android/server/dreams/OWNERS
+++ b/services/core/java/com/android/server/dreams/OWNERS
@@ -1,3 +1,4 @@
+brycelee@google.com
 dsandler@android.com
 michaelwr@google.com
 roosa@google.com
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 157057d..aaa9ee5 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -337,6 +337,8 @@
     static final int AUDIO_CODEC_WMAPRO = 0xE; // Support WMA-Pro
     static final int AUDIO_CODEC_MAX = 0xF;
 
+    static final int AUDIO_FORMAT_MASK = 0b0111_1000;
+
     @StringDef({
         AUDIO_DEVICE_ARC_IN,
         AUDIO_DEVICE_SPDIF,
diff --git a/services/core/java/com/android/server/hdmi/RequestSadAction.java b/services/core/java/com/android/server/hdmi/RequestSadAction.java
index 23aaf32..0188e96 100644
--- a/services/core/java/com/android/server/hdmi/RequestSadAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestSadAction.java
@@ -200,8 +200,14 @@
     }
 
     private boolean isValidCodec(byte codec) {
-        return Constants.AUDIO_CODEC_NONE < (codec & 0xFF)
-                && (codec & 0xFF) <= Constants.AUDIO_CODEC_MAX;
+        // Bit 7 needs to be 0.
+        if ((codec & (1 << 7)) != 0) {
+            return false;
+        }
+        // Bit [6, 3] is the audio format code.
+        int audioFormatCode = (codec & Constants.AUDIO_FORMAT_MASK) >> 3;
+        return Constants.AUDIO_CODEC_NONE < audioFormatCode
+                && audioFormatCode <= Constants.AUDIO_CODEC_MAX;
     }
 
     private void updateResult(byte[] sad) {
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 4ffad91..6c75dbf 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -108,12 +108,17 @@
     }
 
     @AnyThread
-    void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
-            int configChanges, boolean stylusHwSupported,
-            @InputMethodNavButtonFlags int navButtonFlags) {
+    void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations,
+            int configChanges, boolean stylusHandWritingSupported,
+            @InputMethodNavButtonFlags int navigationBarFlags) {
+        final IInputMethod.InitParams params = new IInputMethod.InitParams();
+        params.token = token;
+        params.privilegedOperations = privilegedOperations;
+        params.configChanges = configChanges;
+        params.stylusHandWritingSupported = stylusHandWritingSupported;
+        params.navigationBarFlags = navigationBarFlags;
         try {
-            mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported,
-                    navButtonFlags);
+            mTarget.initializeInternal(params);
         } catch (RemoteException e) {
             logRemoteException(e);
         }
@@ -148,13 +153,19 @@
     }
 
     @AnyThread
-    void startInput(IBinder startInputToken, IRemoteInputConnection inputConnection,
+    void startInput(IBinder startInputToken, IRemoteInputConnection remoteInputConnection,
             EditorInfo editorInfo, boolean restarting,
             @InputMethodNavButtonFlags int navButtonFlags,
             @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+        final IInputMethod.StartInputParams params = new IInputMethod.StartInputParams();
+        params.startInputToken = startInputToken;
+        params.remoteInputConnection = remoteInputConnection;
+        params.editorInfo = editorInfo;
+        params.restarting = restarting;
+        params.navigationBarFlags = navButtonFlags;
+        params.imeDispatcher = imeDispatcher;
         try {
-            mTarget.startInput(startInputToken, inputConnection, editorInfo, restarting,
-                    navButtonFlags, imeDispatcher);
+            mTarget.startInput(params);
         } catch (RemoteException e) {
             logRemoteException(e);
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3fceba7..b45dc7f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2108,9 +2108,24 @@
     }
 
     @Override
-    public boolean isStylusHandwritingAvailable() {
+    public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+        }
+
         synchronized (ImfLock.class) {
-            return mBindingController.supportsStylusHandwriting();
+            if (userId == mSettings.getCurrentUserId()) {
+                return mBindingController.supportsStylusHandwriting();
+            }
+
+            //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
+            //TODO(b/210039666): use cache.
+            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
+            final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
+                    mContext.getContentResolver(), methodMap, userId, true);
+            final InputMethodInfo imi = methodMap.get(settings.getSelectedInputMethod());
+            return imi != null && imi.supportsStylusHandwriting();
         }
     }
 
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 1de3e37..ee8a23c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -73,7 +73,6 @@
 import android.database.ContentObserver;
 import android.database.sqlite.SQLiteDatabase;
 import android.hardware.authsecret.V1_0.IAuthSecret;
-import android.hardware.biometrics.BiometricManager;
 import android.hardware.face.Face;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.Fingerprint;
@@ -89,7 +88,6 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ShellCallback;
-import android.os.StrictMode;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -110,7 +108,6 @@
 import android.security.keystore.recovery.WrappedApplicationKey;
 import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
 import android.security.keystore2.AndroidKeyStoreProvider;
-import android.service.gatekeeper.GateKeeperResponse;
 import android.service.gatekeeper.IGateKeeperService;
 import android.system.keystore2.Domain;
 import android.text.TextUtils;
@@ -141,7 +138,6 @@
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
-import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
@@ -163,7 +159,6 @@
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.KeyStoreException;
-import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.security.UnrecoverableKeyException;
@@ -517,11 +512,6 @@
             return new RebootEscrowManager(mContext, callbacks, storage);
         }
 
-        public boolean hasEnrolledBiometrics(int userId) {
-            BiometricManager bm = mContext.getSystemService(BiometricManager.class);
-            return bm.hasEnrolledBiometrics(userId);
-        }
-
         public int binderGetCallingUid() {
             return Binder.getCallingUid();
         }
@@ -1285,11 +1275,8 @@
         return mStorage.getString(key, defaultValue, userId);
     }
 
-    private void setKeyguardStoredQuality(int quality, int userId) {
-        if (DEBUG) Slog.d(TAG, "setKeyguardStoredQuality: user=" + userId + " quality=" + quality);
-        mStorage.setLong(LockPatternUtils.PASSWORD_TYPE_KEY, quality, userId);
-    }
-
+    // Not relevant for new devices, but some legacy devices still have PASSWORD_TYPE_KEY around to
+    // distinguish between credential types.
     private int getKeyguardStoredQuality(int userId) {
         return (int) mStorage.getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
                 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
@@ -1326,18 +1313,6 @@
                 return pinOrPasswordQualityToCredentialType(getKeyguardStoredQuality(userId));
             }
         }
-        // Intentional duplication of the getKeyguardStoredQuality() call above since this is a
-        // unlikely code path (device with pre-synthetic password credential). We want to skip
-        // calling getKeyguardStoredQuality whenever possible.
-        final int savedQuality = getKeyguardStoredQuality(userId);
-        if (savedQuality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
-                && mStorage.hasPattern(userId)) {
-            return CREDENTIAL_TYPE_PATTERN;
-        }
-        if (savedQuality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
-                && mStorage.hasPassword(userId)) {
-            return pinOrPasswordQualityToCredentialType(savedQuality);
-        }
         return CREDENTIAL_TYPE_NONE;
     }
 
@@ -1742,33 +1717,15 @@
             LockscreenCredential savedCredential, int userId, boolean isLockTiedToParent) {
         Objects.requireNonNull(credential);
         Objects.requireNonNull(savedCredential);
+        if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId);
         synchronized (mSpManager) {
-            if (isSyntheticPasswordBasedCredentialLocked(userId)) {
-                return spBasedSetLockCredentialInternalLocked(credential, savedCredential, userId,
-                        isLockTiedToParent);
-            }
-        }
-
-        if (credential.isNone()) {
-            clearUserKeyProtection(userId, null);
-            gateKeeperClearSecureUserId(userId);
-            mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), userId);
-            // Still update PASSWORD_TYPE_KEY if we are running in pre-synthetic password code path,
-            // since it forms part of the state that determines the credential type
-            // @see getCredentialTypeInternal
-            setKeyguardStoredQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
-            setKeystorePassword(null, userId);
-            fixateNewestUserKeyAuth(userId);
-            synchronizeUnifiedWorkChallengeForProfiles(userId, null);
-            setUserPasswordMetrics(LockscreenCredential.createNone(), userId);
-            sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
-            return true;
-        }
-
-        CredentialHash currentHandle = mStorage.readCredentialHash(userId);
-        if (isProfileWithUnifiedLock(userId)) {
-            // get credential from keystore when managed/clone profile has unified lock
-            if (savedCredential.isNone()) {
+            if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
+                if (!savedCredential.isNone()) {
+                    throw new IllegalStateException("Saved credential given, but user has no SP");
+                }
+                initializeSyntheticPasswordLocked(savedCredential, userId);
+            } else if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
+                // get credential from keystore when profile has unified lock
                 try {
                     //TODO: remove as part of b/80170828
                     savedCredential = getDecryptedPasswordForTiedProfile(userId);
@@ -1781,19 +1738,31 @@
                     Slog.e(TAG, "Failed to decrypt child profile key", e);
                 }
             }
-        } else {
-            if (currentHandle.hash == null) {
-                if (!savedCredential.isNone()) {
-                    Slog.w(TAG, "Saved credential provided, but none stored");
+            final long origHandle = getSyntheticPasswordHandleLocked(userId);
+            AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
+                    getGateKeeperService(), origHandle, savedCredential, userId, null);
+            VerifyCredentialResponse response = authResult.gkResponse;
+            AuthenticationToken auth = authResult.authToken;
+
+            if (auth == null) {
+                if (response == null
+                        || response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR) {
+                    Slog.w(TAG, "Failed to enroll: incorrect credential.");
+                    return false;
                 }
-                savedCredential.close();
-                savedCredential = LockscreenCredential.createNone();
+                if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
+                    Slog.w(TAG, "Failed to enroll: rate limit exceeded.");
+                    return false;
+                }
+                // Should not be reachable, but just in case.
+                throw new IllegalStateException("password change failed");
             }
-        }
-        synchronized (mSpManager) {
-            initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential, userId);
-            return spBasedSetLockCredentialInternalLocked(credential, savedCredential, userId,
-                    isLockTiedToParent);
+
+            onAuthTokenKnownForUser(userId, auth);
+            setLockCredentialWithAuthTokenLocked(credential, auth, userId);
+            mSpManager.destroyPasswordBasedSyntheticPassword(origHandle, userId);
+            sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
+            return true;
         }
     }
 
@@ -1912,10 +1881,6 @@
         return getUserManagerFromCache(userId).isCredentialSharableWithParent();
     }
 
-    private VerifyCredentialResponse convertResponse(GateKeeperResponse gateKeeperResponse) {
-        return VerifyCredentialResponse.fromGateKeeperResponse(gateKeeperResponse);
-    }
-
     private void setCredentialRequiredToDecrypt(boolean required) {
         if (isDeviceEncryptionEnabled()) {
             Settings.Global.putInt(mContext.getContentResolver(),
@@ -2089,21 +2054,6 @@
         }
     }
 
-    private static byte[] secretFromCredential(LockscreenCredential credential) {
-        try {
-            MessageDigest digest = MessageDigest.getInstance("SHA-512");
-            // Personalize the hash
-            byte[] personalization = "Android FBE credential hash".getBytes();
-            // Pad it to the block size of the hash function
-            personalization = Arrays.copyOf(personalization, 128);
-            digest.update(personalization);
-            digest.update(credential.getCredential());
-            return digest.digest();
-        } catch (NoSuchAlgorithmException e) {
-            throw new IllegalStateException("NoSuchAlgorithmException for SHA-512");
-        }
-    }
-
     private boolean isUserKeyUnlocked(int userId) {
         try {
             return mStorageManager.isUserKeyUnlocked(userId);
@@ -2262,9 +2212,8 @@
         }
     }
 
-    /*
-     * Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
-     * format.
+    /**
+     * Verify user credential and unlock the user.
      * @param credential User's lockscreen credential
      * @param userId User to verify the credential for
      * @param progressCallback Receive progress callbacks
@@ -2282,36 +2231,59 @@
             Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
             return VerifyCredentialResponse.ERROR;
         }
+        Slog.d(TAG, "doVerifyCredential: user=" + userId);
 
-        VerifyCredentialResponse response = spBasedDoVerifyCredential(credential, userId,
-                progressCallback, flags);
+        final AuthenticationResult authResult;
+        VerifyCredentialResponse response;
 
-        // The user employs synthetic password based credential.
-        if (response != null) {
-            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-                sendCredentialsOnUnlockIfRequired(credential, userId);
+        synchronized (mSpManager) {
+            if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
+                Slog.wtf(TAG, "Unexpected credential type, should be SP based.");
+                return VerifyCredentialResponse.ERROR;
             }
-            return response;
+            if (userId == USER_FRP) {
+                return mSpManager.verifyFrpCredential(getGateKeeperService(), credential,
+                        progressCallback);
+            }
+
+            long handle = getSyntheticPasswordHandleLocked(userId);
+            authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
+                    getGateKeeperService(), handle, credential, userId, progressCallback);
+            response = authResult.gkResponse;
+
+            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+                // credential has matched
+                mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
+                        authResult.authToken.deriveGkPassword());
+
+                // perform verifyChallenge with synthetic password which generates the real GK auth
+                // token and response for the current user
+                response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
+                        0L /* challenge */, userId);
+                if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+                    // This shouldn't really happen: the unwrapping of SP succeeds, but SP doesn't
+                    // match the recorded GK password handle.
+                    Slog.wtf(TAG, "verifyChallenge with SP failed.");
+                    return VerifyCredentialResponse.ERROR;
+                }
+            }
         }
-
-        if (userId == USER_FRP) {
-            Slog.wtf(TAG, "Unexpected FRP credential type, should be SP based.");
-            return VerifyCredentialResponse.ERROR;
-        }
-
-        final CredentialHash storedHash = mStorage.readCredentialHash(userId);
-        if (!credential.checkAgainstStoredType(storedHash.type)) {
-            Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??"
-                    + " stored: " + storedHash.type + " passed in: " + credential.getType());
-            return VerifyCredentialResponse.ERROR;
-        }
-
-        response = verifyCredential(userId, storedHash, credential, progressCallback);
-
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-            mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
+            onCredentialVerified(authResult.authToken,
+                    PasswordMetrics.computeForCredential(credential), userId);
+            if ((flags & VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0) {
+                final long gkHandle = storeGatekeeperPasswordTemporarily(
+                        authResult.authToken.deriveGkPassword());
+                response = new VerifyCredentialResponse.Builder()
+                        .setGatekeeperPasswordHandle(gkHandle)
+                        .build();
+            }
+            sendCredentialsOnUnlockIfRequired(credential, userId);
+        } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
+            if (response.getTimeout() > 0) {
+                requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
+            }
         }
-
         return response;
     }
 
@@ -2351,83 +2323,6 @@
     }
 
     /**
-     * Lowest-level credential verification routine that talks to GateKeeper. If verification
-     * passes, unlock the corresponding user and keystore. Also handles the migration from legacy
-     * hash to GK.
-     */
-    private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
-            LockscreenCredential credential, ICheckCredentialProgressCallback progressCallback) {
-        if ((storedHash == null || storedHash.hash.length == 0) && credential.isNone()) {
-            // don't need to pass empty credentials to GateKeeper
-            return VerifyCredentialResponse.OK;
-        }
-
-        if (storedHash == null || storedHash.hash.length == 0 || credential.isNone()) {
-            return VerifyCredentialResponse.ERROR;
-        }
-
-        // We're potentially going to be doing a bunch of disk I/O below as part
-        // of unlocking the user, so yell if calling from the main thread.
-        StrictMode.noteDiskRead();
-
-        GateKeeperResponse gateKeeperResponse;
-        try {
-            gateKeeperResponse = getGateKeeperService().verifyChallenge(
-                    userId, 0L /* challenge */, storedHash.hash, credential.getCredential());
-        } catch (RemoteException e) {
-            Slog.e(TAG, "gatekeeper verify failed", e);
-            gateKeeperResponse = GateKeeperResponse.ERROR;
-        }
-        VerifyCredentialResponse response = convertResponse(gateKeeperResponse);
-        boolean shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
-
-        if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-
-            // credential has matched
-
-            if (progressCallback != null) {
-                try {
-                    progressCallback.onCredentialVerified();
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "progressCallback throws exception", e);
-                }
-            }
-            setUserPasswordMetrics(credential, userId);
-            unlockKeystore(credential.getCredential(), userId);
-
-            Slog.i(TAG, "Unlocking user " + userId);
-            unlockUser(userId, secretFromCredential(credential));
-
-            if (isProfileWithSeparatedLock(userId)) {
-                setDeviceUnlockedForUser(userId);
-            }
-            if (shouldReEnroll) {
-                setLockCredentialInternal(credential, credential,
-                        userId, /* isLockTiedToParent= */ false);
-            } else {
-                // Now that we've cleared of all required GK migration, let's do the final
-                // migration to synthetic password.
-                synchronized (mSpManager) {
-                    if (shouldMigrateToSyntheticPasswordLocked(userId)) {
-                        AuthenticationToken auth = initializeSyntheticPasswordLocked(
-                                storedHash.hash, credential,  userId);
-                        activateEscrowTokens(auth, userId);
-                    }
-                }
-            }
-            // Use credentials to create recoverable keystore snapshot.
-            sendCredentialsOnUnlockIfRequired(credential, userId);
-
-        } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
-            if (response.getTimeout() > 0) {
-                requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
-            }
-        }
-
-        return response;
-    }
-
-    /**
      * Keep track of the given user's latest password metric. This should be called
      * when the user is authenticating or when a new password is being set. In comparison,
      * {@link #notifyPasswordChanged} only needs to be called when the user changes the password.
@@ -2779,7 +2674,9 @@
      * Precondition: vold and keystore unlocked.
      *
      * Create new synthetic password, set up synthetic password blob protected by the supplied
-     * user credential, and make the newly-created SP blob active.
+     * user credential, and make the newly-created SP blob active. This is called just once in the
+     * lifetime of the user: the first time that a user credential is set (!credential.isNone()), or
+     * when an escrow token is activated on an unsecured device (credential.isNone()).
      *
      * The invariant under a synthetic password is:
      * 1. If user credential exists, then both vold and keystore and protected with keys derived
@@ -2793,47 +2690,21 @@
      *     protected by a default PIN.
      * 4. The user SID is linked with synthetic password, but its cleared/re-created when the user
      *     clears/re-creates their lockscreen PIN.
-     *
-     *
-     * Different cases of calling this method:
-     * 1. credentialHash != null
-     *     This implies credential != null, a new SP blob will be provisioned, and existing SID
-     *     migrated to associate with the new SP.
-     *     This happens during a normal migration case when the user currently has password.
-     *
-     * 2. credentialhash == null and credential == null
-     *     A new SP blob and will be created, while the user has no credentials.
-     *     This can happens when we are activating an escrow token on a unsecured device, during
-     *     which we want to create the SP structure with an empty user credential.
-     *     This could also happen during an untrusted reset to clear password.
-     *
-     * 3. credentialhash == null and credential != null
-     *     The user sets a new lockscreen password FOR THE FIRST TIME on a SP-enabled device.
-     *     New credential and new SID will be created
      */
     @GuardedBy("mSpManager")
     @VisibleForTesting
-    protected AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
-            LockscreenCredential credential, int userId) {
+    AuthenticationToken initializeSyntheticPasswordLocked(LockscreenCredential credential,
+            int userId) {
         Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
         Preconditions.checkState(
                 getSyntheticPasswordHandleLocked(userId) == SyntheticPasswordManager.DEFAULT_HANDLE,
                 "Cannot reinitialize SP");
 
-        final AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(
-                getGateKeeperService(), credentialHash, credential, userId);
-        if (auth == null) {
-            Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token");
-            return null;
-        }
+        final AuthenticationToken auth = mSpManager.newSyntheticPassword(userId);
         long handle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
                 credential, auth, userId);
         if (!credential.isNone()) {
-            if (credentialHash == null) {
-                // Since when initializing SP, we didn't provide an existing password handle
-                // for it to migrate SID, we need to create a new SID for the user.
-                mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
-            }
+            mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
             mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
             setUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
             setKeystorePassword(auth.deriveKeyStorePassword(), userId);
@@ -2878,73 +2749,6 @@
         return handle != SyntheticPasswordManager.DEFAULT_HANDLE;
     }
 
-    @VisibleForTesting
-    protected boolean shouldMigrateToSyntheticPasswordLocked(int userId) {
-        return getSyntheticPasswordHandleLocked(userId) == SyntheticPasswordManager.DEFAULT_HANDLE;
-    }
-
-    private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
-            int userId, ICheckCredentialProgressCallback progressCallback,
-            @LockPatternUtils.VerifyFlag int flags) {
-        final boolean hasEnrolledBiometrics = mInjector.hasEnrolledBiometrics(userId);
-
-        Slog.d(TAG, "spBasedDoVerifyCredential: user=" + userId
-                + " hasEnrolledBiometrics=" + hasEnrolledBiometrics);
-
-        final AuthenticationResult authResult;
-        VerifyCredentialResponse response;
-        final boolean requestGkPw = (flags & VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0;
-
-        synchronized (mSpManager) {
-            if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
-                return null;
-            }
-            if (userId == USER_FRP) {
-                return mSpManager.verifyFrpCredential(getGateKeeperService(),
-                        userCredential, progressCallback);
-            }
-
-            long handle = getSyntheticPasswordHandleLocked(userId);
-            authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
-                    getGateKeeperService(), handle, userCredential, userId, progressCallback);
-            response = authResult.gkResponse;
-
-            // credential has matched
-            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-                mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
-                        authResult.authToken.deriveGkPassword());
-
-                // perform verifyChallenge with synthetic password which generates the real GK auth
-                // token and response for the current user
-                response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
-                        0L /* challenge */, userId);
-                if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
-                    // This shouldn't really happen: the unwrapping of SP succeeds, but SP doesn't
-                    // match the recorded GK password handle.
-                    Slog.wtf(TAG, "verifyChallenge with SP failed.");
-                    return VerifyCredentialResponse.ERROR;
-                }
-            }
-        }
-        if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-            onCredentialVerified(authResult.authToken,
-                    PasswordMetrics.computeForCredential(userCredential), userId);
-        } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
-            if (response.getTimeout() > 0) {
-                requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
-            }
-        }
-
-        if (response.isMatched() && requestGkPw) {
-            final long handle = storeGatekeeperPasswordTemporarily(
-                    authResult.authToken.deriveGkPassword());
-            return new VerifyCredentialResponse.Builder().setGatekeeperPasswordHandle(handle)
-                    .build();
-        } else {
-            return response;
-        }
-    }
-
     /**
      * Stores the gatekeeper password temporarily.
      * @param gatekeeperPassword unlocked upon successful Synthetic Password
@@ -3149,56 +2953,6 @@
     }
 
     /**
-     * @param savedCredential if the user is a profile with unified challenge and
-     *   savedCredential is empty, LSS will try to re-derive the profile password internally.
-     *     TODO (b/80170828): Fix this so profile password is always passed in.
-     */
-    @GuardedBy("mSpManager")
-    private boolean spBasedSetLockCredentialInternalLocked(LockscreenCredential credential,
-            LockscreenCredential savedCredential, int userId, boolean isLockTiedToParent) {
-        if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
-        if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
-            // get credential from keystore when profile has unified lock
-            try {
-                //TODO: remove as part of b/80170828
-                savedCredential = getDecryptedPasswordForTiedProfile(userId);
-            } catch (FileNotFoundException e) {
-                Slog.i(TAG, "Child profile key not found");
-            } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
-                    | NoSuchAlgorithmException | NoSuchPaddingException
-                    | InvalidAlgorithmParameterException | IllegalBlockSizeException
-                    | BadPaddingException | CertificateException | IOException e) {
-                Slog.e(TAG, "Failed to decrypt child profile key", e);
-            }
-        }
-        long handle = getSyntheticPasswordHandleLocked(userId);
-        AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
-                getGateKeeperService(), handle, savedCredential, userId, null);
-        VerifyCredentialResponse response = authResult.gkResponse;
-        AuthenticationToken auth = authResult.authToken;
-
-        if (auth == null) {
-            if (response == null
-                    || response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR) {
-                Slog.w(TAG, "Failed to enroll: incorrect credential.");
-                return false;
-            }
-            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
-                Slog.w(TAG, "Failed to enroll: rate limit exceeded.");
-                return false;
-            }
-            // Should not be reachable, but just in case.
-            throw new IllegalStateException("password change failed");
-        }
-
-        onAuthTokenKnownForUser(userId, auth);
-        setLockCredentialWithAuthTokenLocked(credential, auth, userId);
-        mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
-        sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
-        return true;
-    }
-
-    /**
      * Returns a fixed pseudorandom byte string derived from the user's synthetic password.
      * This is used to salt the password history hash to protect the hash against offline
      * bruteforcing, since rederiving this value requires a successful authentication.
@@ -3244,20 +2998,18 @@
             // the token can then be activated immediately.
             AuthenticationToken auth = null;
             if (!isUserSecure(userId)) {
-                if (shouldMigrateToSyntheticPasswordLocked(userId)) {
-                    auth = initializeSyntheticPasswordLocked(
-                            /* credentialHash */ null, LockscreenCredential.createNone(), userId);
-                } else /* isSyntheticPasswordBasedCredentialLocked(userId) */ {
-                    long pwdHandle = getSyntheticPasswordHandleLocked(userId);
+                long handle = getSyntheticPasswordHandleLocked(userId);
+                if (handle == SyntheticPasswordManager.DEFAULT_HANDLE) {
+                    auth = initializeSyntheticPasswordLocked(LockscreenCredential.createNone(),
+                            userId);
+                } else {
                     auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
-                            pwdHandle, LockscreenCredential.createNone(), userId, null).authToken;
+                            handle, LockscreenCredential.createNone(), userId, null).authToken;
                 }
             }
-            if (isSyntheticPasswordBasedCredentialLocked(userId)) {
-                disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
-                if (!mSpManager.hasEscrowData(userId)) {
-                    throw new SecurityException("Escrow token is disabled on the current user");
-                }
+            disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
+            if (!mSpManager.hasEscrowData(userId)) {
+                throw new SecurityException("Escrow token is disabled on the current user");
             }
             long handle = mSpManager.createTokenBasedSyntheticPassword(token, type, userId,
                     callback);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index f9db5cf..9ddf1ef 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -85,8 +85,6 @@
     };
 
     private static final String SYSTEM_DIRECTORY = "/system/";
-    private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
-    private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
     private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
 
     private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key";
@@ -248,38 +246,6 @@
             }
             cursor.close();
         }
-
-        // Populate cache by reading the password and pattern files.
-        readCredentialHash(userId);
-    }
-
-    private CredentialHash readPasswordHashIfExists(int userId) {
-        byte[] stored = readFile(getLockPasswordFilename(userId));
-        if (!ArrayUtils.isEmpty(stored)) {
-            return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN);
-        }
-        return null;
-    }
-
-    private CredentialHash readPatternHashIfExists(int userId) {
-        byte[] stored = readFile(getLockPatternFilename(userId));
-        if (!ArrayUtils.isEmpty(stored)) {
-            return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
-        }
-        return null;
-    }
-
-    public CredentialHash readCredentialHash(int userId) {
-        CredentialHash passwordHash = readPasswordHashIfExists(userId);
-        if (passwordHash != null) {
-            return passwordHash;
-        }
-
-        CredentialHash patternHash = readPatternHashIfExists(userId);
-        if (patternHash != null) {
-            return patternHash;
-        }
-        return CredentialHash.createEmptyHash();
     }
 
     public void removeChildProfileLock(int userId) {
@@ -336,14 +302,6 @@
         deleteFile(getRebootEscrowServerBlob());
     }
 
-    public boolean hasPassword(int userId) {
-        return hasFile(getLockPasswordFilename(userId));
-    }
-
-    public boolean hasPattern(int userId) {
-        return hasFile(getLockPatternFilename(userId));
-    }
-
     private boolean hasFile(String name) {
         byte[] contents = readFile(name);
         return contents != null && contents.length > 0;
@@ -429,33 +387,6 @@
         }
     }
 
-    public void writeCredentialHash(CredentialHash hash, int userId) {
-        byte[] patternHash = null;
-        byte[] passwordHash = null;
-        if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN
-                || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
-                || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PIN) {
-            passwordHash = hash.hash;
-        } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
-            patternHash = hash.hash;
-        } else {
-            Preconditions.checkArgument(hash.type == LockPatternUtils.CREDENTIAL_TYPE_NONE,
-                    "Unknown credential type");
-        }
-        writeFile(getLockPasswordFilename(userId), passwordHash);
-        writeFile(getLockPatternFilename(userId), patternHash);
-    }
-
-    @VisibleForTesting
-    String getLockPatternFilename(int userId) {
-        return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
-    }
-
-    @VisibleForTesting
-    String getLockPasswordFilename(int userId) {
-        return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
-    }
-
     @VisibleForTesting
     String getChildProfileLockFile(int userId) {
         return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
@@ -567,12 +498,7 @@
 
         if (parentInfo == null) {
             // This user owns its lock settings files - safe to delete them
-            synchronized (mFileWriteLock) {
-                deleteFilesAndRemoveCache(
-                        getLockPasswordFilename(userId),
-                        getLockPatternFilename(userId),
-                        getRebootEscrowFile(userId));
-            }
+            deleteFile(getRebootEscrowFile(userId));
         } else {
             // Managed profile
             removeChildProfileLock(userId);
@@ -594,17 +520,6 @@
         dispatchChange(this);
     }
 
-    private void deleteFilesAndRemoveCache(String... names) {
-        for (String name : names) {
-            File file = new File(name);
-            if (file.exists()) {
-                file.delete();
-                mCache.putFile(name, null);
-                dispatchChange(this);
-            }
-        }
-    }
-
     public void setBoolean(String key, boolean value, int userId) {
         setString(key, value ? "1" : "0", userId);
     }
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index 3386408..c8f1cb2 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -99,8 +99,8 @@
         return outputStream.toByteArray();
     }
 
-    public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) {
-        byte[] keyHash = personalisedHash(personalisation, keyBytes);
+    public static byte[] encrypt(byte[] keyBytes, byte[] personalization, byte[] message) {
+        byte[] keyHash = personalizedHash(personalization, keyBytes);
         SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
                 KeyProperties.KEY_ALGORITHM_AES);
         try {
@@ -113,8 +113,8 @@
         }
     }
 
-    public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) {
-        byte[] keyHash = personalisedHash(personalisation, keyBytes);
+    public static byte[] decrypt(byte[] keyBytes, byte[] personalization, byte[] ciphertext) {
+        byte[] keyHash = personalizedHash(personalization, keyBytes);
         SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
                 KeyProperties.KEY_ALGORITHM_AES);
         try {
@@ -220,17 +220,17 @@
         }
     }
 
-    protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
+    protected static byte[] personalizedHash(byte[] personalization, byte[]... message) {
         try {
             final int PADDING_LENGTH = 128;
             MessageDigest digest = MessageDigest.getInstance("SHA-512");
-            if (personalisation.length > PADDING_LENGTH) {
-                throw new IllegalArgumentException("Personalisation too long");
+            if (personalization.length > PADDING_LENGTH) {
+                throw new IllegalArgumentException("Personalization too long");
             }
             // Personalize the hash
             // Pad it to the block size of the hash function
-            personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH);
-            digest.update(personalisation);
+            personalization = Arrays.copyOf(personalization, PADDING_LENGTH);
+            digest.update(personalization);
             for (byte[] data : message) {
                 digest.update(data);
             }
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 3edbfe0..f5151c4 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -129,7 +129,7 @@
     private static final int PASSWORD_TOKEN_LENGTH = 32;
     private static final String TAG = "SyntheticPasswordManager";
 
-    private static final byte[] PERSONALISATION_SECDISCARDABLE = "secdiscardable-transform".getBytes();
+    private static final byte[] PERSONALIZATION_SECDISCARDABLE = "secdiscardable-transform".getBytes();
     private static final byte[] PERSONALIZATION_KEY_STORE_PASSWORD = "keystore-password".getBytes();
     private static final byte[] PERSONALIZATION_USER_GK_AUTH = "user-gk-authentication".getBytes();
     private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes();
@@ -138,11 +138,11 @@
     private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
     private static final byte[] PERSONALIZATION_PASSWORD_HASH = "pw-hash".getBytes();
     private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
-    private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes();
-    private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes();
-    private static final byte[] PERSONALISATION_WEAVER_TOKEN = "weaver-token".getBytes();
+    private static final byte[] PERSONALIZATION_WEAVER_PASSWORD = "weaver-pwd".getBytes();
+    private static final byte[] PERSONALIZATION_WEAVER_KEY = "weaver-key".getBytes();
+    private static final byte[] PERSONALIZATION_WEAVER_TOKEN = "weaver-token".getBytes();
     private static final byte[] PERSONALIZATION_PASSWORD_METRICS = "password-metrics".getBytes();
-    private static final byte[] PERSONALISATION_CONTEXT =
+    private static final byte[] PERSONALIZATION_CONTEXT =
         "android-synthetic-password-personalization-context".getBytes();
 
     static class AuthenticationResult {
@@ -199,9 +199,9 @@
         private byte[] deriveSubkey(byte[] personalization) {
             if (mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
                 return (new SP800Derive(mSyntheticPassword))
-                    .withContext(personalization, PERSONALISATION_CONTEXT);
+                    .withContext(personalization, PERSONALIZATION_CONTEXT);
             } else {
-                return SyntheticPasswordCrypto.personalisedHash(personalization,
+                return SyntheticPasswordCrypto.personalizedHash(personalization,
                         mSyntheticPassword);
             }
         }
@@ -278,7 +278,7 @@
          * AuthenticationToken.mSyntheticPassword for details on what each block means.
          */
         private void recreate(byte[] escrowSplit0, byte[] escrowSplit1) {
-            mSyntheticPassword = bytesToHex(SyntheticPasswordCrypto.personalisedHash(
+            mSyntheticPassword = bytesToHex(SyntheticPasswordCrypto.personalizedHash(
                     PERSONALIZATION_SP_SPLIT, escrowSplit0, escrowSplit1));
         }
 
@@ -599,47 +599,19 @@
     }
 
     /**
-     * Initializing a new Authentication token, possibly from an existing credential and hash.
+     * Initializes a new Authentication token for the given user.
      *
-     * The authentication token would bear a randomly-generated synthetic password.
+     * The authentication token will bear a randomly-generated synthetic password.
      *
-     * This method has the side effect of rebinding the SID of the given user to the
-     * newly-generated SP.
-     *
-     * If the existing credential hash is non-null, the existing SID mill be migrated so
-     * the synthetic password in the authentication token will produce the same SID
-     * (the corresponding synthetic password handle is persisted by SyntheticPasswordManager
-     * in a per-user data storage.)
-     *
-     * If the existing credential hash is null, it means the given user should have no SID so
-     * SyntheticPasswordManager will nuke any SP handle previously persisted. In this case,
-     * the supplied credential parameter is also ignored.
+     * Any existing SID for the user is cleared.
      *
      * Also saves the escrow information necessary to re-generate the synthetic password under
      * an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
      * password escrow should be disabled completely on the given user.
-     *
      */
-    public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper,
-            byte[] hash, LockscreenCredential credential, int userId) {
+    AuthenticationToken newSyntheticPassword(int userId) {
+        clearSidForUser(userId);
         AuthenticationToken result = AuthenticationToken.create();
-        GateKeeperResponse response;
-        if (hash != null) {
-            try {
-                response = gatekeeper.enroll(userId, hash, credential.getCredential(),
-                        result.deriveGkPassword());
-            } catch (RemoteException e) {
-                throw new IllegalStateException("Failed to enroll credential duing SP init", e);
-            }
-            if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
-                Slog.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId);
-                clearSidForUser(userId);
-            } else {
-                saveSyntheticPasswordHandle(response.getPayload(), userId);
-            }
-        } else {
-            clearSidForUser(userId);
-        }
         saveEscrowData(result, userId);
         return result;
     }
@@ -948,7 +920,7 @@
         if (isWeaverAvailable()) {
             tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
             tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret,
-                            PERSONALISATION_WEAVER_TOKEN, secdiscardable);
+                            PERSONALIZATION_WEAVER_TOKEN, secdiscardable);
         } else {
             tokenData.secdiscardableOnDisk = secdiscardable;
             tokenData.weaverSecret = null;
@@ -1191,7 +1163,7 @@
                 return result;
             }
             secdiscardable = SyntheticPasswordCrypto.decrypt(response.getGatekeeperHAT(),
-                    PERSONALISATION_WEAVER_TOKEN, secdiscardable);
+                    PERSONALIZATION_WEAVER_TOKEN, secdiscardable);
         }
         byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
         result.authToken = unwrapSyntheticPasswordBlob(handle, type, applicationId, 0L, userId);
@@ -1357,8 +1329,8 @@
     }
 
     private byte[] transformUnderWeaverSecret(byte[] data, byte[] secret) {
-        byte[] weaverSecret = SyntheticPasswordCrypto.personalisedHash(
-                PERSONALISATION_WEAVER_PASSWORD, secret);
+        byte[] weaverSecret = SyntheticPasswordCrypto.personalizedHash(
+                PERSONALIZATION_WEAVER_PASSWORD, secret);
         byte[] result = new byte[data.length + weaverSecret.length];
         System.arraycopy(data, 0, result, 0, data.length);
         System.arraycopy(weaverSecret, 0, result, data.length, weaverSecret.length);
@@ -1366,8 +1338,8 @@
     }
 
     private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) {
-        byte[] secdiscardable = SyntheticPasswordCrypto.personalisedHash(
-                PERSONALISATION_SECDISCARDABLE, rawSecdiscardable);
+        byte[] secdiscardable = SyntheticPasswordCrypto.personalizedHash(
+                PERSONALIZATION_SECDISCARDABLE, rawSecdiscardable);
         byte[] result = new byte[data.length + secdiscardable.length];
         System.arraycopy(data, 0, result, 0, data.length);
         System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length);
@@ -1487,11 +1459,11 @@
     }
 
     private byte[] passwordTokenToGkInput(byte[] token) {
-        return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_USER_GK_AUTH, token);
+        return SyntheticPasswordCrypto.personalizedHash(PERSONALIZATION_USER_GK_AUTH, token);
     }
 
     private byte[] passwordTokenToWeaverKey(byte[] token) {
-        byte[] key = SyntheticPasswordCrypto.personalisedHash(PERSONALISATION_WEAVER_KEY, token);
+        byte[] key = SyntheticPasswordCrypto.personalizedHash(PERSONALIZATION_WEAVER_KEY, token);
         if (key.length < mWeaverConfig.keySize) {
             throw new IllegalArgumentException("weaver key length too small");
         }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index de9102a..6135fe8 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -340,7 +340,8 @@
             int newRuleInstanceCount = getCurrentInstanceCount(automaticZenRule.getOwner())
                     + getCurrentInstanceCount(automaticZenRule.getConfigurationActivity())
                     + 1;
-            if (newRuleInstanceCount > RULE_LIMIT_PER_PACKAGE
+            int newPackageRuleCount = getPackageRuleCount(pkg) + 1;
+            if (newPackageRuleCount > RULE_LIMIT_PER_PACKAGE
                     || (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount)) {
                 throw new IllegalArgumentException("Rule instance limit exceeded");
             }
@@ -521,6 +522,23 @@
         return count;
     }
 
+    // Equivalent method to getCurrentInstanceCount, but for all rules associated with a specific
+    // package rather than a condition provider service or activity.
+    private int getPackageRuleCount(String pkg) {
+        if (pkg == null) {
+            return 0;
+        }
+        int count = 0;
+        synchronized (mConfig) {
+            for (ZenRule rule : mConfig.automaticRules.values()) {
+                if (pkg.equals(rule.getPkg())) {
+                    count++;
+                }
+            }
+        }
+        return count;
+    }
+
     public boolean canManageAutomaticZenRule(ZenRule rule) {
         final int callingUid = Binder.getCallingUid();
         if (callingUid == 0 || callingUid == Process.SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b0b9e61c..075fb47 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -163,7 +163,8 @@
                 return PackageManager.DELETE_FAILED_INTERNAL_ERROR;
             }
 
-            if (PackageManagerServiceUtils.isSystemApp(uninstalledPs)) {
+            if (PackageManagerServiceUtils.isUpdatedSystemApp(uninstalledPs)
+                    && ((deleteFlags & PackageManager.DELETE_SYSTEM_APP) == 0)) {
                 UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
                 if (userInfo == null || (!userInfo.isAdmin() && !mUserManagerInternal.getUserInfo(
                         mUserManagerInternal.getProfileParentId(userId)).isAdmin())) {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 622de57..270891f 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -56,6 +56,10 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
 import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_EVENT_REPORTED__EVENT__APPEARED_APPLY_TREATMENT;
@@ -1376,7 +1380,7 @@
             return;
         }
 
-        logAppCompatStateInternal(activity, state, packageUid, compatStateInfo);
+        logAppCompatStateInternal(activity, state, compatStateInfo);
     }
 
     /**
@@ -1416,18 +1420,61 @@
             }
         }
         if (activityToLog != null && stateToLog != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) {
-            logAppCompatStateInternal(activityToLog, stateToLog, packageUid, compatStateInfo);
+            logAppCompatStateInternal(activityToLog, stateToLog, compatStateInfo);
         }
     }
 
+    private static boolean isAppCompateStateChangedToLetterboxed(int state) {
+        return state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO
+                || state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION
+                || state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
+    }
+
     private void logAppCompatStateInternal(@NonNull ActivityRecord activity, int state,
-            int packageUid, PackageCompatStateInfo compatStateInfo) {
+             PackageCompatStateInfo compatStateInfo) {
         compatStateInfo.mLastLoggedState = state;
         compatStateInfo.mLastLoggedActivity = activity;
-        FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED, packageUid, state);
+        int packageUid = activity.info.applicationInfo.uid;
+
+        int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
+        if (isAppCompateStateChangedToLetterboxed(state)) {
+            positionToLog = activity.mLetterboxUiController.getLetterboxPositionForLogging();
+        }
+        FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED,
+                packageUid, state, positionToLog);
 
         if (DEBUG_METRICS) {
-            Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s)", packageUid, state));
+            Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s, %s)",
+                    packageUid, state, positionToLog));
+        }
+    }
+
+    /**
+     * Logs the changing of the letterbox position along with its package UID
+     */
+    void logLetterboxPositionChange(@NonNull ActivityRecord activity, int position) {
+        int packageUid = activity.info.applicationInfo.uid;
+        FrameworkStatsLog.write(FrameworkStatsLog.LETTERBOX_POSITION_CHANGED, packageUid, position);
+
+        if (!mPackageUidToCompatStateInfo.contains(packageUid)) {
+            // There is no last logged activity for this packageUid so we should not log the
+            // position change as we can only log the position change for the current activity
+            return;
+        }
+        final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid);
+        final ActivityRecord lastLoggedActivity = compatStateInfo.mLastLoggedActivity;
+        if (activity != lastLoggedActivity) {
+            // Only log the position change for the current activity to be consistent with
+            // findAppCompatStateToLog and ensure that metrics for the state changes are computed
+            // correctly
+            return;
+        }
+        int state = activity.getAppCompatState();
+        logAppCompatStateInternal(activity, state, compatStateInfo);
+
+        if (DEBUG_METRICS) {
+            Slog.i(TAG, String.format("LETTERBOX_POSITION_CHANGED(%s, %s)",
+                    packageUid, position));
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 406b9ee..85f6f0c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4752,6 +4752,8 @@
         if (mPendingRemoteAnimation != null) {
             mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
                     mPendingRemoteAnimation);
+            mTransitionController.setStatusBarTransitionDelay(
+                    mPendingRemoteAnimation.getStatusBarTransitionDelay());
         } else {
             if (mPendingOptions == null
                     || mPendingOptions.getAnimationType() == ANIM_SCENE_TRANSITION) {
@@ -7813,11 +7815,15 @@
                 newParentConfiguration.windowConfiguration.getWindowingMode();
         final boolean isFixedOrientationLetterboxAllowed =
                 parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
-                        || parentWindowingMode == WINDOWING_MODE_FULLSCREEN;
+                        || parentWindowingMode == WINDOWING_MODE_FULLSCREEN
+                        // Switching from PiP to fullscreen.
+                        || (parentWindowingMode == WINDOWING_MODE_PINNED
+                                && resolvedConfig.windowConfiguration.getWindowingMode()
+                                        == WINDOWING_MODE_FULLSCREEN);
         // TODO(b/181207944): Consider removing the if condition and always run
         // resolveFixedOrientationConfiguration() since this should be applied for all cases.
         if (isFixedOrientationLetterboxAllowed) {
-            resolveFixedOrientationConfiguration(newParentConfiguration, parentWindowingMode);
+            resolveFixedOrientationConfiguration(newParentConfiguration);
         }
 
         if (mCompatDisplayInsets != null) {
@@ -8109,8 +8115,7 @@
      * <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied
      * in this method.
      */
-    private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
-            int windowingMode) {
+    private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
         mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
         mIsEligibleForFixedOrientationLetterbox = false;
         final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
@@ -8130,11 +8135,6 @@
         if (organizedTf != null && !organizedTf.fillsParent()) {
             return;
         }
-        if (windowingMode == WINDOWING_MODE_PINNED) {
-            // PiP bounds have higher priority than the requested orientation. Otherwise the
-            // activity may be squeezed into a small piece.
-            return;
-        }
 
         final Rect resolvedBounds =
                 getResolvedOverrideConfiguration().windowConfiguration.getBounds();
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 9637aca4c..c92cf33 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -79,6 +79,10 @@
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
 import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import android.annotation.NonNull;
@@ -132,6 +136,7 @@
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
 import com.android.server.wm.LaunchParamsController.LaunchParams;
+import com.android.server.wm.TaskFragment.EmbeddingCheckResult;
 
 import java.io.PrintWriter;
 import java.text.DateFormat;
@@ -2074,24 +2079,6 @@
             }
         }
 
-        if (mInTaskFragment != null && !canEmbedActivity(mInTaskFragment, r, newTask, targetTask)) {
-            final StringBuilder errorMsg = new StringBuilder("Permission denied: Cannot embed " + r
-                    + " to " + mInTaskFragment.getTask() + ". newTask=" + newTask + ", targetTask= "
-                    + targetTask);
-            if (newTask && isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE,
-                    LAUNCH_SINGLE_INSTANCE_PER_TASK, LAUNCH_SINGLE_TASK)) {
-                errorMsg.append("\nActivity tries to launch on a new task because the launch mode"
-                        + " is " + launchModeToString(mLaunchMode));
-            } else if (newTask && (mLaunchFlags & (FLAG_ACTIVITY_NEW_DOCUMENT
-                    | FLAG_ACTIVITY_NEW_TASK)) != 0) {
-                errorMsg.append("\nActivity tries to launch on a new task because the launch flags"
-                        + " contains FLAG_ACTIVITY_NEW_DOCUMENT or FLAG_ACTIVITY_NEW_TASK. "
-                        + "mLaunchFlag=" + mLaunchFlags);
-            }
-            Slog.e(TAG, errorMsg.toString());
-            return START_PERMISSION_DENIED;
-        }
-
         // Do not start the activity if target display's DWPC does not allow it.
         // We can't return fatal error code here because it will crash the caller of
         // startActivity() if they don't catch the exception. We don't expect 3P apps to make
@@ -2118,19 +2105,21 @@
     }
 
     /**
-     * Return {@code true} if an activity can be embedded to the TaskFragment.
+     * Returns whether embedding of {@code starting} is allowed.
+     *
      * @param taskFragment the TaskFragment for embedding.
      * @param starting the starting activity.
-     * @param newTask whether the starting activity is going to be launched on a new task.
      * @param targetTask the target task for launching activity, which could be different from
      *                   the one who hosting the embedding.
      */
-    private boolean canEmbedActivity(@NonNull TaskFragment taskFragment,
-            @NonNull ActivityRecord starting, boolean newTask, Task targetTask) {
+    @VisibleForTesting
+    @EmbeddingCheckResult
+    static int canEmbedActivity(@NonNull TaskFragment taskFragment,
+            @NonNull ActivityRecord starting, @NonNull Task targetTask) {
         final Task hostTask = taskFragment.getTask();
         // Not allowed embedding a separate task or without host task.
-        if (hostTask == null || newTask || targetTask != hostTask) {
-            return false;
+        if (hostTask == null || targetTask != hostTask) {
+            return EMBEDDING_DISALLOWED_NEW_TASK;
         }
 
         return taskFragment.isAllowedToEmbedActivity(starting);
@@ -2970,19 +2959,16 @@
         mIntentDelivered = true;
     }
 
+    /** Places {@link #mStartActivity} in {@code task} or an embedded {@link TaskFragment}. */
     private void addOrReparentStartingActivity(@NonNull Task task, String reason) {
         TaskFragment newParent = task;
         if (mInTaskFragment != null) {
-            // TODO(b/234351413): remove remaining embedded Task logic.
-            // mInTaskFragment is created and added to the leaf task by task fragment organizer's
-            // request. If the task was resolved and different than mInTaskFragment, reparent the
-            // task to mInTaskFragment for embedding.
-            if (mInTaskFragment.getTask() != task) {
-                if (shouldReparentInTaskFragment(task)) {
-                    task.reparent(mInTaskFragment, POSITION_TOP);
-                }
-            } else {
+            int embeddingCheckResult = canEmbedActivity(mInTaskFragment, mStartActivity, task);
+            if (embeddingCheckResult == EMBEDDING_ALLOWED) {
                 newParent = mInTaskFragment;
+            } else {
+                // Start mStartActivity to task instead if it can't be embedded to mInTaskFragment.
+                sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult);
             }
         } else {
             TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null;
@@ -2994,20 +2980,12 @@
                 }
             }
             if (candidateTf != null && candidateTf.isEmbedded()
-                    && canEmbedActivity(candidateTf, mStartActivity, false /* newTask */, task)) {
+                    && canEmbedActivity(candidateTf, mStartActivity, task) == EMBEDDING_ALLOWED) {
                 // Use the embedded TaskFragment of the top activity as the new parent if the
                 // activity can be embedded.
                 newParent = candidateTf;
             }
         }
-        // Start Activity to the Task if mStartActivity's min dimensions are not satisfied.
-        if (newParent.isEmbedded() && newParent.smallerThanMinDimension(mStartActivity)) {
-            reason += " - MinimumDimensionViolation";
-            mService.mWindowOrganizerController.sendMinimumDimensionViolation(
-                    newParent, mStartActivity.getMinDimensions(), mRequest.errorCallbackToken,
-                    reason);
-            newParent = task;
-        }
         if (mStartActivity.getTaskFragment() == null
                 || mStartActivity.getTaskFragment() == newParent) {
             newParent.addChild(mStartActivity, POSITION_TOP);
@@ -3016,16 +2994,41 @@
         }
     }
 
-    private boolean shouldReparentInTaskFragment(Task task) {
-        // The task has not been embedded. We should reparent the task to TaskFragment.
-        if (!task.isEmbedded()) {
-            return true;
+    /**
+     * Notifies the client side that {@link #mStartActivity} cannot be embedded to
+     * {@code taskFragment}.
+     */
+    private void sendCanNotEmbedActivityError(TaskFragment taskFragment,
+            @EmbeddingCheckResult int result) {
+        final String errMsg;
+        switch(result) {
+            case EMBEDDING_DISALLOWED_NEW_TASK: {
+                errMsg = "Cannot embed " + mStartActivity + " that launched on another task"
+                        + ",mLaunchMode=" + launchModeToString(mLaunchMode)
+                        + ",mLaunchFlag=" + Integer.toHexString(mLaunchFlags);
+                break;
+            }
+            case EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION: {
+                errMsg = "Cannot embed " + mStartActivity
+                        + ". TaskFragment's bounds:" + taskFragment.getBounds()
+                        + ", minimum dimensions:" + mStartActivity.getMinDimensions();
+                break;
+            }
+            case EMBEDDING_DISALLOWED_UNTRUSTED_HOST: {
+                errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity;
+                break;
+            }
+            default:
+                errMsg = "Unhandled embed result:" + result;
         }
-        WindowContainer<?> parent = task.getParent();
-        // If the Activity is going to launch on top of embedded Task in the same TaskFragment,
-        // we don't need to reparent the Task. Otherwise, the embedded Task should reparent to
-        // another TaskFragment.
-        return parent.asTaskFragment() != mInTaskFragment;
+        if (taskFragment.isOrganized()) {
+            mService.mWindowOrganizerController.sendTaskFragmentOperationFailure(
+                    taskFragment.getTaskFragmentOrganizer(), mRequest.errorCallbackToken,
+                    new SecurityException(errMsg));
+        } else {
+            // If the taskFragment is not organized, just dump error message as warning logs.
+            Slog.w(TAG, errMsg);
+        }
     }
 
     private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a790ca5..6de1b95 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3660,7 +3660,8 @@
     }
 
     @Override
-    public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) {
+    public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution,
+            boolean takeSnapshotIfNeeded) {
         mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -3674,8 +3675,12 @@
                 }
             }
             // Don't call this while holding the lock as this operation might hit the disk.
-            return mWindowManager.mTaskSnapshotController.getSnapshot(taskId, task.mUserId,
-                    true /* restoreFromDisk */, isLowResolution);
+            TaskSnapshot taskSnapshot = mWindowManager.mTaskSnapshotController.getSnapshot(taskId,
+                    task.mUserId, true /* restoreFromDisk */, isLowResolution);
+            if (taskSnapshot == null && takeSnapshotIfNeeded) {
+                taskSnapshot = takeTaskSnapshot(taskId);
+            }
+            return taskSnapshot;
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6648,7 +6653,8 @@
         @Override
         public TaskSnapshot getTaskSnapshotBlocking(
                 int taskId, boolean isLowResolution) {
-            return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution);
+            return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution,
+                    false /* takeSnapshotIfNeeded */);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index e3de18b..2e1d3b1 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -357,7 +357,7 @@
      * or seamless transformation in a rotated display.
      */
     boolean shouldFreezeInsetsPosition(WindowState w) {
-        return mTransitionOp == OP_APP_SWITCH && w.mTransitionController.inTransition()
+        return mTransitionOp != OP_LEGACY && w.mTransitionController.inTransition()
                 && isTargetToken(w.mToken);
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 68f6015..6619f98 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1611,7 +1611,8 @@
         if (mTransitionController.useShellTransitionsRotation()) {
             return ROTATION_UNDEFINED;
         }
-        if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) {
+        if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM
+                || getIgnoreOrientationRequest()) {
             return ROTATION_UNDEFINED;
         }
         if (r.mOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 08715b1..91b2fb6 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -687,6 +687,24 @@
         }
     }
 
+    /*
+     * Gets the horizontal position of the letterboxed app window when horizontal reachability is
+     * enabled.
+     */
+    @LetterboxHorizontalReachabilityPosition
+    int getLetterboxPositionForHorizontalReachability() {
+        return mLetterboxPositionForHorizontalReachability;
+    }
+
+    /*
+     * Gets the vertical position of the letterboxed app window when vertical reachability is
+     * enabled.
+     */
+    @LetterboxVerticalReachabilityPosition
+    int getLetterboxPositionForVerticalReachability() {
+        return mLetterboxPositionForVerticalReachability;
+    }
+
     /** Returns a string representing the given {@link LetterboxHorizontalReachabilityPosition}. */
     static String letterboxHorizontalReachabilityPositionToString(
             @LetterboxHorizontalReachabilityPosition int position) {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f849d28..d652767 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -21,6 +21,20 @@
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
 import static com.android.server.wm.ActivityRecord.computeAspectRatio;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -28,6 +42,12 @@
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
 import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
 import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString;
 
@@ -259,12 +279,26 @@
             return;
         }
 
+        int letterboxPositionForHorizontalReachability = mLetterboxConfiguration
+                .getLetterboxPositionForHorizontalReachability();
         if (mLetterbox.getInnerFrame().left > x) {
             // Moving to the next stop on the left side of the app window: right > center > left.
             mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextLeftStop();
+            int changeToLog =
+                    letterboxPositionForHorizontalReachability
+                            == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+                                ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
+                                : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+            logLetterboxPositionChange(changeToLog);
         } else if (mLetterbox.getInnerFrame().right < x) {
             // Moving to the next stop on the right side of the app window: left > center > right.
             mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
+            int changeToLog =
+                    letterboxPositionForHorizontalReachability
+                            == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+                                ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
+                                : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+            logLetterboxPositionChange(changeToLog);
         }
 
         // TODO(197549949): Add animation for transition.
@@ -280,13 +314,26 @@
             // Only react to clicks at the top and bottom of the letterboxed app window.
             return;
         }
-
+        int letterboxPositionForVerticalReachability = mLetterboxConfiguration
+                .getLetterboxPositionForVerticalReachability();
         if (mLetterbox.getInnerFrame().top > y) {
             // Moving to the next stop on the top side of the app window: bottom > center > top.
             mLetterboxConfiguration.movePositionForVerticalReachabilityToNextTopStop();
+            int changeToLog =
+                    letterboxPositionForVerticalReachability
+                            == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+                                ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
+                                : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+            logLetterboxPositionChange(changeToLog);
         } else if (mLetterbox.getInnerFrame().bottom < y) {
             // Moving to the next stop on the bottom side of the app window: top > center > bottom.
             mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
+            int changeToLog =
+                    letterboxPositionForVerticalReachability
+                            == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+                                ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
+                                : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
+            logLetterboxPositionChange(changeToLog);
         }
 
         // TODO(197549949): Add animation for transition.
@@ -577,4 +624,63 @@
         return "UNKNOWN_REASON";
     }
 
+    private int letterboxHorizontalReachabilityPositionToLetterboxPosition(
+            @LetterboxConfiguration.LetterboxHorizontalReachabilityPosition int position) {
+        switch (position) {
+            case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
+                return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+            case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
+                return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+            case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT:
+                return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+            default:
+                throw new AssertionError(
+                        "Unexpected letterbox horizontal reachability position type: "
+                                + position);
+        }
+    }
+
+    private int letterboxVerticalReachabilityPositionToLetterboxPosition(
+            @LetterboxConfiguration.LetterboxVerticalReachabilityPosition int position) {
+        switch (position) {
+            case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
+                return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+            case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
+                return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+            case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM:
+                return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+            default:
+                throw new AssertionError(
+                        "Unexpected letterbox vertical reachability position type: "
+                                + position);
+        }
+    }
+
+    int getLetterboxPositionForLogging() {
+        int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+        if (isHorizontalReachabilityEnabled()) {
+            int letterboxPositionForHorizontalReachability = getLetterboxConfiguration()
+                    .getLetterboxPositionForHorizontalReachability();
+            positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition(
+                            letterboxPositionForHorizontalReachability);
+        } else if (isVerticalReachabilityEnabled()) {
+            int letterboxPositionForVerticalReachability = getLetterboxConfiguration()
+                    .getLetterboxPositionForVerticalReachability();
+            positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition(
+                            letterboxPositionForVerticalReachability);
+        }
+        return positionToLog;
+    }
+
+    private LetterboxConfiguration getLetterboxConfiguration() {
+        return mLetterboxConfiguration;
+    }
+
+    /**
+     * Logs letterbox position changes via {@link ActivityMetricsLogger#logLetterboxPositionChange}.
+     */
+    private void logLetterboxPositionChange(int letterboxPositionChange) {
+        mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
+                .logLetterboxPositionChange(mActivityRecord, letterboxPositionChange);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index baa31a0..927604e 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -26,7 +26,9 @@
 import static android.app.WindowConfiguration.activityTypeToString;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -249,8 +251,25 @@
         }
         // Check if the caller is allowed to launch on the specified display area.
         final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
-        final TaskDisplayArea taskDisplayArea = daToken != null
+        TaskDisplayArea taskDisplayArea = daToken != null
                 ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
+
+        // If we do not have a task display area token, check if the launch task display area
+        // feature id is specified.
+        if (taskDisplayArea == null) {
+            final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
+            if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+                final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
+                        ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
+                final DisplayContent dc = supervisor.mRootWindowContainer
+                        .getDisplayContent(launchDisplayId);
+                if (dc != null) {
+                    taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
+                            tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
+                }
+            }
+        }
+
         if (aInfo != null && taskDisplayArea != null
                 && !supervisor.isCallerAllowedToLaunchOnTaskDisplayArea(callingPid, callingUid,
                 taskDisplayArea, aInfo)) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a1c22d6..3e6546e 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -140,6 +140,45 @@
     static final boolean SHOW_APP_STARTING_PREVIEW = true;
 
     /**
+     * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+     * indicate that an Activity can be embedded successfully.
+     */
+    static final int EMBEDDING_ALLOWED = 0;
+    /**
+     * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+     * indicate that an Activity can't be embedded because either the Activity does not allow
+     * untrusted embedding, and the embedding host app is not trusted.
+     */
+    static final int EMBEDDING_DISALLOWED_UNTRUSTED_HOST = 1;
+    /**
+     * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+     * indicate that an Activity can't be embedded because this taskFragment's bounds are
+     * {@link #smallerThanMinDimension(ActivityRecord)}.
+     */
+    static final int EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION = 2;
+    /**
+     * An embedding check result of
+     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+     * indicate that an Activity can't be embedded because the Activity is started on a new task.
+     */
+    static final int EMBEDDING_DISALLOWED_NEW_TASK = 3;
+
+    /**
+     * Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}.
+     */
+    @IntDef(prefix = {"EMBEDDING_"}, value = {
+            EMBEDDING_ALLOWED,
+            EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+            EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
+            EMBEDDING_DISALLOWED_NEW_TASK,
+    })
+    @interface EmbeddingCheckResult {}
+
+    /**
      * Indicate that the minimal width/height should use the default value.
      *
      * @see #mMinWidth
@@ -509,20 +548,29 @@
         return false;
     }
 
-    boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) {
+    @EmbeddingCheckResult
+    int isAllowedToEmbedActivity(@NonNull ActivityRecord a) {
         return isAllowedToEmbedActivity(a, mTaskFragmentOrganizerUid);
     }
 
     /**
      * Checks if the organized task fragment is allowed to have the specified activity, which is
-     * allowed if an activity allows embedding in untrusted mode, or if the trusted mode can be
-     * enabled.
-     * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
+     * allowed if an activity allows embedding in untrusted mode, if the trusted mode can be
+     * enabled, or if the organized task fragment bounds are not
+     * {@link #smallerThanMinDimension(ActivityRecord)}.
+     *
      * @param uid   uid of the TaskFragment organizer.
+     * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
      */
-    boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) {
-        return isAllowedToEmbedActivityInUntrustedMode(a)
-                || isAllowedToEmbedActivityInTrustedMode(a, uid);
+    @EmbeddingCheckResult
+    int isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) {
+        if (!isAllowedToEmbedActivityInUntrustedMode(a)
+                && !isAllowedToEmbedActivityInTrustedMode(a, uid)) {
+            return EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
+        } else if (smallerThanMinDimension(a)) {
+            return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
+        }
+        return EMBEDDING_ALLOWED;
     }
 
     boolean smallerThanMinDimension(@NonNull ActivityRecord activity) {
@@ -539,9 +587,8 @@
         }
         final int minWidth = minDimensions.x;
         final int minHeight = minDimensions.y;
-        final boolean smaller = taskFragBounds.width() < minWidth
+        return taskFragBounds.width() < minWidth
                 || taskFragBounds.height() < minHeight;
-        return smaller;
     }
 
     /**
@@ -598,7 +645,7 @@
         // The system is trusted to embed other apps securely and for all users.
         return UserHandle.getAppId(uid) == SYSTEM_UID
                 // Activities from the same UID can be embedded freely by the host.
-                || uid == a.getUid();
+                || a.isUid(uid);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 9aff23d..392d4c2 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -19,6 +19,7 @@
 import static android.window.TaskFragmentOrganizer.putExceptionInBundle;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
 
 import android.annotation.IntDef;
@@ -235,7 +236,7 @@
                         + " is not in a task belong to the organizer app.");
                 return;
             }
-            if (!task.isAllowedToEmbedActivity(activity, mOrganizerUid)) {
+            if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
                 Slog.d(TAG, "Reparent activity=" + activity.token
                         + " is not allowed to be embedded.");
                 return;
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 9bb0271..4141156 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -35,6 +35,7 @@
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import static com.android.server.wm.ActivityStarter.Request;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -307,7 +308,8 @@
         TaskDisplayArea taskDisplayArea = suggestedDisplayArea;
         // If launch task display area is set in options we should just use it. We assume the
         // suggestedDisplayArea has the right one in this case.
-        if (options == null || options.getLaunchTaskDisplayArea() == null) {
+        if (options == null || (options.getLaunchTaskDisplayArea() == null
+                && options.getLaunchTaskDisplayAreaFeatureId() == FEATURE_UNDEFINED)) {
             final int activityType =
                     mSupervisor.mRootWindowContainer.resolveActivityType(root, options, task);
             display.forAllTaskDisplayAreas(displayArea -> {
@@ -391,7 +393,22 @@
         if (optionLaunchTaskDisplayAreaToken != null) {
             taskDisplayArea = (TaskDisplayArea) WindowContainer.fromBinder(
                     optionLaunchTaskDisplayAreaToken.asBinder());
-            if (DEBUG) appendLog("display-area-from-option=" + taskDisplayArea);
+            if (DEBUG) appendLog("display-area-token-from-option=" + taskDisplayArea);
+        }
+
+        if (taskDisplayArea == null && options != null) {
+            final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
+            if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+                final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
+                        ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
+                final DisplayContent dc = mSupervisor.mRootWindowContainer
+                        .getDisplayContent(launchDisplayId);
+                if (dc != null) {
+                    taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
+                            tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
+                    if (DEBUG) appendLog("display-area-feature-from-option=" + taskDisplayArea);
+                }
+            }
         }
 
         // If task display area is not specified in options - try display id
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 9da78e1..27d181f 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -36,6 +36,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
@@ -56,7 +57,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.WeakHashMap;
@@ -94,7 +94,7 @@
      * lifecycle order since we may be updating the visibility of task surface controls in a pending
      * transaction before they are presented to the task org.
      */
-    private class TaskOrganizerCallbacks {
+    private static class TaskOrganizerCallbacks {
         final ITaskOrganizer mTaskOrganizer;
         final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
 
@@ -123,7 +123,6 @@
             }
         }
 
-
         void onTaskVanished(Task task) {
             ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task vanished taskId=%d", task.mTaskId);
             final RunningTaskInfo taskInfo = task.getTaskInfo();
@@ -173,11 +172,160 @@
         }
     }
 
+    /**
+     * Maintains a list of all the pending events for a given {@link android.window.TaskOrganizer}
+     */
+    static final class TaskOrganizerPendingEventsQueue {
+        private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
+        private final TaskOrganizerState mOrganizerState;
+        private RunningTaskInfo mTmpTaskInfo;
+        // Pending task events due to layout deferred.
+        private final ArrayList<PendingTaskEvent> mPendingTaskEvents = new ArrayList<>();
+
+        TaskOrganizerPendingEventsQueue(TaskOrganizerState taskOrganizerState) {
+            mOrganizerState = taskOrganizerState;
+        }
+
+        @VisibleForTesting
+        public ArrayList<PendingTaskEvent> getPendingEventList() {
+            return mPendingTaskEvents;
+        }
+
+        int numPendingTaskEvents() {
+            return mPendingTaskEvents.size();
+        }
+
+        void clearPendingTaskEvents() {
+            mPendingTaskEvents.clear();
+        }
+
+        void addPendingTaskEvent(PendingTaskEvent event) {
+            mPendingTaskEvents.add(event);
+        }
+
+        void removePendingTaskEvent(PendingTaskEvent event) {
+            mPendingTaskEvents.remove(event);
+        }
+
+        /**
+         * Removes all the pending task events for the given {@code task}.
+         *
+         * @param task
+         * @return true if a {@link PendingTaskEvent#EVENT_APPEARED} is still pending for the given
+         * {code task}.
+         */
+        boolean removePendingTaskEvents(Task task) {
+            boolean foundPendingAppearedEvents = false;
+            for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
+                PendingTaskEvent entry = mPendingTaskEvents.get(i);
+                if (task.mTaskId == entry.mTask.mTaskId) {
+                    // This task is vanished so remove all pending event of it.
+                    mPendingTaskEvents.remove(i);
+
+                    if (entry.mEventType == PendingTaskEvent.EVENT_APPEARED) {
+                        foundPendingAppearedEvents = true;
+                    }
+                }
+            }
+            return foundPendingAppearedEvents;
+        }
+
+        @Nullable
+        private PendingTaskEvent getPendingTaskEvent(Task task, int type) {
+            for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
+                PendingTaskEvent entry = mPendingTaskEvents.get(i);
+                if (task.mTaskId == entry.mTask.mTaskId && type == entry.mEventType) {
+                    return entry;
+                }
+            }
+            return null;
+        }
+
+        @VisibleForTesting
+        @Nullable
+        PendingTaskEvent getPendingLifecycleTaskEvent(Task task) {
+            for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
+                PendingTaskEvent entry = mPendingTaskEvents.get(i);
+                if (task.mTaskId == entry.mTask.mTaskId && entry.isLifecycleEvent()) {
+                    return entry;
+                }
+            }
+            return null;
+        }
+
+        void dispatchPendingEvents() {
+            if (mPendingTaskEvents.isEmpty()) {
+                return;
+            }
+            for (int i = 0, n = mPendingTaskEvents.size(); i < n; i++) {
+                dispatchPendingEvent(mPendingTaskEvents.get(i));
+            }
+            mPendingTaskEvents.clear();
+        }
+
+        private void dispatchPendingEvent(PendingTaskEvent event) {
+            final Task task = event.mTask;
+            switch (event.mEventType) {
+                case PendingTaskEvent.EVENT_APPEARED:
+                    if (task.taskAppearedReady()) {
+                        mOrganizerState.mOrganizer.onTaskAppeared(task);
+                    }
+                    break;
+                case PendingTaskEvent.EVENT_VANISHED:
+                    mOrganizerState.mOrganizer.onTaskVanished(task);
+                    mLastSentTaskInfos.remove(task);
+                    break;
+                case PendingTaskEvent.EVENT_INFO_CHANGED:
+                    dispatchTaskInfoChanged(event.mTask, event.mForce);
+                    break;
+                case PendingTaskEvent.EVENT_ROOT_BACK_PRESSED:
+                    mOrganizerState.mOrganizer.onBackPressedOnTaskRoot(task);
+                    break;
+            }
+        }
+
+        private void dispatchTaskInfoChanged(Task task, boolean force) {
+            RunningTaskInfo lastInfo = mLastSentTaskInfos.get(task);
+            if (mTmpTaskInfo == null) {
+                mTmpTaskInfo = new RunningTaskInfo();
+            }
+            mTmpTaskInfo.configuration.unset();
+            task.fillTaskInfo(mTmpTaskInfo);
+
+            boolean changed = !mTmpTaskInfo
+                    .equalsForTaskOrganizer(lastInfo)
+                    || !configurationsAreEqualForOrganizer(
+                    mTmpTaskInfo.configuration,
+                    lastInfo.configuration);
+            if (!(changed || force)) {
+                // mTmpTaskInfo will be reused next time.
+                return;
+            }
+            final RunningTaskInfo newInfo = mTmpTaskInfo;
+            mLastSentTaskInfos.put(task,
+                    mTmpTaskInfo);
+            // Since we've stored this, clean up the reference so a new one will be created next
+            // time.
+            // Transferring it this way means we only have to construct new RunningTaskInfos when
+            // they change.
+            mTmpTaskInfo = null;
+
+            if (task.isOrganized()) {
+                // Because we defer sending taskAppeared() until the app has drawn, we may receive a
+                // configuration change before the state actually has the task registered. As such
+                // we should ignore these change events to the organizer until taskAppeared(). If
+                // the task was created by the organizer, then we always send the info change.
+                mOrganizerState.mOrganizer.onTaskInfoChanged(task, newInfo);
+            }
+        }
+    }
+
     @VisibleForTesting
     class TaskOrganizerState {
         private final TaskOrganizerCallbacks mOrganizer;
         private final DeathRecipient mDeathRecipient;
         private final ArrayList<Task> mOrganizedTasks = new ArrayList<>();
+        private final TaskOrganizerPendingEventsQueue mPendingEventsQueue;
         private final int mUid;
 
         TaskOrganizerState(ITaskOrganizer organizer, int uid) {
@@ -187,6 +335,7 @@
                             : mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable;
             mOrganizer = new TaskOrganizerCallbacks(organizer, deferTaskOrgCallbacksConsumer);
             mDeathRecipient = new DeathRecipient(organizer);
+            mPendingEventsQueue = new TaskOrganizerPendingEventsQueue(this);
             try {
                 organizer.asBinder().linkToDeath(mDeathRecipient, 0);
             } catch (RemoteException e) {
@@ -200,6 +349,11 @@
             return mDeathRecipient;
         }
 
+        @VisibleForTesting
+        TaskOrganizerPendingEventsQueue getPendingEventsQueue() {
+            return mPendingEventsQueue;
+        }
+
         /**
          * Register this task with this state, but doesn't trigger the task appeared callback to
          * the organizer.
@@ -263,8 +417,7 @@
                     // updateTaskOrganizerState should remove the task from the list, but still
                     // check it again to avoid while-loop isn't terminate.
                     if (removeTask(t, t.mRemoveWithTaskOrganizer)) {
-                        TaskOrganizerController.this.onTaskVanishedInternal(
-                                mOrganizer.mTaskOrganizer, t);
+                        TaskOrganizerController.this.onTaskVanishedInternal(this, t);
                     }
                 }
                 if (mService.getTransitionController().isShellTransitionsEnabled()) {
@@ -278,8 +431,9 @@
                 }
             }
 
-            // Remove organizer state after removing tasks so we get a chance to send
-            // onTaskVanished.
+            // Pending events queue for this organizer need to be cleared because this organizer
+            // has either died or unregistered itself.
+            mPendingEventsQueue.clearPendingTaskEvents();
             mTaskOrganizerStates.remove(mOrganizer.getBinder());
         }
 
@@ -320,14 +474,10 @@
 
     // List of task organizers by priority
     private final ArrayDeque<ITaskOrganizer> mTaskOrganizers = new ArrayDeque<>();
-    private final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>();
-    private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
-    // Pending task events due to layout deferred.
-    private final ArrayList<PendingTaskEvent> mPendingTaskEvents = new ArrayList<>();
+    private final ArrayMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new ArrayMap<>();
     // Set of organized tasks (by taskId) that dispatch back pressed to their organizers
     private final HashSet<Integer> mInterceptBackPressedOnRootTasks = new HashSet<>();
 
-    private RunningTaskInfo mTmpTaskInfo;
     private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
 
     TaskOrganizerController(ActivityTaskManagerService atm) {
@@ -354,11 +504,6 @@
         mDeferTaskOrgCallbacksConsumer = consumer;
     }
 
-    @VisibleForTesting
-    ArrayList<PendingTaskEvent> getPendingEventList() {
-        return mPendingTaskEvents;
-    }
-
     /**
      * Register a TaskOrganizer to manage tasks as they enter the a supported windowing mode.
      */
@@ -592,10 +737,13 @@
     void onTaskAppeared(ITaskOrganizer organizer, Task task) {
         final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
         if (state != null && state.addTask(task)) {
-            PendingTaskEvent pending = getPendingTaskEvent(task, PendingTaskEvent.EVENT_APPEARED);
+            final TaskOrganizerPendingEventsQueue pendingEvents =
+                    state.mPendingEventsQueue;
+            PendingTaskEvent pending = pendingEvents.getPendingTaskEvent(task,
+                    PendingTaskEvent.EVENT_APPEARED);
             if (pending == null) {
-                pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_APPEARED);
-                mPendingTaskEvents.add(pending);
+                pendingEvents.addPendingTaskEvent(new PendingTaskEvent(task,
+                        PendingTaskEvent.EVENT_APPEARED));
             }
         }
     }
@@ -603,26 +751,25 @@
     void onTaskVanished(ITaskOrganizer organizer, Task task) {
         final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
         if (state != null && state.removeTask(task, task.mRemoveWithTaskOrganizer)) {
-            onTaskVanishedInternal(organizer, task);
+            onTaskVanishedInternal(state, task);
         }
     }
 
-    private void onTaskVanishedInternal(ITaskOrganizer organizer, Task task) {
-        for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
-            PendingTaskEvent entry = mPendingTaskEvents.get(i);
-            if (task.mTaskId == entry.mTask.mTaskId && entry.mTaskOrg == organizer) {
-                // This task is vanished so remove all pending event of it.
-                mPendingTaskEvents.remove(i);
-                if (entry.mEventType == PendingTaskEvent.EVENT_APPEARED) {
-                    // If task appeared callback still pend, ignore this callback too.
-                    return;
-                }
-            }
+    private void onTaskVanishedInternal(TaskOrganizerState organizerState, Task task) {
+        if (organizerState == null) {
+            Slog.i(TAG, "cannot send onTaskVanished because organizer state is not "
+                    + "present for this organizer");
+            return;
         }
-
-        PendingTaskEvent pending =
-                new PendingTaskEvent(task, organizer, PendingTaskEvent.EVENT_VANISHED);
-        mPendingTaskEvents.add(pending);
+        TaskOrganizerPendingEventsQueue pendingEventsQueue =
+                organizerState.mPendingEventsQueue;
+        boolean hadPendingAppearedEvents =
+                pendingEventsQueue.removePendingTaskEvents(task);
+        if (hadPendingAppearedEvents) {
+            return;
+        }
+        pendingEventsQueue.addPendingTaskEvent(new PendingTaskEvent(task,
+                organizerState.mOrganizer.mTaskOrganizer, PendingTaskEvent.EVENT_VANISHED));
     }
 
     @Override
@@ -690,48 +837,13 @@
     }
 
     void dispatchPendingEvents() {
-        if (mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
-                || mPendingTaskEvents.isEmpty()) {
+        if (mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {
             return;
         }
-
-        for (int i = 0, n = mPendingTaskEvents.size(); i < n; i++) {
-            PendingTaskEvent event = mPendingTaskEvents.get(i);
-            final Task task = event.mTask;
-            final TaskOrganizerState state;
-            switch (event.mEventType) {
-                case PendingTaskEvent.EVENT_APPEARED:
-                    state = mTaskOrganizerStates.get(event.mTaskOrg.asBinder());
-                    if (state != null && task.taskAppearedReady()) {
-                        state.mOrganizer.onTaskAppeared(task);
-                    }
-                    break;
-                case PendingTaskEvent.EVENT_VANISHED:
-                    // TaskOrganizerState cannot be used here because it might have already been
-                    // removed.
-                    // The state is removed when an organizer dies or is unregistered. In order to
-                    // send the pending vanished task events, the mTaskOrg from event is used.
-                    // These events should not ideally be sent and will be removed as part of
-                    // b/224812558.
-                    try {
-                        event.mTaskOrg.onTaskVanished(task.getTaskInfo());
-                    } catch (RemoteException ex) {
-                        Slog.e(TAG, "Exception sending onTaskVanished callback", ex);
-                    }
-                    mLastSentTaskInfos.remove(task);
-                    break;
-                case PendingTaskEvent.EVENT_INFO_CHANGED:
-                    dispatchTaskInfoChanged(event.mTask, event.mForce);
-                    break;
-                case PendingTaskEvent.EVENT_ROOT_BACK_PRESSED:
-                    state = mTaskOrganizerStates.get(event.mTaskOrg.asBinder());
-                    if (state != null) {
-                        state.mOrganizer.onBackPressedOnTaskRoot(task);
-                    }
-                    break;
-            }
+        for (int taskOrgIdx = 0; taskOrgIdx < mTaskOrganizerStates.size(); taskOrgIdx++) {
+            TaskOrganizerState taskOrganizerState = mTaskOrganizerStates.valueAt(taskOrgIdx);
+            taskOrganizerState.mPendingEventsQueue.dispatchPendingEvents();
         }
-        mPendingTaskEvents.clear();
     }
 
     void reportImeDrawnOnTask(Task task) {
@@ -750,20 +862,30 @@
             // Skip if task still not appeared.
             return;
         }
-        if (force && mPendingTaskEvents.isEmpty()) {
+        final TaskOrganizerState taskOrganizerState =
+                mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder());
+        final TaskOrganizerPendingEventsQueue pendingEventsQueue =
+                taskOrganizerState.mPendingEventsQueue;
+        if (pendingEventsQueue == null) {
+            Slog.i(TAG, "cannot send onTaskInfoChanged because pending events queue is not "
+                    + "present for this organizer");
+            return;
+        }
+        if (force && pendingEventsQueue.numPendingTaskEvents() == 0) {
             // There are task-info changed events do not result in
             // - RootWindowContainer#performSurfacePlacementNoTrace OR
             // - WindowAnimator#animate
             // For instance, when an app requesting aspect ratio change when in PiP mode.
             // To solve this, we directly dispatch the pending event if there are no events queued (
             // otherwise, all pending events should be dispatched on next drawn).
-            dispatchTaskInfoChanged(task, true /* force */);
+            pendingEventsQueue.dispatchTaskInfoChanged(task, true /* force */);
             return;
         }
 
         // Defer task info reporting while layout is deferred. This is because layout defer
         // blocks tend to do lots of re-ordering which can mess up animations in receivers.
-        PendingTaskEvent pending = getPendingLifecycleTaskEvent(task);
+        PendingTaskEvent pending = pendingEventsQueue
+                .getPendingLifecycleTaskEvent(task);
         if (pending == null) {
             pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_INFO_CHANGED);
         } else {
@@ -774,45 +896,10 @@
                 return;
             }
             // Remove and add for re-ordering.
-            mPendingTaskEvents.remove(pending);
+            pendingEventsQueue.removePendingTaskEvent(pending);
         }
         pending.mForce |= force;
-        mPendingTaskEvents.add(pending);
-    }
-
-    private void dispatchTaskInfoChanged(Task task, boolean force) {
-        RunningTaskInfo lastInfo = mLastSentTaskInfos.get(task);
-        if (mTmpTaskInfo == null) {
-            mTmpTaskInfo = new RunningTaskInfo();
-        }
-        mTmpTaskInfo.configuration.unset();
-        task.fillTaskInfo(mTmpTaskInfo);
-
-        boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo)
-                || !configurationsAreEqualForOrganizer(
-                        mTmpTaskInfo.configuration, lastInfo.configuration);
-        if (!(changed || force)) {
-            // mTmpTaskInfo will be reused next time.
-            return;
-        }
-        final RunningTaskInfo newInfo = mTmpTaskInfo;
-        mLastSentTaskInfos.put(task, mTmpTaskInfo);
-        // Since we've stored this, clean up the reference so a new one will be created next time.
-        // Transferring it this way means we only have to construct new RunningTaskInfos when they
-        // change.
-        mTmpTaskInfo = null;
-
-        if (task.isOrganized()) {
-            // Because we defer sending taskAppeared() until the app has drawn, we may receive a
-            // configuration change before the state actually has the task registered. As such we
-            // should ignore these change events to the organizer until taskAppeared(). If the task
-            // was created by the organizer, then we always send the info change.
-            final TaskOrganizerState state = mTaskOrganizerStates.get(
-                    task.mTaskOrganizer.asBinder());
-            if (state != null) {
-                state.mOrganizer.onTaskInfoChanged(task, newInfo);
-            }
-        }
+        pendingEventsQueue.addPendingTaskEvent(pending);
     }
 
     @Override
@@ -1018,50 +1105,36 @@
                 || !mInterceptBackPressedOnRootTasks.contains(task.mTaskId)) {
             return false;
         }
+        final TaskOrganizerPendingEventsQueue pendingEventsQueue =
+                mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder())
+                        .mPendingEventsQueue;
+        if (pendingEventsQueue == null) {
+            Slog.w(TAG, "cannot get handle BackPressedOnTaskRoot because organizerState is "
+                    + "not present");
+            return false;
+        }
 
         PendingTaskEvent pendingVanished =
-                getPendingTaskEvent(task, PendingTaskEvent.EVENT_VANISHED);
+                pendingEventsQueue.getPendingTaskEvent(task,
+                        PendingTaskEvent.EVENT_VANISHED);
         if (pendingVanished != null) {
             // This task will vanish before this callback so just ignore.
             return false;
         }
 
-        PendingTaskEvent pending = getPendingTaskEvent(
+        PendingTaskEvent pending = pendingEventsQueue.getPendingTaskEvent(
                 task, PendingTaskEvent.EVENT_ROOT_BACK_PRESSED);
         if (pending == null) {
             pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_ROOT_BACK_PRESSED);
         } else {
             // Pending already exist, remove and add for re-ordering.
-            mPendingTaskEvents.remove(pending);
+            pendingEventsQueue.removePendingTaskEvent(pending);
         }
-        mPendingTaskEvents.add(pending);
+        pendingEventsQueue.addPendingTaskEvent(pending);
         mService.mWindowManager.mWindowPlacerLocked.requestTraversal();
         return true;
     }
 
-    @Nullable
-    private PendingTaskEvent getPendingTaskEvent(Task task, int type) {
-        for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
-            PendingTaskEvent entry = mPendingTaskEvents.get(i);
-            if (task.mTaskId == entry.mTask.mTaskId && type == entry.mEventType) {
-                return entry;
-            }
-        }
-        return null;
-    }
-
-    @VisibleForTesting
-    @Nullable
-    PendingTaskEvent getPendingLifecycleTaskEvent(Task task) {
-        for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
-            PendingTaskEvent entry = mPendingTaskEvents.get(i);
-            if (task.mTaskId == entry.mTask.mTaskId && entry.isLifecycleEvent()) {
-                return entry;
-            }
-        }
-        return null;
-    }
-
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.print(prefix); pw.println("TaskOrganizerController:");
@@ -1084,4 +1157,9 @@
     TaskOrganizerState getTaskOrganizerState(IBinder taskOrganizer) {
         return mTaskOrganizerStates.get(taskOrganizer);
     }
+
+    @VisibleForTesting
+    TaskOrganizerPendingEventsQueue getTaskOrganizerPendingEvents(IBinder taskOrganizer) {
+        return mTaskOrganizerStates.get(taskOrganizer).mPendingEventsQueue;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3c0cac0..09f6110 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -149,9 +149,6 @@
 
     final @TransitionType int mType;
     private int mSyncId = -1;
-    // Used for tracking a Transition throughout a lifecycle (i.e. from STATE_COLLECTING to
-    // STATE_FINISHED or STATE_ABORT), and should only be used for testing and debugging.
-    private int mDebugId = -1;
     private @TransitionFlags int mFlags;
     private final TransitionController mController;
     private final BLASTSyncEngine mSyncEngine;
@@ -202,6 +199,9 @@
     private boolean mNavBarAttachedToApp = false;
     private int mRecentsDisplayId = INVALID_DISPLAY;
 
+    /** The delay for light bar appearance animation. */
+    long mStatusBarTransitionDelay;
+
     /** @see #setCanPipOnFinish */
     private boolean mCanPipOnFinish = true;
 
@@ -295,11 +295,6 @@
         return mSyncId;
     }
 
-    @VisibleForTesting
-    int getDebugId() {
-        return mDebugId;
-    }
-
     @TransitionFlags
     int getFlags() {
         return mFlags;
@@ -315,6 +310,10 @@
         return mFinishTransaction;
     }
 
+    private boolean isCollecting() {
+        return mState == STATE_COLLECTING || mState == STATE_STARTED;
+    }
+
     /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
     void startCollecting(long timeoutMs) {
         if (mState != STATE_PENDING) {
@@ -322,7 +321,6 @@
         }
         mState = STATE_COLLECTING;
         mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
-        mDebugId = mSyncId;
 
         mController.mTransitionTracer.logState(this);
     }
@@ -353,7 +351,10 @@
         if (mState < STATE_COLLECTING) {
             throw new IllegalStateException("Transition hasn't started collecting.");
         }
-        if (mSyncId < 0) return;
+        if (!isCollecting()) {
+            // Too late, transition already started playing, so don't collect.
+            return;
+        }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
                 mSyncId, wc);
         // "snapshot" all parents (as potential promotion targets). Do this before checking
@@ -403,7 +404,10 @@
      * or waiting until after the animation to close).
      */
     void collectExistenceChange(@NonNull WindowContainer wc) {
-        if (mSyncId < 0) return;
+        if (mState >= STATE_PLAYING) {
+            // Too late to collect. Don't check too-early here since `collect` will check that.
+            return;
+        }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
                 + " %s", mSyncId, wc);
         collect(wc);
@@ -437,7 +441,7 @@
      */
     void setOverrideAnimation(TransitionInfo.AnimationOptions options,
             @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
-        if (mSyncId < 0) return;
+        if (!isCollecting()) return;
         mOverrideOptions = options;
         sendRemoteCallback(mClientAnimationStartCallback);
         mClientAnimationStartCallback = startCallback;
@@ -455,7 +459,7 @@
      *           The transition will wait for all groups to be ready.
      */
     void setReady(WindowContainer wc, boolean ready) {
-        if (mSyncId < 0) return;
+        if (!isCollecting() || mSyncId < 0) return;
         mReadyTracker.setReadyFrom(wc, ready);
         applyReady();
     }
@@ -473,7 +477,7 @@
      * @see ReadyTracker#setAllReady.
      */
     void setAllReady() {
-        if (mSyncId < 0) return;
+        if (!isCollecting() || mSyncId < 0) return;
         mReadyTracker.setAllReady();
         applyReady();
     }
@@ -672,7 +676,7 @@
         SurfaceControl.Transaction inputSinkTransaction = null;
         for (int i = 0; i < mParticipants.size(); ++i) {
             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
-            if (ar == null || !ar.isVisible()) continue;
+            if (ar == null || !ar.isVisible() || ar.getParent() == null) continue;
             if (inputSinkTransaction == null) {
                 inputSinkTransaction = new SurfaceControl.Transaction();
             }
@@ -870,7 +874,7 @@
         }
         buildFinishTransaction(mFinishTransaction, info.getRootLeash());
         if (mController.getTransitionPlayer() != null) {
-            mController.dispatchLegacyAppTransitionStarting(info);
+            mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
             try {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "Calling onTransitionReady: %s", info);
@@ -889,7 +893,6 @@
             // No player registered, so just finish/apply immediately
             cleanUpOnFailure();
         }
-        mSyncId = -1;
         mOverrideOptions = null;
 
         reportStartReasonsToLogger();
@@ -1614,7 +1617,7 @@
     }
 
     boolean getLegacyIsReady() {
-        return (mState == STATE_STARTED || mState == STATE_COLLECTING) && mSyncId >= 0;
+        return isCollecting() && mSyncId >= 0;
     }
 
     static Transition fromBinder(IBinder binder) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 6d31e93..dbc2c5f 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -462,6 +462,12 @@
         }, true /* traverseTopToBottom */);
     }
 
+    /** @see Transition#mStatusBarTransitionDelay */
+    void setStatusBarTransitionDelay(long delay) {
+        if (mCollectingTransition == null) return;
+        mCollectingTransition.mStatusBarTransitionDelay = delay;
+    }
+
     /** @see Transition#setOverrideAnimation */
     void setOverrideAnimation(TransitionInfo.AnimationOptions options,
             @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
@@ -600,13 +606,14 @@
         }
     }
 
-    void dispatchLegacyAppTransitionStarting(TransitionInfo info) {
+    void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) {
         final boolean keyguardGoingAway = info.isKeyguardGoingAway();
         for (int i = 0; i < mLegacyListeners.size(); ++i) {
             // TODO(shell-transitions): handle (un)occlude transition.
             mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
                     false /* keyguardOcclude */, 0 /* durationHint */,
-                    SystemClock.uptimeMillis(), AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
+                    SystemClock.uptimeMillis() + statusBarTransitionDelay,
+                    AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index b1951e0..c1927d8 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -79,7 +79,7 @@
             final ProtoOutputStream outputStream = new ProtoOutputStream();
             final long transitionEntryToken = outputStream.start(TRANSITION);
 
-            outputStream.write(ID, transition.getDebugId());
+            outputStream.write(ID, transition.getSyncId());
             outputStream.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
             outputStream.write(TRANSITION_TYPE, transition.mType);
             outputStream.write(STATE, transition.getState());
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 7f1096b..00ee94a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2818,6 +2818,10 @@
      *                     snapshot from {@link #getFreezeSnapshotTarget()}.
      */
     void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
+        if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+            // TODO(b/207070762): request shell transition for activityEmbedding change.
+            return;
+        }
         mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
         mDisplayContent.mChangingContainers.add(this);
         // Calculate the relative position in parent container.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d7c85d4..bcf05a6f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2082,7 +2082,7 @@
         if (win.mAttrs.type == TYPE_WALLPAPER) {
             dc.mWallpaperController.clearLastWallpaperTimeoutTime();
             dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
-        } else if (win.hasWallpaper()) {
+        } else if (dc.mWallpaperController.isWallpaperTarget(win)) {
             dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 1d93c89..97dcb75 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -46,6 +46,7 @@
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -814,7 +815,7 @@
                     sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
                     break;
                 }
-                if (!parent.isAllowedToEmbedActivity(activity)) {
+                if (parent.isAllowedToEmbedActivity(activity) != EMBEDDING_ALLOWED) {
                     final Throwable exception = new SecurityException(
                             "The task fragment is not trusted to embed the given activity.");
                     sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
@@ -1057,7 +1058,7 @@
     }
 
     /** A helper method to send minimum dimension violation error to the client. */
-    void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
+    private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
             IBinder errorCallbackToken, String reason) {
         if (taskFragment == null || taskFragment.getTaskFragmentOrganizer() == null) {
             return;
@@ -1672,7 +1673,7 @@
             // We are reparenting activities to a new embedded TaskFragment, this operation is only
             // allowed if the new parent is trusted by all reparent activities.
             final boolean isEmbeddingDisallowed = oldParent.forAllActivities(activity ->
-                    !newParentTF.isAllowedToEmbedActivity(activity));
+                    newParentTF.isAllowedToEmbedActivity(activity) == EMBEDDING_ALLOWED);
             if (isEmbeddingDisallowed) {
                 final Throwable exception = new SecurityException(
                         "The new parent is not trusted to embed the activities.");
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index ec38f77..4078996 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -119,7 +119,7 @@
         "libutils",
         "libui",
         "libvibratorservice",
-        "PlatformProperties",
+        "libPlatformProperties",
         "libinput",
         "libinputflinger",
         "libinputflinger_base",
diff --git a/services/smartspace/OWNERS b/services/smartspace/OWNERS
index 19ef9d7..4d9a633 100644
--- a/services/smartspace/OWNERS
+++ b/services/smartspace/OWNERS
@@ -1,2 +1 @@
-srazdan@google.com
-alexmang@google.com
\ No newline at end of file
+include /core/java/android/service/smartspace/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
index 69584e0..7ccd6d9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -70,13 +70,12 @@
     @Test
     fun deleteSystemPackageFailsIfNotAdminAndNotProfile() {
         val ps = mPms.mSettings.getPackageLPr("a.data.package")
-        whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
+        whenever(PackageManagerServiceUtils.isUpdatedSystemApp(ps)).thenReturn(true)
         whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0))
         whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1)
 
         val dph = DeletePackageHelper(mPms)
-        val result = dph.deletePackageX("a.data.package", 1L, 1,
-            PackageManager.DELETE_SYSTEM_APP, false)
+        val result = dph.deletePackageX("a.data.package", 1L, 1, 0, false)
 
         assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED)
     }
@@ -86,7 +85,7 @@
         val userId = 1
         val parentId = 5
         val ps = mPms.mSettings.getPackageLPr("a.data.package")
-        whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
+        whenever(PackageManagerServiceUtils.isUpdatedSystemApp(ps)).thenReturn(true)
         whenever(mUserManagerInternal.getUserInfo(userId)).thenReturn(
             UserInfo(userId, "test", UserInfo.FLAG_PROFILE))
         whenever(mUserManagerInternal.getProfileParentId(userId)).thenReturn(parentId)
@@ -94,8 +93,7 @@
             UserInfo(userId, "testparent", 0))
 
         val dph = DeletePackageHelper(mPms)
-        val result = dph.deletePackageX("a.data.package", 1L, userId,
-            PackageManager.DELETE_SYSTEM_APP, false)
+        val result = dph.deletePackageX("a.data.package", 1L, userId, 0, false)
 
         assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED)
     }
@@ -132,4 +130,18 @@
 
         assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED)
     }
-}
\ No newline at end of file
+
+    @Test
+    fun deleteSystemPackageSucceedsIfNotAdminButDeleteSystemAppSpecified() {
+        val ps = mPms.mSettings.getPackageLPr("a.data.package")
+        whenever(PackageManagerServiceUtils.isUpdatedSystemApp(ps)).thenReturn(true)
+        whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0))
+        whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1)
+
+        val dph = DeletePackageHelper(mPms)
+        val result = dph.deletePackageX("a.data.package", 1L, 1,
+                PackageManager.DELETE_SYSTEM_APP, false)
+
+        assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED)
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 8b314cd..c2519caa 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -295,10 +295,10 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_1 = new byte[]{
-                0x01, 0x18, 0x4A,
-                0x02, 0x64, 0x5A,
-                0x03, 0x4B, 0x00,
-                0x04, 0x20, 0x0A};
+                0x0F, 0x18, 0x4A,
+                0x17, 0x64, 0x5A,
+                0x1F, 0x4B, 0x00,
+                0x27, 0x20, 0x0A};
         HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
@@ -310,10 +310,10 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_2 = new byte[]{
-                0x05, 0x18, 0x4A,
-                0x06, 0x64, 0x5A,
-                0x07, 0x4B, 0x00,
-                0x08, 0x20, 0x0A};
+                0x2F, 0x18, 0x4A,
+                0x37, 0x64, 0x5A,
+                0x3F, 0x4B, 0x00,
+                0x47, 0x20, 0x0A};
         HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
@@ -325,10 +325,10 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_3 = new byte[]{
-                0x09, 0x18, 0x4A,
-                0x0A, 0x64, 0x5A,
-                0x0B, 0x4B, 0x00,
-                0x0C, 0x20, 0x0A};
+                0x4F, 0x18, 0x4A,
+                0x57, 0x64, 0x5A,
+                0x5F, 0x4B, 0x00,
+                0x67, 0x20, 0x0A};
         HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
@@ -340,9 +340,9 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_4 = new byte[]{
-                0x0D, 0x18, 0x4A,
-                0x0E, 0x64, 0x5A,
-                0x0F, 0x4B, 0x00};
+                0x6F, 0x18, 0x4A,
+                0x77, 0x64, 0x5A,
+                0x7F, 0x4B, 0x00};
         HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
@@ -371,9 +371,9 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_1 = new byte[]{
-                0x01, 0x18, 0x4A,
-                0x03, 0x4B, 0x00,
-                0x04, 0x20, 0x0A};
+                0x0F, 0x18, 0x4A,
+                0x1F, 0x4B, 0x00,
+                0x27, 0x20, 0x0A};
         HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
@@ -385,7 +385,7 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_2 = new byte[]{
-                0x08, 0x20, 0x0A};
+                0x2F, 0x20, 0x0A};
         HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
@@ -397,10 +397,10 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_3 = new byte[]{
-                0x09, 0x18, 0x4A,
-                0x0A, 0x64, 0x5A,
-                0x0B, 0x4B, 0x00,
-                0x0C, 0x20, 0x0A};
+                0x4F, 0x18, 0x4A,
+                0x57, 0x64, 0x5A,
+                0x5F, 0x4B, 0x00,
+                0x67, 0x20, 0x0A};
         HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
@@ -412,7 +412,7 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_4 = new byte[]{
-                0x0F, 0x4B, 0x00};
+                0x7F, 0x4B, 0x00};
         HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
@@ -440,11 +440,12 @@
         HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+        // Negative values require explicit casting
         byte[] sadsToRespond_1 = new byte[]{
-                0x20, 0x18, 0x4A,
-                0x21, 0x64, 0x5A,
-                0x22, 0x4B, 0x00,
-                0x23, 0x20, 0x0A};
+                (byte) 0x80, 0x18, 0x4A,
+                (byte) 0x82, 0x64, 0x5A,
+                (byte) 0x87, 0x4B, 0x00,
+                (byte) 0x8F, 0x20, 0x0A};
         HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
@@ -456,10 +457,10 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_2 = new byte[]{
-                0x24, 0x18, 0x4A,
-                0x25, 0x64, 0x5A,
-                0x26, 0x4B, 0x00,
-                0x27, 0x20, 0x0A};
+                (byte) 0x92, 0x18, 0x4A,
+                (byte) 0x98, 0x64, 0x5A,
+                (byte) 0xA1, 0x4B, 0x00,
+                (byte) 0xA4, 0x20, 0x0A};
         HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
@@ -471,10 +472,10 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_3 = new byte[]{
-                0x28, 0x18, 0x4A,
-                0x29, 0x64, 0x5A,
-                0x2A, 0x4B, 0x00,
-                0x2B, 0x20, 0x0A};
+                (byte) 0xB3, 0x18, 0x4A,
+                (byte) 0xBA, 0x64, 0x5A,
+                (byte) 0xCB, 0x4B, 0x00,
+                (byte) 0xCF, 0x20, 0x0A};
         HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
@@ -486,9 +487,9 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_4 = new byte[]{
-                0x2C, 0x18, 0x4A,
-                0x2D, 0x64, 0x5A,
-                0x2E, 0x4B, 0x00};
+                (byte) 0xF0, 0x18, 0x4A,
+                (byte) 0xF1, 0x64, 0x5A,
+                (byte) 0xF3, 0x4B, 0x00};
         HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
@@ -510,10 +511,10 @@
                 mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
                 CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
         byte[] sadsToRespond_1 = new byte[]{
-                0x01, 0x18,
-                0x02, 0x64, 0x5A,
-                0x03, 0x4B, 0x00,
-                0x04, 0x20, 0x0A};
+                0x0F, 0x18,
+                0x17, 0x64, 0x5A,
+                0x1F, 0x4B, 0x00,
+                0x27, 0x20, 0x0A};
         HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
@@ -580,10 +581,10 @@
                 new int[]{Constants.AUDIO_CODEC_LPCM, Constants.AUDIO_CODEC_DD,
                         Constants.AUDIO_CODEC_MP3, Constants.AUDIO_CODEC_MPEG2});
         byte[] sadsToRespond_1 = new byte[]{
-                0x01, 0x18, 0x4A,
-                0x02, 0x64, 0x5A,
-                0x04, 0x20, 0x0A,
-                0x05, 0x18, 0x4A};
+                0x0F, 0x18, 0x4A,
+                0x17, 0x64, 0x5A,
+                0x27, 0x20, 0x0A,
+                0x2F, 0x18, 0x4A};
         HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
@@ -596,10 +597,10 @@
                 new int[]{Constants.AUDIO_CODEC_AAC, Constants.AUDIO_CODEC_DTS,
                         Constants.AUDIO_CODEC_ATRAC, Constants.AUDIO_CODEC_DDP});
         byte[] sadsToRespond_2 = new byte[]{
-                0x06, 0x64, 0x5A,
-                0x07, 0x4B, 0x00,
-                0x08, 0x20, 0x0A,
-                0x09, 0x18, 0x4A};
+                0x37, 0x64, 0x5A,
+                0x3F, 0x4B, 0x00,
+                0x47, 0x20, 0x0A,
+                0x4F, 0x18, 0x4A};
         HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
@@ -612,10 +613,10 @@
                 new int[]{Constants.AUDIO_CODEC_DTSHD, Constants.AUDIO_CODEC_TRUEHD,
                         Constants.AUDIO_CODEC_DST, Constants.AUDIO_CODEC_MAX});
         byte[] sadsToRespond_3 = new byte[]{
-                0x0B, 0x4B, 0x00,
-                0x0C, 0x20, 0x0A,
-                0x0D, 0x18, 0x4A,
-                0x0F, 0x4B, 0x00};
+                0x5F, 0x4B, 0x00,
+                0x67, 0x20, 0x0A,
+                0x6F, 0x18, 0x4A,
+                0x7F, 0x4B, 0x00};
         HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                 Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
         assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 1d10b8a..85db23c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -136,11 +136,6 @@
         }
 
         @Override
-        public boolean hasEnrolledBiometrics(int userId) {
-            return false;
-        }
-
-        @Override
         public int binderGetCallingUid() {
             return Process.SYSTEM_UID;
         }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index e7f4d3d..20cc42c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -42,11 +42,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle;
-import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -96,17 +93,16 @@
 
     @Test
     public void testChangePasswordFailPrimaryUser() throws RemoteException {
-        final long sid = 1234;
-        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"), sid);
+        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"));
 
         assertFalse(mService.setLockCredential(newPassword("newpwd"), newPassword("badpwd"),
                     PRIMARY_USER_ID));
-        assertVerifyCredentials(PRIMARY_USER_ID, newPassword("password"), sid);
+        assertVerifyCredentials(PRIMARY_USER_ID, newPassword("password"));
     }
 
     @Test
     public void testClearPasswordPrimaryUser() throws RemoteException {
-        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"), 1234);
+        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"));
         assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
                 PRIMARY_USER_ID));
         assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(PRIMARY_USER_ID));
@@ -264,10 +260,7 @@
     public void testSetLockCredential_forProfileWithSeparateChallenge_updatesCredentials()
             throws Exception {
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, true, null);
-        initializeStorageWithCredential(
-                MANAGED_PROFILE_USER_ID,
-                newPattern("12345"),
-                1234);
+        initializeStorageWithCredential(MANAGED_PROFILE_USER_ID, newPattern("12345"));
 
         assertTrue(mService.setLockCredential(
                 newPassword("newPassword"),
@@ -300,8 +293,7 @@
                     throws Exception {
         final LockscreenCredential oldCredential = newPassword("oldPassword");
         final LockscreenCredential newCredential = newPassword("newPassword");
-        initializeStorageWithCredential(
-                PRIMARY_USER_ID, oldCredential, 1234);
+        initializeStorageWithCredential(PRIMARY_USER_ID, oldCredential);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
         assertTrue(mService.setLockCredential(
@@ -321,7 +313,7 @@
     public void
             testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials()
                     throws Exception {
-        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("oldPassword"), 1234);
+        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("oldPassword"));
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
         assertTrue(mService.setLockCredential(
@@ -337,10 +329,7 @@
 
     @Test
     public void testSetLockCredential_nullCredential_removeBiometrics() throws RemoteException {
-        initializeStorageWithCredential(
-                PRIMARY_USER_ID,
-                newPattern("123654"),
-                1234);
+        initializeStorageWithCredential(PRIMARY_USER_ID, newPattern("123654"));
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
         mService.setLockCredential(nonePassword(), newPattern("123654"), PRIMARY_USER_ID);
@@ -358,7 +347,7 @@
             throws Exception {
         final LockscreenCredential parentPassword = newPassword("parentPassword");
         final LockscreenCredential profilePassword = newPassword("profilePassword");
-        initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword, 1234);
+        initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
         assertTrue(mService.setLockCredential(
@@ -377,7 +366,7 @@
                     throws Exception {
         final LockscreenCredential parentPassword = newPassword("parentPassword");
         final LockscreenCredential profilePassword = newPattern("12345");
-        initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword, 1234);
+        initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
         // Create and verify separate profile credentials.
         testCreateCredential(MANAGED_PROFILE_USER_ID, profilePassword);
 
@@ -393,7 +382,7 @@
     @Test
     public void testVerifyCredential_forPrimaryUser_sendsCredentials() throws Exception {
         final LockscreenCredential password = newPassword("password");
-        initializeStorageWithCredential(PRIMARY_USER_ID, password, 1234);
+        initializeStorageWithCredential(PRIMARY_USER_ID, password);
         reset(mRecoverableKeyStoreManager);
 
         mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */);
@@ -424,7 +413,7 @@
     public void verifyCredential_forPrimaryUserWithUnifiedChallengeProfile_sendsCredentialsForBoth()
                     throws Exception {
         final LockscreenCredential pattern = newPattern("12345");
-        initializeStorageWithCredential(PRIMARY_USER_ID, pattern, 1234);
+        initializeStorageWithCredential(PRIMARY_USER_ID, pattern);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         reset(mRecoverableKeyStoreManager);
 
@@ -464,7 +453,7 @@
     private void testCreateCredential(int userId, LockscreenCredential credential)
             throws RemoteException {
         assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
-        assertVerifyCredentials(userId, credential, -1);
+        assertVerifyCredentials(userId, credential);
     }
 
     private void testCreateCredentialFailsWithoutLockScreen(
@@ -483,19 +472,17 @@
 
     private void testChangeCredentials(int userId, LockscreenCredential newCredential,
             LockscreenCredential oldCredential) throws RemoteException {
-        final long sid = 1234;
-        initializeStorageWithCredential(userId, oldCredential, sid);
+        initializeStorageWithCredential(userId, oldCredential);
         assertTrue(mService.setLockCredential(newCredential, oldCredential, userId));
-        assertVerifyCredentials(userId, newCredential, sid);
+        assertVerifyCredentials(userId, newCredential);
     }
 
-    private void assertVerifyCredentials(int userId, LockscreenCredential credential, long sid)
+    private void assertVerifyCredentials(int userId, LockscreenCredential credential)
             throws RemoteException{
         VerifyCredentialResponse response = mService.verifyCredential(credential, userId,
                 0 /* flags */);
 
         assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
-        if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
         if (credential.isPassword()) {
             assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId));
         } else if (credential.isPin()) {
@@ -517,19 +504,11 @@
                 badCredential, userId, 0 /* flags */).getResponseCode());
     }
 
-    private void initializeStorageWithCredential(int userId, LockscreenCredential credential,
-            long sid) throws RemoteException {
-        byte[] oldHash = new VerifyHandle(credential.getCredential(), sid).toBytes();
-        if (mService.shouldMigrateToSyntheticPasswordLocked(userId)) {
-            mService.initializeSyntheticPasswordLocked(oldHash, credential, userId);
-        } else {
-            if (credential.isPassword() || credential.isPin()) {
-                mStorage.writeCredentialHash(CredentialHash.create(oldHash,
-                        LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
-            } else {
-                mStorage.writeCredentialHash(CredentialHash.create(oldHash,
-                        LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
-            }
-        }
+    @SuppressWarnings("GuardedBy") // for initializeSyntheticPasswordLocked
+    private void initializeStorageWithCredential(int userId, LockscreenCredential credential)
+            throws RemoteException {
+        assertEquals(0, mGateKeeperService.getSecureUserId(userId));
+        mService.initializeSyntheticPasswordLocked(credential, userId);
+        assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index f2bb1d6..c30af4c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -64,18 +64,6 @@
     }
 
     @Override
-    String getLockPatternFilename(int userId) {
-        return makeDirs(mStorageDir,
-                super.getLockPatternFilename(userId)).getAbsolutePath();
-    }
-
-    @Override
-    String getLockPasswordFilename(int userId) {
-        return makeDirs(mStorageDir,
-                super.getLockPasswordFilename(userId)).getAbsolutePath();
-    }
-
-    @Override
     String getChildProfileLockFile(int userId) {
         return makeDirs(mStorageDir,
                 super.getChildProfileLockFile(userId)).getAbsolutePath();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 5f38a35..609c05c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -48,9 +48,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.server.PersistentDataBlockManagerInternal;
-import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
 
 import org.junit.After;
@@ -72,10 +70,7 @@
 @RunWith(AndroidJUnit4.class)
 public class LockSettingsStorageTests {
     private static final int SOME_USER_ID = 1034;
-    private final byte[] PASSWORD_0 = "thepassword0".getBytes();
-    private final byte[] PASSWORD_1 = "password1".getBytes();
-    private final byte[] PATTERN_0 = "123654".getBytes();
-    private final byte[] PATTERN_1 = "147852369".getBytes();
+    private final byte[] PASSWORD = "thepassword".getBytes();
 
     public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 33};
 
@@ -216,161 +211,64 @@
     @Test
     public void testRemoveUser() {
         mStorage.writeKeyValue("key", "value", 0);
-        writePasswordBytes(PASSWORD_0, 0);
-        writePatternBytes(PATTERN_0, 0);
-
         mStorage.writeKeyValue("key", "value", 1);
-        writePasswordBytes(PASSWORD_1, 1);
-        writePatternBytes(PATTERN_1, 1);
 
         mStorage.removeUser(0);
 
         assertEquals("value", mStorage.readKeyValue("key", "default", 1));
         assertEquals("default", mStorage.readKeyValue("key", "default", 0));
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_NONE, mStorage.readCredentialHash(0).type);
-        assertPatternBytes(PATTERN_1, 1);
-    }
-
-    @Test
-    public void testCredential_Default() {
-        assertEquals(mStorage.readCredentialHash(0).type, LockPatternUtils.CREDENTIAL_TYPE_NONE);
-    }
-
-    @Test
-    public void testPassword_Write() {
-        writePasswordBytes(PASSWORD_0, 0);
-
-        assertPasswordBytes(PASSWORD_0, 0);
-        mStorage.clearCache();
-        assertPasswordBytes(PASSWORD_0, 0);
-    }
-
-    @Test
-    public void testPassword_WriteProfileWritesParent() {
-        writePasswordBytes(PASSWORD_0, 1);
-        writePasswordBytes(PASSWORD_1, 2);
-
-        assertPasswordBytes(PASSWORD_0, 1);
-        assertPasswordBytes(PASSWORD_1, 2);
-        mStorage.clearCache();
-        assertPasswordBytes(PASSWORD_0, 1);
-        assertPasswordBytes(PASSWORD_1, 2);
-    }
-
-    @Test
-    public void testLockType_WriteProfileWritesParent() {
-        writePasswordBytes(PASSWORD_0, 10);
-        writePatternBytes(PATTERN_0, 20);
-
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN,
-                mStorage.readCredentialHash(10).type);
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
-                mStorage.readCredentialHash(20).type);
-        mStorage.clearCache();
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN,
-                mStorage.readCredentialHash(10).type);
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
-                mStorage.readCredentialHash(20).type);
-    }
-
-    @Test
-    public void testPassword_WriteParentWritesProfile() {
-        writePasswordBytes(PASSWORD_0, 2);
-        writePasswordBytes(PASSWORD_1, 1);
-
-        assertPasswordBytes(PASSWORD_1, 1);
-        assertPasswordBytes(PASSWORD_0, 2);
-        mStorage.clearCache();
-        assertPasswordBytes(PASSWORD_1, 1);
-        assertPasswordBytes(PASSWORD_0, 2);
     }
 
     @Test
     public void testProfileLock_ReadWriteChildProfileLock() {
         assertFalse(mStorage.hasChildProfileLock(20));
-        mStorage.writeChildProfileLock(20, PASSWORD_0);
-        assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20));
+        mStorage.writeChildProfileLock(20, PASSWORD);
+        assertArrayEquals(PASSWORD, mStorage.readChildProfileLock(20));
         assertTrue(mStorage.hasChildProfileLock(20));
         mStorage.clearCache();
-        assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20));
+        assertArrayEquals(PASSWORD, mStorage.readChildProfileLock(20));
         assertTrue(mStorage.hasChildProfileLock(20));
     }
 
     @Test
-    public void testPattern_Write() {
-        writePatternBytes(PATTERN_0, 0);
-
-        assertPatternBytes(PATTERN_0, 0);
-        mStorage.clearCache();
-        assertPatternBytes(PATTERN_0, 0);
-    }
-
-    @Test
-    public void testPattern_WriteProfileWritesParent() {
-        writePatternBytes(PATTERN_0, 1);
-        writePatternBytes(PATTERN_1, 2);
-
-        assertPatternBytes(PATTERN_0, 1);
-        assertPatternBytes(PATTERN_1, 2);
-        mStorage.clearCache();
-        assertPatternBytes(PATTERN_0, 1);
-        assertPatternBytes(PATTERN_1, 2);
-    }
-
-    @Test
-    public void testPattern_WriteParentWritesProfile() {
-        writePatternBytes(PATTERN_1, 2);
-        writePatternBytes(PATTERN_0, 1);
-
-        assertPatternBytes(PATTERN_0, 1);
-        assertPatternBytes(PATTERN_1, 2);
-        mStorage.clearCache();
-        assertPatternBytes(PATTERN_0, 1);
-        assertPatternBytes(PATTERN_1, 2);
-    }
-
-    @Test
     public void testPrefetch() {
         mStorage.writeKeyValue("key", "toBeFetched", 0);
-        writePatternBytes(PATTERN_0, 0);
 
         mStorage.clearCache();
         mStorage.prefetchUser(0);
 
         assertEquals("toBeFetched", mStorage.readKeyValue("key", "default", 0));
-        assertPatternBytes(PATTERN_0, 0);
     }
 
     @Test
     public void testFileLocation_Owner() {
         LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
 
-        assertEquals("/data/system/gatekeeper.pattern.key", storage.getLockPatternFilename(0));
-        assertEquals("/data/system/gatekeeper.password.key", storage.getLockPasswordFilename(0));
+        assertEquals("/data/system/gatekeeper.profile.key", storage.getChildProfileLockFile(0));
     }
 
     @Test
     public void testFileLocation_SecondaryUser() {
         LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
 
-        assertEquals("/data/system/users/1/gatekeeper.pattern.key", storage.getLockPatternFilename(1));
-        assertEquals("/data/system/users/1/gatekeeper.password.key", storage.getLockPasswordFilename(1));
+        assertEquals("/data/system/users/1/gatekeeper.profile.key",
+                storage.getChildProfileLockFile(1));
     }
 
     @Test
     public void testFileLocation_ProfileToSecondary() {
         LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
 
-        assertEquals("/data/system/users/2/gatekeeper.pattern.key", storage.getLockPatternFilename(2));
-        assertEquals("/data/system/users/2/gatekeeper.password.key", storage.getLockPasswordFilename(2));
+        assertEquals("/data/system/users/2/gatekeeper.profile.key",
+                storage.getChildProfileLockFile(2));
     }
 
     @Test
     public void testFileLocation_ProfileToOwner() {
         LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
 
-        assertEquals("/data/system/users/3/gatekeeper.pattern.key", storage.getLockPatternFilename(3));
-        assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3));
+        assertEquals("/data/system/users/3/gatekeeper.profile.key",
+                storage.getChildProfileLockFile(3));
     }
 
     @Test
@@ -483,28 +381,6 @@
         }
     }
 
-    private void writePasswordBytes(byte[] password, int userId) {
-        mStorage.writeCredentialHash(CredentialHash.create(
-                password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
-    }
-
-    private void writePatternBytes(byte[] pattern, int userId) {
-        mStorage.writeCredentialHash(CredentialHash.create(
-                pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
-    }
-
-    private void assertPasswordBytes(byte[] password, int userId) {
-        CredentialHash cred = mStorage.readCredentialHash(userId);
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN, cred.type);
-        assertArrayEquals(password, cred.hash);
-    }
-
-    private void assertPatternBytes(byte[] pattern, int userId) {
-        CredentialHash cred = mStorage.readCredentialHash(userId);
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN, cred.type);
-        assertArrayEquals(pattern, cred.hash);
-    }
-
     /**
      * Suppresses reporting of the WTF to system_server, so we don't pollute the dropbox with
      * intentionally caused WTFs.
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 09aa345..6d1df2c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -19,7 +19,6 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
-import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
 
 import static org.junit.Assert.assertEquals;
@@ -38,7 +37,6 @@
 import android.app.admin.PasswordMetrics;
 import android.app.PropertyInvalidatedCache;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -82,8 +80,7 @@
         final LockscreenCredential badPassword = newPassword("bad-password");
         MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
                 mGateKeeperService, mUserManager, mPasswordSlotManager);
-        AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
-                null, USER_ID);
+        AuthenticationToken authToken = manager.newSyntheticPassword(USER_ID);
         long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService,
                 password, authToken, USER_ID);
 
@@ -97,32 +94,23 @@
         assertNull(result.authToken);
     }
 
-    private void disableSyntheticPassword() throws RemoteException {
-        mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
-    }
-
-    private void enableSyntheticPassword() throws RemoteException {
-        mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
-    }
-
     private boolean hasSyntheticPassword(int userId) throws RemoteException {
         return mService.getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId) != 0;
     }
 
-    protected void initializeCredentialUnderSP(LockscreenCredential password, int userId)
+    private void initializeCredential(LockscreenCredential password, int userId)
             throws RemoteException {
-        enableSyntheticPassword();
         assertTrue(mService.setLockCredential(password, nonePassword(), userId));
         assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId));
         assertTrue(mService.isSyntheticPasswordBasedCredential(userId));
     }
 
     @Test
-    public void testSyntheticPasswordChangeCredential() throws RemoteException {
+    public void testChangeCredential() throws RemoteException {
         final LockscreenCredential password = newPassword("password");
         final LockscreenCredential newPassword = newPassword("newpassword");
 
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         mService.setLockCredential(newPassword, password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
@@ -131,11 +119,11 @@
     }
 
     @Test
-    public void testSyntheticPasswordVerifyCredential() throws RemoteException {
+    public void testVerifyCredential() throws RemoteException {
         LockscreenCredential password = newPassword("password");
         LockscreenCredential badPassword = newPassword("badpassword");
 
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
 
@@ -144,11 +132,11 @@
     }
 
     @Test
-    public void testSyntheticPasswordClearCredential() throws RemoteException {
+    public void testClearCredential() throws RemoteException {
         LockscreenCredential password = newPassword("password");
         LockscreenCredential badPassword = newPassword("newpassword");
 
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         // clear password
         mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
@@ -162,11 +150,11 @@
     }
 
     @Test
-    public void testSyntheticPasswordChangeCredentialKeepsAuthSecret() throws RemoteException {
+    public void testChangeCredentialKeepsAuthSecret() throws RemoteException {
         LockscreenCredential password = newPassword("password");
         LockscreenCredential badPassword = newPassword("new");
 
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
         mService.setLockCredential(badPassword, password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
@@ -178,11 +166,10 @@
     }
 
     @Test
-    public void testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret() throws RemoteException {
+    public void testVerifyPassesPrimaryUserAuthSecret() throws RemoteException {
         LockscreenCredential password = newPassword("password");
-        LockscreenCredential newPassword = newPassword("new");
 
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
         reset(mAuthSecretService);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
@@ -193,7 +180,7 @@
     public void testSecondaryUserDoesNotPassAuthSecret() throws RemoteException {
         LockscreenCredential password = newPassword("password");
 
-        initializeCredentialUnderSP(password, SECONDARY_USER_ID);
+        initializeCredential(password, SECONDARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, SECONDARY_USER_ID, 0 /* flags */).getResponseCode());
         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
@@ -207,9 +194,9 @@
     }
 
     @Test
-    public void testSyntheticPasswordAndCredentialDoesNotPassAuthSecret() throws RemoteException {
-        LockscreenCredential password = newPassword("passwordForASyntheticPassword");
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+    public void testCredentialDoesNotPassAuthSecret() throws RemoteException {
+        LockscreenCredential password = newPassword("password");
+        initializeCredential(password, PRIMARY_USER_ID);
 
         reset(mAuthSecretService);
         mService.onUnlockUser(PRIMARY_USER_ID);
@@ -219,8 +206,8 @@
 
     @Test
     public void testSyntheticPasswordButNoCredentialPassesAuthSecret() throws RemoteException {
-        LockscreenCredential password = newPassword("getASyntheticPassword");
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        LockscreenCredential password = newPassword("password");
+        initializeCredential(password, PRIMARY_USER_ID);
         mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
 
         reset(mAuthSecretService);
@@ -234,7 +221,7 @@
         LockscreenCredential password = newPassword("password");
         LockscreenCredential pattern = newPattern("123654");
         byte[] token = "some-high-entropy-secure-token".getBytes();
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
         // Disregard any reportPasswordChanged() invocations as part of credential setup.
         flushHandlerTasks();
         reset(mDevicePolicyManager);
@@ -269,7 +256,7 @@
         LockscreenCredential password = newPassword("password");
         LockscreenCredential pattern = newPattern("123654");
         byte[] token = "some-high-entropy-secure-token".getBytes();
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
         byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
 
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
@@ -295,7 +282,7 @@
         LockscreenCredential pattern = newPattern("123654");
         LockscreenCredential newPassword = newPassword("password");
         byte[] token = "some-high-entropy-secure-token".getBytes();
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
         byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
 
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
@@ -318,7 +305,6 @@
     public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration()
             throws RemoteException {
         final byte[] token = "some-high-entropy-secure-token".getBytes();
-        enableSyntheticPassword();
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
@@ -331,7 +317,7 @@
         final byte[] token = "some-high-entropy-secure-token".getBytes();
         // By first setting a password and then clearing it, we enter the state where SP is
         // initialized but the user currently has no password
-        initializeCredentialUnderSP(newPassword("password"), PRIMARY_USER_ID);
+        initializeCredential(newPassword("password"), PRIMARY_USER_ID);
         assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
                 PRIMARY_USER_ID));
         assertTrue(mService.isSyntheticPasswordBasedCredential(PRIMARY_USER_ID));
@@ -343,19 +329,15 @@
     }
 
     @Test
-    public void testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration()
-            throws RemoteException {
+    public void testEscrowTokenActivatedLaterWithUserPassword() throws RemoteException {
         byte[] token = "some-high-entropy-secure-token".getBytes();
         LockscreenCredential password = newPassword("password");
-        // Set up pre-SP user password
-        disableSyntheticPassword();
         mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
-        enableSyntheticPassword();
 
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         // Token not activated immediately since user password exists
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
-        // Activate token (password gets migrated to SP at the same time)
+        // Activate token
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         // Verify token is activated
@@ -383,7 +365,7 @@
 
         LockscreenCredential password = newPassword("password");
         LockscreenCredential pattern = newPattern("123654");
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
 
         long handle0 = mLocalService.addEscrowToken(token0, PRIMARY_USER_ID, null);
         long handle1 = mLocalService.addEscrowToken(token1, PRIMARY_USER_ID, null);
@@ -412,7 +394,6 @@
         byte[] token = "some-high-entropy-secure-token".getBytes();
 
         mService.mHasSecureLockScreen = false;
-        enableSyntheticPassword();
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
@@ -515,7 +496,7 @@
 
         LockscreenCredential password = newPassword("testGsiDisablesAuthSecret-password");
 
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
@@ -525,7 +506,7 @@
     public void testUnlockUserWithToken() throws Exception {
         LockscreenCredential password = newPassword("password");
         byte[] token = "some-high-entropy-secure-token".getBytes();
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
         // Disregard any reportPasswordChanged() invocations as part of credential setup.
         flushHandlerTasks();
         reset(mDevicePolicyManager);
@@ -546,7 +527,7 @@
     @Test
     public void testPasswordChange_NoOrphanedFilesLeft() throws Exception {
         LockscreenCredential password = newPassword("password");
-        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        initializeCredential(password, PRIMARY_USER_ID);
         assertTrue(mService.setLockCredential(password, password, PRIMARY_USER_ID));
         assertNoOrphanedFilesLeft(PRIMARY_USER_ID);
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index fd1536c..4550b56 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1622,7 +1622,9 @@
                     ZenModeConfig.toScheduleConditionId(si),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+            // We need the package name to be something that's not "android" so there aren't any
+            // existing rules under that package.
+            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
             assertNotNull(id);
         }
         try {
@@ -1632,12 +1634,41 @@
                     ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
             // yay
         }
+    }
 
+    @Test
+    public void testAddAutomaticZenRule_beyondSystemLimit_differentComponents() {
+        // Make sure the system limit is enforced per-package even with different component provider
+        // names.
+        for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
+            ScheduleInfo si = new ScheduleInfo();
+            si.startHour = i;
+            AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
+                    null,
+                    new ComponentName("android", "ScheduleConditionProvider" + i),
+                    ZenModeConfig.toScheduleConditionId(si),
+                    new ZenPolicy.Builder().build(),
+                    NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+            assertNotNull(id);
+        }
+        try {
+            AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                    null,
+                    new ComponentName("android", "ScheduleConditionProviderFinal"),
+                    ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                    new ZenPolicy.Builder().build(),
+                    NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+            fail("allowed too many rules to be created");
+        } catch (IllegalArgumentException e) {
+            // yay
+        }
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 5be1ece..fe3c26a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -37,6 +37,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
 import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
 import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
@@ -52,6 +53,11 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityStarter.canEmbedActivity;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -59,6 +65,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -87,6 +94,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.Pair;
+import android.util.Size;
 import android.view.Gravity;
 import android.window.TaskFragmentOrganizerToken;
 
@@ -1171,6 +1179,7 @@
                 null /* inTask */, taskFragment);
 
         assertFalse(taskFragment.hasChild());
+        assertNotNull("Target record must be started on Task.", targetRecord.getParent().asTask());
     }
 
     @Test
@@ -1341,6 +1350,58 @@
                 any());
     }
 
+    @Test
+    public void testCanEmbedActivity() {
+        final Size minDimensions = new Size(1000, 1000);
+        final WindowLayout windowLayout = new WindowLayout(0, 0, 0, 0, 0,
+                minDimensions.getWidth(), minDimensions.getHeight());
+        final ActivityRecord starting = new ActivityBuilder(mAtm)
+                .setUid(UNIMPORTANT_UID)
+                .setWindowLayout(windowLayout)
+                .build();
+
+        // Task fragment hasn't attached to a task yet. Start activity to a new task.
+        TaskFragment taskFragment = new TaskFragmentBuilder(mAtm).build();
+        final Task task = new TaskBuilder(mSupervisor).build();
+
+        assertEquals(EMBEDDING_DISALLOWED_NEW_TASK,
+                canEmbedActivity(taskFragment, starting, task));
+
+        // Starting activity is going to be started on a task different from task fragment's parent
+        // task. Start activity to a new task.
+        task.addChild(taskFragment, POSITION_TOP);
+        final Task newTask = new TaskBuilder(mSupervisor).build();
+
+        assertEquals(EMBEDDING_DISALLOWED_NEW_TASK,
+                canEmbedActivity(taskFragment, starting, newTask));
+
+        // Make task fragment bounds exceed task bounds.
+        final Rect taskBounds = task.getBounds();
+        taskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.right + 1,
+                taskBounds.bottom + 1);
+
+        assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+                canEmbedActivity(taskFragment, starting, task));
+
+        taskFragment.setBounds(taskBounds);
+        starting.info.flags |= FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
+
+        assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task));
+
+        starting.info.flags &= ~FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
+        // Set task fragment's uid as the same as starting activity's uid.
+        taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class),
+                UNIMPORTANT_UID, "test");
+
+        assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task));
+
+        // Make task fragment bounds smaller than starting activity's minimum dimensions
+        taskFragment.setBounds(0, 0, minDimensions.getWidth() - 1, minDimensions.getHeight() - 1);
+
+        assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
+                canEmbedActivity(taskFragment, starting, task));
+    }
+
     private static void startActivityInner(ActivityStarter starter, ActivityRecord target,
             ActivityRecord source, ActivityOptions options, Task inTask,
             TaskFragment inTaskFragment) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d737963..40e266c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1699,6 +1699,13 @@
         assertFalse(displayContent.mPinnedTaskController.isFreezingTaskConfig(pinnedTask));
         assertEquals(pinnedActivity.getConfiguration().orientation,
                 displayContent.getConfiguration().orientation);
+
+        // No need to apply rotation if the display ignores orientation request.
+        doCallRealMethod().when(displayContent).rotationForActivityInDifferentOrientation(any());
+        pinnedActivity.mOrientation = SCREEN_ORIENTATION_LANDSCAPE;
+        displayContent.setIgnoreOrientationRequest(true);
+        assertEquals(WindowConfiguration.ROTATION_UNDEFINED,
+                displayContent.rotationForActivityInDifferentOrientation(pinnedActivity));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 1708ed7..83f375f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1332,7 +1332,7 @@
         });
         assertSecurityException(expectCallable,
                 () -> mAtm.startActivityFromRecents(0, new Bundle()));
-        assertSecurityException(expectCallable, () -> mAtm.getTaskSnapshot(0, true));
+        assertSecurityException(expectCallable, () -> mAtm.getTaskSnapshot(0, true, false));
         assertSecurityException(expectCallable, () -> mAtm.registerTaskStackListener(null));
         assertSecurityException(expectCallable,
                 () -> mAtm.unregisterTaskStackListener(null));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 1c3b869..e47bcc9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -21,6 +21,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.testing.Assert.assertThrows;
 
@@ -530,7 +531,7 @@
         mWindowOrganizerController.mLaunchTaskFragments
                 .put(mFragmentToken, mTaskFragment);
         mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token);
-        doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity);
+        doReturn(EMBEDDING_ALLOWED).when(mTaskFragment).isAllowedToEmbedActivity(activity);
         clearInvocations(mAtm.mRootWindowContainer);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
@@ -920,7 +921,6 @@
                 .setOrganizer(mOrganizer)
                 .setBounds(mTaskFragBounds)
                 .build();
-        doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity);
         mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
 
         // Reparent activity to mTaskFragment, which is smaller than activity's
@@ -956,7 +956,6 @@
                 .setOrganizer(mOrganizer)
                 .setBounds(mTaskFragBounds)
                 .build();
-        doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity);
         mWindowOrganizerController.mLaunchTaskFragments.put(oldFragToken, oldTaskFrag);
         mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index c0759c1..22101c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -196,6 +196,28 @@
     }
 
     @Test
+    public void testUsesOptionsDisplayAreaFeatureIdIfSet() {
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FREEFORM);
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FULLSCREEN);
+
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
+        ActivityRecord source = createSourceActivity(freeformDisplay);
+
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(fullscreenDisplay.mDisplayId);
+        options.setLaunchTaskDisplayAreaFeatureId(
+                fullscreenDisplay.getDefaultTaskDisplayArea().mFeatureId);
+
+        assertEquals(RESULT_CONTINUE,
+                new CalculateRequestBuilder().setSource(source).setOptions(options).calculate());
+
+        assertEquals(fullscreenDisplay.getDefaultTaskDisplayArea(),
+                mResult.mPreferredTaskDisplayArea);
+    }
+
+    @Test
     public void testUsesSourcesDisplayAreaIdPriorToTaskIfSet() {
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
@@ -453,7 +475,7 @@
     }
 
     @Test
-    public void testNotOverrideDisplayAreaWhenActivityOptionsHasDisplayArea() {
+    public void testNotOverrideDisplayAreaWhenActivityOptionsHasDisplayAreaToken() {
         final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay,
                 mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
         final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN,
@@ -475,6 +497,52 @@
     }
 
     @Test
+    public void testNotOverrideDisplayAreaWhenActivityOptionsHasDisplayAreaFeatureId() {
+        final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay,
+                mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
+        final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD);
+        launchRoot.mCreatedByOrganizer = true;
+
+        secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FULLSCREEN },
+                new int[] { ACTIVITY_TYPE_STANDARD });
+
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchTaskDisplayAreaFeatureId(
+                mDefaultDisplay.getDefaultTaskDisplayArea().mFeatureId);
+
+        assertEquals(RESULT_CONTINUE,
+                new CalculateRequestBuilder().setOptions(options).calculate());
+
+        assertEquals(
+                mDefaultDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea);
+    }
+
+    @Test
+    public void testUsesOptionsDisplayAreaFeatureIdDisplayIdNotSet() {
+        final TestDisplayContent secondaryDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FULLSCREEN);
+        final TaskDisplayArea tdaOnSecondaryDisplay = createTaskDisplayArea(secondaryDisplay,
+                mWm, "TestTaskDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
+
+        final TaskDisplayArea tdaOnDefaultDisplay = createTaskDisplayArea(mDefaultDisplay,
+                mWm, "TestTaskDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
+
+        mCurrent.mPreferredTaskDisplayArea = tdaOnSecondaryDisplay;
+        ActivityRecord source = createSourceActivity(tdaOnSecondaryDisplay,
+                WINDOWING_MODE_FULLSCREEN);
+
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchTaskDisplayAreaFeatureId(tdaOnSecondaryDisplay.mFeatureId);
+
+        assertEquals(RESULT_CONTINUE,
+                new CalculateRequestBuilder().setSource(source).setOptions(options).calculate());
+        // Display id wasn't specified in ActivityOptions - the activity should be placed on the
+        // default display, into the TaskDisplayArea with the same feature id.
+        assertEquals(tdaOnDefaultDisplay, mResult.mPreferredTaskDisplayArea);
+    }
+
+    @Test
     public void testRecalculateFreeformInitialBoundsWithOverrideDisplayArea() {
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
@@ -1822,6 +1890,13 @@
         return new ActivityBuilder(mAtm).setTask(rootTask).build();
     }
 
+    private ActivityRecord createSourceActivity(TaskDisplayArea taskDisplayArea,
+            int windowingMode) {
+        final Task rootTask = taskDisplayArea.createRootTask(windowingMode, ACTIVITY_TYPE_STANDARD,
+                true);
+        return new ActivityBuilder(mAtm).setTask(rootTask).build();
+    }
+
     private void addFreeformTaskTo(TestDisplayContent display, Rect bounds) {
         final Task rootTask = display.getDefaultTaskDisplayArea()
                 .createRootTask(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 53595a5..67e0a35 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -686,6 +686,7 @@
         assertTrue(ime.mToken.inTransition());
         assertTrue(task.inTransition());
         assertTrue(asyncRotationController.isTargetToken(decorToken));
+        assertTrue(asyncRotationController.shouldFreezeInsetsPosition(navBar));
 
         screenDecor.setOrientationChanging(false);
         // Status bar finishes drawing before the start transaction. Its fade-in animation will be
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9963827..fba4ff1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -246,6 +246,22 @@
         assertEquals(otherWindowInitialZoom, wallpaperWindow.mWallpaperZoomOut, .01f);
     }
 
+    @Test
+    public void testUpdateWallpaperTarget() {
+        final DisplayContent dc = mDisplayContent;
+        final WindowState homeWin = createWallpaperTargetWindow(dc);
+        final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, "app");
+        final RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
+        doReturn(true).when(recentsController).isWallpaperVisible(eq(appWin));
+        mWm.setRecentsAnimationController(recentsController);
+
+        dc.mWallpaperController.adjustWallpaperWindows();
+        assertEquals(appWin, dc.mWallpaperController.getWallpaperTarget());
+        // The wallpaper target is gone, so it should adjust to the next target.
+        appWin.removeImmediately();
+        assertEquals(homeWin, dc.mWallpaperController.getWallpaperTarget());
+    }
+
     /**
      * Tests that the windowing mode of the wallpaper window must always be fullscreen.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 08bad70..9c2aac0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -223,7 +223,7 @@
         mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
         // Ensure events dispatch to organizer.
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
-        assertTaskVanished(organizer, false /* expectVanished */, rootTask);
+        verify(organizer, times(0)).onTaskVanished(any());
         assertFalse(rootTask.isOrganized());
     }
 
@@ -297,7 +297,7 @@
         // Ensure events dispatch to organizer.
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
 
-        assertTaskVanished(organizer, true /* expectVanished */, rootTask);
+        verify(organizer, times(0)).onTaskVanished(any());
         assertFalse(rootTask.isOrganized());
     }
 
@@ -341,7 +341,7 @@
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         verify(organizer, times(3))
                 .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
-        assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3);
+        verify(organizer2, times(0)).onTaskVanished(any());
     }
 
     @Test
@@ -395,6 +395,7 @@
         assertFalse(task2.isAttached());
         // Normal task should keep.
         assertTrue(task.isAttached());
+        verify(organizer2, times(0)).onTaskVanished(any());
     }
 
     @Test
@@ -439,7 +440,7 @@
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         verify(organizer, times(3))
                 .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
-        assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3);
+        verify(organizer2, times(0)).onTaskVanished(any());
     }
 
     @Test
@@ -1264,7 +1265,7 @@
         record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
         waitUntilHandlersIdle();
 
-        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
         assertEquals(1, pendingEvents.size());
         assertEquals(PendingTaskEvent.EVENT_APPEARED, pendingEvents.get(0).mEventType);
         assertEquals("TestDescription",
@@ -1284,7 +1285,7 @@
         rootTask.removeImmediately();
         waitUntilHandlersIdle();
 
-        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
         assertEquals(0, pendingEvents.size());
     }
 
@@ -1302,7 +1303,7 @@
         record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
         waitUntilHandlersIdle();
 
-        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
         assertEquals(1, pendingEvents.size());
         assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType);
         assertEquals("TestDescription",
@@ -1311,7 +1312,7 @@
         record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription2"));
         waitUntilHandlersIdle();
 
-        pendingEvents = getTaskPendingEvent(rootTask);
+        pendingEvents = getTaskPendingEvent(organizer, rootTask);
         assertEquals(1, pendingEvents.size());
         assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType);
         assertEquals("TestDescription2",
@@ -1334,7 +1335,7 @@
         rootTask.removeImmediately();
         waitUntilHandlersIdle();
 
-        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
         assertEquals(1, pendingEvents.size());
         assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
         assertEquals("TestDescription",
@@ -1355,7 +1356,7 @@
         rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         waitUntilHandlersIdle();
 
-        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
         assertEquals(1, pendingEvents.size());
         assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
     }
@@ -1375,14 +1376,16 @@
                 new IRequestFinishCallback.Default());
         waitUntilHandlersIdle();
 
-        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+        ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
         assertEquals(1, pendingEvents.size());
         assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
     }
 
-    private ArrayList<PendingTaskEvent> getTaskPendingEvent(Task task) {
+    private ArrayList<PendingTaskEvent> getTaskPendingEvent(ITaskOrganizer organizer, Task task) {
         ArrayList<PendingTaskEvent> total =
-                mWm.mAtmService.mTaskOrganizerController.getPendingEventList();
+                mWm.mAtmService.mTaskOrganizerController
+                        .getTaskOrganizerPendingEvents(organizer.asBinder())
+                        .getPendingEventList();
         ArrayList<PendingTaskEvent> result = new ArrayList();
 
         for (int i = 0; i < total.size(); i++) {
diff --git a/telephony/java/android/telephony/AccessNetworkUtils.java b/telephony/java/android/telephony/AccessNetworkUtils.java
index b5d97ab..c2a9864 100644
--- a/telephony/java/android/telephony/AccessNetworkUtils.java
+++ b/telephony/java/android/telephony/AccessNetworkUtils.java
@@ -4,8 +4,8 @@
 import static android.telephony.ServiceState.DUPLEX_MODE_TDD;
 import static android.telephony.ServiceState.DUPLEX_MODE_UNKNOWN;
 
-import android.telephony.AccessNetworkConstants.EutranBandArfcnFrequency;
 import android.telephony.AccessNetworkConstants.EutranBand;
+import android.telephony.AccessNetworkConstants.EutranBandArfcnFrequency;
 import android.telephony.AccessNetworkConstants.GeranBand;
 import android.telephony.AccessNetworkConstants.GeranBandArfcnFrequency;
 import android.telephony.AccessNetworkConstants.NgranArfcnFrequency;
@@ -13,7 +13,6 @@
 import android.telephony.AccessNetworkConstants.UtranBand;
 import android.telephony.AccessNetworkConstants.UtranBandArfcnFrequency;
 import android.telephony.ServiceState.DuplexMode;
-import android.util.Log;
 
 import java.util.Arrays;
 import java.util.HashSet;
@@ -232,6 +231,108 @@
     }
 
     /**
+     * Gets the NR Operating band for a given downlink NRARFCN.
+     *
+     * <p>See 3GPP TS 38.104 Table 5.2-1 NR operating bands in FR1 and
+     * Table 5.2-2 NR operating bands in FR2
+     *
+     * @param nrarfcn The downlink NRARFCN
+     * @return Operating band number, or {@link #INVALID_BAND} if no corresponding band exists
+     */
+    public static int getOperatingBandForNrarfcn(int nrarfcn) {
+        if (nrarfcn >= 422000 && nrarfcn <= 434000) {
+            return NgranBands.BAND_1;
+        } else if (nrarfcn >= 386000 && nrarfcn <= 398000) {
+            return NgranBands.BAND_2;
+        } else if (nrarfcn >= 361000 && nrarfcn <= 376000) {
+            return NgranBands.BAND_3;
+        } else if (nrarfcn >= 173800 && nrarfcn <= 178800) {
+            return NgranBands.BAND_5;
+        } else if (nrarfcn >= 524000 && nrarfcn <= 538000) {
+            return NgranBands.BAND_7;
+        } else if (nrarfcn >= 185000 && nrarfcn <= 192000) {
+            return NgranBands.BAND_8;
+        } else if (nrarfcn >= 145800 && nrarfcn <= 149200) {
+            return NgranBands.BAND_12;
+        } else if (nrarfcn >= 151600 && nrarfcn <= 153600) {
+            return NgranBands.BAND_14;
+        } else if (nrarfcn >= 172000 && nrarfcn <= 175000) {
+            return NgranBands.BAND_18;
+        } else if (nrarfcn >= 158200 && nrarfcn <= 164200) {
+            return NgranBands.BAND_20;
+        } else if (nrarfcn >= 386000 && nrarfcn <= 399000) {
+            return NgranBands.BAND_25;
+        } else if (nrarfcn >= 171800 && nrarfcn <= 178800) {
+            return NgranBands.BAND_26;
+        } else if (nrarfcn >= 151600 && nrarfcn <= 160600) {
+            return NgranBands.BAND_28;
+        } else if (nrarfcn >= 143400 && nrarfcn <= 145600) {
+            return NgranBands.BAND_29;
+        } else if (nrarfcn >= 470000 && nrarfcn <= 472000) {
+            return NgranBands.BAND_30;
+        } else if (nrarfcn >= 402000 && nrarfcn <= 405000) {
+            return NgranBands.BAND_34;
+        } else if (nrarfcn >= 514000 && nrarfcn <= 524000) {
+            return NgranBands.BAND_38;
+        } else if (nrarfcn >= 376000 && nrarfcn <= 384000) {
+            return NgranBands.BAND_39;
+        } else if (nrarfcn >= 460000 && nrarfcn <= 480000) {
+            return NgranBands.BAND_40;
+        } else if (nrarfcn >= 499200 && nrarfcn <= 537999) {
+            return NgranBands.BAND_41;
+        } else if (nrarfcn >= 743334 && nrarfcn <= 795000) {
+            return NgranBands.BAND_46;
+        } else if (nrarfcn >= 636667 && nrarfcn <= 646666) {
+            return NgranBands.BAND_48;
+        } else if (nrarfcn >= 286400 && nrarfcn <= 303400) {
+            return NgranBands.BAND_50;
+        } else if (nrarfcn >= 285400 && nrarfcn <= 286400) {
+            return NgranBands.BAND_51;
+        } else if (nrarfcn >= 496700 && nrarfcn <= 499000) {
+            return NgranBands.BAND_53;
+        } else if (nrarfcn >= 422000 && nrarfcn <= 440000) {
+            return NgranBands.BAND_65; // BAND_66 has the same channels
+        } else if (nrarfcn >= 399000 && nrarfcn <= 404000) {
+            return NgranBands.BAND_70;
+        } else if (nrarfcn >= 123400 && nrarfcn <= 130400) {
+            return NgranBands.BAND_71;
+        } else if (nrarfcn >= 295000 && nrarfcn <= 303600) {
+            return NgranBands.BAND_74;
+        } else if (nrarfcn >= 286400 && nrarfcn <= 303400) {
+            return NgranBands.BAND_75;
+        } else if (nrarfcn >= 285400 && nrarfcn <= 286400) {
+            return NgranBands.BAND_76;
+        } else if (nrarfcn >= 620000 && nrarfcn <= 680000) {
+            return NgranBands.BAND_77;
+        } else if (nrarfcn >= 620000 && nrarfcn <= 653333) {
+            return NgranBands.BAND_78;
+        } else if (nrarfcn >= 693334 && nrarfcn <= 733333) {
+            return NgranBands.BAND_79;
+        } else if (nrarfcn >= 499200 && nrarfcn <= 538000) {
+            return NgranBands.BAND_90;
+        } else if (nrarfcn >= 285400 && nrarfcn <= 286400) {
+            return NgranBands.BAND_91;
+        } else if (nrarfcn >= 286400 && nrarfcn <= 303400) {
+            return NgranBands.BAND_92;
+        } else if (nrarfcn >= 285400 && nrarfcn <= 286400) {
+            return NgranBands.BAND_93;
+        } else if (nrarfcn >= 286400 && nrarfcn <= 303400) {
+            return NgranBands.BAND_94;
+        } else if (nrarfcn >= 795000 && nrarfcn <= 875000) {
+            return NgranBands.BAND_96;
+        } else if (nrarfcn >= 2054166 && nrarfcn <= 2104165) {
+            return NgranBands.BAND_257;
+        } else if (nrarfcn >= 2016667 && nrarfcn <= 2070832) {
+            return NgranBands.BAND_258;
+        } else if (nrarfcn >= 2229166 && nrarfcn <= 2279165) {
+            return NgranBands.BAND_260;
+        } else if (nrarfcn >= 2070833 && nrarfcn <= 2084999) {
+            return NgranBands.BAND_261;
+        }
+        return INVALID_BAND;
+    }
+
+    /**
      * Gets the GERAN Operating band for a given ARFCN.
      *
      * <p>See 3GPP TS 45.005 clause 2 for calculation.
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
index cc2ab19..06c098d 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
@@ -19,6 +19,7 @@
 import com.android.tools.lint.detector.api.JavaContext
 import com.android.tools.lint.detector.api.LintFix
 import com.android.tools.lint.detector.api.Location
+import com.intellij.psi.PsiArrayType
 import com.intellij.psi.PsiCallExpression
 import com.intellij.psi.PsiClassType
 import com.intellij.psi.PsiIntersectionType
@@ -26,8 +27,8 @@
 import com.intellij.psi.PsiType
 import com.intellij.psi.PsiTypeParameter
 import com.intellij.psi.PsiWildcardType
-import org.jetbrains.kotlin.utils.addToStdlib.cast
 import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UExpression
 import org.jetbrains.uast.UVariable
 
@@ -42,11 +43,11 @@
 ) {
     open fun report(context: JavaContext, call: UCallExpression, method: PsiMethod) {
         val location = context.getLocation(call)
-        val itemType = getBoundingClass(context, call, method)
+        val itemType = filter(getBoundingClass(context, call, method))
         val fix = (itemType as? PsiClassType)?.let { type ->
             getParcelFix(location, this.method.name, getArgumentSuffix(type))
         }
-        val message = "Unsafe `Parcel.${this.method.name}()` API usage"
+        val message = "Unsafe `${this.method.className}.${this.method.name}()` API usage"
         context.report(SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, call, location, message, fix)
     }
 
@@ -73,14 +74,14 @@
     }
 
     /**
-     * Tries to obtain the type expected by the "receiving" end given a certain {@link UExpression}.
+     * Tries to obtain the type expected by the "receiving" end given a certain {@link UElement}.
      *
      * This could be an assignment, an argument passed to a method call, to a constructor call, a
      * type cast, etc. If no receiving end is found, the type of the UExpression itself is returned.
      */
-    protected fun getReceivingType(expression: UExpression): PsiType? {
+    protected fun getReceivingType(expression: UElement): PsiType? {
         val parent = expression.uastParent
-        val type = when (parent) {
+        var type = when (parent) {
             is UCallExpression -> {
                 val i = parent.valueArguments.indexOf(expression)
                 val psiCall = parent.sourcePsi as? PsiCallExpression ?: return null
@@ -92,10 +93,13 @@
             is UExpression -> parent.getExpressionType()
             else -> null
         }
-        return filter(type ?: expression.getExpressionType())
+        if (type == null && expression is UExpression) {
+            type = expression.getExpressionType()
+        }
+        return type
     }
 
-    private fun filter(type: PsiType?): PsiType? {
+    protected fun filter(type: PsiType?): PsiType? {
         // It's important that PsiIntersectionType case is above the one that check the type in
         // rejects, because for intersect types, the canonicalText is one of the terms.
         if (type is PsiIntersectionType) {
@@ -169,7 +173,7 @@
     override fun getBoundingClass(
             context: JavaContext, call: UCallExpression, method: PsiMethod
     ): PsiType? {
-        val type = getReceivingType(call.uastParent as UExpression) ?: return null
+        val type = getReceivingType(call.uastParent!!) ?: return null
         return getItemType(type, container)
     }
 }
@@ -184,7 +188,7 @@
     override fun getBoundingClass(
             context: JavaContext, call: UCallExpression, method: PsiMethod
     ): PsiType? {
-        return getReceivingType(call.uastParent as UExpression)
+        return getReceivingType(call.uastParent!!)
     }
 }
 
@@ -199,11 +203,27 @@
     override fun getBoundingClass(
             context: JavaContext, call: UCallExpression, method: PsiMethod
     ): PsiType? {
-        return getReceivingType(call.uastParent as UExpression)
+        return getReceivingType(call.uastParent!!)
     }
 
     override fun getArgumentSuffix(type: PsiClassType): String =
             "${type.rawType().canonicalText}.class.getClassLoader(), " +
                     "${type.rawType().canonicalText}.class"
 
-}
\ No newline at end of file
+}
+
+/**
+ * This class derives the type to be appended by inferring the expected array type
+ * for the method result.
+ */
+class ArrayReturnMigrator(
+    method: Method,
+    rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+    override fun getBoundingClass(
+           context: JavaContext, call: UCallExpression, method: PsiMethod
+    ): PsiType? {
+        val type = getReceivingType(call.uastParent!!)
+        return (type as? PsiArrayType)?.componentType
+    }
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
index c032fa2..0826e8e 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
@@ -35,4 +35,8 @@
             val prefix = if (params.isEmpty()) "" else "${params.joinToString(", ", "<", ">")} "
             return "$prefix$clazz.$name(${parameters.joinToString()})"
         }
-}
\ No newline at end of file
+
+    val className: String by lazy {
+        clazz.split(".").last()
+    }
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
index 89dbcae..f928263 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
@@ -24,6 +24,7 @@
 import org.jetbrains.uast.UCallExpression
 import java.util.*
 
+@Suppress("UnstableApiUsage")
 class SaferParcelChecker : Detector(), SourceCodeScanner {
     override fun getApplicableMethodNames(): List<String> =
             MIGRATORS
@@ -47,27 +48,18 @@
         return "$prefix$name($parameters)"
     }
 
-    /** Taken from androidx-main:core/core/src/main/java/androidx/core/os/BuildCompat.java */
     private fun isAtLeastT(context: Context): Boolean {
         val project = if (context.isGlobalAnalysis()) context.mainProject else context.project
-        return project.isAndroidProject
-                && project.minSdkVersion.featureLevel >= 32
-                && isAtLeastPreReleaseCodename("Tiramisu", project.minSdkVersion.codename)
-    }
-
-    /** Taken from androidx-main:core/core/src/main/java/androidx/core/os/BuildCompat.java */
-    private fun isAtLeastPreReleaseCodename(min: String, actual: String): Boolean {
-        if (actual == "REL") return false
-        return actual.uppercase(Locale.ROOT) >= min.uppercase(Locale.ROOT)
+        return project.isAndroidProject && project.minSdkVersion.featureLevel >= 33
     }
 
     companion object {
         @JvmField
         val ISSUE_UNSAFE_API_USAGE: Issue = Issue.create(
                 id = "UnsafeParcelApi",
-                briefDescription = "Use of unsafe Parcel API",
+                briefDescription = "Use of unsafe deserialization API",
                 explanation = """
-                    You are using a deprecated Parcel API that doesn't accept the expected class as\
+                    You are using a deprecated deserialization API that doesn't accept the expected class as\
                      a parameter. This means that unexpected classes could be instantiated and\
                      unexpected code executed.
 
@@ -83,25 +75,52 @@
                 )
         )
 
-        private val METHOD_READ_SERIALIZABLE = Method("android.os.Parcel", "readSerializable", listOf())
-        private val METHOD_READ_ARRAY_LIST = Method("android.os.Parcel", "readArrayList", listOf("java.lang.ClassLoader"))
-        private val METHOD_READ_LIST = Method("android.os.Parcel", "readList", listOf("java.util.List", "java.lang.ClassLoader"))
-        private val METHOD_READ_PARCELABLE = Method(listOf("T"), "android.os.Parcel", "readParcelable", listOf("java.lang.ClassLoader"))
-        private val METHOD_READ_PARCELABLE_LIST = Method(listOf("T"), "android.os.Parcel", "readParcelableList", listOf("java.util.List<T>", "java.lang.ClassLoader"))
-        private val METHOD_READ_SPARSE_ARRAY = Method(listOf("T"), "android.os.Parcel", "readSparseArray", listOf("java.lang.ClassLoader"))
+        // Parcel
+        private val PARCEL_METHOD_READ_SERIALIZABLE = Method("android.os.Parcel", "readSerializable", listOf())
+        private val PARCEL_METHOD_READ_ARRAY_LIST = Method("android.os.Parcel", "readArrayList", listOf("java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_LIST = Method("android.os.Parcel", "readList", listOf("java.util.List", "java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_PARCELABLE = Method(listOf("T"), "android.os.Parcel", "readParcelable", listOf("java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_PARCELABLE_LIST = Method(listOf("T"), "android.os.Parcel", "readParcelableList", listOf("java.util.List<T>", "java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_SPARSE_ARRAY = Method(listOf("T"), "android.os.Parcel", "readSparseArray", listOf("java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_ARRAY = Method("android.os.Parcel", "readArray", listOf("java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_PARCELABLE_ARRAY = Method("android.os.Parcel", "readParcelableArray", listOf("java.lang.ClassLoader"))
+
+        // Bundle
+        private val BUNDLE_METHOD_GET_SERIALIZABLE = Method("android.os.Bundle", "getSerializable", listOf("java.lang.String"))
+        private val BUNDLE_METHOD_GET_PARCELABLE = Method(listOf("T"), "android.os.Bundle", "getParcelable", listOf("java.lang.String"))
+        private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST = Method(listOf("T"), "android.os.Bundle", "getParcelableArrayList", listOf("java.lang.String"))
+        private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY = Method("android.os.Bundle", "getParcelableArray", listOf("java.lang.String"))
+        private val BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY = Method(listOf("T"), "android.os.Bundle", "getSparseParcelableArray", listOf("java.lang.String"))
+
+        // Intent
+        private val INTENT_METHOD_GET_SERIALIZABLE_EXTRA = Method("android.content.Intent", "getSerializableExtra", listOf("java.lang.String"))
+        private val INTENT_METHOD_GET_PARCELABLE_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableExtra", listOf("java.lang.String"))
+        private val INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA = Method("android.content.Intent", "getParcelableArrayExtra", listOf("java.lang.String"))
+        private val INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableArrayListExtra", listOf("java.lang.String"))
 
         // TODO: Write migrators for methods below
-        private val METHOD_READ_ARRAY = Method("android.os.Parcel", "readArray", listOf("java.lang.ClassLoader"))
-        private val METHOD_READ_PARCELABLE_ARRAY = Method("android.os.Parcel", "readParcelableArray", listOf("java.lang.ClassLoader"))
-        private val METHOD_READ_PARCELABLE_CREATOR = Method("android.os.Parcel", "readParcelableCreator", listOf("java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_PARCELABLE_CREATOR = Method("android.os.Parcel", "readParcelableCreator", listOf("java.lang.ClassLoader"))
 
         private val MIGRATORS = listOf(
-                ReturnMigrator(METHOD_READ_PARCELABLE, setOf("android.os.Parcelable")),
-                ContainerArgumentMigrator(METHOD_READ_LIST, 0, "java.util.List"),
-                ContainerReturnMigrator(METHOD_READ_ARRAY_LIST, "java.util.Collection"),
-                ContainerReturnMigrator(METHOD_READ_SPARSE_ARRAY, "android.util.SparseArray"),
-                ContainerArgumentMigrator(METHOD_READ_PARCELABLE_LIST, 0, "java.util.List"),
-                ReturnMigratorWithClassLoader(METHOD_READ_SERIALIZABLE),
+            ReturnMigrator(PARCEL_METHOD_READ_PARCELABLE, setOf("android.os.Parcelable")),
+            ContainerArgumentMigrator(PARCEL_METHOD_READ_LIST, 0, "java.util.List"),
+            ContainerReturnMigrator(PARCEL_METHOD_READ_ARRAY_LIST, "java.util.Collection"),
+            ContainerReturnMigrator(PARCEL_METHOD_READ_SPARSE_ARRAY, "android.util.SparseArray"),
+            ContainerArgumentMigrator(PARCEL_METHOD_READ_PARCELABLE_LIST, 0, "java.util.List"),
+            ReturnMigratorWithClassLoader(PARCEL_METHOD_READ_SERIALIZABLE),
+            ArrayReturnMigrator(PARCEL_METHOD_READ_ARRAY, setOf("java.lang.Object")),
+            ArrayReturnMigrator(PARCEL_METHOD_READ_PARCELABLE_ARRAY, setOf("android.os.Parcelable")),
+
+            ReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE, setOf("android.os.Parcelable")),
+            ContainerReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST, "java.util.Collection", setOf("android.os.Parcelable")),
+            ArrayReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY, setOf("android.os.Parcelable")),
+            ContainerReturnMigrator(BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY, "android.util.SparseArray", setOf("android.os.Parcelable")),
+            ReturnMigrator(BUNDLE_METHOD_GET_SERIALIZABLE, setOf("java.io.Serializable")),
+
+            ReturnMigrator(INTENT_METHOD_GET_PARCELABLE_EXTRA, setOf("android.os.Parcelable")),
+            ContainerReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA, "java.util.Collection", setOf("android.os.Parcelable")),
+            ArrayReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA, setOf("android.os.Parcelable")),
+            ReturnMigrator(INTENT_METHOD_GET_SERIALIZABLE_EXTRA, setOf("java.io.Serializable")),
         )
     }
-}
\ No newline at end of file
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
index 05c7850..e686695 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
@@ -27,20 +27,22 @@
     override fun getDetector(): Detector = SaferParcelChecker()
 
     override fun getIssues(): List<Issue> = listOf(
-            SaferParcelChecker.ISSUE_UNSAFE_API_USAGE
+        SaferParcelChecker.ISSUE_UNSAFE_API_USAGE
     )
 
     override fun lint(): TestLintTask =
-            super.lint()
-                    .allowMissingSdk(true)
-                    // We don't do partial analysis in the platform
-                    .skipTestModes(TestMode.PARTIAL)
+        super.lint()
+            .allowMissingSdk(true)
+            // We don't do partial analysis in the platform
+            .skipTestModes(TestMode.PARTIAL)
 
-    fun testDetectUnsafeReadSerializable() {
+    /** Parcel Tests */
+
+    fun testParcelDetectUnsafeReadSerializable() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                         package test.pkg;
                         import android.os.Parcel;
                         import java.io.Serializable;
@@ -51,27 +53,27 @@
                             }
                         }
                         """
-                        ).indented(),
-                        *includes
-                )
-                .expectIdenticalTestModeOutput(false)
-                .run()
-                .expect(
-                        """
+                ).indented(),
+                *includes
+            )
+            .expectIdenticalTestModeOutput(false)
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readSerializable() \
                         API usage [UnsafeParcelApi]
                                 Serializable ans = p.readSerializable();
                                                    ~~~~~~~~~~~~~~~~~~~~
                         0 errors, 1 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
-    fun testDoesNotDetectSafeReadSerializable() {
+    fun testParcelDoesNotDetectSafeReadSerializable() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                         package test.pkg;
                         import android.os.Parcel;
                         import java.io.Serializable;
@@ -82,18 +84,18 @@
                             }
                         }
                         """
-                        ).indented(),
-                        *includes
-                )
-                .run()
-                .expect("No warnings.")
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
     }
 
-    fun testDetectUnsafeReadArrayList() {
+    fun testParcelDetectUnsafeReadArrayList() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                         package test.pkg;
                         import android.os.Parcel;
 
@@ -103,26 +105,26 @@
                             }
                         }
                         """
-                        ).indented(),
-                        *includes
-                )
-                .run()
-                .expect(
-                        """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass.java:6: Warning: Unsafe Parcel.readArrayList() API \
                         usage [UnsafeParcelApi]
                                 ArrayList ans = p.readArrayList(null);
                                                 ~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 1 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
-    fun testDoesNotDetectSafeReadArrayList() {
+    fun testParcelDoesNotDetectSafeReadArrayList() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                                 package test.pkg;
                                 import android.content.Intent;
                                 import android.os.Parcel;
@@ -133,18 +135,18 @@
                                     }
                                 }
                                 """
-                        ).indented(),
-                        *includes
-                )
-                .run()
-                .expect("No warnings.")
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
     }
 
-    fun testDetectUnsafeReadList() {
+    fun testParcelDetectUnsafeReadList() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                                 package test.pkg;
                                 import android.content.Intent;
                                 import android.os.Parcel;
@@ -157,26 +159,26 @@
                                     }
                                 }
                                 """
-                        ).indented(),
-                        *includes
-                )
-                .run()
-                .expect(
-                        """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readList() API usage \
                         [UnsafeParcelApi]
                                 p.readList(list, null);
                                 ~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 1 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
-    fun testDoesNotDetectSafeReadList() {
+    fun testDParceloesNotDetectSafeReadList() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                                 package test.pkg;
                                 import android.content.Intent;
                                 import android.os.Parcel;
@@ -189,18 +191,18 @@
                                     }
                                 }
                                 """
-                        ).indented(),
-                        *includes
-                )
-                .run()
-                .expect("No warnings.")
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
     }
 
-    fun testDetectUnsafeReadParcelable() {
+    fun testParcelDetectUnsafeReadParcelable() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                                 package test.pkg;
                                 import android.content.Intent;
                                 import android.os.Parcel;
@@ -211,26 +213,26 @@
                                     }
                                 }
                                 """
-                        ).indented(),
-                        *includes
-                )
-                .run()
-                .expect(
-                        """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readParcelable() API \
                         usage [UnsafeParcelApi]
                                 Intent ans = p.readParcelable(null);
                                              ~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 1 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
-    fun testDoesNotDetectSafeReadParcelable() {
+    fun testParcelDoesNotDetectSafeReadParcelable() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                                 package test.pkg;
                                 import android.content.Intent;
                                 import android.os.Parcel;
@@ -241,18 +243,18 @@
                                     }
                                 }
                                 """
-                        ).indented(),
-                        *includes
-                )
-                .run()
-                .expect("No warnings.")
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
     }
 
-    fun testDetectUnsafeReadParcelableList() {
+    fun testParcelDetectUnsafeReadParcelableList() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                                 package test.pkg;
                                 import android.content.Intent;
                                 import android.os.Parcel;
@@ -265,26 +267,26 @@
                                     }
                                 }
                                 """
-                        ).indented(),
-                        *includes
-                )
-                .run()
-                .expect(
-                        """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readParcelableList() \
                         API usage [UnsafeParcelApi]
                                 List<Intent> ans = p.readParcelableList(list, null);
                                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 1 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
-    fun testDoesNotDetectSafeReadParcelableList() {
+    fun testParcelDoesNotDetectSafeReadParcelableList() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                                 package test.pkg;
                                 import android.content.Intent;
                                 import android.os.Parcel;
@@ -298,18 +300,18 @@
                                     }
                                 }
                                 """
-                        ).indented(),
-                        *includes
-                )
-                .run()
-                .expect("No warnings.")
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
     }
 
-    fun testDetectUnsafeReadSparseArray() {
+    fun testParcelDetectUnsafeReadSparseArray() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                                 package test.pkg;
                                 import android.content.Intent;
                                 import android.os.Parcel;
@@ -321,26 +323,26 @@
                                     }
                                 }
                                 """
-                        ).indented(),
-                        *includes
-                )
-                .run()
-                .expect(
-                        """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass.java:8: Warning: Unsafe Parcel.readSparseArray() API\
                          usage [UnsafeParcelApi]
                                 SparseArray<Intent> ans = p.readSparseArray(null);
                                                           ~~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 1 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
-    fun testDoesNotDetectSafeReadSparseArray() {
+    fun testParcelDoesNotDetectSafeReadSparseArray() {
         lint()
-                .files(
-                        java(
-                                """
+            .files(
+                java(
+                    """
                                 package test.pkg;
                                 import android.content.Intent;
                                 import android.os.Parcel;
@@ -353,21 +355,383 @@
                                     }
                                 }
                                 """
-                        ).indented(),
-                        *includes
-                )
-                .run()
-                .expect("No warnings.")
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
     }
 
+    fun testParcelDetectUnsafeReadSArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent[] ans = p.readArray(null);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                        src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readArray() API\
+                         usage [UnsafeParcelApi]
+                                Intent[] ans = p.readArray(null);
+                                               ~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testParcelDoesNotDetectSafeReadArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent[] ans = p.readArray(null, Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testParcelDetectUnsafeReadParcelableSArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent[] ans = p.readParcelableArray(null);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                        src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readParcelableArray() API\
+                         usage [UnsafeParcelApi]
+                                Intent[] ans = p.readParcelableArray(null);
+                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testParcelDoesNotDetectSafeReadParcelableArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent[] ans = p.readParcelableArray(null, Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    /** Bundle Tests */
+
+    fun testBundleDetectUnsafeGetParcelable() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        Intent ans = b.getParcelable("key");
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                    src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelable() API usage [UnsafeParcelApi]
+                            Intent ans = b.getParcelable("key");
+                                         ~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testBundleDoesNotDetectSafeGetParcelable() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        Intent ans = b.getParcelable("key", Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testBundleDetectUnsafeGetParcelableArrayList() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        ArrayList<Intent> ans = b.getParcelableArrayList("key");
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                    src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelableArrayList() API usage [UnsafeParcelApi]
+                            ArrayList<Intent> ans = b.getParcelableArrayList("key");
+                                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testBundleDoesNotDetectSafeGetParcelableArrayList() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        ArrayList<Intent> ans = b.getParcelableArrayList("key", Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testBundleDetectUnsafeGetParcelableArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        Intent[] ans = b.getParcelableArray("key");
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                    src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelableArray() API usage [UnsafeParcelApi]
+                            Intent[] ans = b.getParcelableArray("key");
+                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testBundleDoesNotDetectSafeGetParcelableArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        Intent[] ans = b.getParcelableArray("key", Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testBundleDetectUnsafeGetSparseParcelableArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        SparseArray<Intent> ans = b.getSparseParcelableArray("key");
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                    src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getSparseParcelableArray() API usage [UnsafeParcelApi]
+                            SparseArray<Intent> ans = b.getSparseParcelableArray("key");
+                                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testBundleDoesNotDetectSafeGetSparseParcelableArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        SparseArray<Intent> ans = b.getSparseParcelableArray("key", Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    /** Intent Tests */
+
+    fun testIntentDetectUnsafeGetParcelableExtra() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+
+                                public class TestClass {
+                                    private TestClass(Intent i) {
+                                        Intent ans = i.getParcelableExtra("name");
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                    src/test/pkg/TestClass.java:6: Warning: Unsafe Intent.getParcelableExtra() API usage [UnsafeParcelApi]
+                            Intent ans = i.getParcelableExtra("name");
+                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testIntentDoesNotDetectSafeGetParcelableExtra() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+
+                                public class TestClass {
+                                    private TestClass(Intent i) {
+                                        Intent ans = i.getParcelableExtra("name", Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+
     /** Stubs for classes used for testing */
 
 
     private val includes =
-            arrayOf(
-                manifest().minSdk("Tiramisu"),
-                java(
-                        """
+        arrayOf(
+            manifest().minSdk("33"),
+            java(
+                """
                         package android.os;
                         import java.util.ArrayList;
                         import java.util.List;
@@ -375,7 +739,7 @@
                         import java.util.HashMap;
 
                         public final class Parcel {
-                            // Deprecateds
+                            // Deprecated
                             public Object[] readArray(ClassLoader loader) { return null; }
                             public ArrayList readArrayList(ClassLoader loader) { return null; }
                             public HashMap readHashMap(ClassLoader loader) { return null; }
@@ -402,26 +766,57 @@
                             public <T> SparseArray<T> readSparseArray(ClassLoader loader, Class<? extends T> clazz) { return null; }
                         }
                         """
-                ).indented(),
-                java(
+            ).indented(),
+            java(
+                """
+                        package android.os;
+                        import java.util.ArrayList;
+                        import java.util.List;
+                        import java.util.Map;
+                        import java.util.HashMap;
+
+                        public final class Bundle {
+                            // Deprecated
+                            public <T extends Parcelable> T getParcelable(String key) { return  null; }
+                            public <T extends Parcelable> ArrayList<T> getParcelableArrayList(String key) { return null; }
+                            public Parcelable[] getParcelableArray(String key) { return null; }
+                            public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(String key) { return null; }
+
+                            // Replacements
+                            public <T> T getParcelable(String key, Class<T> clazz) { return  null; }
+                            public <T> ArrayList<T> getParcelableArrayList(String key, Class<? extends T> clazz) { return null; }
+                            public <T> T[] getParcelableArray(String key, Class<T> clazz) { return null; }
+                            public <T> SparseArray<T> getSparseParcelableArray(String key, Class<? extends T> clazz) { return null; }
+
+                        }
                         """
+            ).indented(),
+            java(
+                """
                         package android.os;
                         public interface Parcelable {}
                         """
-                ).indented(),
-                java(
-                        """
+            ).indented(),
+            java(
+                """
                         package android.content;
-                        public class Intent implements Parcelable, Cloneable {}
+                        public class Intent implements Parcelable, Cloneable {
+                            // Deprecated
+                            public <T extends Parcelable> T getParcelableExtra(String name) { return null; }
+
+                            // Replacements
+                            public <T> T getParcelableExtra(String name, Class<T> clazz) { return null; }
+
+                        }
                         """
-                ).indented(),
-                java(
-                        """
+            ).indented(),
+            java(
+                """
                         package android.util;
                         public class SparseArray<E> implements Cloneable {}
                         """
-                ).indented(),
-            )
+            ).indented(),
+        )
 
     // Substitutes "backslash + new line" with an empty string to imitate line continuation
     private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")