Merge "Import translations. DO NOT MERGE ANYWHERE"
diff --git a/api/test-current.txt b/api/test-current.txt
index 529dcf7..5741fe7 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5579,7 +5579,7 @@
     method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo);
     method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public final void registerOrganizer();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setInterceptBackPressedOnTaskRoot(boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static void setLaunchRoot(int, @NonNull android.window.WindowContainerToken);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public final void unregisterOrganizer();
   }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index cee607f..fef8d10 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1924,10 +1924,8 @@
     @Override
     public Object getSystemService(String name) {
         if (vmIncorrectContextUseEnabled()) {
-            // We may override this API from outer context.
-            final boolean isUiContext = isUiContext() || isOuterUiContext();
             // Check incorrect Context usage.
-            if (isUiComponent(name) && !isUiContext) {
+            if (isUiComponent(name) && !isSelfOrOuterUiContext()) {
                 final String errorMessage = "Tried to access visual service "
                         + SystemServiceRegistry.getSystemServiceClassName(name)
                         + " from a non-visual Context:" + getOuterContext();
@@ -1944,15 +1942,17 @@
         return SystemServiceRegistry.getSystemService(this, name);
     }
 
-    private boolean isOuterUiContext() {
-        return getOuterContext() != null && getOuterContext().isUiContext();
-    }
-
     @Override
     public String getSystemServiceName(Class<?> serviceClass) {
         return SystemServiceRegistry.getSystemServiceName(serviceClass);
     }
 
+    // TODO(b/149463653): check if we still need this method after migrating IMS to WindowContext.
+    private boolean isSelfOrOuterUiContext() {
+        // We may override outer context's isUiContext
+        return isUiContext() || getOuterContext() != null && getOuterContext().isUiContext();
+    }
+
     /** @hide */
     @Override
     public boolean isUiContext() {
@@ -2413,7 +2413,7 @@
         context.setResources(createResources(mToken, mPackageInfo, mSplitName, overrideDisplayId,
                 overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(),
                 mResources.getLoaders()));
-        context.mIsUiContext = isUiContext() || isOuterUiContext();
+        context.mIsUiContext = isSelfOrOuterUiContext();
         return context;
     }
 
@@ -2529,9 +2529,9 @@
 
     @Override
     public Display getDisplay() {
-        if (!mIsSystemOrSystemUiContext && !mIsAssociatedWithDisplay) {
+        if (!mIsSystemOrSystemUiContext && !mIsAssociatedWithDisplay && !isSelfOrOuterUiContext()) {
             throw new UnsupportedOperationException("Tried to obtain display from a Context not "
-                    + "associated with  one. Only visual Contexts (such as Activity or one created "
+                    + "associated with one. Only visual Contexts (such as Activity or one created "
                     + "with Context#createWindowContext) or ones created with "
                     + "Context#createDisplayContext are associated with displays. Other types of "
                     + "Contexts are typically related to background entities and may return an "
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c61426d..98de85d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9751,21 +9751,6 @@
     }
 
     /**
-     * @hide
-     * Return if this user is a system-only user. An admin can manage a device from a system only
-     * user by calling {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE}.
-     * @param admin Which device owner this request is associated with.
-     * @return if this user is a system-only user.
-     */
-    public boolean isSystemOnlyUser(@NonNull ComponentName admin) {
-        try {
-            return mService.isSystemOnlyUser(admin);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Called by device owner, or profile owner on organization-owned device, to get the MAC
      * address of the Wi-Fi device.
      *
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9c6a274..1c7b617 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -345,7 +345,6 @@
     void setKeepUninstalledPackages(in ComponentName admin, in String callerPackage, in List<String> packageList);
     List<String> getKeepUninstalledPackages(in ComponentName admin, in String callerPackage);
     boolean isManagedProfile(in ComponentName admin);
-    boolean isSystemOnlyUser(in ComponentName admin);
     String getWifiMacAddress(in ComponentName admin);
     void reboot(in ComponentName admin);
 
diff --git a/core/java/android/companion/TEST_MAPPING b/core/java/android/companion/TEST_MAPPING
new file mode 100644
index 0000000..63f54fa
--- /dev/null
+++ b/core/java/android/companion/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsOsTestCases",
+      "options": [
+        {
+          "include-filter": "android.os.cts.CompanionDeviceManagerTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index da8d15a..d672c6a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -788,7 +788,6 @@
             INSTALL_ENABLE_ROLLBACK,
             INSTALL_ALLOW_DOWNGRADE,
             INSTALL_STAGED,
-            INSTALL_DRY_RUN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InstallFlags {}
@@ -966,14 +965,6 @@
      */
     public static final int INSTALL_STAGED = 0x00200000;
 
-    /**
-     * Flag parameter for {@link #installPackage} to indicate that package should only be verified
-     * but not installed.
-     *
-     * @hide
-     */
-    public static final int INSTALL_DRY_RUN = 0x00800000;
-
     /** @hide */
     @IntDef(flag = true, value = {
             DONT_KILL_APP,
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index bd909c7..192470e 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -43,11 +43,11 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 
-import libcore.io.IoUtils;
-
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -793,7 +793,7 @@
 
     @VisibleForTesting
     protected List<UserInfo> getUsers() {
-        return UserManager.get(mContext).getUsers(true);
+        return UserManager.get(mContext).getAliveUsers();
     }
 
     @VisibleForTesting
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7250801..55afefe 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1210,15 +1210,19 @@
         mWindow.getWindow().getAttributes().setFitInsetsIgnoringVisibility(true);
 
         // IME layout should always be inset by navigation bar, no matter its current visibility,
-        // unless automotive requests it, since automotive may hide the navigation bar.
+        // unless automotive requests it. Automotive devices may request the navigation bar to be
+        // hidden when the IME shows up (controlled via config_automotiveHideNavBarForKeyboard)
+        // in order to maximize the visible screen real estate. When this happens, the IME window
+        // should animate from the bottom of the screen to reduce the jank that happens from the
+        // lack of synchronization between the bottom system window and the IME window.
+        if (mIsAutomotive && mAutomotiveHideNavBarForKeyboard) {
+            mWindow.getWindow().setDecorFitsSystemWindows(false);
+        }
         mWindow.getWindow().getDecorView().setOnApplyWindowInsetsListener(
                 (v, insets) -> v.onApplyWindowInsets(
                         new WindowInsets.Builder(insets).setInsets(
                                 navigationBars(),
-                                mIsAutomotive && mAutomotiveHideNavBarForKeyboard
-                                        ? android.graphics.Insets.NONE
-                                        : insets.getInsetsIgnoringVisibility(navigationBars())
-                                )
+                                insets.getInsetsIgnoringVisibility(navigationBars()))
                                 .build()));
 
         // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set
diff --git a/core/java/android/inputmethodservice/TEST_MAPPING b/core/java/android/inputmethodservice/TEST_MAPPING
new file mode 100644
index 0000000..0ccd75d
--- /dev/null
+++ b/core/java/android/inputmethodservice/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+    {
+      "path": "frameworks/base/core/java/android/view/inputmethod"
+    }
+  ]
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 81a147c..81ffefd 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1293,7 +1293,7 @@
      * in {@link UserManager} & {@link DevicePolicyManager}.
      * Note: This is slightly different from the real set of user restrictions listed in {@link
      * com.android.server.pm.UserRestrictionsUtils#USER_RESTRICTIONS}. For example
-     * {@link #KEY_RESTRICTIONS_PENDING} is not a real user restriction, but is a a legitimate
+     * {@link #KEY_RESTRICTIONS_PENDING} is not a real user restriction, but is a legitimate
      * value that can be passed into {@link #hasUserRestriction(String)}.
      * @hide
      */
@@ -3252,7 +3252,8 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public @NonNull List<UserHandle> getUserHandles(boolean excludeDying) {
-        List<UserInfo> users = getUsers(excludeDying);
+        List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying,
+                /* excludePreCreated= */ true);
         List<UserHandle> result = new ArrayList<>(users.size());
         for (UserInfo user : users) {
             result.add(user.getUserHandle());
@@ -3270,7 +3271,8 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public long[] getSerialNumbersOfUsers(boolean excludeDying) {
-        List<UserInfo> users = getUsers(excludeDying);
+        List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying,
+                /* excludePreCreated= */ true);
         long[] result = new long[users.size()];
         for (int i = 0; i < result.length; i++) {
             result[i] = users.get(i).serialNumber;
@@ -3336,7 +3338,7 @@
     public boolean canAddMoreUsers() {
         // TODO(b/142482943): UMS has different logic, excluding Demo and Profile from counting. Why
         //                    not here? The logic is inconsistent. See UMS.canAddMoreManagedProfiles
-        final List<UserInfo> users = getUsers(true);
+        final List<UserInfo> users = getAliveUsers();
         final int totalUserCount = users.size();
         int aliveUserCount = 0;
         for (int i = 0; i < totalUserCount; i++) {
@@ -4144,7 +4146,7 @@
 
     /** Returns whether there are any users (other than the current user) to which to switch. */
     private boolean areThereUsersToWhichToSwitch() {
-        final List<UserInfo> users = getUsers(true);
+        final List<UserInfo> users = getAliveUsers();
         if (users == null) {
             return false;
         }
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index be8b929..f351c7d 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -90,6 +90,14 @@
     int unlink(int storageId, in @utf8InCpp String path);
 
     /**
+     * Checks if a file is fully loaded. File is specified by its path.
+     * 0 - fully loaded
+     * >0 - certain pages missing
+     * <0 - -errcode
+     */
+    int isFileFullyLoaded(int storageId, in @utf8InCpp String path);
+
+    /**
      * Returns overall loading progress of all the files on a storage, progress value between [0,1].
      * Returns a negative value on error.
      */
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index b8dbfbb..ed386f7 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -304,6 +304,25 @@
     }
 
     /**
+     * Checks whether a file under the current storage directory is fully loaded.
+     *
+     * @param path The relative path of the file.
+     * @return True if the file is fully loaded.
+     */
+    public boolean isFileFullyLoaded(@NonNull String path) throws IOException {
+        try {
+            int res = mService.isFileFullyLoaded(mId, path);
+            if (res < 0) {
+                throw new IOException("isFileFullyLoaded() failed, errno " + -res);
+            }
+            return res == 0;
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return false;
+        }
+    }
+
+    /**
      * Returns the loading progress of a storage
      *
      * @return progress value between [0, 1].
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 276f162..c3b6d8e 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -870,7 +870,7 @@
 
                 // Otherwise, insert to all other users that are running and unlocked.
 
-                final List<UserInfo> users = userManager.getUsers(true);
+                final List<UserInfo> users = userManager.getAliveUsers();
 
                 final int count = users.size();
                 for (int i = 0; i < count; i++) {
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 327bca2..d55fc51 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -218,8 +218,15 @@
     }
 
     /** {@hide} */
-    private void enforceTree(Uri documentUri) {
-        if (isTreeUri(documentUri)) {
+    private void enforceTreeForExtraUris(Bundle extras) {
+        enforceTree(extras.getParcelable(DocumentsContract.EXTRA_URI));
+        enforceTree(extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI));
+        enforceTree(extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI));
+    }
+
+    /** {@hide} */
+    private void enforceTree(@Nullable Uri documentUri) {
+        if (documentUri != null && isTreeUri(documentUri)) {
             final String parent = getTreeDocumentId(documentUri);
             final String child = getDocumentId(documentUri);
             if (Objects.equals(parent, child)) {
@@ -1076,6 +1083,9 @@
         final Context context = getContext();
         final Bundle out = new Bundle();
 
+        // If the URI is a tree URI performs some validation.
+        enforceTreeForExtraUris(extras);
+
         if (METHOD_EJECT_ROOT.equals(method)) {
             // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
             // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
@@ -1099,9 +1109,6 @@
                     "Requested authority " + authority + " doesn't match provider " + mAuthority);
         }
 
-        // If the URI is a tree URI performs some validation.
-        enforceTree(documentUri);
-
         if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
             enforceReadPermissionInner(documentUri, getCallingPackage(),
                     getCallingAttributionTag(), null);
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index b807180..cbc304b 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -950,10 +950,6 @@
      * This method will be called when an emergency call is placed on any subscription (including
      * the no-SIM case), regardless of which subscription this listener was registered on.
      *
-     * This method is deprecated. Both this method and the new
-     * {@link #onOutgoingEmergencyCall(EmergencyNumber, int)} will be called when an outgoing
-     * emergency call is placed.
-     *
      * @param placedEmergencyNumber The {@link EmergencyNumber} the emergency call was placed to.
      *
      * @deprecated Use {@link #onOutgoingEmergencyCall(EmergencyNumber, int)}.
@@ -972,22 +968,24 @@
      * This method will be called when an emergency call is placed on any subscription (including
      * the no-SIM case), regardless of which subscription this listener was registered on.
      *
-     * Both this method and the deprecated {@link #onOutgoingEmergencyCall(EmergencyNumber)} will be
-     * called when an outgoing emergency call is placed. You should only implement one of these
-     * methods.
+     * The default implementation of this method calls
+     * {@link #onOutgoingEmergencyCall(EmergencyNumber)} for backwards compatibility purposes. Do
+     * not call {@code super(...)} from within your implementation unless you want
+     * {@link #onOutgoingEmergencyCall(EmergencyNumber)} to be called as well.
      *
      * @param placedEmergencyNumber The {@link EmergencyNumber} the emergency call was placed to.
      * @param subscriptionId The subscription ID used to place the emergency call. If the
      *                       emergency call was placed without a valid subscription (e.g. when there
      *                       are no SIM cards in the device), this will be equal to
      *                       {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
-     *
      * @hide
      */
     @SystemApi
     @TestApi
     public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber,
             int subscriptionId) {
+        // Default implementation for backwards compatibility
+        onOutgoingEmergencyCall(placedEmergencyNumber);
     }
 
     /**
@@ -1375,10 +1373,6 @@
 
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
-                            () -> psl.onOutgoingEmergencyCall(placedEmergencyNumber)));
-
-            Binder.withCleanCallingIdentity(
-                    () -> mExecutor.execute(
                             () -> psl.onOutgoingEmergencyCall(placedEmergencyNumber,
                                     subscriptionId)));
         }
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 054dff7..32cc30be8 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -250,8 +250,11 @@
         Index.INTENDED_VSYNC, Index.FRAME_COMPLETED,
     };
 
+    /**
+     * @hide
+     */
     @UnsupportedAppUsage
-    /* package */ final long[] mTimingData;
+    public final long[] mTimingData;
 
     /**
      * Constructs a FrameMetrics object as a copy.
@@ -270,7 +273,7 @@
     /**
      * @hide
      */
-    FrameMetrics() {
+    public FrameMetrics() {
         mTimingData = new long[Index.FRAME_STATS_COUNT];
     }
 
diff --git a/core/java/android/view/inputmethod/TEST_MAPPING b/core/java/android/view/inputmethod/TEST_MAPPING
new file mode 100644
index 0000000..4b2ea1a
--- /dev/null
+++ b/core/java/android/view/inputmethod/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAutoFillServiceTestCases",
+      "options": [
+        {
+          "include-filter": "android.autofillservice.cts.inline"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.AppModeFull"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 92fa80e..12b16ff 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -60,5 +60,6 @@
      * Requests that the given task organizer is notified when back is pressed on the root activity
      * of one of its controlled tasks.
      */
-    void setInterceptBackPressedOnTaskRoot(ITaskOrganizer organizer, boolean interceptBackPressed);
+    void setInterceptBackPressedOnTaskRoot(in WindowContainerToken task,
+            boolean interceptBackPressed);
 }
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 7ec4f99..38fb023 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -149,9 +149,10 @@
      * of one of its controlled tasks.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-    public void setInterceptBackPressedOnTaskRoot(boolean interceptBackPressed) {
+    public void setInterceptBackPressedOnTaskRoot(@NonNull WindowContainerToken task,
+            boolean interceptBackPressed) {
         try {
-            getController().setInterceptBackPressedOnTaskRoot(mInterface, interceptBackPressed);
+            getController().setInterceptBackPressedOnTaskRoot(task, interceptBackPressed);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/TaskOrganizerTaskEmbedder.java b/core/java/android/window/TaskOrganizerTaskEmbedder.java
index 46c72f8..eb9dfed 100644
--- a/core/java/android/window/TaskOrganizerTaskEmbedder.java
+++ b/core/java/android/window/TaskOrganizerTaskEmbedder.java
@@ -74,7 +74,7 @@
         // windowing mode tasks. Plan is to migrate this to a wm-shell front-end when that
         // infrastructure is ready.
         // mTaskOrganizer.registerOrganizer();
-        mTaskOrganizer.setInterceptBackPressedOnTaskRoot(true);
+        // mTaskOrganizer.setInterceptBackPressedOnTaskRoot(true);
 
         return super.onInitialize();
     }
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
new file mode 100644
index 0000000..f9a2ecc
--- /dev/null
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -0,0 +1,234 @@
+/*
+ * 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.internal.jank;
+
+import android.annotation.NonNull;
+import android.graphics.HardwareRendererObserver;
+import android.os.Handler;
+import android.os.Trace;
+import android.util.Log;
+import android.view.FrameMetrics;
+import android.view.ThreadedRenderer;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor.Session;
+
+/**
+ * @hide
+ */
+public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
+    private static final String TAG = FrameTracker.class.getSimpleName();
+    private static final boolean DEBUG = false;
+    //TODO (163431584): need also consider other refresh rates.
+    private static final long CRITERIA = 1000000000 / 60;
+    @VisibleForTesting
+    public static final long UNKNOWN_TIMESTAMP = -1;
+
+    @VisibleForTesting
+    public long mBeginTime = UNKNOWN_TIMESTAMP;
+    @VisibleForTesting
+    public long mEndTime = UNKNOWN_TIMESTAMP;
+    public boolean mShouldTriggerTrace;
+    public HardwareRendererObserver mObserver;
+    public ThreadedRendererWrapper mRendererWrapper;
+    public FrameMetricsWrapper mMetricsWrapper;
+
+    private Session mSession;
+
+    public FrameTracker(@NonNull Session session,
+            @NonNull Handler handler, @NonNull ThreadedRenderer renderer) {
+        mSession = session;
+        mRendererWrapper = new ThreadedRendererWrapper(renderer);
+        mMetricsWrapper = new FrameMetricsWrapper();
+        mObserver = new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), handler);
+    }
+
+    /**
+     * This constructor is only for unit tests.
+     * @param session a trace session.
+     * @param renderer a test double for ThreadedRenderer
+     * @param metrics a test double for FrameMetrics
+     */
+    @VisibleForTesting
+    public FrameTracker(@NonNull Session session, Handler handler,
+            @NonNull ThreadedRendererWrapper renderer, @NonNull FrameMetricsWrapper metrics) {
+        mSession = session;
+        mRendererWrapper = renderer;
+        mMetricsWrapper = metrics;
+        mObserver = new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), handler);
+    }
+
+    /**
+     * Begin a trace session of the CUJ.
+     */
+    public void begin() {
+        long timestamp = System.nanoTime();
+        if (DEBUG) {
+            Log.d(TAG, "begin: time(ns)=" + timestamp + ", begin(ns)=" + mBeginTime
+                    + ", end(ns)=" + mEndTime + ", session=" + mSession);
+        }
+        if (mBeginTime != UNKNOWN_TIMESTAMP && mEndTime == UNKNOWN_TIMESTAMP) {
+            // We have an ongoing tracing already, skip subsequent calls.
+            return;
+        }
+        mBeginTime = timestamp;
+        mEndTime = UNKNOWN_TIMESTAMP;
+        Trace.beginAsyncSection(mSession.getName(), (int) mBeginTime);
+        mRendererWrapper.addObserver(mObserver);
+    }
+
+    /**
+     * End the trace session of the CUJ.
+     */
+    public void end() {
+        long timestamp = System.nanoTime();
+        if (DEBUG) {
+            Log.d(TAG, "end: time(ns)=" + timestamp + ", begin(ns)=" + mBeginTime
+                    + ", end(ns)=" + mEndTime + ", session=" + mSession);
+        }
+        if (mBeginTime == UNKNOWN_TIMESTAMP || mEndTime != UNKNOWN_TIMESTAMP) {
+            // We haven't started a trace yet.
+            return;
+        }
+        mEndTime = timestamp;
+        Trace.endAsyncSection(mSession.getName(), (int) mBeginTime);
+    }
+
+    /**
+     * Check if we had a janky frame according to the metrics.
+     * @param metrics frame metrics
+     * @return true if it is a janky frame
+     */
+    @VisibleForTesting
+    public boolean isJankyFrame(FrameMetricsWrapper metrics) {
+        long totalDurationMs = metrics.getMetric(FrameMetrics.TOTAL_DURATION);
+        boolean isFirstFrame = metrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1;
+        boolean isJanky = !isFirstFrame && totalDurationMs - CRITERIA > 0;
+
+        if (DEBUG) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(isJanky).append(",");
+            sb.append(metrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME)).append(",");
+            sb.append(metrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION)).append(",");
+            sb.append(metrics.getMetric(FrameMetrics.ANIMATION_DURATION)).append(",");
+            sb.append(metrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION)).append(",");
+            sb.append(metrics.getMetric(FrameMetrics.DRAW_DURATION)).append(",");
+            sb.append(metrics.getMetric(FrameMetrics.SYNC_DURATION)).append(",");
+            sb.append(metrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION)).append(",");
+            sb.append(metrics.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION)).append(",");
+            sb.append(totalDurationMs).append(",");
+            sb.append(metrics.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP)).append(",");
+            sb.append(metrics.getMetric(FrameMetrics.VSYNC_TIMESTAMP)).append(",");
+            Log.v(TAG, "metrics=" + sb.toString());
+        }
+
+        return isJanky;
+    }
+
+    @Override
+    public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
+        // Since this callback might come a little bit late after the end() call.
+        // We should keep tracking the begin / end timestamp.
+        // Then compare with vsync timestamp to check if the frame is in the duration of the CUJ.
+
+        if (mBeginTime == UNKNOWN_TIMESTAMP) return; // We haven't started tracing yet.
+        long vsyncTimestamp = mMetricsWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP);
+        if (vsyncTimestamp < mBeginTime) return; // The tracing has been started.
+
+        // If the end time has not been set, we are still in the tracing.
+        if (mEndTime != UNKNOWN_TIMESTAMP && vsyncTimestamp > mEndTime) {
+            // The tracing has been ended, remove the observer, see if need to trigger perfetto.
+            mRendererWrapper.removeObserver(mObserver);
+            // Trigger perfetto if necessary.
+            if (mShouldTriggerTrace) {
+                if (DEBUG) {
+                    Log.v(TAG, "Found janky frame, triggering perfetto.");
+                }
+                triggerPerfetto();
+            }
+            return;
+        }
+
+        // The frame is in the duration of the CUJ, check if it catches the deadline.
+        if (isJankyFrame(mMetricsWrapper)) {
+            mShouldTriggerTrace = true;
+        }
+    }
+
+    /**
+     * Trigger the prefetto daemon.
+     */
+    @VisibleForTesting
+    public void triggerPerfetto() {
+        InteractionJankMonitor.trigger();
+    }
+
+    /**
+     * A wrapper class that we can spy FrameMetrics (a final class) in unit tests.
+     */
+    public static class FrameMetricsWrapper {
+        private FrameMetrics mFrameMetrics;
+
+        public FrameMetricsWrapper() {
+            mFrameMetrics = new FrameMetrics();
+        }
+
+        /**
+         * Wrapper method.
+         * @return timing data of the metrics
+         */
+        public long[] getTiming() {
+            return mFrameMetrics.mTimingData;
+        }
+
+        /**
+         * Wrapper method.
+         * @param index specific index of the timing data
+         * @return the timing data of the specified index
+         */
+        public long getMetric(int index) {
+            return mFrameMetrics.getMetric(index);
+        }
+    }
+
+    /**
+     * A wrapper class that we can spy ThreadedRenderer (a final class) in unit tests.
+     */
+    public static class ThreadedRendererWrapper {
+        private ThreadedRenderer mRenderer;
+
+        public ThreadedRendererWrapper(ThreadedRenderer renderer) {
+            mRenderer = renderer;
+        }
+
+        /**
+         * Wrapper method.
+         * @param observer observer
+         */
+        public void addObserver(HardwareRendererObserver observer) {
+            mRenderer.addObserver(observer);
+        }
+
+        /**
+         * Wrapper method.
+         * @param observer observer
+         */
+        public void removeObserver(HardwareRendererObserver observer) {
+            mRenderer.removeObserver(observer);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
new file mode 100644
index 0000000..6bfb178
--- /dev/null
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -0,0 +1,181 @@
+/*
+ * 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.internal.jank;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.HandlerThread;
+import android.view.ThreadedRenderer;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class let users to begin and end the always on tracing mechanism.
+ * @hide
+ */
+public class InteractionJankMonitor {
+    private static final String TAG = InteractionJankMonitor.class.getSimpleName();
+    private static final boolean DEBUG = false;
+    private static final Object LOCK = new Object();
+    public static final int CUJ_NOTIFICATION_SHADE_MOTION = 0;
+    public static final int CUJ_NOTIFICATION_SHADE_GESTURE = 1;
+
+    private static ThreadedRenderer sRenderer;
+    private static Map<String, FrameTracker> sRunningTracker;
+    private static HandlerThread sWorker;
+    private static boolean sInitialized;
+
+    /** @hide */
+    @IntDef({
+            CUJ_NOTIFICATION_SHADE_MOTION,
+            CUJ_NOTIFICATION_SHADE_GESTURE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CujType {}
+
+    /**
+     * @param view Any view in the view tree to get context and ThreadedRenderer.
+     */
+    public static void init(@NonNull View view) {
+        init(view, null, null, null);
+    }
+
+    /**
+     * Should be only invoked internally or from unit tests.
+     */
+    @VisibleForTesting
+    public static void init(@NonNull View view, @NonNull ThreadedRenderer renderer,
+            @NonNull Map<String, FrameTracker> map, @NonNull HandlerThread worker) {
+        //TODO (163505250): This should be no-op if not in droid food rom.
+        synchronized (LOCK) {
+            if (!sInitialized) {
+                if (!view.isAttachedToWindow()) {
+                    throw new IllegalStateException("View is not attached!");
+                }
+                sRenderer = renderer == null ? view.getThreadedRenderer() : renderer;
+                sRunningTracker = map == null ? new HashMap<>() : map;
+                sWorker = worker == null ? new HandlerThread("Aot-Worker") : worker;
+                sWorker.start();
+                sInitialized = true;
+            }
+        }
+    }
+
+    /**
+     * Must invoke init() before invoking this method.
+     */
+    public static void begin(@NonNull @CujType int cujType) {
+        begin(cujType, null);
+    }
+
+    /**
+     * Should be only invoked internally or from unit tests.
+     */
+    @VisibleForTesting
+    public static void begin(@NonNull @CujType int cujType, FrameTracker tracker) {
+        //TODO (163505250): This should be no-op if not in droid food rom.
+        //TODO (163510843): Remove synchronized, add @UiThread if only invoked from ui threads.
+        synchronized (LOCK) {
+            checkInitStateLocked();
+            Session session = new Session(cujType);
+            FrameTracker currentTracker = getTracker(session.getName());
+            if (currentTracker != null) return;
+            if (tracker == null) {
+                tracker = new FrameTracker(session, sWorker.getThreadHandler(), sRenderer);
+            }
+            sRunningTracker.put(session.getName(), tracker);
+            tracker.begin();
+        }
+    }
+
+    /**
+     * Must invoke init() before invoking this method.
+     */
+    public static void end(@NonNull @CujType int cujType) {
+        //TODO (163505250): This should be no-op if not in droid food rom.
+        //TODO (163510843): Remove synchronized, add @UiThread if only invoked from ui threads.
+        synchronized (LOCK) {
+            checkInitStateLocked();
+            Session session = new Session(cujType);
+            FrameTracker tracker = getTracker(session.getName());
+            if (tracker != null) {
+                tracker.end();
+                sRunningTracker.remove(session.getName());
+            }
+        }
+    }
+
+    private static void checkInitStateLocked() {
+        if (!sInitialized) {
+            throw new IllegalStateException("InteractionJankMonitor not initialized!");
+        }
+    }
+
+    /**
+     * Should be only invoked from unit tests.
+     */
+    @VisibleForTesting
+    public static void reset() {
+        sInitialized = false;
+        sRenderer = null;
+        sRunningTracker = null;
+        if (sWorker != null) {
+            sWorker.quit();
+            sWorker = null;
+        }
+    }
+
+    private static FrameTracker getTracker(String sessionName) {
+        synchronized (LOCK) {
+            return sRunningTracker.get(sessionName);
+        }
+    }
+
+    /**
+     * Trigger the perfetto daemon to collect and upload data.
+     */
+    public static void trigger() {
+        sWorker.getThreadHandler().post(
+                () -> PerfettoTrigger.trigger(PerfettoTrigger.TRIGGER_TYPE_JANK));
+    }
+
+    /**
+     * A class to represent a session.
+     */
+    public static class Session {
+        private @CujType int mId;
+
+        public Session(@CujType int session) {
+            mId = session;
+        }
+
+        public int getId() {
+            return mId;
+        }
+
+        public String getName() {
+            return "CujType<" + mId + ">";
+        }
+    }
+
+}
diff --git a/core/java/com/android/internal/jank/PerfettoTrigger.java b/core/java/com/android/internal/jank/PerfettoTrigger.java
new file mode 100644
index 0000000..6c8d3cd
--- /dev/null
+++ b/core/java/com/android/internal/jank/PerfettoTrigger.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//TODO (165884885): Make PerfettoTrigger more generic and move it to another package.
+package com.android.internal.jank;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A trigger implementation with perfetto backend.
+ * @hide
+ */
+public class PerfettoTrigger {
+    private static final String TAG = PerfettoTrigger.class.getSimpleName();
+    private static final boolean DEBUG = false;
+    private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto";
+    private static final String[] TRIGGER_TYPE_NAMES = new String[] { "jank-tracker" };
+    public static final int TRIGGER_TYPE_JANK = 0;
+
+    /** @hide */
+    @IntDef({
+            TRIGGER_TYPE_JANK
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TriggerType {}
+
+    /**
+     * @param type the trigger type
+     */
+    public static void trigger(@NonNull @TriggerType int type) {
+        try {
+            Token token = new Token(type, TRIGGER_TYPE_NAMES[type]);
+            ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, token.getName());
+            if (DEBUG) {
+                StringBuilder sb = new StringBuilder();
+                for (String arg : pb.command()) {
+                    sb.append(arg).append(" ");
+                }
+                Log.d(TAG, "Triggering " + sb.toString());
+            }
+            Process process = pb.start();
+            if (DEBUG) {
+                readConsoleOutput(process);
+            }
+        } catch (IOException | InterruptedException e) {
+            Log.w(TAG, "Failed to trigger " + type, e);
+        }
+    }
+
+    private static void readConsoleOutput(@NonNull Process process)
+            throws IOException, InterruptedException {
+        process.waitFor();
+        try (BufferedReader errReader =
+                     new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
+            StringBuilder errLine = new StringBuilder();
+            String line;
+            while ((line = errReader.readLine()) != null) {
+                errLine.append(line).append("\n");
+            }
+            errLine.append(", code=").append(process.exitValue());
+            Log.d(TAG, "err message=" + errLine.toString());
+        }
+    }
+
+    /**
+     * Token which is used to trigger perfetto.
+     */
+    public static class Token {
+        private int mType;
+        private String mName;
+
+        Token(@TriggerType int type, String name) {
+            mType = type;
+            mName = name;
+        }
+
+        /**
+         * Get trigger type.
+         * @return trigger type, should be @TriggerType
+         */
+        public int getType() {
+            return mType;
+        }
+
+        /**
+         * Get name of this token as the argument while triggering perfetto.
+         * @return name
+         */
+        public String getName() {
+            return mName;
+        }
+    }
+
+}
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewHelper.java b/core/java/com/android/internal/view/ScrollCaptureViewHelper.java
index 9f100bd..a92e978 100644
--- a/core/java/com/android/internal/view/ScrollCaptureViewHelper.java
+++ b/core/java/com/android/internal/view/ScrollCaptureViewHelper.java
@@ -26,6 +26,36 @@
     int DOWN = 1;
 
     /**
+     * Contains the result of a scroll request.
+     */
+    class ScrollResult {
+        /**
+         * The area requested in pixels, within {@link #onComputeScrollBounds scroll bounds}, with
+         * top/bottom relative to the scroll position at the start of capture.
+         */
+        public Rect requestedArea;
+        /**
+         * The area, in pixels of the request which is visible and available for capture. In the
+         * same coordinate space as {@link #requestedArea}.
+         */
+        public Rect availableArea;
+        /**
+         * The updated scroll delta (the relative distance, in pixels that the scroll position has
+         * moved from the starting position since capture started).
+         */
+        public int scrollDelta; // visible top offset from start
+
+        @Override
+        public String toString() {
+            return "ScrollResult{"
+                    + "requestedArea=" + requestedArea
+                    + ", availableArea=" + availableArea
+                    + ", scrollDelta=" + scrollDelta
+                    + '}';
+        }
+    }
+
+    /**
      * Verifies that the view is still visible and scrollable. If true is returned here, expect a
      * call to {@link #onComputeScrollBounds(View)} to follow.
      *
@@ -48,6 +78,7 @@
                 view.getWidth() - view.getPaddingRight(),
                 view.getHeight() - view.getPaddingBottom());
     }
+
     /**
      * Adjust the target for capture.
      * <p>
@@ -67,14 +98,14 @@
      * needed and return the resulting rectangle describing the position and bounds of the area
      * which is visible.
      *
+     * @param view the view being captured
      * @param scrollBounds the area in which scrolling content moves, local to the {@code containing
      *                     view}
      * @param requestRect  the area relative to {@code scrollBounds} which describes the location of
      *                     content to capture for the request
-     * @return the visible area within scrollBounds of the requested rectangle, return {@code null}
-     * in the case of an unrecoverable error condition, to abort the capture process
+     * @return the result of the request as a {@link ScrollResult}
      */
-    Rect onScrollRequested(@NonNull V view, Rect scrollBounds, Rect requestRect);
+    ScrollResult onScrollRequested(@NonNull V view, Rect scrollBounds, Rect requestRect);
 
     /**
      * Restore the target after capture.
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
index 4087eda..7b4f73f 100644
--- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
+++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
@@ -30,21 +30,24 @@
 import android.view.Surface;
 import android.view.View;
 
+import com.android.internal.view.ScrollCaptureViewHelper.ScrollResult;
+
 import java.lang.ref.WeakReference;
 import java.util.function.Consumer;
 
 /**
- * Provides a ScrollCaptureCallback implementation for to handle arbitrary View-based scrolling
- * containers.
- * <p>
- * To use this class, supply the target view and an implementation of {@ScrollCaptureViewHelper}
- * to the callback.
+ * Provides a base ScrollCaptureCallback implementation to handle arbitrary View-based scrolling
+ * containers. This class handles the bookkeeping aspects of {@link ScrollCaptureCallback}
+ * including rendering output using HWUI. Adaptable to any {@link View} using
+ * {@link ScrollCaptureViewHelper}.
  *
  * @param <V> the specific View subclass handled
- * @hide
+ * @see ScrollCaptureViewHelper
  */
 public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCallback {
 
+    private static final String TAG = "ScrollCaptureViewSupport";
+
     private final WeakReference<V> mWeakView;
     private final ScrollCaptureViewHelper<V> mViewHelper;
     private ViewRenderer mRenderer;
@@ -52,11 +55,6 @@
     private boolean mStarted;
     private boolean mEnded;
 
-    static <V extends View> ScrollCaptureCallback createCallback(V view,
-            ScrollCaptureViewHelper<V> impl) {
-        return new ScrollCaptureViewSupport<>(view, impl);
-    }
-
     ScrollCaptureViewSupport(V containingView, ScrollCaptureViewHelper<V> viewHelper) {
         mWeakView = new WeakReference<>(containingView);
         mRenderer = new ViewRenderer();
@@ -82,6 +80,7 @@
     @Override
     public final void onScrollCaptureStart(ScrollCaptureSession session, Runnable onReady) {
         V view = mWeakView.get();
+
         mEnded = false;
         mStarted = true;
 
@@ -103,21 +102,30 @@
             session.notifyBufferSent(0, null);
             return;
         }
-        Rect captureArea = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
+        // Ask the view to scroll as needed to bring this area into view.
+        ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
                 requestRect);
-        mRenderer.renderFrame(view, captureArea, mUiHandler,
-                () -> session.notifyBufferSent(0, captureArea));
+        view.invalidate(); // don't wait for vsync
+
+        // For image capture, shift back by scrollDelta to arrive at the location within the view
+        // where the requested content will be drawn
+        Rect viewCaptureArea = new Rect(scrollResult.availableArea);
+        viewCaptureArea.offset(0, -scrollResult.scrollDelta);
+
+        mRenderer.renderView(view, viewCaptureArea, mUiHandler,
+                (frameNumber) -> session.notifyBufferSent(frameNumber, scrollResult.availableArea));
     }
 
     @Override
     public final void onScrollCaptureEnd(Runnable onReady) {
         V view = mWeakView.get();
         if (mStarted && !mEnded) {
-            mViewHelper.onPrepareForEnd(view);
-            /* empty */
+            if (view != null) {
+                mViewHelper.onPrepareForEnd(view);
+                view.invalidate();
+            }
             mEnded = true;
-            mRenderer.trimMemory();
-            mRenderer.setSurface(null);
+            mRenderer.destroy();
         }
         onReady.run();
     }
@@ -142,7 +150,7 @@
         private static final String TAG = "ViewRenderer";
 
         private HardwareRenderer mRenderer;
-        private RenderNode mRootRenderNode;
+        private RenderNode mCaptureRenderNode;
         private final RectF mTempRectF = new RectF();
         private final Rect mSourceRect = new Rect();
         private final Rect mTempRect = new Rect();
@@ -151,10 +159,14 @@
         private long mLastRenderedSourceDrawingId = -1;
 
 
+        public interface FrameCompleteListener {
+            void onFrameComplete(long frameNumber);
+        }
+
         ViewRenderer() {
             mRenderer = new HardwareRenderer();
-            mRootRenderNode = new RenderNode("ScrollCaptureRoot");
-            mRenderer.setContentRoot(mRootRenderNode);
+            mCaptureRenderNode = new RenderNode("ScrollCaptureRoot");
+            mRenderer.setContentRoot(mCaptureRenderNode);
 
             // TODO: Figure out a way to flip this on when we are sure the source window is opaque
             mRenderer.setOpaque(false);
@@ -193,18 +205,36 @@
             // Enable shadows for elevation/Z
             mRenderer.setLightSourceGeometry(lightX, lightY, lightZ, lightRadius);
             mRenderer.setLightSourceAlpha(AMBIENT_SHADOW_ALPHA, SPOT_SHADOW_ALPHA);
-
         }
 
-        public void renderFrame(View localReference, Rect sourceRect, Handler handler,
-                Runnable onFrameCommitted) {
-            if (updateForView(localReference)) {
-                setupLighting(localReference);
+        private void updateRootNode(View source, Rect localSourceRect) {
+            final View rootView = source.getRootView();
+            transformToRoot(source, localSourceRect, mTempRect);
+
+            mCaptureRenderNode.setPosition(0, 0, mTempRect.width(), mTempRect.height());
+            RecordingCanvas canvas = mCaptureRenderNode.beginRecording();
+            canvas.enableZ();
+            canvas.translate(-mTempRect.left, -mTempRect.top);
+
+            RenderNode rootViewRenderNode = rootView.updateDisplayListIfDirty();
+            if (rootViewRenderNode.hasDisplayList()) {
+                canvas.drawRenderNode(rootViewRenderNode);
             }
-            buildRootDisplayList(localReference, sourceRect);
+            mCaptureRenderNode.endRecording();
+        }
+
+        public void renderView(View view, Rect sourceRect, Handler handler,
+                FrameCompleteListener frameListener) {
+            if (updateForView(view)) {
+                setupLighting(view);
+            }
+            view.invalidate();
+            updateRootNode(view, sourceRect);
             HardwareRenderer.FrameRenderRequest request = mRenderer.createRenderRequest();
             request.setVsyncTime(SystemClock.elapsedRealtimeNanos());
-            request.setFrameCommitCallback(handler::post, onFrameCommitted);
+            // private API b/c request.setFrameCommitCallback does not provide access to frameNumber
+            mRenderer.setFrameCompleteCallback(
+                    frameNr -> handler.post(() -> frameListener.onFrameComplete(frameNr)));
             request.setWaitForPresent(true);
             request.syncAndDraw();
         }
@@ -225,15 +255,5 @@
             mTempRectF.round(outRect);
         }
 
-        private void buildRootDisplayList(View source, Rect localSourceRect) {
-            final View captureSource = source.getRootView();
-            transformToRoot(source, localSourceRect, mTempRect);
-            mRootRenderNode.setPosition(0, 0, mTempRect.width(), mTempRect.height());
-            RecordingCanvas canvas = mRootRenderNode.beginRecording(mTempRect.width(),
-                    mTempRect.height());
-            canvas.translate(-mTempRect.left, -mTempRect.top);
-            canvas.drawRenderNode(captureSource.updateDisplayListIfDirty());
-            mRootRenderNode.endRecording();
-        }
     }
 }
diff --git a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
index 12bd461..1514b9a 100644
--- a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
+++ b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
@@ -35,13 +35,14 @@
  * <li>correctly implements {@link ViewParent#requestChildRectangleOnScreen(View,
  * Rect, boolean)}
  * </ul>
+ *
+ * @see ScrollCaptureViewSupport
  */
 public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGroup> {
     private int mStartScrollY;
     private boolean mScrollBarEnabled;
     private int mOverScrollMode;
 
-    /** @see ScrollCaptureViewHelper#onPrepareForStart(View, Rect) */
     public void onPrepareForStart(@NonNull ViewGroup view, Rect scrollBounds) {
         mStartScrollY = view.getScrollY();
         mOverScrollMode = view.getOverScrollMode();
@@ -54,8 +55,8 @@
         }
     }
 
-    /** @see ScrollCaptureViewHelper#onScrollRequested(View, Rect, Rect) */
-    public Rect onScrollRequested(@NonNull ViewGroup view, Rect scrollBounds, Rect requestRect) {
+    public ScrollResult onScrollRequested(@NonNull ViewGroup view, Rect scrollBounds,
+            Rect requestRect) {
         final View contentView = view.getChildAt(0); // returns null, does not throw IOOBE
         if (contentView == null) {
             return null;
@@ -87,6 +88,9 @@
             \__ Requested Bounds[0,300 - 200,400] (200x100)
        */
 
+        ScrollResult result = new ScrollResult();
+        result.requestedArea = new Rect(requestRect);
+
         // 0) adjust the requestRect to account for scroll change since start
         //
         //  Scroll Bounds[50,50 - 250,250]  (w=200,h=200)
@@ -117,8 +121,6 @@
                 view.getScrollX() - contentView.getLeft(),
                 view.getScrollY() - contentView.getTop());
 
-
-
         // requestRect is now local to contentView as requestedContentBounds
         // contentView (and each parent in turn if possible) will be scrolled
         // (if necessary) to make all of requestedContent visible, (if possible!)
@@ -126,35 +128,37 @@
 
         // update new offset between starting and current scroll position
         scrollDelta = view.getScrollY() - mStartScrollY;
+        result.scrollDelta = scrollDelta;
 
-
-        // TODO: adjust to avoid occlusions/minimize scroll changes
+        // TODO: crop capture area to avoid occlusions/minimize scroll changes
 
         Point offset = new Point();
-        final Rect capturedRect = new Rect(requestedContentBounds); // empty
-        if (!view.getChildVisibleRect(contentView, capturedRect, offset)) {
-            capturedRect.setEmpty();
-            return capturedRect;
+        final Rect available = new Rect(requestedContentBounds); // empty
+        if (!view.getChildVisibleRect(contentView, available, offset)) {
+            available.setEmpty();
+            result.availableArea = available;
+            return result;
         }
         // Transform back from global to content-view local
-        capturedRect.offset(-offset.x, -offset.y);
+        available.offset(-offset.x, -offset.y);
 
         // Then back to container view
-        capturedRect.offset(
+        available.offset(
                 contentView.getLeft() - view.getScrollX(),
                 contentView.getTop() - view.getScrollY());
 
 
         // And back to relative to scrollBounds
-        capturedRect.offset(-scrollBounds.left, -scrollBounds.top);
+        available.offset(-scrollBounds.left, -scrollBounds.top);
 
-        // Apply scrollDelta again to return to make capturedRect relative to scrollBounds at
+        // Apply scrollDelta again to return to make `available` relative to `scrollBounds` at
         // the scroll position at start of capture.
-        capturedRect.offset(0, scrollDelta);
-        return capturedRect;
+        available.offset(0, scrollDelta);
+
+        result.availableArea = new Rect(available);
+        return result;
     }
 
-    /** @see ScrollCaptureViewHelper#onPrepareForEnd(View)  */
     public void onPrepareForEnd(@NonNull ViewGroup view) {
         view.scrollTo(0, mStartScrollY);
         if (mOverScrollMode != View.OVER_SCROLL_NEVER) {
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 5c045b6..7a5c383 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -20,6 +20,7 @@
 #include "android_media_AudioTrack.h"
 
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "core_jni_helpers.h"
 
 #include <utils/Log.h>
@@ -251,7 +252,7 @@
                                            jint audioFormat, jint buffSizeInBytes, jint memoryMode,
                                            jintArray jSession, jlong nativeAudioTrack,
                                            jboolean offload, jint encapsulationMode,
-                                           jobject tunerConfiguration) {
+                                           jobject tunerConfiguration, jstring opPackageName) {
     ALOGV("sampleRates=%p, channel mask=%x, index mask=%x, audioFormat(Java)=%d, buffSize=%d,"
           " nativeAudioTrack=0x%" PRIX64 ", offload=%d encapsulationMode=%d tuner=%p",
           jSampleRate, channelPositionMask, channelIndexMask, audioFormat, buffSizeInBytes,
@@ -337,7 +338,8 @@
         }
 
         // create the native AudioTrack object
-        lpTrack = new AudioTrack();
+        ScopedUtfChars opPackageNameStr(env, opPackageName);
+        lpTrack = new AudioTrack(opPackageNameStr.c_str());
 
         // read the AudioAttributes values
         auto paa = JNIAudioAttributeHelper::makeUnique();
@@ -371,23 +373,24 @@
         status_t status = NO_ERROR;
         switch (memoryMode) {
         case MODE_STREAM:
-            status = lpTrack->set(
-                    AUDIO_STREAM_DEFAULT,// stream type, but more info conveyed in paa (last argument)
-                    sampleRateInHertz,
-                    format,// word length, PCM
-                    nativeChannelMask,
-                    offload ? 0 : frameCount,
-                    offload ? AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD : AUDIO_OUTPUT_FLAG_NONE,
-                    audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)
-                    0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
-                    0,// shared mem
-                    true,// thread can call Java
-                    sessionId,// audio session ID
-                    offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK : AudioTrack::TRANSFER_SYNC,
-                    offload ? &offloadInfo : NULL,
-                    -1, -1,                       // default uid, pid values
-                    paa.get());
-
+            status = lpTrack->set(AUDIO_STREAM_DEFAULT, // stream type, but more info conveyed
+                                                        // in paa (last argument)
+                                  sampleRateInHertz,
+                                  format, // word length, PCM
+                                  nativeChannelMask, offload ? 0 : frameCount,
+                                  offload ? AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD
+                                          : AUDIO_OUTPUT_FLAG_NONE,
+                                  audioCallback,
+                                  &(lpJniStorage->mCallbackData), // callback, callback data (user)
+                                  0,    // notificationFrames == 0 since not using EVENT_MORE_DATA
+                                        // to feed the AudioTrack
+                                  0,    // shared mem
+                                  true, // thread can call Java
+                                  sessionId, // audio session ID
+                                  offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK
+                                          : AudioTrack::TRANSFER_SYNC,
+                                  offload ? &offloadInfo : NULL, -1, -1, // default uid, pid values
+                                  paa.get());
             break;
 
         case MODE_STATIC:
@@ -398,22 +401,22 @@
                 goto native_init_failure;
             }
 
-            status = lpTrack->set(
-                    AUDIO_STREAM_DEFAULT,// stream type, but more info conveyed in paa (last argument)
-                    sampleRateInHertz,
-                    format,// word length, PCM
-                    nativeChannelMask,
-                    frameCount,
-                    AUDIO_OUTPUT_FLAG_NONE,
-                    audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user));
-                    0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
-                    lpJniStorage->mMemBase,// shared mem
-                    true,// thread can call Java
-                    sessionId,// audio session ID
-                    AudioTrack::TRANSFER_SHARED,
-                    NULL,                         // default offloadInfo
-                    -1, -1,                       // default uid, pid values
-                    paa.get());
+            status = lpTrack->set(AUDIO_STREAM_DEFAULT, // stream type, but more info conveyed
+                                                        // in paa (last argument)
+                                  sampleRateInHertz,
+                                  format, // word length, PCM
+                                  nativeChannelMask, frameCount, AUDIO_OUTPUT_FLAG_NONE,
+                                  audioCallback,
+                                  &(lpJniStorage->mCallbackData), // callback, callback data (user)
+                                  0, // notificationFrames == 0 since not using EVENT_MORE_DATA
+                                     // to feed the AudioTrack
+                                  lpJniStorage->mMemBase, // shared mem
+                                  true,                   // thread can call Java
+                                  sessionId,              // audio session ID
+                                  AudioTrack::TRANSFER_SHARED,
+                                  NULL,   // default offloadInfo
+                                  -1, -1, // default uid, pid values
+                                  paa.get());
             break;
 
         default:
@@ -1428,7 +1431,8 @@
         {"native_stop", "()V", (void *)android_media_AudioTrack_stop},
         {"native_pause", "()V", (void *)android_media_AudioTrack_pause},
         {"native_flush", "()V", (void *)android_media_AudioTrack_flush},
-        {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZILjava/lang/Object;)I",
+        {"native_setup",
+         "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZILjava/lang/Object;Ljava/lang/String;)I",
          (void *)android_media_AudioTrack_setup},
         {"native_finalize", "()V", (void *)android_media_AudioTrack_finalize},
         {"native_release", "()V", (void *)android_media_AudioTrack_release},
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 34e6efb..fcf9b83 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1261,7 +1261,7 @@
     <string name="volume_ringtone" msgid="134784084629229029">"Hlasitost vyzvánění"</string>
     <string name="volume_music" msgid="7727274216734955095">"Hlasitost médií"</string>
     <string name="volume_music_hint_playing_through_bluetooth" msgid="2614142915948898228">"Přehrávání pomocí rozhraní Bluetooth"</string>
-    <string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"Je nastaven tichý vyzváněcí tón"</string>
+    <string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"Je nastaven tichý vyzvánění"</string>
     <string name="volume_call" msgid="7625321655265747433">"Hlasitost hovoru"</string>
     <string name="volume_bluetooth_call" msgid="2930204618610115061">"Hlasitost příchozích hovorů při připojení Bluetooth"</string>
     <string name="volume_alarm" msgid="4486241060751798448">"Hlasitost budíku"</string>
@@ -1272,10 +1272,10 @@
     <string name="volume_icon_description_incall" msgid="4491255105381227919">"Hlasitost hovoru"</string>
     <string name="volume_icon_description_media" msgid="4997633254078171233">"Hlasitost médií"</string>
     <string name="volume_icon_description_notification" msgid="579091344110747279">"Hlasitost oznámení"</string>
-    <string name="ringtone_default" msgid="9118299121288174597">"Výchozí vyzváněcí tón"</string>
+    <string name="ringtone_default" msgid="9118299121288174597">"Výchozí vyzvánění"</string>
     <string name="ringtone_default_with_actual" msgid="2709686194556159773">"Výchozí (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
     <string name="ringtone_silent" msgid="397111123930141876">"Žádný"</string>
-    <string name="ringtone_picker_title" msgid="667342618626068253">"Vyzváněcí tóny"</string>
+    <string name="ringtone_picker_title" msgid="667342618626068253">"Vyzvánění"</string>
     <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Zvuky budíku"</string>
     <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Zvuky upozornění"</string>
     <string name="ringtone_unknown" msgid="5059495249862816475">"Neznámé"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 117fecb..0b7e396 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -657,7 +657,7 @@
     <string name="permlab_accessNetworkConditions" msgid="1270732533356286514">"observer netværksforhold"</string>
     <string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"Tillader, at en applikation observerer netværksforhold. Bør aldrig være nødvendigt for almindelige apps."</string>
     <string name="permlab_setInputCalibration" msgid="932069700285223434">"skift kalibrering for inputenheden"</string>
-    <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"Tillader, at appen ændrer kalibreringsparametrene for berøringsskærmen. Dette bør aldrig være nødvendigt for almindelige apps."</string>
+    <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"Tillader, at appen ændrer kalibreringsparametrene for touchskærmen. Dette bør aldrig være nødvendigt for almindelige apps."</string>
     <string name="permlab_accessDrmCertificates" msgid="6473765454472436597">"få adgang til DRM-certifikater"</string>
     <string name="permdesc_accessDrmCertificates" msgid="6983139753493781941">"Tillader, at en applikation provisionerer og anvender DRM-certifikater. Dette bør aldrig være nødvendigt for almindelige apps."</string>
     <string name="permlab_handoverStatus" msgid="7620438488137057281">"modtag status for Android Beam-overførsler"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index ecce980..c93dbbf 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -484,8 +484,8 @@
     <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"Telefonoaren ordu-zona aldatzeko baimena ematen die aplikazioei."</string>
     <string name="permlab_getAccounts" msgid="5304317160463582791">"bilatu gailuko kontuak"</string>
     <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Tabletak ezagutzen dituen kontuen zerrenda lortzeko baimena ematen die aplikazioei. Instalatuta dauzkazun aplikazioek sortutako kontuak har daitezke barnean."</string>
-    <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Android TV gailuak ezagutzen dituen kontuen zerrenda lortzeko baimena ematen die aplikazioei. Kontu horien artean, instalatuta dituzun aplikazioek sortutako kontuak egon litezke."</string>
-    <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Telefonoak ezagutzen dituen kontuen zerrenda lortzeko baimena ematen die aplikazioei. Instalatuta dituzun aplikazioek sortutako kontuak har daitezke barnean."</string>
+    <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Android TV gailuak ezagutzen dituen kontuen zerrenda lortzeko baimena ematen die aplikazioei. Kontu horien artean, instalatuta dauzkazun aplikazioek sortutako kontuak egon litezke."</string>
+    <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Telefonoak ezagutzen dituen kontuen zerrenda lortzeko baimena ematen die aplikazioei. Instalatuta dauzkazun aplikazioek sortutako kontuak har daitezke barnean."</string>
     <string name="permlab_accessNetworkState" msgid="2349126720783633918">"ikusi sareko konexioak"</string>
     <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"Sareko konexioei buruzko informazioa ikusteko baimena ematen die aplikazioei; adibidez, zer sare dauden eta zeintzuk dauden konektatuta."</string>
     <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"izan sarerako sarbide osoa"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index f9e3e2f..f4fb83d 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -2035,7 +2035,7 @@
     <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Aplikasi ini tidak diberi izin merekam, tetapi dapat merekam audio melalui perangkat USB ini."</string>
     <string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Beranda"</string>
     <string name="accessibility_system_action_back_label" msgid="4205361367345537608">"Kembali"</string>
-    <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Aplikasi yang Baru Dipakai"</string>
+    <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Aplikasi Terbaru"</string>
     <string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Notifikasi"</string>
     <string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Setelan Cepat"</string>
     <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Dialog Daya"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index bb79c21..ee0362e 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -304,13 +304,13 @@
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS सन्देशहरू पठाउनुहोस् र हेर्नुहोस्"</string>
     <string name="permgrouplab_storage" msgid="1938416135375282333">"फाइल र मिडिया"</string>
-    <string name="permgroupdesc_storage" msgid="6351503740613026600">"तपाईंको यन्त्रमा तस्बिर, मिडिया, र फाइलहरूमाथि पहुँच गर्नुहोस्"</string>
+    <string name="permgroupdesc_storage" msgid="6351503740613026600">"तपाईंको यन्त्रमा फोटो, मिडिया, र फाइलहरूमाथि पहुँच गर्नुहोस्"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"माइक्रोफोन"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"अडियो रेकर्ड गर्नुहोस्"</string>
     <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"शारीरिक क्रियाकलाप"</string>
     <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"आफ्नो शारीरिक क्रियाकलापको डेटामाथि पहुँच राख्नु"</string>
     <string name="permgrouplab_camera" msgid="9090413408963547706">"क्यामेरा"</string>
-    <string name="permgroupdesc_camera" msgid="7585150538459320326">"तस्बिर खिच्नुका साथै भिडियो रेकर्ड गर्नुहोस्"</string>
+    <string name="permgroupdesc_camera" msgid="7585150538459320326">"फोटो खिच्नुका साथै भिडियो रेकर्ड गर्नुहोस्"</string>
     <string name="permgrouplab_calllog" msgid="7926834372073550288">"कलका लगहरू"</string>
     <string name="permgroupdesc_calllog" msgid="2026996642917801803">"फोन कलको लग पढ्नुहोस् र लेख्नुहोस्"</string>
     <string name="permgrouplab_phone" msgid="570318944091926620">"फोन"</string>
@@ -437,10 +437,10 @@
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM लाई आदेश पठाउन एपलाई अनुमति दिन्छ। यो निकै खतरनाक हुन्छ।"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"शारीरिक गतिविधि पहिचान गर्नुहोस्‌"</string>
     <string name="permdesc_activityRecognition" msgid="8667484762991357519">"यो अनुप्रयोगले तपाईंको शारीरिक गतिविधिको पहिचान गर्न सक्छ।"</string>
-    <string name="permlab_camera" msgid="6320282492904119413">"तस्बिरहरू र भिडियोहरू लिनुहोस्।"</string>
-    <string name="permdesc_camera" msgid="1354600178048761499">"यस अनुप्रयोगले जुनसुकै समय क्यामेराको प्रयोग गरी तस्बिर खिच्न र भिडियो रेकर्ड गर्न सक्छ।"</string>
-    <string name="permlab_systemCamera" msgid="3642917457796210580">"एप वा सेवालाई तस्बिर र भिडियो खिच्न प्रणालीका क्यामेराहरूमाथि पहुँच राख्न दिनुहोस्"</string>
-    <string name="permdesc_systemCamera" msgid="5938360914419175986">"प्रणालीको यस विशेषाधिकार प्राप्त अनुप्रयोगले जुनसुकै बेला प्रणालीको क्यामेरा प्रयोग गरी तस्बिर खिच्न र भिडियो रेकर्ड गर्न सक्छ। अनुप्रयोगसँग पनि android.permission.CAMERA प्रयोग गर्ने अनुमति हुनु पर्छ"</string>
+    <string name="permlab_camera" msgid="6320282492904119413">"फोटोहरू र भिडियोहरू लिनुहोस्।"</string>
+    <string name="permdesc_camera" msgid="1354600178048761499">"यस अनुप्रयोगले जुनसुकै समय क्यामेराको प्रयोग गरी फोटो खिच्न र भिडियो रेकर्ड गर्न सक्छ।"</string>
+    <string name="permlab_systemCamera" msgid="3642917457796210580">"एप वा सेवालाई फोटो र भिडियो खिच्न प्रणालीका क्यामेराहरूमाथि पहुँच राख्न दिनुहोस्"</string>
+    <string name="permdesc_systemCamera" msgid="5938360914419175986">"प्रणालीको यस विशेषाधिकार प्राप्त अनुप्रयोगले जुनसुकै बेला प्रणालीको क्यामेरा प्रयोग गरी फोटो खिच्न र भिडियो रेकर्ड गर्न सक्छ। अनुप्रयोगसँग पनि android.permission.CAMERA प्रयोग गर्ने अनुमति हुनु पर्छ"</string>
     <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"कुनै एप वा सेवालाई खोलिँदै वा बन्द गरिँदै गरेका क्यामेरा यन्त्रहरूका बारेमा कलब्याक प्राप्त गर्ने अनुमति दिनुहोस्।"</string>
     <string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"कुनै क्यामेरा यन्त्र खोलिँदा (कुन अनुप्रयोगले खोलेको भन्ने बारेमा) वा बन्द गरिँदा यो अनुप्रयोगले कलब्याक प्राप्त गर्न सक्छ।"</string>
     <string name="permlab_vibrate" msgid="8596800035791962017">"कम्पन नियन्त्रण गर्नुहोस्"</string>
@@ -1347,7 +1347,7 @@
     <string name="ext_media_new_notification_title" product="automotive" msgid="9085349544984742727">"<xliff:g id="NAME">%s</xliff:g> ले काम गरिरहेको छैन"</string>
     <string name="ext_media_new_notification_message" msgid="6095403121990786986">"सेटअप गर्न ट्याप गर्नुहोस्"</string>
     <string name="ext_media_new_notification_message" product="automotive" msgid="5140127881613227162">"तपाईंले यो यन्त्र पुनः फर्म्याट गर्नु पर्ने हुन सक्छ। यो यन्त्र हटाउन ट्याप गर्नुहोस्।"</string>
-    <string name="ext_media_ready_notification_message" msgid="777258143284919261">"तस्बिरहरू र मिडिया स्थानान्तरणका लागि"</string>
+    <string name="ext_media_ready_notification_message" msgid="777258143284919261">"फोटोहरू र मिडिया स्थानान्तरणका लागि"</string>
     <string name="ext_media_unmountable_notification_title" msgid="4895444667278979910">"<xliff:g id="NAME">%s</xliff:g> मा समस्या देखियो"</string>
     <string name="ext_media_unmountable_notification_title" product="automotive" msgid="3142723758949023280">"<xliff:g id="NAME">%s</xliff:g> ले काम गरिरहेको छैन"</string>
     <string name="ext_media_unmountable_notification_message" msgid="3256290114063126205">"समस्या समाधान गर्न ट्याप गर्नुहोस्"</string>
@@ -1914,7 +1914,7 @@
     <string name="app_category_game" msgid="4534216074910244790">"खेलहरू"</string>
     <string name="app_category_audio" msgid="8296029904794676222">"सङ्गीत तथा अडियो"</string>
     <string name="app_category_video" msgid="2590183854839565814">"चलचित्र तथा भिडियो"</string>
-    <string name="app_category_image" msgid="7307840291864213007">"तस्बिर तथा छविहरू"</string>
+    <string name="app_category_image" msgid="7307840291864213007">"फोटो तथा छविहरू"</string>
     <string name="app_category_social" msgid="2278269325488344054">"सामाजिक तथा सञ्चार"</string>
     <string name="app_category_news" msgid="1172762719574964544">"समाचार तथा पत्रिकाहरू"</string>
     <string name="app_category_maps" msgid="6395725487922533156">"नक्सा तथा नेभिगेसन"</string>
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
new file mode 100644
index 0000000..7480a20
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.internal.jank;
+
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_GESTURE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+import android.view.FrameMetrics;
+import android.view.View;
+import android.view.ViewAttachTestActivity;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
+import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
+import com.android.internal.jank.InteractionJankMonitor.Session;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+public class FrameTrackerTest {
+    private ViewAttachTestActivity mActivity;
+
+    @Rule
+    public ActivityTestRule<ViewAttachTestActivity> mRule =
+            new ActivityTestRule<>(ViewAttachTestActivity.class);
+
+    private FrameTracker mTracker;
+    private ThreadedRendererWrapper mRenderer;
+    private FrameMetricsWrapper mWrapper;
+
+    @Before
+    public void setup() {
+        // Prepare an activity for getting ThreadedRenderer later.
+        mActivity = mRule.getActivity();
+        View view = mActivity.getWindow().getDecorView();
+        assertThat(view.isAttachedToWindow()).isTrue();
+
+        Handler handler = mRule.getActivity().getMainThreadHandler();
+        mWrapper = Mockito.spy(new FrameMetricsWrapper());
+        mRenderer = Mockito.spy(new ThreadedRendererWrapper(view.getThreadedRenderer()));
+        doNothing().when(mRenderer).addObserver(any());
+        doNothing().when(mRenderer).removeObserver(any());
+
+        Session session = new Session(CUJ_NOTIFICATION_SHADE_GESTURE);
+        mTracker = Mockito.spy(new FrameTracker(session, handler, mRenderer, mWrapper));
+        doNothing().when(mTracker).triggerPerfetto();
+    }
+
+    @Test
+    public void testIsJankyFrame() {
+        // We skip the first frame.
+        doReturn(1L).when(mWrapper).getMetric(FrameMetrics.FIRST_DRAW_FRAME);
+        doReturn(TimeUnit.MILLISECONDS.toNanos(20L))
+                .when(mWrapper).getMetric(FrameMetrics.TOTAL_DURATION);
+        assertThat(mTracker.isJankyFrame(mWrapper)).isFalse();
+
+        // Should exceed the criteria.
+        doReturn(0L).when(mWrapper).getMetric(FrameMetrics.FIRST_DRAW_FRAME);
+        doReturn(TimeUnit.MILLISECONDS.toNanos(20L))
+                .when(mWrapper).getMetric(FrameMetrics.TOTAL_DURATION);
+        assertThat(mTracker.isJankyFrame(mWrapper)).isTrue();
+
+        // Should be safe.
+        doReturn(TimeUnit.MILLISECONDS.toNanos(10L))
+                .when(mWrapper).getMetric(FrameMetrics.TOTAL_DURATION);
+        assertThat(mTracker.isJankyFrame(mWrapper)).isFalse();
+    }
+
+    @Test
+    public void testBeginEnd() {
+        // assert the initial values
+        assertThat(mTracker.mBeginTime).isEqualTo(FrameTracker.UNKNOWN_TIMESTAMP);
+        assertThat(mTracker.mEndTime).isEqualTo(FrameTracker.UNKNOWN_TIMESTAMP);
+
+        // Observer should be only added once in continuous calls.
+        mTracker.begin();
+        mTracker.begin();
+        verify(mRenderer, only()).addObserver(any());
+
+        // assert the values after begin call.
+        assertThat(mTracker.mBeginTime).isNotEqualTo(FrameTracker.UNKNOWN_TIMESTAMP);
+        assertThat(mTracker.mEndTime).isEqualTo(FrameTracker.UNKNOWN_TIMESTAMP);
+
+        // simulate the callback during trace session
+        // assert the isJankyFrame should be invoked as well.
+        doReturn(System.nanoTime()).when(mWrapper).getMetric(FrameMetrics.VSYNC_TIMESTAMP);
+        doReturn(true).when(mTracker).isJankyFrame(any());
+        mTracker.onFrameMetricsAvailable(0);
+        verify(mTracker).isJankyFrame(any());
+
+        // end the trace session, simulate a callback came after the end call.
+        // assert the end time should be set, the observer should be removed.
+        // triggerPerfetto should be invoked as well.
+        mTracker.end();
+        doReturn(System.nanoTime()).when(mWrapper).getMetric(FrameMetrics.VSYNC_TIMESTAMP);
+        assertThat(mTracker.mEndTime).isNotEqualTo(FrameTracker.UNKNOWN_TIMESTAMP);
+        mTracker.onFrameMetricsAvailable(0);
+        verify(mRenderer).removeObserver(any());
+        verify(mTracker).triggerPerfetto();
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
new file mode 100644
index 0000000..5c0b0c9
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.internal.jank;
+
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_GESTURE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.view.View;
+import android.view.ViewAttachTestActivity;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
+import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
+import com.android.internal.jank.InteractionJankMonitor.Session;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.testng.Assert;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@SmallTest
+public class InteractionJankMonitorTest {
+    private ViewAttachTestActivity mActivity;
+    private View mView;
+    private FrameTracker mTracker;
+
+    @Rule
+    public ActivityTestRule<ViewAttachTestActivity> mRule =
+            new ActivityTestRule<>(ViewAttachTestActivity.class);
+
+    @Before
+    public void setup() {
+        // Prepare an activity for getting ThreadedRenderer later.
+        mActivity = mRule.getActivity();
+        mView = mActivity.getWindow().getDecorView();
+        assertThat(mView.isAttachedToWindow()).isTrue();
+
+        InteractionJankMonitor.reset();
+
+        // Prepare a FrameTracker to inject.
+        Session session = new Session(CUJ_NOTIFICATION_SHADE_GESTURE);
+        FrameMetricsWrapper wrapper = Mockito.spy(new FrameTracker.FrameMetricsWrapper());
+        ThreadedRendererWrapper renderer =
+                Mockito.spy(new ThreadedRendererWrapper(mView.getThreadedRenderer()));
+        Handler handler = mActivity.getMainThreadHandler();
+        mTracker = Mockito.spy(new FrameTracker(session, handler, renderer, wrapper));
+    }
+
+    @Test
+    public void testBeginEnd() {
+        // Should throw exception if the view is not attached.
+        Assert.assertThrows(IllegalStateException.class,
+                () -> InteractionJankMonitor.init(new View(mActivity)));
+
+        // Verify we init InteractionJankMonitor correctly.
+        Map<String, FrameTracker> map = new HashMap<>();
+        HandlerThread worker = Mockito.spy(new HandlerThread("Aot-test"));
+        doNothing().when(worker).start();
+        InteractionJankMonitor.init(mView, mView.getThreadedRenderer(), map, worker);
+        verify(worker).start();
+
+        // Simulate a trace session and see if begin / end are invoked.
+        Session session = new Session(CUJ_NOTIFICATION_SHADE_GESTURE);
+        assertThat(map.get(session.getName())).isNull();
+        InteractionJankMonitor.begin(CUJ_NOTIFICATION_SHADE_GESTURE, mTracker);
+        verify(mTracker).begin();
+        assertThat(map.get(session.getName())).isEqualTo(mTracker);
+        InteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_GESTURE);
+        verify(mTracker).end();
+        assertThat(map.get(session.getName())).isNull();
+    }
+
+    @Test
+    public void testCheckInitState() {
+        // Should throw exception if invoking begin / end without init invocation.
+        Assert.assertThrows(IllegalStateException.class,
+                () -> InteractionJankMonitor.begin(CUJ_NOTIFICATION_SHADE_GESTURE));
+        Assert.assertThrows(IllegalStateException.class,
+                () -> InteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_GESTURE));
+
+        // Everything should be fine if invoking init first.
+        boolean thrown = false;
+        try {
+            InteractionJankMonitor.init(mActivity.getWindow().getDecorView());
+            InteractionJankMonitor.begin(CUJ_NOTIFICATION_SHADE_GESTURE);
+            InteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_GESTURE);
+        } catch (Exception ex) {
+            thrown = true;
+        } finally {
+            assertThat(thrown).isFalse();
+        }
+    }
+
+}
diff --git a/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java b/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java
index 63a68e9..ab13fd7d 100644
--- a/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java
@@ -21,12 +21,11 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static androidx.test.InstrumentationRegistry.getContext;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.content.Context;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
@@ -40,10 +39,12 @@
 import android.widget.TextView;
 
 import androidx.test.annotation.UiThreadTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.view.ScrollCaptureViewHelper.ScrollResult;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
 import java.util.Random;
@@ -67,28 +68,27 @@
 
     private Random mRandom;
 
-    private static float sDensity;
-
-    @BeforeClass
-    public static void setUpClass() {
-        sDensity = getContext().getResources().getDisplayMetrics().density;
-    }
+    private Context mContext;
+    private float mDensity;
 
     @Before
     @UiThreadTest
     public void setUp() {
-        mRandom = new Random();
-        mParent = new FrameLayout(getContext());
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mDensity = mContext.getResources().getDisplayMetrics().density;
 
-        mTarget = new ScrollView(getContext());
+        mRandom = new Random();
+        mParent = new FrameLayout(mContext);
+
+        mTarget = new ScrollView(mContext);
         mParent.addView(mTarget, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
 
-        mContent = new LinearLayout(getContext());
+        mContent = new LinearLayout(mContext);
         mContent.setOrientation(LinearLayout.VERTICAL);
         mTarget.addView(mContent, new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
 
         for (int i = 0; i < CHILD_VIEWS; i++) {
-            TextView view = new TextView(getContext());
+            TextView view = new TextView(mContext);
             view.setText("Child #" + i);
             view.setTextColor(Color.WHITE);
             view.setTextSize(30f);
@@ -99,7 +99,7 @@
 
         // Window -> Parent -> Target -> Content
 
-        mWm = getContext().getSystemService(WindowManager.class);
+        mWm = mContext.getSystemService(WindowManager.class);
 
         // Setup the window that we are going to use
         mWindowLayoutParams = new WindowManager.LayoutParams(WINDOW_WIDTH, WINDOW_HEIGHT,
@@ -123,6 +123,190 @@
         svc.onPrepareForStart(mTarget, scrollBounds);
     }
 
+    @Test
+    @UiThreadTest
+    public void onScrollRequested_up_fromTop() {
+        final int startScrollY = assertScrollToY(mTarget, 0);
+
+        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+        svc.onPrepareForStart(mTarget, scrollBounds);
+
+        assertTrue(scrollBounds.height() > CAPTURE_HEIGHT);
+
+        Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
+
+        ScrollResult scrollResult = svc.onScrollRequested(mTarget,
+                scrollBounds, request);
+
+        // The result is an empty rectangle and no scrolling, since it
+        // is not possible to physically scroll further up to make the
+        // requested area visible at all (it doesn't exist).
+        assertEmpty(scrollResult.availableArea);
+    }
+
+    @Test
+    @UiThreadTest
+    public void onScrollRequested_down_fromTop() {
+        final int startScrollY = assertScrollToY(mTarget, 0);
+
+        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+        svc.onPrepareForStart(mTarget, scrollBounds);
+
+        assertTrue(scrollBounds.height() > CAPTURE_HEIGHT);
+
+        // Capture between y = +1200 to +1500 pixels BELOW current top
+        Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
+                WINDOW_HEIGHT + CAPTURE_HEIGHT);
+
+        ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request);
+        assertRectEquals(request, scrollResult.requestedArea);
+        assertRectEquals(request, scrollResult.availableArea);
+        assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
+        assertEquals(CAPTURE_HEIGHT, scrollResult.scrollDelta);
+    }
+
+    @Test
+    @UiThreadTest
+    public void onScrollRequested_up_fromMiddle() {
+        final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT);
+
+        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+        svc.onPrepareForStart(mTarget, scrollBounds);
+
+        Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
+
+        ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request);
+        assertRectEquals(request, scrollResult.requestedArea);
+        assertRectEquals(request, scrollResult.availableArea);
+        assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
+        assertEquals(-CAPTURE_HEIGHT, scrollResult.scrollDelta);
+    }
+
+    @Test
+    @UiThreadTest
+    public void onScrollRequested_down_fromMiddle() {
+        final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT);
+
+        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+        svc.onPrepareForStart(mTarget, scrollBounds);
+
+        Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
+                WINDOW_HEIGHT + CAPTURE_HEIGHT);
+
+        ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request);
+        assertRectEquals(request, scrollResult.requestedArea);
+        assertRectEquals(request, scrollResult.availableArea);
+        assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
+        assertEquals(CAPTURE_HEIGHT, scrollResult.scrollDelta);
+
+    }
+
+    @Test
+    @UiThreadTest
+    public void onScrollRequested_up_fromBottom() {
+        final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2);
+
+        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+        svc.onPrepareForStart(mTarget, scrollBounds);
+
+        Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
+
+        ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request);
+        assertRectEquals(request, scrollResult.requestedArea);
+        assertRectEquals(request, scrollResult.availableArea);
+        assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
+        assertEquals(-CAPTURE_HEIGHT, scrollResult.scrollDelta);
+    }
+
+    @Test
+    @UiThreadTest
+    public void onScrollRequested_down_fromBottom() {
+        final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2);
+
+        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+        svc.onPrepareForStart(mTarget, scrollBounds);
+
+        Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
+                WINDOW_HEIGHT + CAPTURE_HEIGHT);
+
+        ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request);
+        assertRectEquals(request, scrollResult.requestedArea);
+
+        // The result is an empty rectangle and no scrolling, since it
+        // is not possible to physically scroll further down to make the
+        // requested area visible at all (it doesn't exist).
+        assertEmpty(scrollResult.availableArea);
+        assertEquals(0, scrollResult.scrollDelta);
+    }
+
+    @Test
+    @UiThreadTest
+    public void onScrollRequested_offTopEdge() {
+        final int startScrollY = assertScrollToY(mTarget, 0);
+
+        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+        svc.onPrepareForStart(mTarget, scrollBounds);
+
+        // Create a request which lands halfway off the top of the content
+        //from -1500 to -900, (starting at 1200 = -300 to +300 within the content)
+        int top = 0;
+        Rect request = new Rect(
+                0, top - (CAPTURE_HEIGHT / 2),
+                scrollBounds.width(), top + (CAPTURE_HEIGHT / 2));
+
+        ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request);
+        assertRectEquals(request, scrollResult.requestedArea);
+
+        ScrollResult result = svc.onScrollRequested(mTarget, scrollBounds, request);
+        // The result is a partial result
+        Rect expectedResult = new Rect(request);
+        expectedResult.top += 300; // top half clipped
+        assertRectEquals(expectedResult, result.availableArea);
+        assertRequestedRectPartiallyVisible(startScrollY, request, getVisibleRect(mContent));
+        assertEquals(0, scrollResult.scrollDelta);
+    }
+
+    @Test
+    @UiThreadTest
+    public void onScrollRequested_offBottomEdge() {
+        final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2); // 2400
+
+        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+        svc.onPrepareForStart(mTarget, scrollBounds);
+
+        // Create a request which lands halfway off the bottom of the content
+        //from 600 to to 1200, (starting at 2400 = 3000 to  3600 within the content)
+
+        int bottom = WINDOW_HEIGHT;
+        Rect request = new Rect(
+                0, bottom - (CAPTURE_HEIGHT / 2),
+                scrollBounds.width(), bottom + (CAPTURE_HEIGHT / 2));
+
+        ScrollResult result = svc.onScrollRequested(mTarget, scrollBounds, request);
+
+        Rect expectedResult = new Rect(request);
+        expectedResult.bottom -= 300; // bottom half clipped
+        assertRectEquals(expectedResult, result.availableArea);
+        assertRequestedRectPartiallyVisible(startScrollY, request, getVisibleRect(mContent));
+        assertEquals(0, result.scrollDelta);
+    }
+
+    @Test
+    @UiThreadTest
+    public void onPrepareForEnd() {
+        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+        svc.onPrepareForEnd(mTarget);
+    }
+
+
     static void assertEmpty(Rect r) {
         if (r != null && !r.isEmpty()) {
             fail("Not true that " + r + " is empty");
@@ -155,8 +339,7 @@
         return scrollY;
     }
 
-
-    static void assertCapturedAreaCompletelyVisible(int startScrollY, Rect requestRect,
+    static void assertRequestedRectCompletelyVisible(int startScrollY, Rect requestRect,
             Rect localVisibleNow) {
         Rect captured = new Rect(localVisibleNow);
         captured.offset(0, -startScrollY); // make relative
@@ -165,7 +348,7 @@
             fail("Not true that all of " + requestRect + " is contained by " + captured);
         }
     }
-    static void assertCapturedAreaPartiallyVisible(int startScrollY, Rect requestRect,
+    static void assertRequestedRectPartiallyVisible(int startScrollY, Rect requestRect,
             Rect localVisibleNow) {
         Rect captured = new Rect(localVisibleNow);
         captured.offset(0, -startScrollY); // make relative
@@ -174,179 +357,4 @@
             fail("Not true that any of " + requestRect + " intersects " + captured);
         }
     }
-
-    @Test
-    @UiThreadTest
-    public void onScrollRequested_up_fromTop() {
-        final int startScrollY = assertScrollToY(mTarget, 0);
-
-        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
-        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
-        svc.onPrepareForStart(mTarget, scrollBounds);
-
-        assertTrue(scrollBounds.height() > CAPTURE_HEIGHT);
-
-        Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
-
-        Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
-
-        // The result is an empty rectangle and no scrolling, since it
-        // is not possible to physically scroll further up to make the
-        // requested area visible at all (it doesn't exist).
-        assertEmpty(result);
-    }
-
-    @Test
-    @UiThreadTest
-    public void onScrollRequested_down_fromTop() {
-        final int startScrollY = assertScrollToY(mTarget, 0);
-
-
-        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
-        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
-        svc.onPrepareForStart(mTarget, scrollBounds);
-
-        assertTrue(scrollBounds.height() > CAPTURE_HEIGHT);
-
-        // Capture between y = +1200 to +1500 pixels BELOW current top
-        Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
-                WINDOW_HEIGHT + CAPTURE_HEIGHT);
-
-        Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
-        assertRectEquals(request, result);
-
-        assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
-    }
-
-
-    @Test
-    @UiThreadTest
-    public void onScrollRequested_up_fromMiddle() {
-        final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT);
-
-        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
-        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
-        svc.onPrepareForStart(mTarget, scrollBounds);
-
-        Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
-
-
-        Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
-
-        assertRectEquals(request, result);
-
-        assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
-    }
-
-    @Test
-    @UiThreadTest
-    public void onScrollRequested_down_fromMiddle() {
-        final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT);
-
-        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
-        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
-        svc.onPrepareForStart(mTarget, scrollBounds);
-
-        Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
-                WINDOW_HEIGHT + CAPTURE_HEIGHT);
-
-        Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
-        assertRectEquals(request, result);
-
-        assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
-    }
-
-    @Test
-    @UiThreadTest
-    public void onScrollRequested_up_fromBottom() {
-        final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2);
-
-        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
-        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
-        svc.onPrepareForStart(mTarget, scrollBounds);
-
-        Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
-
-        Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
-        assertRectEquals(request, result);
-
-        assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
-    }
-
-    @Test
-    @UiThreadTest
-    public void onScrollRequested_down_fromBottom() {
-        final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2);
-
-        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
-        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
-        svc.onPrepareForStart(mTarget, scrollBounds);
-
-        Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
-                WINDOW_HEIGHT + CAPTURE_HEIGHT);
-
-        Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
-
-        // The result is an empty rectangle and no scrolling, since it
-        // is not possible to physically scroll further down to make the
-        // requested area visible at all (it doesn't exist).
-        assertEmpty(result);
-    }
-
-    @Test
-    @UiThreadTest
-    public void onScrollRequested_offTopEdge() {
-        final int startScrollY = assertScrollToY(mTarget, 0);
-
-        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
-        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
-        svc.onPrepareForStart(mTarget, scrollBounds);
-
-        // Create a request which lands halfway off the top of the content
-        //from -1500 to -900, (starting at 1200 = -300 to +300 within the content)
-        int top = 0;
-        Rect request = new Rect(
-                0, top - (CAPTURE_HEIGHT / 2),
-                scrollBounds.width(), top + (CAPTURE_HEIGHT / 2));
-
-        Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
-        // The result is a partial result
-        Rect expectedResult = new Rect(request);
-        expectedResult.top += 300; // top half clipped
-        assertRectEquals(expectedResult, result);
-        assertCapturedAreaPartiallyVisible(startScrollY, request, getVisibleRect(mContent));
-    }
-
-    @Test
-    @UiThreadTest
-    public void onScrollRequested_offBottomEdge() {
-        final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2); // 2400
-
-        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
-        Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
-        svc.onPrepareForStart(mTarget, scrollBounds);
-
-        // Create a request which lands halfway off the bottom of the content
-        //from 600 to to 1200, (starting at 2400 = 3000 to  3600 within the content)
-
-        int bottom = WINDOW_HEIGHT;
-        Rect request = new Rect(
-                0, bottom - (CAPTURE_HEIGHT / 2),
-                scrollBounds.width(), bottom + (CAPTURE_HEIGHT / 2));
-
-        Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
-
-        Rect expectedResult = new Rect(request);
-        expectedResult.bottom -= 300; // bottom half clipped
-        assertRectEquals(expectedResult, result);
-        assertCapturedAreaPartiallyVisible(startScrollY, request, getVisibleRect(mContent));
-
-    }
-
-    @Test
-    @UiThreadTest
-    public void onPrepareForEnd() {
-        ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
-        svc.onPrepareForEnd(mTarget);
-    }
 }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 75eb7b6..a2a2216 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -325,12 +325,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "-1587841219": {
-      "message": "Focus moving from %s to %s displayId=%d",
-      "level": "INFO",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-1568331821": {
       "message": "Enabling listeners",
       "level": "VERBOSE",
@@ -415,6 +409,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1438175584": {
+      "message": "Input focus has changed to %s display=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/InputMonitor.java"
+    },
     "-1434147454": {
       "message": "cleanupAnimation(): Notify animation finished mPendingAnimations=%d reorderMode=%d",
       "level": "DEBUG",
@@ -565,6 +565,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
+    "-1142279614": {
+      "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "-1130891072": {
       "message": "Orientation continue waiting for draw in %s",
       "level": "VERBOSE",
@@ -799,12 +805,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-771282525": {
-      "message": "Losing focus: %s",
-      "level": "INFO",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-771177730": {
       "message": "Removing focused app token:%s displayId=%d",
       "level": "VERBOSE",
@@ -847,12 +847,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DragState.java"
     },
-    "-687185281": {
-      "message": "New topFocusedDisplayId=%d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
-    },
     "-668956537": {
       "message": "  THUMBNAIL %s: CREATE",
       "level": "INFO",
@@ -877,6 +871,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowToken.java"
     },
+    "-639217716": {
+      "message": "setFocusedApp %s displayId=%d Callers=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "-635082269": {
       "message": "******** booted=%b msg=%b haveBoot=%b haveApp=%b haveWall=%b wallEnabled=%b haveKeyguard=%b",
       "level": "INFO",
@@ -895,12 +895,6 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-603199586": {
-      "message": "Clearing focused app, displayId=%d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/DisplayContent.java"
-    },
     "-593535526": {
       "message": "Binding proc %s with config %s",
       "level": "VERBOSE",
@@ -919,6 +913,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "-561092364": {
+      "message": "onPointerDownOutsideFocusLocked called on %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-549028919": {
       "message": "enableScreenIfNeededLocked: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b. %s",
       "level": "INFO",
@@ -1201,12 +1201,6 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
-    "-96848838": {
-      "message": "Gaining focus: %s",
-      "level": "INFO",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-90559682": {
       "message": "Config is skipping already pausing %s",
       "level": "VERBOSE",
@@ -1363,6 +1357,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "115358443": {
+      "message": "Focus changing: %s -> %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "123161180": {
       "message": "SEVER CHILDREN",
       "level": "INFO",
@@ -1495,12 +1495,6 @@
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/AppTransition.java"
     },
-    "285317231": {
-      "message": "Input focus has changed to %s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/InputMonitor.java"
-    },
     "288485303": {
       "message": "Attempted to set remove mode to a display that does not exist: %d",
       "level": "WARN",
@@ -1537,6 +1531,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
     },
+    "312030608": {
+      "message": "New topFocusedDisplayId=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
     "315395835": {
       "message": "Trying to add window with invalid user=%d",
       "level": "WARN",
@@ -1699,12 +1699,6 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
     },
-    "584499099": {
-      "message": "Set focused app to: %s moveFocusNow=%b displayId=%d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/DisplayContent.java"
-    },
     "585096182": {
       "message": "SURFACE isColorSpaceAgnostic=%b: %s",
       "level": "INFO",
@@ -1747,6 +1741,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "620519522": {
+      "message": "findFocusedWindow: No focusable windows, display=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "628276090": {
       "message": "Delaying app transition for screen rotation animation to finish",
       "level": "VERBOSE",
@@ -1903,6 +1903,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "872933199": {
+      "message": "Changing focus from %s to %s displayId=%d Callers=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "873914452": {
       "message": "goodToGo()",
       "level": "DEBUG",
@@ -2137,12 +2143,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
     },
-    "1358462645": {
-      "message": "Looking for focus: %s, flags=%d, canReceive=%b",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_FOCUS",
-      "at": "com\/android\/server\/wm\/DisplayContent.java"
-    },
     "1360551978": {
       "message": "Trying to update display configuration for non-existing displayId=%d",
       "level": "WARN",
@@ -2233,12 +2233,6 @@
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
-    "1469292670": {
-      "message": "Changing focus from %s to %s displayId=%d Callers=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/DisplayContent.java"
-    },
     "1495525537": {
       "message": "createWallpaperAnimations()",
       "level": "DEBUG",
@@ -2677,12 +2671,6 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
-    "2128604122": {
-      "message": "findFocusedWindow: No focusable windows.",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/DisplayContent.java"
-    },
     "2128917433": {
       "message": "onProposedRotationChanged, rotation=%d",
       "level": "VERBOSE",
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 1c0a526..de2a7b2 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -807,7 +807,8 @@
         int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
                 sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
                 mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/,
-                offload, encapsulationMode, tunerConfiguration);
+                offload, encapsulationMode, tunerConfiguration,
+                getCurrentOpPackageName());
         if (initResult != SUCCESS) {
             loge("Error code "+initResult+" when initializing AudioTrack.");
             return; // with mState == STATE_UNINITIALIZED
@@ -893,7 +894,8 @@
                     nativeTrackInJavaObj,
                     false /*offload*/,
                     ENCAPSULATION_MODE_NONE,
-                    null /* tunerConfiguration */);
+                    null /* tunerConfiguration */,
+                    "" /* opPackagename */);
             if (initResult != SUCCESS) {
                 loge("Error code "+initResult+" when initializing AudioTrack.");
                 return; // with mState == STATE_UNINITIALIZED
@@ -4062,7 +4064,8 @@
             Object /*AudioAttributes*/ attributes,
             int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
             int buffSizeInBytes, int mode, int[] sessionId, long nativeAudioTrack,
-            boolean offload, int encapsulationMode, Object tunerConfiguration);
+            boolean offload, int encapsulationMode, Object tunerConfiguration,
+            @NonNull String opPackageName);
 
     private native final void native_finalize();
 
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 49e4160..36ae3ec 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -672,7 +672,8 @@
         /* Native setup requires a weak reference to our object.
          * It's easier to create it here than in C++.
          */
-        native_setup(new WeakReference<MediaPlayer>(this));
+        native_setup(new WeakReference<MediaPlayer>(this),
+                getCurrentOpPackageName());
 
         baseRegisterPlayer();
     }
@@ -2378,7 +2379,7 @@
     private native final int native_setMetadataFilter(Parcel request);
 
     private static native final void native_init();
-    private native final void native_setup(Object mediaplayer_this);
+    private native void native_setup(Object mediaplayerThis, @NonNull String opPackageName);
     private native final void native_finalize();
 
     /**
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index ee8f1b3..df5e85e 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -27,6 +27,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -622,4 +623,8 @@
         Log.w(className, "See the documentation of " + opName + " for what to use instead with " +
                 "android.media.AudioAttributes to qualify your playback use case");
     }
+
+    protected String getCurrentOpPackageName() {
+        return TextUtils.emptyIfNull(ActivityThread.currentOpPackageName());
+    }
 }
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 55aac09..bd8d2e9 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -33,6 +33,7 @@
 #include <utils/threads.h>
 #include "jni.h"
 #include <nativehelper/JNIPlatformHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
 #include "android_runtime/Log.h"
@@ -944,10 +945,12 @@
 }
 
 static void
-android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
+android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
+                                       jstring opPackageName)
 {
     ALOGV("native_setup");
-    sp<MediaPlayer> mp = new MediaPlayer();
+    ScopedUtfChars opPackageNameStr(env, opPackageName);
+    sp<MediaPlayer> mp = new MediaPlayer(opPackageNameStr.c_str());
     if (mp == NULL) {
         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
         return;
@@ -1403,7 +1406,7 @@
     {"native_setMetadataFilter", "(Landroid/os/Parcel;)I",      (void *)android_media_MediaPlayer_setMetadataFilter},
     {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_getMetadata},
     {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},
-    {"native_setup",        "(Ljava/lang/Object;)V",            (void *)android_media_MediaPlayer_native_setup},
+    {"native_setup",        "(Ljava/lang/Object;Ljava/lang/String;)V",(void *)android_media_MediaPlayer_native_setup},
     {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
     {"getAudioSessionId",   "()I",                              (void *)android_media_MediaPlayer_get_audio_session_id},
     {"setAudioSessionId",   "(I)V",                             (void *)android_media_MediaPlayer_set_audio_session_id},
diff --git a/media/packages/BluetoothMidiService/Android.bp b/media/packages/BluetoothMidiService/Android.bp
index 48fc329..25c34c3 100644
--- a/media/packages/BluetoothMidiService/Android.bp
+++ b/media/packages/BluetoothMidiService/Android.bp
@@ -29,6 +29,5 @@
         "src/**/*.java",
     ],
     platform_apis: true,
-    certificate: "platform",
     manifest: "AndroidManifest.xml",
 }
diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml
index b88bf2a..fc96fd9 100644
--- a/media/packages/BluetoothMidiService/AndroidManifest.xml
+++ b/media/packages/BluetoothMidiService/AndroidManifest.xml
@@ -19,8 +19,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:tools="http://schemas.android.com/tools"
         package="com.android.bluetoothmidiservice"
-        android:versionCode="1"
-        android:versionName="R-initial"
         >
     <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
 
diff --git a/media/packages/BluetoothMidiService/AndroidManifestBase.xml b/media/packages/BluetoothMidiService/AndroidManifestBase.xml
index ebe62b0..bfb0546 100644
--- a/media/packages/BluetoothMidiService/AndroidManifestBase.xml
+++ b/media/packages/BluetoothMidiService/AndroidManifestBase.xml
@@ -18,8 +18,6 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.bluetoothmidiservice"
-          android:versionCode="1"
-          android:versionName="R-initial"
           >
     <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
     <application
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index 3eea513..51fda96 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -52,10 +52,10 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.ShadeControllerImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -92,7 +92,7 @@
             Context context,
             StatusBarStateController statusBarStateController,
             KeyguardBypassController bypassController,
-            NotificationGroupManager groupManager,
+            GroupMembershipManager groupManager,
             ConfigurationController configurationController) {
         return new HeadsUpManagerPhone(context, statusBarStateController, bypassController,
                 groupManager, configurationController);
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
index a2ba880..fef0324 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
@@ -16,20 +16,19 @@
 
 package com.android.systemui.car;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.car.settings.CarSettings;
-import android.content.ContentResolver;
-import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
-import android.provider.Settings;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
 
 import javax.inject.Inject;
 
@@ -40,30 +39,33 @@
 @SysUISingleton
 public class CarDeviceProvisionedControllerImpl extends DeviceProvisionedControllerImpl implements
         CarDeviceProvisionedController {
-    private static final Uri USER_SETUP_IN_PROGRESS_URI = Settings.Secure.getUriFor(
-            CarSettings.Secure.KEY_SETUP_WIZARD_IN_PROGRESS);
-    private final ContentObserver mCarSettingsObserver = new ContentObserver(
-            Dependency.get(Dependency.MAIN_HANDLER)) {
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri, int flags) {
-            if (USER_SETUP_IN_PROGRESS_URI.equals(uri)) {
-                notifyUserSetupInProgressChanged();
-            }
-        }
-    };
-    private final ContentResolver mContentResolver;
+    private final Uri mUserSetupInProgressUri;
+    private final ContentObserver mCarSettingsObserver;
+    private final Handler mMainHandler;
+    private final SecureSettings mSecureSettings;
 
     @Inject
-    public CarDeviceProvisionedControllerImpl(Context context, @Main Handler mainHandler,
-            BroadcastDispatcher broadcastDispatcher) {
-        super(context, mainHandler, broadcastDispatcher);
-        mContentResolver = context.getContentResolver();
+    public CarDeviceProvisionedControllerImpl(@Main Handler mainHandler,
+            BroadcastDispatcher broadcastDispatcher, GlobalSettings globalSetting,
+            SecureSettings secureSettings) {
+        super(mainHandler, broadcastDispatcher, globalSetting, secureSettings);
+        mMainHandler = mainHandler;
+        mSecureSettings = secureSettings;
+        mUserSetupInProgressUri = mSecureSettings.getUriFor(
+                CarSettings.Secure.KEY_SETUP_WIZARD_IN_PROGRESS);
+        mCarSettingsObserver = new ContentObserver(mMainHandler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri, int flags) {
+                if (mUserSetupInProgressUri.equals(uri)) {
+                    notifyUserSetupInProgressChanged();
+                }
+            }
+        };
     }
 
     @Override
     public boolean isUserSetupInProgress(int user) {
-        return Settings.Secure.getIntForUser(mContentResolver,
+        return mSecureSettings.getIntForUser(
                 CarSettings.Secure.KEY_SETUP_WIZARD_IN_PROGRESS, /* def= */ 0, user) != 0;
     }
 
@@ -73,7 +75,7 @@
     }
 
     @Override
-    public void addCallback(DeviceProvisionedListener listener) {
+    public void addCallback(@NonNull DeviceProvisionedListener listener) {
         super.addCallback(listener);
         if (listener instanceof CarDeviceProvisionedListener) {
             ((CarDeviceProvisionedListener) listener).onUserSetupInProgressChanged();
@@ -82,9 +84,9 @@
 
     @Override
     protected void startListening(int user) {
-        mContentResolver.registerContentObserver(
-                USER_SETUP_IN_PROGRESS_URI, /* notifyForDescendants= */ true, mCarSettingsObserver,
-                user);
+        mSecureSettings.registerContentObserverForUser(
+                mUserSetupInProgressUri, /* notifyForDescendants= */ true,
+                mCarSettingsObserver, user);
         // The SUW Flag observer is registered before super.startListening() so that the observer is
         // in place before DeviceProvisionedController starts to track user switches which avoids
         // an edge case where our observer gets registered twice.
@@ -94,16 +96,16 @@
     @Override
     protected void stopListening() {
         super.stopListening();
-        mContentResolver.unregisterContentObserver(mCarSettingsObserver);
+        mSecureSettings.unregisterContentObserver(mCarSettingsObserver);
     }
 
     @Override
     public void onUserSwitched(int newUserId) {
         super.onUserSwitched(newUserId);
-        mContentResolver.unregisterContentObserver(mCarSettingsObserver);
-        mContentResolver.registerContentObserver(
-                USER_SETUP_IN_PROGRESS_URI, /* notifyForDescendants= */ true, mCarSettingsObserver,
-                newUserId);
+        mSecureSettings.unregisterContentObserver(mCarSettingsObserver);
+        mSecureSettings.registerContentObserverForUser(
+                mUserSetupInProgressUri, /* notifyForDescendants= */ true,
+                mCarSettingsObserver, newUserId);
     }
 
     private void notifyUserSetupInProgressChanged() {
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index 276ddfb..dadbc22 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -18,7 +18,6 @@
 
 import android.car.Car;
 import android.car.user.CarUserManager;
-import android.content.Context;
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.Log;
@@ -28,20 +27,17 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.car.CarServiceProvider;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayViewController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -63,18 +59,14 @@
     private static final String TAG = "CarKeyguardViewController";
     private static final boolean DEBUG = true;
 
-    private final Context mContext;
     private final Handler mHandler;
     private final CarServiceProvider mCarServiceProvider;
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
-    private final LockPatternUtils mLockPatternUtils;
-    private final FalsingManager mFalsingManager;
-    private final Lazy<KeyguardBypassController> mKeyguardBypassControllerLazy;
-    private final DismissCallbackRegistry mDismissCallbackRegistry;
     private final ViewMediatorCallback mViewMediatorCallback;
     private final CarNavigationBarController mCarNavigationBarController;
+    private final KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
     // Needed to instantiate mBouncer.
     private final KeyguardBouncer.BouncerExpansionCallback
             mExpansionCallback = new KeyguardBouncer.BouncerExpansionCallback() {
@@ -107,7 +99,6 @@
 
     @Inject
     public CarKeyguardViewController(
-            Context context,
             @Main Handler mainHandler,
             CarServiceProvider carServiceProvider,
             OverlayViewGlobalStateController overlayViewGlobalStateController,
@@ -116,26 +107,18 @@
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
             ViewMediatorCallback viewMediatorCallback,
             CarNavigationBarController carNavigationBarController,
-            /* The params below are only used to reuse KeyguardBouncer */
-            LockPatternUtils lockPatternUtils,
-            DismissCallbackRegistry dismissCallbackRegistry,
-            FalsingManager falsingManager,
-            Lazy<KeyguardBypassController> keyguardBypassControllerLazy) {
+            KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) {
 
         super(R.id.keyguard_stub, overlayViewGlobalStateController);
 
-        mContext = context;
         mHandler = mainHandler;
         mCarServiceProvider = carServiceProvider;
         mKeyguardStateController = keyguardStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
-        mLockPatternUtils = lockPatternUtils;
-        mFalsingManager = falsingManager;
-        mKeyguardBypassControllerLazy = keyguardBypassControllerLazy;
-        mDismissCallbackRegistry = dismissCallbackRegistry;
         mViewMediatorCallback = viewMediatorCallback;
         mCarNavigationBarController = carNavigationBarController;
+        mKeyguardBouncerComponentFactory = keyguardBouncerComponentFactory;
 
         registerUserSwitchedListener();
     }
@@ -147,11 +130,9 @@
 
     @Override
     public void onFinishInflate() {
-        mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
-                mViewMediatorCallback, mLockPatternUtils,
-                getLayout().findViewById(R.id.keyguard_container), mDismissCallbackRegistry,
-                mExpansionCallback, mKeyguardStateController, mFalsingManager,
-                mKeyguardBypassControllerLazy.get());
+        mBouncer = mKeyguardBouncerComponentFactory
+                .build(getLayout().findViewById(R.id.keyguard_container), mExpansionCallback)
+                .createKeyguardBouncer();
         mBiometricUnlockControllerLazy.get().setKeyguardViewController(this);
     }
 
@@ -359,9 +340,8 @@
     public void registerStatusBar(StatusBar statusBar, ViewGroup container,
             NotificationPanelViewController notificationPanelViewController,
             BiometricUnlockController biometricUnlockController,
-            DismissCallbackRegistry dismissCallbackRegistry, ViewGroup lockIconContainer,
-            View notificationContainer, KeyguardBypassController bypassController,
-            FalsingManager falsingManager) {
+            ViewGroup lockIconContainer,
+            View notificationContainer, KeyguardBypassController bypassController) {
         // no-op
     }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index 38e1a48..fe4cba8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -91,7 +91,6 @@
     private RecyclerView mNotificationList;
     private NotificationViewController mNotificationViewController;
 
-    private boolean mIsTracking;
     private boolean mNotificationListAtEnd;
     private float mFirstTouchDownOnGlassPane;
     private boolean mNotificationListAtEndAtTimeOfTouch;
@@ -306,7 +305,7 @@
                 mFirstTouchDownOnGlassPane = event.getRawX();
                 mNotificationListAtEndAtTimeOfTouch = mNotificationListAtEnd;
                 // Reset the tracker when there is a touch down on the glass pane.
-                mIsTracking = false;
+                setIsTracking(false);
                 // Pass the down event to gesture detector so that it knows where the touch event
                 // started.
                 closeGestureDetector.onTouchEvent(event);
@@ -341,15 +340,15 @@
 
             // If the card is swiping we should not allow the notification shade to close.
             // Hence setting mNotificationListAtEndAtTimeOfTouch to false will stop that
-            // for us. We are also checking for mIsTracking because while swiping the
+            // for us. We are also checking for isTracking() because while swiping the
             // notification shade to close if the user goes a bit horizontal while swiping
             // upwards then also this should close.
-            if (mIsNotificationCardSwiping && !mIsTracking) {
+            if (mIsNotificationCardSwiping && !isTracking()) {
                 mNotificationListAtEndAtTimeOfTouch = false;
             }
 
             boolean handled = closeGestureDetector.onTouchEvent(event);
-            boolean isTracking = mIsTracking;
+            boolean isTracking = isTracking();
             Rect rect = getLayout().getClipBounds();
             float clippedHeight = 0;
             if (rect != null) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
index 5bd8797..023b5b4 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
@@ -131,7 +131,7 @@
     }
 
     private List<UserInfo> getUsersForUserGrid() {
-        return mUserManager.getUsers(/* excludeDying= */ true)
+        return mUserManager.getAliveUsers()
                 .stream()
                 .filter(UserInfo::supportsSwitchToByUser)
                 .collect(Collectors.toList());
@@ -338,7 +338,7 @@
                 maxSupportedUsers -= 1;
             }
 
-            List<UserInfo> users = mUserManager.getUsers(/* excludeDying= */ true);
+            List<UserInfo> users = mUserManager.getAliveUsers();
 
             // Count all users that are managed profiles of another user.
             int managedProfilesCount = 0;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
index bde31f1..1b00c63 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
@@ -297,14 +297,17 @@
             float from = getCurrentStartPosition(rect);
             if (from != to) {
                 animate(from, to, velocity, isClosing);
-                return;
             }
+
+            // If we swipe down the notification panel all the way to the bottom of the screen
+            // (i.e. from == to), then we have finished animating the panel.
+            return;
         }
 
         // We will only be here if the shade is being opened programmatically or via button when
         // height of the layout was not calculated.
-        ViewTreeObserver notificationTreeObserver = getLayout().getViewTreeObserver();
-        notificationTreeObserver.addOnGlobalLayoutListener(
+        ViewTreeObserver panelTreeObserver = getLayout().getViewTreeObserver();
+        panelTreeObserver.addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
@@ -507,6 +510,11 @@
         return mIsTracking;
     }
 
+    /** Sets whether the panel is currently tracking or not. */
+    protected final void setIsTracking(boolean isTracking) {
+        mIsTracking = isTracking;
+    }
+
     /** Returns {@code true} if the panel is currently animating. */
     protected final boolean isAnimating() {
         return mIsAnimating;
@@ -545,7 +553,7 @@
             }
             setPanelVisible(true);
 
-            // clips the view for the notification shade when the user scrolls to open.
+            // clips the view for the panel when the user scrolls to open.
             setViewClipBounds((int) event2.getRawY());
 
             // Initially the scroll starts with height being zero. This checks protects from divide
@@ -600,11 +608,11 @@
                 boolean isInClosingDirection = mAnimateDirection * distanceY > 0;
 
                 // This check is to figure out if onScroll was called while swiping the card at
-                // bottom of the list. At that time we should not allow notification shade to
+                // bottom of the panel. At that time we should not allow panel to
                 // close. We are also checking for the upwards swipe gesture here because it is
-                // possible if a user is closing the notification shade and while swiping starts
+                // possible if a user is closing the panel and while swiping starts
                 // to open again but does not fling. At that time we should allow the
-                // notification shade to close fully or else it would stuck in between.
+                // panel to close fully or else it would stuck in between.
                 if (Math.abs(getLayout().getHeight() - y)
                         > SWIPE_DOWN_MIN_DISTANCE && isInClosingDirection) {
                     setViewClipBounds((int) y);
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
index 62dc236..63d4004 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -35,20 +34,17 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.car.CarServiceProvider;
 import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -58,31 +54,36 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import dagger.Lazy;
-
 @CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
 public class CarKeyguardViewControllerTest extends SysuiTestCase {
 
-    private TestableCarKeyguardViewController mCarKeyguardViewController;
+    private CarKeyguardViewController mCarKeyguardViewController;
 
     @Mock
     private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
     @Mock
-    private KeyguardBouncer mBouncer;
-    @Mock
-    private CarNavigationBarController mCarNavigationBarController;
-    @Mock
     private CarKeyguardViewController.OnKeyguardCancelClickedListener mCancelClickedListener;
+    @Mock
+    private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
+    @Mock
+    private KeyguardBouncerComponent mKeyguardBouncerComponent;
+    @Mock
+    private KeyguardBouncer mBouncer;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mCarKeyguardViewController = new TestableCarKeyguardViewController(
-                mContext,
+        when(mKeyguardBouncerComponentFactory.build(
+                any(ViewGroup.class),
+                any(KeyguardBouncer.BouncerExpansionCallback.class)))
+                .thenReturn(mKeyguardBouncerComponent);
+        when(mKeyguardBouncerComponent.createKeyguardBouncer()).thenReturn(mBouncer);
+
+        mCarKeyguardViewController = new CarKeyguardViewController(
                 Handler.getMain(),
                 mock(CarServiceProvider.class),
                 mOverlayViewGlobalStateController,
@@ -91,10 +92,7 @@
                 () -> mock(BiometricUnlockController.class),
                 mock(ViewMediatorCallback.class),
                 mock(CarNavigationBarController.class),
-                mock(LockPatternUtils.class),
-                mock(DismissCallbackRegistry.class),
-                mock(FalsingManager.class),
-                () -> mock(KeyguardBypassController.class)
+                mKeyguardBouncerComponentFactory
         );
         mCarKeyguardViewController.inflate((ViewGroup) LayoutInflater.from(mContext).inflate(
                 R.layout.sysui_overlay_window, /* root= */ null));
@@ -202,33 +200,4 @@
 
         verify(mBouncer).hide(/* destroyView= */ true);
     }
-
-    private class TestableCarKeyguardViewController extends CarKeyguardViewController {
-
-        TestableCarKeyguardViewController(Context context,
-                Handler mainHandler,
-                CarServiceProvider carServiceProvider,
-                OverlayViewGlobalStateController overlayViewGlobalStateController,
-                KeyguardStateController keyguardStateController,
-                KeyguardUpdateMonitor keyguardUpdateMonitor,
-                Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
-                ViewMediatorCallback viewMediatorCallback,
-                CarNavigationBarController carNavigationBarController,
-                LockPatternUtils lockPatternUtils,
-                DismissCallbackRegistry dismissCallbackRegistry,
-                FalsingManager falsingManager,
-                Lazy<KeyguardBypassController> keyguardBypassControllerLazy) {
-            super(context, mainHandler, carServiceProvider, overlayViewGlobalStateController,
-                    keyguardStateController, keyguardUpdateMonitor, biometricUnlockControllerLazy,
-                    viewMediatorCallback, carNavigationBarController, lockPatternUtils,
-                    dismissCallbackRegistry, falsingManager, keyguardBypassControllerLazy);
-        }
-
-        @Override
-        public void onFinishInflate() {
-            super.onFinishInflate();
-            setKeyguardBouncer(CarKeyguardViewControllerTest.this.mBouncer);
-        }
-    }
-
 }
diff --git a/packages/CompanionDeviceManager/TEST_MAPPING b/packages/CompanionDeviceManager/TEST_MAPPING
new file mode 100644
index 0000000..63f54fa
--- /dev/null
+++ b/packages/CompanionDeviceManager/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsOsTestCases",
+      "options": [
+        {
+          "include-filter": "android.os.cts.CompanionDeviceManagerTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index bc58bfc..c57d4ad 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -13,6 +13,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 
 import android.content.Context;
 import android.content.Intent;
@@ -250,6 +251,10 @@
                     statusLabel = mContext.getString(R.string.wifi_status_no_internet);
                 }
                 return;
+            } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null
+                    && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+                statusLabel = mContext.getString(R.string.wifi_connected_low_quality);
+                return;
             }
         }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 807fbed..9c92b46 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2576,7 +2576,7 @@
         public void syncSsaidTableOnStart() {
             synchronized (mLock) {
                 // Verify that each user's packages and ssaid's are in sync.
-                for (UserInfo user : mUserManager.getUsers(true)) {
+                for (UserInfo user : mUserManager.getAliveUsers()) {
                     // Get all uids for the user's packages.
                     final List<PackageInfo> packages;
                     try {
@@ -3007,7 +3007,7 @@
 
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    List<UserInfo> users = mUserManager.getUsers(true);
+                    List<UserInfo> users = mUserManager.getAliveUsers();
 
                     final int userCount = users.size();
                     for (int i = 0; i < userCount; i++) {
@@ -3244,7 +3244,7 @@
             // is a singleton generation entry for the global settings which
             // is already incremented be the caller.
             final Uri uri = getNotificationUriFor(key, name);
-            final List<UserInfo> users = mUserManager.getUsers(/*excludeDying*/ true);
+            final List<UserInfo> users = mUserManager.getAliveUsers();
             for (int i = 0; i < users.size(); i++) {
                 final int userId = users.get(i).id;
                 if (mUserManager.isUserRunning(UserHandle.of(userId))) {
@@ -3255,7 +3255,7 @@
         }
 
         private void notifyLocationChangeForRunningUsers() {
-            final List<UserInfo> users = mUserManager.getUsers(/*excludeDying=*/ true);
+            final List<UserInfo> users = mUserManager.getAliveUsers();
 
             for (int i = 0; i < users.size(); i++) {
                 final int userId = users.get(i).id;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index b048333..0517227 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -796,16 +796,6 @@
                 securityMode != SecurityMode.None && newView.needsInput());
     }
 
-    private KeyguardSecurityViewFlipper getFlipper() {
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-            if (child instanceof KeyguardSecurityViewFlipper) {
-                return (KeyguardSecurityViewFlipper) child;
-            }
-        }
-        return null;
-    }
-
     private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() {
         public void userActivity() {
             if (mSecurityCallback != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 6a90d00..9766ee1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -21,9 +21,7 @@
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 
-import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
@@ -180,22 +178,18 @@
 
     /**
      * Registers the StatusBar to which this Keyguard View is mounted.
-     *
      * @param statusBar
      * @param container
      * @param notificationPanelViewController
      * @param biometricUnlockController
-     * @param dismissCallbackRegistry
      * @param lockIconContainer
      * @param notificationContainer
      * @param bypassController
-     * @param falsingManager
      */
     void registerStatusBar(StatusBar statusBar,
             ViewGroup container,
             NotificationPanelViewController notificationPanelViewController,
             BiometricUnlockController biometricUnlockController,
-            DismissCallbackRegistry dismissCallbackRegistry,
             ViewGroup lockIconContainer, View notificationContainer,
-            KeyguardBypassController bypassController, FalsingManager falsingManager);
+            KeyguardBypassController bypassController);
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ContainerView.java b/packages/SystemUI/src/com/android/keyguard/dagger/ContainerView.java
new file mode 100644
index 0000000..e65f19d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ContainerView.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ContainerView {
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
new file mode 100644
index 0000000..84deaca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
@@ -0,0 +1,42 @@
+/*
+ * 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.dagger;
+
+import android.view.ViewGroup;
+
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Dagger Subcomponent for the {@link KeyguardBouncer}.
+ */
+@Subcomponent
+@KeyguardBouncerScope
+public interface KeyguardBouncerComponent {
+    /** Simple factory for {@link KeyguardBouncerComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        KeyguardBouncerComponent build(
+                @BindsInstance @ContainerView ViewGroup container,
+                @BindsInstance KeyguardBouncer.BouncerExpansionCallback bouncerExpansionCallback);
+    }
+
+    /** */
+    KeyguardBouncer createKeyguardBouncer();
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerScope.java
new file mode 100644
index 0000000..207ac28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerScope.java
@@ -0,0 +1,32 @@
+/*
+ * 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.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the StatusBarComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface KeyguardBouncerScope {}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 27809b5..ed78c94 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -77,6 +77,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
@@ -88,7 +89,6 @@
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -276,7 +276,7 @@
     @Inject Lazy<StatusBarStateController> mStatusBarStateController;
     @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager;
     @Inject Lazy<NotificationGroupAlertTransferHelper> mNotificationGroupAlertTransferHelper;
-    @Inject Lazy<NotificationGroupManager> mNotificationGroupManager;
+    @Inject Lazy<NotificationGroupManagerLegacy> mNotificationGroupManager;
     @Inject Lazy<VisualStabilityManager> mVisualStabilityManager;
     @Inject Lazy<NotificationGutsManager> mNotificationGutsManager;
     @Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
@@ -468,7 +468,7 @@
         mProviders.put(NotificationLockscreenUserManager.class,
                 mNotificationLockscreenUserManager::get);
         mProviders.put(VisualStabilityManager.class, mVisualStabilityManager::get);
-        mProviders.put(NotificationGroupManager.class, mNotificationGroupManager::get);
+        mProviders.put(NotificationGroupManagerLegacy.class, mNotificationGroupManager::get);
         mProviders.put(NotificationGroupAlertTransferHelper.class,
                 mNotificationGroupAlertTransferHelper::get);
         mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index f5c3649..f159499 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -19,25 +19,16 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Handler;
-import android.os.Looper;
 import android.util.Log;
-import android.view.ViewGroup;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.dagger.DaggerGlobalRootComponent;
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.WMComponent;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 
 /**
@@ -83,11 +74,16 @@
 
     public SystemUIFactory() {}
 
-    private void init(Context context) {
+    private void init(Context context) throws ExecutionException, InterruptedException {
         mRootComponent = buildGlobalRootComponent(context);
+        // Stand up WMComponent
         mWMComponent = mRootComponent.getWMComponentBuilder().build();
-        // TODO: use WMComponent to pass APIs into the SysUIComponent.
-        mSysUIComponent = mRootComponent.getSysUIComponent().build();
+
+        // And finally, retrieve whatever SysUI needs from WMShell and build SysUI.
+        // TODO: StubAPIClass is just a placeholder.
+        mSysUIComponent = mRootComponent.getSysUIComponent()
+                .setStubAPIClass(mWMComponent.createStubAPIClass())
+                .build();
 
         // Every other part of our codebase currently relies on Dependency, so we
         // really need to ensure the Dependency gets initialized early on.
@@ -101,10 +97,15 @@
                 .build();
     }
 
+
     public GlobalRootComponent getRootComponent() {
         return mRootComponent;
     }
 
+    public WMComponent getWMComponent() {
+        return mWMComponent;
+    }
+
     public SysUIComponent getSysUIComponent() {
         return mSysUIComponent;
     }
@@ -129,17 +130,4 @@
             Handler uiHandler) {
         return new ScreenshotNotificationSmartActionsProvider();
     }
-
-    public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback,
-            LockPatternUtils lockPatternUtils, ViewGroup container,
-            DismissCallbackRegistry dismissCallbackRegistry,
-            KeyguardBouncer.BouncerExpansionCallback expansionCallback,
-            KeyguardStateController keyguardStateController, FalsingManager falsingManager,
-            KeyguardBypassController bypassController) {
-        return new KeyguardBouncer(context, callback, lockPatternUtils, container,
-                dismissCallbackRegistry, falsingManager,
-                expansionCallback, keyguardStateController,
-                Dependency.get(KeyguardUpdateMonitor.class), bypassController,
-                new Handler(Looper.getMainLooper()));
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 9e9d85a..c81b7ce 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -104,11 +104,11 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.coordinator.BubbleCoordinator;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -164,7 +164,7 @@
     private final BubbleTaskStackListener mTaskStackListener;
     private BubbleExpandListener mExpandListener;
     @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
-    private final NotificationGroupManager mNotificationGroupManager;
+    private final NotificationGroupManagerLegacy mNotificationGroupManager;
     private final ShadeController mShadeController;
     private final FloatingContentCoordinator mFloatingContentCoordinator;
     private final BubbleDataRepository mDataRepository;
@@ -355,7 +355,7 @@
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
-            NotificationGroupManager groupManager,
+            NotificationGroupManagerLegacy groupManager,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
             FeatureFlags featureFlags,
@@ -588,11 +588,11 @@
                     }
                 });
 
-        mNotificationGroupManager.addOnGroupChangeListener(
-                new NotificationGroupManager.OnGroupChangeListener() {
+        mNotificationGroupManager.registerGroupChangeListener(
+                new NotificationGroupManagerLegacy.OnGroupChangeListener() {
                     @Override
                     public void onGroupSuppressionChanged(
-                            NotificationGroupManager.NotificationGroup group,
+                            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.
@@ -650,8 +650,7 @@
                 // 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.getSbn());
+                NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(entry);
                 if (summary != null) {
                     ArrayList<NotificationEntry> summaryChildren =
                             mNotificationGroupManager.getLogicalChildren(summary.getSbn());
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index eecc41c..9efc3c2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -34,8 +34,8 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -62,7 +62,7 @@
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
-            NotificationGroupManager groupManager,
+            NotificationGroupManagerLegacy groupManager,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
             FeatureFlags featureFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
index fd4a409..c5dc8cc 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.dagger;
 
+import com.android.systemui.util.concurrency.GlobalConcurrencyModule;
+
 import dagger.Module;
 
 /**
@@ -33,6 +35,8 @@
  *
  * Please use discretion when adding things to the global scope.
  */
-@Module(includes = {FrameworkServicesModule.class})
+@Module(includes = {
+        FrameworkServicesModule.class,
+        GlobalConcurrencyModule.class})
 public class GlobalModule {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index 36fd337..00fdf55 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import com.android.systemui.util.concurrency.ThreadFactory;
+
 import javax.inject.Singleton;
 
 import dagger.BindsInstance;
@@ -53,4 +55,9 @@
      * Builder for a SysuiComponent.
      */
     SysUIComponent.Builder getSysUIComponent();
+
+    /**
+     * Build a {@link ThreadFactory}.
+     */
+    ThreadFactory createThreadFactory();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index a8ed043..2622593 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.InjectionInflationController;
 
+import dagger.BindsInstance;
 import dagger.Subcomponent;
 
 /**
@@ -46,6 +47,9 @@
      */
     @Subcomponent.Builder
     interface Builder {
+        @BindsInstance
+        Builder setStubAPIClass(WMComponent.StubAPIClass stubAPIClass);
+
         SysUIComponent build();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index a021114..2c0b04f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -46,11 +46,11 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.ShadeControllerImpl;
@@ -143,7 +143,7 @@
             Context context,
             StatusBarStateController statusBarStateController,
             KeyguardBypassController bypassController,
-            NotificationGroupManager groupManager,
+            GroupMembershipManager groupManager,
             ConfigurationController configurationController) {
         return new HeadsUpManagerPhone(context, statusBarStateController, bypassController,
                 groupManager, configurationController);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 3afe721..8f4e738 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dagger;
 
+import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.appops.dagger.AppOpsModule;
@@ -42,7 +43,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
 import com.android.systemui.tuner.dagger.TunerModule;
-import com.android.systemui.util.concurrency.ConcurrencyModule;
+import com.android.systemui.util.concurrency.SysUIConcurrencyModule;
 import com.android.systemui.util.dagger.UtilModule;
 import com.android.systemui.util.sensors.SensorModule;
 import com.android.systemui.util.settings.SettingsUtilModule;
@@ -62,7 +63,6 @@
 @Module(includes = {
             AppOpsModule.class,
             AssistModule.class,
-            ConcurrencyModule.class,
             ControlsModule.class,
             DemoModeModule.class,
             LogModule.class,
@@ -74,6 +74,7 @@
             SettingsModule.class,
             SettingsUtilModule.class,
             StatusBarPolicyModule.class,
+            SysUIConcurrencyModule.class,
             TunerModule.class,
             UtilModule.class,
             VolumeModule.class
@@ -82,6 +83,7 @@
                 NotificationRowComponent.class,
                 DozeComponent.class,
                 ExpandableNotificationRowComponent.class,
+                KeyguardBouncerComponent.class,
                 NotificationShelfComponent.class,
                 FragmentService.FragmentCreator.class})
 public abstract class SystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 929b61a..ad90eff 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.dagger;
 
+import javax.inject.Inject;
+
 import dagger.Subcomponent;
 
 /**
@@ -32,4 +34,19 @@
     interface Builder {
         WMComponent build();
     }
+
+
+    /**
+     *  Example class used for passing an API to SysUI from WMShell.
+     *
+     *  TODO: Remove this once real WM classes are ready to go.
+     **/
+    @WMSingleton
+    class StubAPIClass {
+        @Inject
+        StubAPIClass() {}
+    }
+
+    /** Create a StubAPIClass. */
+    StubAPIClass createStubAPIClass();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 342818d..d9f9717 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -30,6 +30,8 @@
 import android.provider.Settings;
 import android.view.Display;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.doze.dagger.BrightnessSensor;
 import com.android.systemui.doze.dagger.DozeScope;
 import com.android.systemui.doze.dagger.WrappedService;
@@ -75,7 +77,7 @@
 
     @Inject
     public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service,
-            AsyncSensorManager sensorManager, @BrightnessSensor Sensor lightSensor,
+            AsyncSensorManager sensorManager, @Nullable @BrightnessSensor Sensor lightSensor,
             DozeHost host, Handler handler, AlwaysOnDisplayPolicy alwaysOnDisplayPolicy) {
         mContext = context;
         mDozeService = service;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 04f7c36..b899460 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -20,6 +20,8 @@
 import android.hardware.Sensor;
 import android.os.Handler;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeAuthRemover;
@@ -91,6 +93,7 @@
 
     @Provides
     @BrightnessSensor
+    @Nullable
     static Sensor providesBrightnessSensor(AsyncSensorManager sensorManager, Context context) {
         return DozeSensors.findSensorWithType(sensorManager,
                 context.getString(R.string.doze_brightness_sensor_type));
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3340791..2705f07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2184,8 +2184,8 @@
             BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer,
             View notificationContainer, KeyguardBypassController bypassController) {
         mKeyguardViewControllerLazy.get().registerStatusBar(statusBar, container, panelView,
-                biometricUnlockController, mDismissCallbackRegistry, lockIconContainer,
-                notificationContainer, bypassController, mFalsingManager);
+                biometricUnlockController, lockIconContainer,
+                notificationContainer, bypassController);
         return mKeyguardViewControllerLazy.get();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index a003d83..e5a9ac1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -172,7 +172,6 @@
                     // This view is inactive, let's remove this! This happens e.g when dismissing /
                     // timing out a view. We still have the data around because resumption could
                     // be on, but we should save the resources and release this.
-                    oldKey?.let { MediaPlayerData.removeMediaPlayer(it) }
                     onMediaDataRemoved(key)
                 } else {
                     addOrUpdatePlayer(key, oldKey, data)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index aec3543..c7e7817 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -662,7 +662,7 @@
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
                 PixelFormat.TRANSLUCENT);
         mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId());
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
index 0354c72..8ef9b09 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
@@ -158,7 +158,9 @@
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 mDisplaySize.x, mTutorialAreaHeight, 0, 0,
                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                 PixelFormat.TRANSLUCENT);
         lp.gravity = Gravity.TOP | Gravity.LEFT;
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index e24fbc6..7dd4edd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -573,7 +573,12 @@
 
     private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
             Insets screenInsets, boolean showFlash) {
-        dismissScreenshot("new screenshot requested", true);
+        if (mScreenshotLayout.isAttachedToWindow()) {
+            if (!mDismissAnimation.isRunning()) { // if we didn't already dismiss for another reason
+                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED);
+            }
+            dismissScreenshot("new screenshot requested", true);
+        }
 
         mScreenBitmap = screenshot;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 6b42f2e..74e0229 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -59,7 +59,9 @@
     @UiEvent(doc = "screenshot interaction timed out")
     SCREENSHOT_INTERACTION_TIMEOUT(310),
     @UiEvent(doc = "screenshot explicitly dismissed")
-    SCREENSHOT_EXPLICIT_DISMISSAL(311);
+    SCREENSHOT_EXPLICIT_DISMISSAL(311),
+    @UiEvent(doc = "screenshot reentered for new screenshot")
+    SCREENSHOT_REENTERED(640);
 
     private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 852c055..38c7e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -36,12 +36,12 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.util.Assert;
 
 import java.util.ArrayList;
@@ -72,7 +72,7 @@
     // Dependencies:
     private final DynamicChildBindController mDynamicChildBindController;
     protected final NotificationLockscreenUserManager mLockscreenUserManager;
-    protected final NotificationGroupManager mGroupManager;
+    protected final NotificationGroupManagerLegacy mGroupManager;
     protected final VisualStabilityManager mVisualStabilityManager;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final NotificationEntryManager mEntryManager;
@@ -107,7 +107,7 @@
             Context context,
             @Main Handler mainHandler,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
-            NotificationGroupManager groupManager,
+            NotificationGroupManagerLegacy groupManager,
             VisualStabilityManager visualStabilityManager,
             StatusBarStateController statusBarStateController,
             NotificationEntryManager notificationEntryManager,
@@ -187,13 +187,13 @@
             ent.setSensitive(sensitive, deviceSensitive);
             ent.getRow().setNeedsRedaction(needsRedaction);
             mLowPriorityInflationHelper.recheckLowPriorityViewAndInflate(ent, ent.getRow());
-            boolean isChildInGroup = mGroupManager.isChildInGroupWithSummary(ent.getSbn());
+            boolean isChildInGroup = mGroupManager.isChildInGroup(ent);
 
             boolean groupChangesAllowed =
                     mVisualStabilityManager.areGroupChangesAllowed() // user isn't looking at notifs
                     || !ent.hasFinishedInitialization(); // notif recently added
 
-            NotificationEntry parent = mGroupManager.getGroupSummary(ent.getSbn());
+            NotificationEntry parent = mGroupManager.getGroupSummary(ent);
             if (!groupChangesAllowed) {
                 // We don't to change groups while the user is looking at them
                 boolean wasChildInGroup = ent.isChildInGroup();
@@ -431,8 +431,7 @@
         while(!stack.isEmpty()) {
             ExpandableNotificationRow row = stack.pop();
             NotificationEntry entry = row.getEntry();
-            boolean isChildNotification =
-                    mGroupManager.isChildInGroupWithSummary(entry.getSbn());
+            boolean isChildNotification = mGroupManager.isChildInGroup(entry);
 
             if (!onKeyguard) {
                 // If mAlwaysExpandNonGroupedNotification is false, then only expand the
@@ -448,9 +447,8 @@
             boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry);
             if (!showOnKeyguard) {
                 // min priority notifications should show if their summary is showing
-                if (mGroupManager.isChildInGroupWithSummary(entry.getSbn())) {
-                    NotificationEntry summary = mGroupManager.getLogicalGroupSummary(
-                            entry.getSbn());
+                if (mGroupManager.isChildInGroup(entry)) {
+                    NotificationEntry summary = mGroupManager.getLogicalGroupSummary(entry);
                     if (summary != null && mLockscreenUserManager.shouldShowOnKeyguard(summary)) {
                         showOnKeyguard = true;
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index db2875a3..d15b847 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -44,12 +44,12 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
@@ -157,7 +157,7 @@
             Context context,
             @Main Handler mainHandler,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
-            NotificationGroupManager groupManager,
+            NotificationGroupManagerLegacy groupManager,
             VisualStabilityManager visualStabilityManager,
             StatusBarStateController statusBarStateController,
             NotificationEntryManager notificationEntryManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index c68625c..433c8b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationContentView
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
-import com.android.systemui.statusbar.phone.NotificationGroupManager
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
 import java.util.concurrent.ConcurrentHashMap
 import javax.inject.Inject
 
@@ -64,7 +64,7 @@
 @SysUISingleton
 class ConversationNotificationManager @Inject constructor(
     private val notificationEntryManager: NotificationEntryManager,
-    private val notificationGroupManager: NotificationGroupManager,
+    private val notificationGroupManager: NotificationGroupManagerLegacy,
     private val context: Context,
     @Main private val mainHandler: Handler
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index b5f1c7f..e1e77b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -48,13 +48,13 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.leak.LeakDetector;
 
@@ -139,7 +139,7 @@
     private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
 
     private final KeyguardEnvironment mKeyguardEnvironment;
-    private final NotificationGroupManager mGroupManager;
+    private final NotificationGroupManagerLegacy mGroupManager;
     private final NotificationRankingManager mRankingManager;
     private final FeatureFlags mFeatureFlags;
     private final ForegroundServiceDismissalFeatureController mFgsFeatureController;
@@ -199,7 +199,7 @@
      */
     public NotificationEntryManager(
             NotificationEntryManagerLogger logger,
-            NotificationGroupManager groupManager,
+            NotificationGroupManagerLegacy groupManager,
             NotificationRankingManager rankingManager,
             KeyguardEnvironment keyguardEnvironment,
             FeatureFlags featureFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 590ccf83..73c7fd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -34,7 +34,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 
 import javax.inject.Inject;
@@ -46,8 +45,6 @@
 @SysUISingleton
 public class NotificationFilter {
 
-    private final NotificationGroupManager mGroupManager = Dependency.get(
-            NotificationGroupManager.class);
     private final StatusBarStateController mStatusBarStateController;
     private final Boolean mIsMediaFlagEnabled;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 8ce9d94..789e78e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -60,6 +60,7 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 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.collection.notifcollection.NotifDismissInterceptor;
@@ -69,7 +70,6 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
 import com.android.systemui.statusbar.notification.stack.PriorityBucket;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -430,7 +430,7 @@
      * Get the children that are actually attached to this notification's row.
      *
      * TODO: Seems like most callers here should probably be using
-     * {@link NotificationGroupManager#getChildren}
+     * {@link NotificationGroupManagerLegacy#getChildren}
      */
     public @Nullable List<NotificationEntry> getAttachedNotifChildren() {
         if (row == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index bab2686..fb42c42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.notification.stack.PriorityBucket
-import com.android.systemui.statusbar.phone.NotificationGroupManager
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import dagger.Lazy
 import java.util.Objects
@@ -52,7 +52,7 @@
  */
 open class NotificationRankingManager @Inject constructor(
     private val mediaManagerLazy: Lazy<NotificationMediaManager>,
-    private val groupManager: NotificationGroupManager,
+    private val groupManager: NotificationGroupManagerLegacy,
     private val headsUpManager: HeadsUpManager,
     private val notifFilter: NotificationFilter,
     private val logger: NotificationEntryManagerLogger,
@@ -191,7 +191,7 @@
     private fun NotificationEntry.isConversation() = getPeopleNotificationType() != TYPE_NON_PERSON
 
     private fun NotificationEntry.getPeopleNotificationType() =
-            peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
+            peopleNotificationIdentifier.getPeopleNotificationType(this)
 
     private fun NotificationEntry.isHighPriority() =
             highPriorityProvider.isHighPriority(this)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index dea1162..3aaa9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -53,8 +53,7 @@
     }
 
     private fun isConversation(entry: NotificationEntry): Boolean =
-        peopleNotificationIdentifier.getPeopleNotificationType(entry.sbn, entry.ranking) !=
-            TYPE_NON_PERSON
+        peopleNotificationIdentifier.getPeopleNotificationType(entry) != TYPE_NON_PERSON
 
     companion object {
         private const val TAG = "ConversationCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
index 6089aa2..aec2647 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
@@ -20,10 +20,10 @@
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.RowContentBindParams;
 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 
 import javax.inject.Inject;
 
@@ -34,13 +34,13 @@
 @SysUISingleton
 public class LowPriorityInflationHelper {
     private final FeatureFlags mFeatureFlags;
-    private final NotificationGroupManager mGroupManager;
+    private final NotificationGroupManagerLegacy mGroupManager;
     private final RowContentBindStage mRowContentBindStage;
 
     @Inject
     LowPriorityInflationHelper(
             FeatureFlags featureFlags,
-            NotificationGroupManager groupManager,
+            NotificationGroupManagerLegacy groupManager,
             RowContentBindStage rowContentBindStage) {
         mFeatureFlags = featureFlags;
         mGroupManager = groupManager;
@@ -78,7 +78,7 @@
         if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
             isGroupChild = (entry.getParent() != GroupEntry.ROOT_ENTRY);
         } else {
-            isGroupChild = mGroupManager.isChildInGroupWithSummary(entry.getSbn());
+            isGroupChild = mGroupManager.isChildInGroup(entry);
         }
         return entry.isAmbient() && !isGroupChild;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index c44c59c..21d54c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.statusbar.notification.collection.legacy;
 
 import android.annotation.Nullable;
 import android.service.notification.StatusBarNotification;
@@ -22,14 +22,17 @@
 import android.util.Log;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
@@ -37,6 +40,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
@@ -46,13 +50,19 @@
 
 /**
  * A class to handle notifications and their corresponding groups.
+ * This includes:
+ * 1. Determining whether an entry is a member of a group and whether it is a summary or a child
+ * 2. Tracking group expansion states
  */
 @SysUISingleton
-public class NotificationGroupManager implements OnHeadsUpChangedListener, StateListener {
+public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, StateListener,
+        GroupMembershipManager, GroupExpansionManager, Dumpable {
 
     private static final String TAG = "NotificationGroupManager";
     private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
-    private final ArraySet<OnGroupChangeListener> mListeners = new ArraySet<>();
+    private final ArraySet<OnGroupExpansionChangeListener> mExpansionChangeListeners =
+            new ArraySet<>();
+    private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>();
     private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
     private int mBarState = -1;
     private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
@@ -61,7 +71,7 @@
     @Nullable private BubbleController mBubbleController = null;
 
     @Inject
-    public NotificationGroupManager(
+    public NotificationGroupManagerLegacy(
             StatusBarStateController statusBarStateController,
             Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier) {
         statusBarStateController.addCallback(this);
@@ -77,15 +87,19 @@
 
     /**
      * Add a listener for changes to groups.
-     *
-     * @param listener listener to add
      */
-    public void addOnGroupChangeListener(OnGroupChangeListener listener) {
-        mListeners.add(listener);
+    public void registerGroupChangeListener(OnGroupChangeListener listener) {
+        mGroupChangeListeners.add(listener);
     }
 
-    public boolean isGroupExpanded(StatusBarNotification sbn) {
-        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
+    @Override
+    public void registerGroupExpansionChangeListener(OnGroupExpansionChangeListener listener) {
+        mExpansionChangeListeners.add(listener);
+    }
+
+    @Override
+    public boolean isGroupExpanded(NotificationEntry entry) {
+        NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
         if (group == null) {
             return false;
         }
@@ -103,8 +117,9 @@
         return group.expanded;
     }
 
-    public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) {
-        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
+    @Override
+    public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
+        NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
         if (group == null) {
             return;
         }
@@ -114,12 +129,15 @@
     private void setGroupExpanded(NotificationGroup group, boolean expanded) {
         group.expanded = expanded;
         if (group.summary != null) {
-            for (OnGroupChangeListener listener : mListeners) {
-                listener.onGroupExpansionChanged(group.summary.getRow(), expanded);
+            for (OnGroupExpansionChangeListener listener : mExpansionChangeListeners) {
+                listener.onGroupExpansionChange(group.summary.getRow(), expanded);
             }
         }
     }
 
+    /**
+     * When we want to remove an entry from being tracked for grouping
+     */
     public void onEntryRemoved(NotificationEntry removed) {
         onEntryRemovedInternal(removed, removed.getSbn());
         mIsolatedEntries.remove(removed.getKey());
@@ -158,7 +176,7 @@
         if (group.children.isEmpty()) {
             if (group.summary == null) {
                 mGroupMap.remove(groupKey);
-                for (OnGroupChangeListener listener : mListeners) {
+                for (OnGroupChangeListener listener : mGroupChangeListeners) {
                     listener.onGroupRemoved(group, groupKey);
                 }
             }
@@ -184,7 +202,8 @@
         if (group == null) {
             group = new NotificationGroup();
             mGroupMap.put(groupKey, group);
-            for (OnGroupChangeListener listener : mListeners) {
+
+            for (OnGroupChangeListener listener : mGroupChangeListeners) {
                 listener.onGroupCreated(group, groupKey);
             }
         }
@@ -195,9 +214,8 @@
                 Log.wtf(TAG, "Inconsistent entries found with the same key " + added.getKey()
                         + "existing removed: " + existing.isRowRemoved()
                         + (existingThrowable != null
-                                ? Log.getStackTraceString(existingThrowable) + "\n": "")
-                        + " added removed" + added.isRowRemoved()
-                        , new Throwable());
+                                ? Log.getStackTraceString(existingThrowable) + "\n" : "")
+                        + " added removed" + added.isRowRemoved(), new Throwable());
             }
             group.children.put(added.getKey(), added);
             updateSuppression(group);
@@ -206,12 +224,12 @@
             group.expanded = added.areChildrenExpanded();
             updateSuppression(group);
             if (!group.children.isEmpty()) {
-                ArrayList<NotificationEntry> childrenCopy
-                        = new ArrayList<>(group.children.values());
+                ArrayList<NotificationEntry> childrenCopy =
+                        new ArrayList<>(group.children.values());
                 for (NotificationEntry child : childrenCopy) {
                     onEntryBecomingChild(child);
                 }
-                for (OnGroupChangeListener listener : mListeners) {
+                for (OnGroupChangeListener listener : mGroupChangeListeners) {
                     listener.onGroupCreatedFromChildren(group);
                 }
             }
@@ -243,7 +261,7 @@
                         && group.summary.getSbn().getNotification().isGroupSummary()
                         && (hasIsolatedChildren(group) || hasBubbles)));
         if (prevSuppressed != group.suppressed) {
-            for (OnGroupChangeListener listener : mListeners) {
+            for (OnGroupChangeListener listener : mGroupChangeListeners) {
                 if (!mIsUpdatingUnchangedGroup) {
                     listener.onGroupSuppressionChanged(group, group.suppressed);
                     listener.onGroupsChanged();
@@ -306,6 +324,9 @@
         }
     }
 
+    /**
+     * Whether the given notification is the summary of a group that is being suppressed
+     */
     public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) {
         return isGroupSuppressed(getGroupKey(sbn)) && sbn.getNotification().isGroupSummary();
     }
@@ -315,13 +336,14 @@
                 && getTotalNumberOfChildren(sbn) == 1;
     }
 
-    public boolean isOnlyChildInGroup(StatusBarNotification sbn) {
+    @Override
+    public boolean isOnlyChildInGroup(NotificationEntry entry) {
+        final StatusBarNotification sbn = entry.getSbn();
         if (!isOnlyChild(sbn)) {
             return false;
         }
-        NotificationEntry logicalGroupSummary = getLogicalGroupSummary(sbn);
-        return logicalGroupSummary != null
-                && !logicalGroupSummary.getSbn().equals(sbn);
+        NotificationEntry logicalGroupSummary = getLogicalGroupSummary(entry);
+        return logicalGroupSummary != null && !logicalGroupSummary.getSbn().equals(sbn);
     }
 
     private int getTotalNumberOfChildren(StatusBarNotification sbn) {
@@ -339,11 +361,12 @@
     private void setStatusBarState(int newState) {
         mBarState = newState;
         if (mBarState == StatusBarState.KEYGUARD) {
-            collapseAllGroups();
+            collapseGroups();
         }
     }
 
-    public void collapseAllGroups() {
+    @Override
+    public void collapseGroups() {
         // Because notifications can become isolated when the group becomes suppressed it can
         // lead to concurrent modifications while looping. We need to make a copy.
         ArrayList<NotificationGroup> groupCopy = new ArrayList<>(mGroupMap.values());
@@ -357,10 +380,9 @@
         }
     }
 
-    /**
-     * @return whether a given notification is a child in a group which has a summary
-     */
-    public boolean isChildInGroupWithSummary(StatusBarNotification sbn) {
+    @Override
+    public boolean isChildInGroup(NotificationEntry entry) {
+        final StatusBarNotification sbn = entry.getSbn();
         if (!isGroupChild(sbn)) {
             return false;
         }
@@ -377,10 +399,9 @@
         return true;
     }
 
-    /**
-     * @return whether a given notification is a summary in a group which has children
-     */
-    public boolean isSummaryOfGroup(StatusBarNotification sbn) {
+    @Override
+    public boolean isGroupSummary(NotificationEntry entry) {
+        final StatusBarNotification sbn = entry.getSbn();
         if (!isGroupSummary(sbn)) {
             return false;
         }
@@ -391,21 +412,14 @@
         return !group.children.isEmpty() && Objects.equals(group.summary.getSbn(), sbn);
     }
 
-    /**
-     * Get the summary of a specified status bar notification. For isolated notification this return
-     * itself.
-     */
-    public NotificationEntry getGroupSummary(StatusBarNotification sbn) {
-        return getGroupSummary(getGroupKey(sbn));
+    @Override
+    public NotificationEntry getGroupSummary(NotificationEntry entry) {
+        return getGroupSummary(getGroupKey(entry.getSbn()));
     }
 
-    /**
-     * Similar to {@link #getGroupSummary(StatusBarNotification)} but doesn't get the visual summary
-     * but the logical summary, i.e when a child is isolated, it still returns the summary as if
-     * it wasn't isolated.
-     */
-    public NotificationEntry getLogicalGroupSummary(StatusBarNotification sbn) {
-        return getGroupSummary(sbn.getGroupKey());
+    @Override
+    public NotificationEntry getLogicalGroupSummary(NotificationEntry entry) {
+        return getGroupSummary(entry.getSbn().getGroupKey());
     }
 
     @Nullable
@@ -436,14 +450,10 @@
         return children;
     }
 
-    /**
-     * Get the children that are in the summary's group, not including those isolated.
-     *
-     * @param summary summary of a group
-     * @return list of the children
-     */
-    public @Nullable ArrayList<NotificationEntry> getChildren(StatusBarNotification summary) {
-        NotificationGroup group = mGroupMap.get(summary.getGroupKey());
+    @Override
+    public @Nullable List<NotificationEntry> getChildren(ListEntry listEntrySummary) {
+        NotificationEntry summary = listEntrySummary.getRepresentativeEntry();
+        NotificationGroup group = mGroupMap.get(summary.getSbn().getGroupKey());
         if (group == null) {
             return null;
         }
@@ -479,9 +489,9 @@
         return groupKey;
     }
 
-    /** @return group expansion state after toggling. */
-    public boolean toggleGroupExpansion(StatusBarNotification sbn) {
-        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
+    @Override
+    public boolean toggleGroupExpansion(NotificationEntry entry) {
+        NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
         if (group == null) {
             return false;
         }
@@ -494,10 +504,7 @@
     }
 
     /**
-     * Whether a notification is visually a group summary.
-     *
-     * @param sbn notification to check
-     * @return true if it is visually a group summary
+     * Is this notification the summary of a group?
      */
     public boolean isGroupSummary(StatusBarNotification sbn) {
         if (isIsolated(sbn.getKey())) {
@@ -536,14 +543,13 @@
      * @param entry the notification to check
      * @return true if the entry should be isolated
      */
-
     private boolean shouldIsolate(NotificationEntry entry) {
         StatusBarNotification sbn = entry.getSbn();
         if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
             return false;
         }
-        int peopleNotificationType = mPeopleNotificationIdentifier.get().getPeopleNotificationType(
-                entry.getSbn(), entry.getRanking());
+        int peopleNotificationType =
+                mPeopleNotificationIdentifier.get().getPeopleNotificationType(entry);
         if (peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON) {
             return true;
         }
@@ -576,7 +582,7 @@
         // When the notification gets added afterwards it is already isolated and therefore
         // it doesn't lead to an update.
         updateSuppression(mGroupMap.get(entry.getSbn().getGroupKey()));
-        for (OnGroupChangeListener listener : mListeners) {
+        for (OnGroupChangeListener listener : mGroupChangeListeners) {
             listener.onGroupsChanged();
         }
     }
@@ -607,7 +613,7 @@
             onEntryRemovedInternal(entry, entry.getSbn());
             mIsolatedEntries.remove(sbn.getKey());
             onEntryAddedInternal(entry);
-            for (OnGroupChangeListener listener : mListeners) {
+            for (OnGroupChangeListener listener : mGroupChangeListeners) {
                 listener.onGroupsChanged();
             }
         }
@@ -618,12 +624,16 @@
                 || notificationGroup.summary.isGroupNotFullyVisible();
     }
 
+    /**
+     * Directly set the heads up manager to avoid circular dependencies
+     */
     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
         mHeadsUpManager = headsUpManager;
     }
 
+    @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("GroupManager state:");
+        pw.println("GroupManagerLegacy state:");
         pw.println("  number of groups: " +  mGroupMap.size());
         for (Map.Entry<String, NotificationGroup>  entry : mGroupMap.entrySet()) {
             pw.println("\n    key: " + entry.getKey()); pw.println(entry.getValue());
@@ -640,6 +650,9 @@
         setStatusBarState(newState);
     }
 
+    /**
+     * Represents a notification group in the notification shade.
+     */
     public static class NotificationGroup {
         public final HashMap<String, NotificationEntry> children = new HashMap<>();
         public NotificationEntry summary;
@@ -659,24 +672,29 @@
             result += "\n    children size: " + children.size();
             for (NotificationEntry child : children.values()) {
                 result += "\n      " + child.getSbn()
-                + (child.getDebugThrowable() != null
-                        ? Log.getStackTraceString(child.getDebugThrowable())
-                        : "");
+                        + (child.getDebugThrowable() != null
+                            ? Log.getStackTraceString(child.getDebugThrowable())
+                            : "");
             }
             result += "\n    summary suppressed: " + suppressed;
             return result;
         }
     }
 
+    /**
+     * Listener for group changes not including group expansion changes which are handled by
+     * {@link OnGroupExpansionChangeListener}.
+     */
     public interface OnGroupChangeListener {
-
         /**
          * A new group has been created.
          *
          * @param group the group that was created
          * @param groupKey the group's key
          */
-        default void onGroupCreated(NotificationGroup group, String groupKey) {}
+        default void onGroupCreated(
+                NotificationGroup group,
+                String groupKey) {}
 
         /**
          * A group has been removed.
@@ -684,7 +702,9 @@
          * @param group the group that was removed
          * @param groupKey the group's key
          */
-        default void onGroupRemoved(NotificationGroup group, String groupKey) {}
+        default void onGroupRemoved(
+                NotificationGroup group,
+                String groupKey) {}
 
         /**
          * The suppression of a group has changed.
@@ -692,16 +712,9 @@
          * @param group the group that has changed
          * @param suppressed true if the group is now suppressed, false o/w
          */
-        default void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {}
-
-        /**
-         * The expansion of a group has changed.
-         *
-         * @param changedRow the row for which the expansion has changed, which is also the summary
-         * @param expanded a boolean indicating the new expanded state
-         */
-        default void onGroupExpansionChanged(ExpandableNotificationRow changedRow,
-                boolean expanded) {}
+        default void onGroupSuppressionChanged(
+                NotificationGroup group,
+                boolean suppressed) {}
 
         /**
          * A group of children just received a summary notification and should therefore become
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
index 8b803b5..18806ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -20,11 +20,10 @@
 import android.app.NotificationManager;
 
 import com.android.systemui.dagger.SysUISingleton;
-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.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 
 import java.util.List;
 
@@ -39,14 +38,14 @@
 @SysUISingleton
 public class HighPriorityProvider {
     private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
-    private final NotificationGroupManager mGroupManager;
+    private final GroupMembershipManager mGroupMembershipManager;
 
     @Inject
     public HighPriorityProvider(
             PeopleNotificationIdentifier peopleNotificationIdentifier,
-            NotificationGroupManager groupManager) {
+            GroupMembershipManager groupManager) {
         mPeopleNotificationIdentifier = peopleNotificationIdentifier;
-        mGroupManager = groupManager;
+        mGroupMembershipManager = groupManager;
     }
 
     /**
@@ -81,20 +80,15 @@
 
 
     private boolean hasHighPriorityChild(ListEntry entry) {
-        List<NotificationEntry> children = null;
-
-        if (entry instanceof GroupEntry) {
-            // New notification pipeline
-            children = ((GroupEntry) entry).getChildren();
-        } else if (entry.getRepresentativeEntry() != null
-                && mGroupManager.isGroupSummary(entry.getRepresentativeEntry().getSbn())) {
-            // Old notification pipeline
-            children = mGroupManager.getChildren(entry.getRepresentativeEntry().getSbn());
+        if (entry instanceof NotificationEntry
+                && !mGroupMembershipManager.isGroupSummary((NotificationEntry) entry)) {
+            return false;
         }
 
+        List<NotificationEntry> children = mGroupMembershipManager.getChildren(entry);
         if (children != null) {
             for (NotificationEntry child : children) {
-                if (isHighPriority(child)) {
+                if (child != entry && isHighPriority(child)) {
                     return true;
                 }
             }
@@ -122,8 +116,8 @@
     }
 
     private boolean isPeopleNotification(NotificationEntry entry) {
-        return mPeopleNotificationIdentifier.getPeopleNotificationType(
-                entry.getSbn(), entry.getRanking()) != PeopleNotificationIdentifier.TYPE_NON_PERSON;
+        return mPeopleNotificationIdentifier.getPeopleNotificationType(entry)
+                != PeopleNotificationIdentifier.TYPE_NON_PERSON;
     }
 
     private boolean hasUserSetImportance(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManager.java
new file mode 100644
index 0000000..d2df07e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManager.java
@@ -0,0 +1,66 @@
+/*
+ * 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.statusbar.notification.collection.render;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+
+/**
+ * Tracks expanded notification states for groups. This expanded state should not be confused by the
+ * expanded/collapsed state of a single notification which is tracked within each
+ * ExpandableNotificationRow.
+ */
+public interface GroupExpansionManager extends Dumpable {
+
+    /**
+     * Register a listener for group expansion changes
+     */
+    void registerGroupExpansionChangeListener(OnGroupExpansionChangeListener listener);
+
+    /**
+     * Whether the group associated with this notification is expanded.
+     * If this notification is not part of a group, it will always return false.
+     */
+    boolean isGroupExpanded(NotificationEntry entry);
+
+    /**
+     * Set whether the group associated with this notification is expanded or not.
+     */
+    void setGroupExpanded(NotificationEntry entry, boolean expanded);
+
+    /** @return group expansion state after toggling. */
+    boolean toggleGroupExpansion(NotificationEntry entry);
+
+    /**
+     * Set expanded=false for all groups
+     */
+    void collapseGroups();
+
+    /**
+     * Listener for group expansion changes.
+     */
+    interface OnGroupExpansionChangeListener {
+        /**
+         * The expansion of a group has changed.
+         *
+         * @param changedRow the row for which the expansion has changed, which is also the summary
+         * @param expanded a boolean indicating the new expanded state
+         */
+        void onGroupExpansionChange(ExpandableNotificationRow changedRow, boolean expanded);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
new file mode 100644
index 0000000..b9aa26f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -0,0 +1,113 @@
+/*
+ * 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.statusbar.notification.collection.render;
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.Coordinator;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Provides grouping information for notification entries including information about a group's
+ * expanded state.
+ */
+public class GroupExpansionManagerImpl implements GroupExpansionManager, Coordinator {
+    private final GroupMembershipManager mGroupMembershipManager;
+    private final Set<OnGroupExpansionChangeListener> mOnGroupChangeListeners = new HashSet<>();
+
+    // Set of summary keys whose groups are expanded
+    private final Set<NotificationEntry> mExpandedGroups = new HashSet<>();
+
+    public GroupExpansionManagerImpl(GroupMembershipManager groupMembershipManager) {
+        mGroupMembershipManager = groupMembershipManager;
+    }
+
+    /**
+     * Cleanup entries from mExpandedGroups that no longer exist in the pipeline.
+     */
+    private final OnBeforeRenderListListener mNotifTracker = (entries) -> {
+        final Set<NotificationEntry> renderingSummaries = new HashSet<>();
+        for (ListEntry entry : entries) {
+            if (entry instanceof GroupEntry) {
+                renderingSummaries.add(entry.getRepresentativeEntry());
+            }
+        }
+        mExpandedGroups.removeIf(expandedGroup -> !renderingSummaries.contains(expandedGroup));
+    };
+
+    @Override
+    public void attach(NotifPipeline pipeline) {
+        pipeline.addOnBeforeRenderListListener(mNotifTracker);
+    }
+
+    @Override
+    public void registerGroupExpansionChangeListener(OnGroupExpansionChangeListener listener) {
+        mOnGroupChangeListeners.add(listener);
+    }
+
+    @Override
+    public boolean isGroupExpanded(NotificationEntry entry) {
+        return mExpandedGroups.contains(mGroupMembershipManager.getGroupSummary(entry));
+    }
+
+    @Override
+    public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
+        final NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
+        if (expanded) {
+            mExpandedGroups.add(groupSummary);
+        } else {
+            mExpandedGroups.remove(groupSummary);
+        }
+
+        sendOnGroupExpandedChange(entry, expanded);
+    }
+
+    @Override
+    public boolean toggleGroupExpansion(NotificationEntry entry) {
+        setGroupExpanded(entry, !isGroupExpanded(entry));
+        return isGroupExpanded(entry);
+    }
+
+    @Override
+    public void collapseGroups() {
+        for (NotificationEntry entry : mExpandedGroups) {
+            setGroupExpanded(entry, false);
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("NotificationEntryExpansion state:");
+        pw.println("  # expanded groups: " +  mExpandedGroups.size());
+        for (NotificationEntry entry : mExpandedGroups) {
+            pw.println("    summary key of expanded group: " + entry.getKey());
+        }
+    }
+
+    private void sendOnGroupExpandedChange(NotificationEntry entry, boolean expanded) {
+        for (OnGroupExpansionChangeListener listener : mOnGroupChangeListeners) {
+            listener.onGroupExpansionChange(entry.getRow(), expanded);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java
new file mode 100644
index 0000000..196cb8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java
@@ -0,0 +1,73 @@
+/*
+ * 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.statusbar.notification.collection.render;
+
+import android.annotation.Nullable;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.util.List;
+
+/**
+ * Helper that determines the group states (parent, summary, children) of a notification.
+ */
+public interface GroupMembershipManager {
+    /**
+     * @return whether a given notification is a top level entry or is the summary in a group which
+     * has children
+     */
+    boolean isGroupSummary(NotificationEntry entry);
+
+    /**
+     * Get the summary of a specified status bar notification. For an isolated notification this
+     * returns itself.
+     */
+    NotificationEntry getGroupSummary(NotificationEntry entry);
+
+    /**
+     * Similar to {@link #getGroupSummary(NotificationEntry)} but doesn't get the visual summary
+     * but the logical summary, i.e when a child is isolated, it still returns the summary as if
+     * it wasn't isolated.
+     * TODO: remove this when migrating to the new pipeline, this is taken care of in the
+     * dismissal logic built into NotifCollection
+     */
+    default NotificationEntry getLogicalGroupSummary(NotificationEntry entry) {
+        return getGroupSummary(entry);
+    }
+
+    /**
+     * @return whether a given notification is a child in a group
+     */
+    boolean isChildInGroup(NotificationEntry entry);
+
+    /**
+     * Whether this is the only child in a group
+     * TODO: remove this when migrating to the new pipeline, this is taken care of in the
+     * dismissal logic built into NotifCollection
+     */
+    boolean isOnlyChildInGroup(NotificationEntry entry);
+
+    /**
+     * Get the children that are in the summary's group, not including those isolated.
+     *
+     * @param summary summary of a group
+     * @return list of the children
+     */
+    @Nullable
+    List<NotificationEntry> getChildren(ListEntry summary);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
new file mode 100644
index 0000000..c1f468a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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.statusbar.notification.collection.render;
+
+import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
+
+import androidx.annotation.Nullable;
+
+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 java.util.List;
+
+/**
+ * ShadeListBuilder groups notifications from system server. This manager translates
+ * ShadeListBuilder's method of grouping to be used within SystemUI.
+ */
+public class GroupMembershipManagerImpl implements GroupMembershipManager {
+    @Override
+    public boolean isGroupSummary(NotificationEntry entry) {
+        return getGroupSummary(entry) == entry;
+    }
+
+    @Override
+    public NotificationEntry getGroupSummary(NotificationEntry entry) {
+        if (isEntryTopLevel(entry) || entry.getParent() == null) {
+            return null;
+        }
+
+        return entry.getParent().getRepresentativeEntry();
+    }
+
+    @Override
+    public boolean isChildInGroup(NotificationEntry entry) {
+        return !isEntryTopLevel(entry);
+    }
+
+    @Override
+    public boolean isOnlyChildInGroup(NotificationEntry entry) {
+        if (entry.getParent() == null) {
+            return false;
+        }
+
+        return entry.getParent().getChildren().size() == 1;
+    }
+
+    @Nullable
+    @Override
+    public List<NotificationEntry> getChildren(ListEntry entry) {
+        if (entry instanceof GroupEntry) {
+            return ((GroupEntry) entry).getChildren();
+        }
+
+        if (isGroupSummary(entry.getRepresentativeEntry())) {
+            // maybe we were actually passed the summary
+            return entry.getRepresentativeEntry().getParent().getChildren();
+        }
+
+        return null;
+    }
+
+    private boolean isEntryTopLevel(NotificationEntry entry) {
+        return entry.getParent() == ROOT_ENTRY;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 3c35b7bd..5243854 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import java.lang.RuntimeException
 import javax.inject.Inject
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index f3ed95b..e2aae64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -50,10 +50,15 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -67,7 +72,6 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.leak.LeakDetector;
@@ -91,7 +95,7 @@
     @Provides
     static NotificationEntryManager provideNotificationEntryManager(
             NotificationEntryManagerLogger logger,
-            NotificationGroupManager groupManager,
+            NotificationGroupManagerLegacy groupManager,
             NotificationRankingManager rankingManager,
             NotificationEntryManager.KeyguardEnvironment keyguardEnvironment,
             FeatureFlags featureFlags,
@@ -206,6 +210,29 @@
                 context, notificationGutsManager, notificationEntryManager, metricsLogger);
     }
 
+    /** Provides an instance of {@link GroupMembershipManager} */
+    @SysUISingleton
+    @Provides
+    static GroupMembershipManager provideGroupMembershipManager(
+            FeatureFlags featureFlags,
+            Lazy<NotificationGroupManagerLegacy> groupManagerLegacy) {
+        return featureFlags.isNewNotifPipelineRenderingEnabled()
+                ? new GroupMembershipManagerImpl()
+                : groupManagerLegacy.get();
+    }
+
+    /** Provides an instance of {@link GroupExpansionManager} */
+    @SysUISingleton
+    @Provides
+    static GroupExpansionManager provideGroupExpansionManager(
+            FeatureFlags featureFlags,
+            Lazy<GroupMembershipManager> groupMembershipManager,
+            Lazy<NotificationGroupManagerLegacy> groupManagerLegacy) {
+        return featureFlags.isNewNotifPipelineRenderingEnabled()
+                ? new GroupExpansionManagerImpl(groupMembershipManager.get())
+                : groupManagerLegacy.get();
+    }
+
     /** Initializes the notification data pipeline (can be disabled via config). */
     @SysUISingleton
     @Provides
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 6460892..9fb2928 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
@@ -30,12 +30,12 @@
 import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
 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.interruption.HeadsUpController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper
-import com.android.systemui.statusbar.phone.NotificationGroupManager
 import com.android.systemui.statusbar.phone.StatusBar
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -65,7 +65,7 @@
     private val deviceProvisionedController: DeviceProvisionedController,
     private val notificationRowBinder: NotificationRowBinderImpl,
     private val remoteInputUriController: RemoteInputUriController,
-    private val groupManager: NotificationGroupManager,
+    private val groupManagerLegacy: Lazy<NotificationGroupManagerLegacy>,
     private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper,
     private val headsUpManager: HeadsUpManager,
     private val headsUpController: HeadsUpController,
@@ -111,11 +111,11 @@
         } else {
             targetSdkResolver.initialize(entryManager)
             remoteInputUriController.attach(entryManager)
-            groupAlertTransferHelper.bind(entryManager, groupManager)
-            headsUpManager.addListener(groupManager)
+            groupAlertTransferHelper.bind(entryManager, groupManagerLegacy.get())
+            headsUpManager.addListener(groupManagerLegacy.get())
             headsUpManager.addListener(groupAlertTransferHelper)
             headsUpController.attach(entryManager, headsUpManager)
-            groupManager.setHeadsUpManager(headsUpManager)
+            groupManagerLegacy.get().setHeadsUpManager(headsUpManager)
             groupAlertTransferHelper.setHeadsUpManager(headsUpManager)
 
             entryManager.attach(notificationListener)
@@ -131,7 +131,6 @@
         if (dumpTruck) {
             entryManager.dump(pw, "  ")
         }
-        groupManager.dump(fd, pw, args)
     }
 
     // TODO: Convert all functions below this line into listeners instead of public methods
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 743bf33..99b2fcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -214,7 +214,7 @@
     }
 
     private fun NotificationEntry.extractPerson(): PersonModel? {
-        val type = peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
+        val type = peopleNotificationIdentifier.getPeopleNotificationType(this)
         if (type == TYPE_NON_PERSON) {
             return null
         }
@@ -249,7 +249,7 @@
 
     private fun NotificationEntry.extractPersonKey(): PersonKey? {
         // TODO migrate to shortcut id when snoozing is conversation wide
-        val type = peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
+        val type = peopleNotificationIdentifier.getPeopleNotificationType(this)
         return if (type != TYPE_NON_PERSON) key else null
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index 1ac2cb5..0d92616 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -20,12 +20,13 @@
 import android.service.notification.NotificationListenerService.Ranking
 import android.service.notification.StatusBarNotification
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
-import com.android.systemui.statusbar.phone.NotificationGroupManager
 import javax.inject.Inject
 import kotlin.math.max
 
@@ -40,10 +41,12 @@
      *  that users shortcuts.
      */
     @PeopleNotificationType
-    fun getPeopleNotificationType(sbn: StatusBarNotification, ranking: Ranking): Int
+    fun getPeopleNotificationType(entry: NotificationEntry): Int
 
-    fun compareTo(@PeopleNotificationType a: Int,
-                  @PeopleNotificationType b: Int): Int
+    fun compareTo(
+        @PeopleNotificationType a: Int,
+        @PeopleNotificationType b: Int
+    ): Int
 
     companion object {
 
@@ -62,24 +65,27 @@
 @SysUISingleton
 class PeopleNotificationIdentifierImpl @Inject constructor(
     private val personExtractor: NotificationPersonExtractor,
-    private val groupManager: NotificationGroupManager
+    private val groupManager: GroupMembershipManager
 ) : PeopleNotificationIdentifier {
 
     @PeopleNotificationType
-    override fun getPeopleNotificationType(sbn: StatusBarNotification, ranking: Ranking): Int =
-            when (val type = ranking.personTypeInfo) {
+    override fun getPeopleNotificationType(entry: NotificationEntry): Int =
+            when (val type = entry.ranking.personTypeInfo) {
                 TYPE_IMPORTANT_PERSON -> TYPE_IMPORTANT_PERSON
                 else -> {
-                    when (val type = upperBound(type, extractPersonTypeInfo(sbn))) {
+                    when (val type = upperBound(type, extractPersonTypeInfo(entry.sbn))) {
                         TYPE_IMPORTANT_PERSON -> TYPE_IMPORTANT_PERSON
-                        else -> upperBound(type, getPeopleTypeOfSummary(sbn))
+                        else -> upperBound(type, getPeopleTypeOfSummary(entry))
                     }
                 }
             }
 
-    override fun compareTo(@PeopleNotificationType a: Int,
-                  @PeopleNotificationType b: Int): Int {
-        return b.compareTo(a);
+    override fun compareTo(
+        @PeopleNotificationType a: Int,
+        @PeopleNotificationType b: Int
+    ): Int
+    {
+        return b.compareTo(a)
     }
 
     /**
@@ -105,14 +111,14 @@
     private fun extractPersonTypeInfo(sbn: StatusBarNotification) =
             if (personExtractor.isPersonNotification(sbn)) TYPE_PERSON else TYPE_NON_PERSON
 
-    private fun getPeopleTypeOfSummary(statusBarNotification: StatusBarNotification): Int {
-        if (!groupManager.isSummaryOfGroup(statusBarNotification)) {
+    private fun getPeopleTypeOfSummary(entry: NotificationEntry): Int {
+        if (!groupManager.isGroupSummary(entry)) {
             return TYPE_NON_PERSON
         }
 
-        val childTypes = groupManager.getChildren(statusBarNotification)
+        val childTypes = groupManager.getChildren(entry)
                 ?.asSequence()
-                ?.map { getPeopleNotificationType(it.sbn, it.ranking) }
+                ?.map { getPeopleNotificationType(it) }
                 ?: return TYPE_NON_PERSON
 
         var groupType = TYPE_NON_PERSON
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 46b4973..89f7205 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -86,6 +86,8 @@
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -97,7 +99,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.SwipeableView;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
@@ -220,7 +221,8 @@
     private boolean mNeedsRedaction;
     private boolean mLastChronometerRunning = true;
     private ViewStub mChildrenContainerStub;
-    private NotificationGroupManager mGroupManager;
+    private GroupMembershipManager mGroupMembershipManager;
+    private GroupExpansionManager mGroupExpansionManager;
     private boolean mChildrenExpanded;
     private boolean mIsSummaryWithChildren;
     private NotificationChildrenContainer mChildrenContainer;
@@ -269,10 +271,10 @@
         @Override
         public void onClick(View v) {
             if (!shouldShowPublic() && (!mIsLowPriority || isExpanded())
-                    && mGroupManager.isSummaryOfGroup(mEntry.getSbn())) {
+                    && mGroupMembershipManager.isGroupSummary(mEntry)) {
                 mGroupExpansionChanging = true;
-                final boolean wasExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn());
-                boolean nowExpanded = mGroupManager.toggleGroupExpansion(mEntry.getSbn());
+                final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
+                boolean nowExpanded = mGroupExpansionManager.toggleGroupExpansion(mEntry);
                 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER,
                         nowExpanded);
@@ -527,8 +529,7 @@
     }
 
     private boolean isConversation() {
-        return mPeopleNotificationIdentifier
-                .getPeopleNotificationType(mEntry.getSbn(), mEntry.getRanking())
+        return mPeopleNotificationIdentifier.getPeopleNotificationType(mEntry)
                 != PeopleNotificationIdentifier.TYPE_NON_PERSON;
     }
 
@@ -820,7 +821,7 @@
      * @return whether this notification is the only child in the group summary
      */
     public boolean isOnlyChildInGroup() {
-        return mGroupManager.isOnlyChildInGroup(mEntry.getSbn());
+        return mGroupMembershipManager.isOnlyChildInGroup(mEntry);
     }
 
     public ExpandableNotificationRow getNotificationParent() {
@@ -1425,8 +1426,7 @@
 
     public void performDismiss(boolean fromAccessibility) {
         if (isOnlyChildInGroup()) {
-            NotificationEntry groupSummary =
-                    mGroupManager.getLogicalGroupSummary(mEntry.getSbn());
+            NotificationEntry groupSummary = mGroupMembershipManager.getLogicalGroupSummary(mEntry);
             if (groupSummary.isClearable()) {
                 // If this is the only child in the group, dismiss the group, but don't try to show
                 // the blocking helper affordance!
@@ -1579,7 +1579,8 @@
             String notificationKey,
             ExpansionLogger logger,
             KeyguardBypassController bypassController,
-            NotificationGroupManager groupManager,
+            GroupMembershipManager groupMembershipManager,
+            GroupExpansionManager groupExpansionManager,
             HeadsUpManager headsUpManager,
             RowContentBindStage rowContentBindStage,
             OnExpandClickListener onExpandClickListener,
@@ -1600,8 +1601,9 @@
         mLogger = logger;
         mLoggingKey = notificationKey;
         mBypassController = bypassController;
-        mGroupManager = groupManager;
-        mPrivateLayout.setGroupManager(groupManager);
+        mGroupMembershipManager = groupMembershipManager;
+        mGroupExpansionManager = groupExpansionManager;
+        mPrivateLayout.setGroupMembershipManager(groupMembershipManager);
         mHeadsUpManager = headsUpManager;
         mRowContentBindStage = rowContentBindStage;
         mOnExpandClickListener = onExpandClickListener;
@@ -2184,8 +2186,8 @@
         mFalsingManager.setNotificationExpanded();
         if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion
                 && !mChildrenContainer.showingAsLowPriority()) {
-            final boolean wasExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn());
-            mGroupManager.setGroupExpanded(mEntry.getSbn(), userExpanded);
+            final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
+            mGroupExpansionManager.setGroupExpanded(mEntry, userExpanded);
             onExpansionChanged(true /* userAction */, wasExpanded);
             return;
         }
@@ -2328,7 +2330,7 @@
 
     @Override
     public boolean isGroupExpanded() {
-        return mGroupManager.isGroupExpanded(mEntry.getSbn());
+        return mGroupExpansionManager.isGroupExpanded(mEntry);
     }
 
     private void onAttachedChildrenCountChanged() {
@@ -2574,7 +2576,7 @@
     public void makeActionsVisibile() {
         setUserExpanded(true, true);
         if (isChildInGroup()) {
-            mGroupManager.setGroupExpanded(mEntry.getSbn(), true);
+            mGroupExpansionManager.setGroupExpanded(mEntry, true);
         }
         notifyHeightChanged(false /* needsAnimation */);
     }
@@ -2867,7 +2869,7 @@
 
     public void onExpandedByGesture(boolean userExpanded) {
         int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
-        if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) {
+        if (mGroupMembershipManager.isGroupSummary(mEntry)) {
             event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER;
         }
         MetricsLogger.action(mContext, event, userExpanded);
@@ -2912,7 +2914,7 @@
     private void onExpansionChanged(boolean userAction, boolean wasExpanded) {
         boolean nowExpanded = isExpanded();
         if (mIsSummaryWithChildren && (!mIsLowPriority || wasExpanded)) {
-            nowExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn());
+            nowExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
         }
         if (nowExpanded != wasExpanded) {
             updateShelfIconColor();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index ce760cb..05d9fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -31,6 +31,8 @@
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.collection.render.NodeController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -39,7 +41,6 @@
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.time.SystemClock;
 
@@ -62,7 +63,8 @@
     private final String mAppName;
     private final String mNotificationKey;
     private final KeyguardBypassController mKeyguardBypassController;
-    private final NotificationGroupManager mNotificationGroupManager;
+    private final GroupMembershipManager mGroupMembershipManager;
+    private final GroupExpansionManager mGroupExpansionManager;
     private final RowContentBindStage mRowContentBindStage;
     private final NotificationLogger mNotificationLogger;
     private final HeadsUpManager mHeadsUpManager;
@@ -85,7 +87,8 @@
             NotificationMediaManager mediaManager, PluginManager pluginManager,
             SystemClock clock, @AppName String appName, @NotificationKey String notificationKey,
             KeyguardBypassController keyguardBypassController,
-            NotificationGroupManager notificationGroupManager,
+            GroupMembershipManager groupMembershipManager,
+            GroupExpansionManager groupExpansionManager,
             RowContentBindStage rowContentBindStage,
             NotificationLogger notificationLogger, HeadsUpManager headsUpManager,
             ExpandableNotificationRow.OnExpandClickListener onExpandClickListener,
@@ -103,7 +106,8 @@
         mAppName = appName;
         mNotificationKey = notificationKey;
         mKeyguardBypassController = keyguardBypassController;
-        mNotificationGroupManager = notificationGroupManager;
+        mGroupMembershipManager = groupMembershipManager;
+        mGroupExpansionManager = groupExpansionManager;
         mRowContentBindStage = rowContentBindStage;
         mNotificationLogger = notificationLogger;
         mHeadsUpManager = headsUpManager;
@@ -128,7 +132,8 @@
                 mNotificationKey,
                 mExpansionLogger,
                 mKeyguardBypassController,
-                mNotificationGroupManager,
+                mGroupMembershipManager,
+                mGroupExpansionManager,
                 mHeadsUpManager,
                 mRowContentBindStage,
                 mOnExpandClickListener,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index c7e44c5..1de9308 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -28,9 +28,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.provider.Settings;
-import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -52,11 +50,10 @@
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
-import com.android.systemui.statusbar.notification.row.wrapper.NotificationTemplateViewWrapper;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.InflatedSmartReplies;
 import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
 import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -122,8 +119,8 @@
     private int mSmallHeight;
     private int mHeadsUpHeight;
     private int mNotificationMaxHeight;
-    private StatusBarNotification mStatusBarNotification;
-    private NotificationGroupManager mGroupManager;
+    private NotificationEntry mNotificationEntry;
+    private GroupMembershipManager mGroupMembershipManager;
     private RemoteInputController mRemoteInputController;
     private Runnable mExpandedVisibleListener;
     private PeopleNotificationIdentifier mPeopleIdentifier;
@@ -778,7 +775,7 @@
     }
 
     private boolean isGroupExpanded() {
-        return mGroupManager.isGroupExpanded(mStatusBarNotification);
+        return mContainingNotification.isGroupExpanded();
     }
 
     public void setClipTopAmount(int clipTopAmount) {
@@ -908,10 +905,10 @@
     public int getBackgroundColorForExpansionState() {
         // When expanding or user locked we want the new type, when collapsing we want
         // the original type
-        final int visibleType = (mContainingNotification.isGroupExpanded()
-                || mContainingNotification.isUserLocked())
-                        ? calculateVisibleType()
-                        : getVisibleType();
+        final int visibleType = (
+                isGroupExpanded() || mContainingNotification.isUserLocked())
+                    ? calculateVisibleType()
+                    : getVisibleType();
         return getBackgroundColor(visibleType);
     }
 
@@ -1145,7 +1142,7 @@
     }
 
     public void onNotificationUpdated(NotificationEntry entry) {
-        mStatusBarNotification = entry.getSbn();
+        mNotificationEntry = entry;
         mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
         updateAllSingleLineViews();
         ExpandableNotificationRow row = entry.getRow();
@@ -1176,7 +1173,7 @@
         if (mIsChildInGroup) {
             boolean isNewView = mSingleLineView == null;
             mSingleLineView = mHybridGroupManager.bindFromNotification(
-                    mSingleLineView, mContractedChild, mStatusBarNotification, this);
+                    mSingleLineView, mContractedChild, mNotificationEntry.getSbn(), this);
             if (isNewView) {
                 updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
                         mSingleLineView, mSingleLineView);
@@ -1363,7 +1360,7 @@
             return;
         }
         boolean isPersonWithShortcut =
-                mPeopleIdentifier.getPeopleNotificationType(entry.getSbn(), entry.getRanking())
+                mPeopleIdentifier.getPeopleNotificationType(entry)
                         >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
         boolean showButton = isBubblesEnabled()
                 && isPersonWithShortcut
@@ -1516,8 +1513,8 @@
         }
     }
 
-    public void setGroupManager(NotificationGroupManager groupManager) {
-        mGroupManager = groupManager;
+    public void setGroupMembershipManager(GroupMembershipManager groupMembershipManager) {
+        mGroupMembershipManager = groupMembershipManager;
     }
 
     public void setRemoteInputController(RemoteInputController r) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 205cecc..65a72cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -24,7 +24,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
-import android.app.Notification;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
@@ -260,8 +259,7 @@
         }
         mFeedbackItem = createFeedbackItem(mContext);
         NotificationEntry entry = mParent.getEntry();
-        int personNotifType = mPeopleNotificationIdentifier
-                .getPeopleNotificationType(entry.getSbn(), entry.getRanking());
+        int personNotifType = mPeopleNotificationIdentifier.getPeopleNotificationType(entry);
         if (personNotifType == PeopleNotificationIdentifier.TYPE_PERSON) {
             mInfoItem = createPartialConversationItem(mContext);
         } else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3370773..60883f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -117,6 +117,8 @@
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -129,8 +131,6 @@
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -233,7 +233,8 @@
     private final StackScrollAlgorithm mStackScrollAlgorithm;
 
     private final AmbientState mAmbientState;
-    private NotificationGroupManager mGroupManager;
+    private GroupMembershipManager mGroupMembershipManager;
+    private GroupExpansionManager mGroupExpansionManager;
     private NotificationActivityStarter mNotificationActivityStarter;
     private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
     private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
@@ -415,7 +416,6 @@
     };
     private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
     private boolean mPulsing;
-    private boolean mGroupExpandedForMeasure;
     private boolean mScrollable;
     private View mForcedScroll;
 
@@ -560,7 +560,9 @@
             NotifPipeline notifPipeline,
             NotificationEntryManager entryManager,
             NotifCollection notifCollection,
-            UiEventLogger uiEventLogger
+            UiEventLogger uiEventLogger,
+            GroupMembershipManager groupMembershipManager,
+            GroupExpansionManager groupExpansionManager
     ) {
         super(context, attrs, 0, 0);
         Resources res = getResources();
@@ -636,6 +638,8 @@
                 }
             });
         }
+        mGroupMembershipManager = groupMembershipManager;
+        mGroupExpansionManager = groupExpansionManager;
 
         mDynamicPrivacyController = dynamicPrivacyController;
         mStatusbarStateController = statusbarStateController;
@@ -1440,7 +1444,7 @@
         ExpandableNotificationRow row = mTopHeadsUpEntry.getRow();
         if (row.isChildInGroup()) {
             final NotificationEntry groupSummary =
-                    mGroupManager.getGroupSummary(row.getEntry().getSbn());
+                    mGroupMembershipManager.getGroupSummary(row.getEntry());
             if (groupSummary != null) {
                 row = groupSummary.getRow();
             }
@@ -1617,9 +1621,7 @@
                     NotificationEntry entry = row.getEntry();
                     if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
                             && mTopHeadsUpEntry.getRow() != row
-                            && mGroupManager.getGroupSummary(
-                            mTopHeadsUpEntry.getSbn())
-                            != entry) {
+                            && mGroupMembershipManager.getGroupSummary(mTopHeadsUpEntry) != entry) {
                         continue;
                     }
                     return row.getViewAtPosition(touchY - childTop);
@@ -3017,8 +3019,8 @@
     @ShadeViewRefactor(RefactorComponent.ADAPTER)
     private boolean isChildInGroup(View child) {
         return child instanceof ExpandableNotificationRow
-                && mGroupManager.isChildInGroupWithSummary(
-                ((ExpandableNotificationRow) child).getEntry().getSbn());
+                && mGroupMembershipManager.isChildInGroup(
+                ((ExpandableNotificationRow) child).getEntry());
     }
 
     /**
@@ -3083,6 +3085,8 @@
         return hasAddEvent;
     }
 
+    // TODO (b/162832756): remove since this won't happen in new pipeline (we prune groups in
+    //  ShadeListBuilder)
     /**
      * @param child the child to query
      * @return whether a view is not a top level child but a child notification and that group is
@@ -3093,7 +3097,7 @@
         if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
             NotificationEntry groupSummary =
-                    mGroupManager.getGroupSummary(row.getEntry().getSbn());
+                    mGroupMembershipManager.getGroupSummary(row.getEntry());
             if (groupSummary != null && groupSummary.getRow() != row) {
                 return row.getVisibility() == View.INVISIBLE;
             }
@@ -4349,7 +4353,7 @@
         if (changed) {
             mWillExpand = false;
             if (!mIsExpanded) {
-                mGroupManager.collapseAllGroups();
+                mGroupExpansionManager.collapseGroups();
                 mExpandHelper.cancelImmediately();
             }
             updateNotificationAnimationStates();
@@ -4886,12 +4890,6 @@
         this.mStatusBar = statusBar;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setGroupManager(NotificationGroupManager groupManager) {
-        this.mGroupManager = groupManager;
-        mGroupManager.addOnGroupChangeListener(mOnGroupChangeListener);
-    }
-
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void requestAnimateEverything() {
         if (mIsExpanded && mAnimationsEnabled) {
@@ -4990,17 +4988,29 @@
     public void removeContainerView(View v) {
         Assert.isMainThread();
         removeView(v);
+        if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
+            mController.updateShowEmptyShadeView();
+            updateFooter();
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void addContainerView(View v) {
         Assert.isMainThread();
         addView(v);
+        if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
+            mController.updateShowEmptyShadeView();
+            updateFooter();
+        }
     }
 
     public void addContainerViewAt(View v, int index) {
         Assert.isMainThread();
         addView(v, index);
+        if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
+            mController.updateShowEmptyShadeView();
+            updateFooter();
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -5101,6 +5111,10 @@
         updateScrollability();
     }
 
+    boolean isQsExpanded() {
+        return mQsExpanded;
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setQsExpansionFraction(float qsExpansionFraction) {
         mQsExpansionFraction = qsExpansionFraction;
@@ -6296,39 +6310,22 @@
 
     public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; }
 
+    void onGroupExpandChanged(ExpandableNotificationRow changedRow, boolean expanded) {
+        boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
+        if (animated) {
+            mExpandedGroupView = changedRow;
+            mNeedsAnimation = true;
+        }
+        changedRow.setChildrenExpanded(expanded, animated);
+        onChildHeightChanged(changedRow, false /* needsAnimation */);
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() {
-        @Override
-        public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
-            boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled
-                    && (mIsExpanded || changedRow.isPinned());
-            if (animated) {
-                mExpandedGroupView = changedRow;
-                mNeedsAnimation = true;
+        runAfterAnimationFinished(new Runnable() {
+            @Override
+            public void run() {
+                changedRow.onFinishedExpansionChange();
             }
-            changedRow.setChildrenExpanded(expanded, animated);
-            if (!mGroupExpandedForMeasure) {
-                onChildHeightChanged(changedRow, false /* needsAnimation */);
-            }
-            runAfterAnimationFinished(new Runnable() {
-                @Override
-                public void run() {
-                    changedRow.onFinishedExpansionChange();
-                }
-            });
-        }
-
-        @Override
-        public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
-            mStatusBar.requestNotificationUpdate("onGroupCreatedFromChildren");
-        }
-
-        @Override
-        public void onGroupsChanged() {
-            mStatusBar.requestNotificationUpdate("onGroupsChanged");
-        }
-    };
+        });
+    }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 70892e0..a0b49ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.stack;
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 
 import android.content.res.Resources;
 import android.graphics.Point;
@@ -56,6 +57,10 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -67,7 +72,6 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -117,6 +121,8 @@
     private NotificationStackScrollLayout mView;
     private boolean mFadeNotificationsOnDismiss;
     private NotificationSwipeHelper mSwipeHelper;
+    private boolean mShowEmptyShadeView;
+    private int mBarState;
 
     private final NotificationListContainerImpl mNotificationListContainer =
             new NotificationListContainerImpl();
@@ -127,6 +133,8 @@
                 @Override
                 public void onViewAttachedToWindow(View v) {
                     mConfigurationController.addCallback(mConfigurationListener);
+                    mZenModeController.addCallback(mZenModeControllerCallback);
+                    mBarState = mStatusBarStateController.getState();
                     mStatusBarStateController.addCallback(
                             mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER);
                 }
@@ -134,6 +142,7 @@
                 @Override
                 public void onViewDetachedFromWindow(View v) {
                     mConfigurationController.removeCallback(mConfigurationListener);
+                    mZenModeController.removeCallback(mZenModeControllerCallback);
                     mStatusBarStateController.removeCallback(mStateListener);
                 }
             };
@@ -154,11 +163,13 @@
     final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
         @Override
         public void onDensityOrFontScaleChanged() {
+            updateShowEmptyShadeView();
             mView.reinflateViews();
         }
 
         @Override
         public void onOverlayChanged() {
+            updateShowEmptyShadeView();
             mView.updateCornerRadius();
             mView.reinflateViews();
         }
@@ -179,14 +190,15 @@
                 @Override
                 public void onStatePreChange(int oldState, int newState) {
                     if (oldState == StatusBarState.SHADE_LOCKED
-                            && newState == StatusBarState.KEYGUARD) {
+                            && newState == KEYGUARD) {
                         mView.requestAnimateEverything();
                     }
                 }
 
                 @Override
                 public void onStateChanged(int newState) {
-                    mView.setStatusBarState(newState);
+                    mBarState = newState;
+                    mView.setStatusBarState(mBarState);
                 }
 
                 @Override
@@ -480,6 +492,14 @@
         }
     };
 
+    private final ZenModeController.Callback mZenModeControllerCallback =
+            new ZenModeController.Callback() {
+                @Override
+                public void onZenChanged(int zen) {
+                    updateShowEmptyShadeView();
+                }
+            };
+
     @Inject
     public NotificationStackScrollLayoutController(
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
@@ -501,7 +521,9 @@
             @Main Resources resources,
             NotificationSwipeHelper.Builder notificationSwipeHelperBuilder,
             StatusBar statusBar,
-            ScrimController scrimController) {
+            ScrimController scrimController,
+            NotificationGroupManagerLegacy legacyGroupManager,
+            GroupExpansionManager groupManager) {
         mAllowLongPress = allowLongPress;
         mNotificationGutsManager = notificationGutsManager;
         mHeadsUpManager = headsUpManager;
@@ -522,12 +544,28 @@
         mNotificationSwipeHelperBuilder = notificationSwipeHelperBuilder;
         mStatusBar = statusBar;
         mScrimController = scrimController;
+        groupManager.registerGroupExpansionChangeListener((changedRow, expanded) -> {
+            mView.onGroupExpandChanged(changedRow, expanded);
+        });
+
+        legacyGroupManager.registerGroupChangeListener(new OnGroupChangeListener() {
+            @Override
+            public void onGroupCreatedFromChildren(NotificationGroup group) {
+                mStatusBar.requestNotificationUpdate("onGroupCreatedFromChildren");
+            }
+
+            @Override
+            public void onGroupsChanged() {
+                mStatusBar.requestNotificationUpdate("onGroupsChanged");
+            }
+        });
     }
 
     public void attach(NotificationStackScrollLayout view) {
         mView = view;
         mView.setController(this);
         mView.setTouchHandler(new TouchHandler());
+        mView.setStatusBar(mStatusBar);
 
         mSwipeHelper = mNotificationSwipeHelperBuilder
                 .setSwipeDirection(SwipeHelper.X)
@@ -795,6 +833,7 @@
 
     public void setQsExpanded(boolean expanded) {
         mView.setQsExpanded(expanded);
+        updateShowEmptyShadeView();
     }
 
     public void setScrollingEnabled(boolean enabled) {
@@ -903,8 +942,21 @@
         return mView.getFooterViewHeightWithPadding();
     }
 
-    public void updateEmptyShadeView(boolean visible) {
-        mView.updateEmptyShadeView(visible, mZenModeController.areNotificationsHiddenInShade());
+    /**
+     * Update whether we should show the empty shade view (no notifications in the shade).
+     * If so, send the update to our view.
+     */
+    public void updateShowEmptyShadeView() {
+        mShowEmptyShadeView = mBarState != KEYGUARD
+                && !mView.isQsExpanded()
+                && mView.getVisibleNotificationCount() == 0;
+        mView.updateEmptyShadeView(
+                mShowEmptyShadeView,
+                mZenModeController.areNotificationsHiddenInShade());
+    }
+
+    public boolean isShowingEmptyShadeView() {
+        return mShowEmptyShadeView;
     }
 
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
@@ -1012,14 +1064,6 @@
         mView.setNotificationPanelController(notificationPanelViewController);
     }
 
-    public void setStatusBar(StatusBar statusBar) {
-        mView.setStatusBar(statusBar);
-    }
-
-    public void setGroupManager(NotificationGroupManager groupManager) {
-        mView.setGroupManager(groupManager);
-    }
-
     public void setShelfController(NotificationShelfController notificationShelfController) {
         mView.setShelfController(notificationShelfController);
     }
@@ -1095,6 +1139,7 @@
     }
 
     private class NotificationListContainerImpl implements NotificationListContainer {
+
         @Override
         public void setChildTransferInProgress(boolean childTransferInProgress) {
             mView.setChildTransferInProgress(childTransferInProgress);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 8092cb9..3827123f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -55,7 +56,7 @@
     @VisibleForTesting
     final int mExtensionTime;
     private final KeyguardBypassController mBypassController;
-    private final NotificationGroupManager mGroupManager;
+    private final GroupMembershipManager mGroupMembershipManager;
     private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
     private final int mAutoHeadsUpNotificationDecay;
     // TODO (b/162832756): remove visual stability manager when migrating to new pipeline
@@ -101,7 +102,7 @@
     public HeadsUpManagerPhone(@NonNull final Context context,
             StatusBarStateController statusBarStateController,
             KeyguardBypassController bypassController,
-            NotificationGroupManager groupManager,
+            GroupMembershipManager groupMembershipManager,
             ConfigurationController configurationController) {
         super(context);
         Resources resources = mContext.getResources();
@@ -110,7 +111,7 @@
                 R.integer.auto_heads_up_notification_decay);
         statusBarStateController.addCallback(mStatusBarStateListener);
         mBypassController = bypassController;
-        mGroupManager = groupManager;
+        mGroupMembershipManager = groupMembershipManager;
 
         updateResources();
         configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@@ -166,7 +167,7 @@
         } else {
             if (topEntry.isChildInGroup()) {
                 final NotificationEntry groupSummary =
-                        mGroupManager.getGroupSummary(topEntry.getSbn());
+                        mGroupMembershipManager.getGroupSummary(topEntry);
                 if (groupSummary != null) {
                     topEntry = groupSummary;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index b6a284c..09034c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -40,6 +40,8 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.keyguard.dagger.ContainerView;
+import com.android.keyguard.dagger.KeyguardBouncerScope;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -50,9 +52,12 @@
 
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+
 /**
  * A class which manages the bouncer on the lockscreen.
  */
+@KeyguardBouncerScope
 public class KeyguardBouncer {
 
     private static final String TAG = "KeyguardBouncer";
@@ -95,8 +100,9 @@
     private boolean mIsAnimatingAway;
     private boolean mIsScrimmed;
 
+    @Inject
     public KeyguardBouncer(Context context, ViewMediatorCallback callback,
-            LockPatternUtils lockPatternUtils, ViewGroup container,
+            LockPatternUtils lockPatternUtils, @ContainerView ViewGroup container,
             DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager,
             BouncerExpansionCallback expansionCallback,
             KeyguardStateController keyguardStateController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index dd9c820..3181f52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -31,11 +31,11 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.row.RowContentBindParams;
 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
-import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
-import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -44,8 +44,8 @@
 import java.util.Objects;
 
 /**
- * A helper class dealing with the alert interactions between {@link NotificationGroupManager} and
- * {@link HeadsUpManager}. In particular, this class deals with keeping
+ * A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy}
+ * and {@link HeadsUpManager}. In particular, this class deals with keeping
  * the correct notification in a group alerting based off the group suppression.
  */
 public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
@@ -66,8 +66,8 @@
 
     private HeadsUpManager mHeadsUpManager;
     private final RowContentBindStage mRowContentBindStage;
-    private final NotificationGroupManager mGroupManager =
-            Dependency.get(NotificationGroupManager.class);
+    private final NotificationGroupManagerLegacy mGroupManager =
+            Dependency.get(NotificationGroupManagerLegacy.class);
 
     private NotificationEntryManager mEntryManager;
 
@@ -83,7 +83,7 @@
 
     /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */
     public void bind(NotificationEntryManager entryManager,
-            NotificationGroupManager groupManager) {
+            NotificationGroupManagerLegacy groupManager) {
         if (mEntryManager != null) {
             throw new IllegalStateException("Already bound.");
         }
@@ -95,7 +95,7 @@
         mEntryManager = entryManager;
 
         mEntryManager.addNotificationEntryListener(mNotificationEntryListener);
-        groupManager.addOnGroupChangeListener(mOnGroupChangeListener);
+        groupManager.registerGroupChangeListener(mOnGroupChangeListener);
     }
 
     /**
@@ -128,7 +128,8 @@
         mIsDozing = isDozing;
     }
 
-    private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() {
+    private final NotificationGroupManagerLegacy.OnGroupChangeListener mOnGroupChangeListener =
+            new NotificationGroupManagerLegacy.OnGroupChangeListener() {
         @Override
         public void onGroupCreated(NotificationGroup group, String groupKey) {
             mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group));
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 1cd85e3..169058a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -107,6 +107,8 @@
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -120,7 +122,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.InjectionInflationController;
 
 import java.io.FileDescriptor;
@@ -168,9 +169,6 @@
             mOnHeadsUpChangedListener =
             new MyOnHeadsUpChangedListener();
     private final HeightListener mHeightListener = new HeightListener();
-    private final ZenModeControllerCallback
-            mZenModeControllerCallback =
-            new ZenModeControllerCallback();
     private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
     private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
     private final ExpansionCallback mExpansionCallback = new ExpansionCallback();
@@ -178,7 +176,6 @@
     private final NotificationPanelView mView;
     private final MetricsLogger mMetricsLogger;
     private final ActivityManager mActivityManager;
-    private final ZenModeController mZenModeController;
     private final ConfigurationController mConfigurationController;
     private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@@ -342,8 +339,6 @@
     private boolean mKeyguardStatusViewAnimating;
     private ValueAnimator mQsSizeChangeAnimator;
 
-    private boolean mShowEmptyShadeView;
-
     private boolean mQsScrimEnabled = true;
     private boolean mQsTouchAboveFalsingThreshold;
     private int mQsFalsingThreshold;
@@ -367,7 +362,8 @@
         setHeadsUpAnimatingAway(false);
         notifyBarPanelExpansionChanged();
     };
-    private NotificationGroupManager mGroupManager;
+    // TODO (b/162832756): once migrated to the new pipeline, delete legacy group manager
+    private NotificationGroupManagerLegacy mGroupManager;
     private boolean mShowIconsWhenExpanded;
     private int mIndicationBottomPadding;
     private int mAmbientIndicationBottomPadding;
@@ -505,7 +501,7 @@
             LatencyTracker latencyTracker, PowerManager powerManager,
             AccessibilityManager accessibilityManager, @DisplayId int displayId,
             KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger,
-            ActivityManager activityManager, ZenModeController zenModeController,
+            ActivityManager activityManager,
             ConfigurationController configurationController,
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -514,20 +510,21 @@
             BiometricUnlockController biometricUnlockController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
-            NotificationIconAreaController notificationIconAreaController,
-            KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
+            KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
+            NotificationGroupManagerLegacy groupManager,
+            NotificationIconAreaController notificationIconAreaController) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
                 latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
         mView = view;
         mMetricsLogger = metricsLogger;
         mActivityManager = activityManager;
-        mZenModeController = zenModeController;
         mConfigurationController = configurationController;
         mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
         mMediaHierarchyManager = mediaHierarchyManager;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
+        mGroupManager = groupManager;
         mNotificationIconAreaController = notificationIconAreaController;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         mView.setWillNotDraw(!DEBUG);
@@ -724,8 +721,6 @@
     }
 
     private void reInflateViews() {
-        updateShowEmptyShadeView();
-
         // Re-inflate the status view group.
         int index = mView.indexOfChild(mKeyguardStatusView);
         mView.removeView(mKeyguardStatusView);
@@ -1727,7 +1722,6 @@
         mNotificationStackScrollLayoutController.setScrollingEnabled(
                 mBarState != KEYGUARD && (!mQsExpanded
                         || mQsExpansionFromOverscroll));
-        updateEmptyShadeView();
 
         mQsNavbarScrim.setVisibility(
                 mBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling
@@ -2145,7 +2139,7 @@
         // it in expanded QS state as well so we don't run into troubles when fading the view in/out
         // and expanding/collapsing the whole panel from/to quick settings.
         if (mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0
-                && mShowEmptyShadeView) {
+                && mNotificationStackScrollLayoutController.isShowingEmptyShadeView()) {
             notificationHeight = mNotificationStackScrollLayoutController.getEmptyShadeViewHeight();
         }
         int maxQsHeight = mQsMaxExpansionHeight;
@@ -2561,17 +2555,6 @@
         return mDozing;
     }
 
-    public void showEmptyShadeView(boolean emptyShadeViewVisible) {
-        mShowEmptyShadeView = emptyShadeViewVisible;
-        updateEmptyShadeView();
-    }
-
-    private void updateEmptyShadeView() {
-        // Hide "No notifications" in QS.
-        mNotificationStackScrollLayoutController.updateEmptyShadeView(
-                mShowEmptyShadeView && !mQsExpanded);
-    }
-
     public void setQsScrimEnabled(boolean qsScrimEnabled) {
         boolean changed = mQsScrimEnabled != qsScrimEnabled;
         mQsScrimEnabled = qsScrimEnabled;
@@ -2873,10 +2856,6 @@
         return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
     }
 
-    private void setGroupManager(NotificationGroupManager groupManager) {
-        mGroupManager = groupManager;
-    }
-
     public boolean hideStatusBarIconsWhenExpanded() {
         if (mLaunchingNotification) {
             return mHideIconsDuringNotificationLaunch;
@@ -3078,22 +3057,21 @@
         return mNotificationStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL);
     }
 
-    private void updateShowEmptyShadeView() {
-        boolean
-                showEmptyShadeView =
-                mBarState != KEYGUARD && !mEntryManager.hasActiveNotifications();
-        showEmptyShadeView(showEmptyShadeView);
-    }
-
     public RemoteInputController.Delegate createRemoteInputDelegate() {
         return mNotificationStackScrollLayoutController.createDelegate();
     }
 
-    void updateNotificationViews(String reason) {
+    /**
+     * Updates the notification views' sections and status bar icons. This is
+     * triggered by the NotificationPresenter whenever there are changes to the underlying
+     * notification data being displayed. In the new notification pipeline, this is handled in
+     * {@link ShadeViewManager}.
+     */
+    public void updateNotificationViews(String reason) {
         mNotificationStackScrollLayoutController.updateSectionBoundaries(reason);
         mNotificationStackScrollLayoutController.updateSpeedBumpIndex();
         mNotificationStackScrollLayoutController.updateFooter();
-        updateShowEmptyShadeView();
+
         mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
     }
 
@@ -3139,15 +3117,10 @@
      */
     public void initDependencies(
             StatusBar statusBar,
-            NotificationGroupManager groupManager,
             NotificationShelfController notificationShelfController) {
         setStatusBar(statusBar);
-        setGroupManager(mGroupManager);
         mNotificationStackScrollLayoutController.setNotificationPanelController(this);
-        mNotificationStackScrollLayoutController.setStatusBar(statusBar);
-        mNotificationStackScrollLayoutController.setGroupManager(groupManager);
         mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
-        updateShowEmptyShadeView();
         mNotificationShelfController = notificationShelfController;
         updateMaxDisplayedNotifications(true);
     }
@@ -3602,20 +3575,8 @@
         }
     }
 
-    private class ZenModeControllerCallback implements ZenModeController.Callback {
-        @Override
-        public void onZenChanged(int zen) {
-            updateShowEmptyShadeView();
-        }
-    }
-
     private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
         @Override
-        public void onDensityOrFontScaleChanged() {
-            updateShowEmptyShadeView();
-        }
-
-        @Override
         public void onThemeChanged() {
             final int themeResId = mView.getContext().getThemeResId();
             if (mThemeResId == themeResId) {
@@ -3712,7 +3673,6 @@
         public void onViewAttachedToWindow(View v) {
             FragmentHostManager.get(mView).addTagListener(QS.TAG, mFragmentListener);
             mStatusBarStateController.addCallback(mStatusBarStateListener);
-            mZenModeController.addCallback(mZenModeControllerCallback);
             mConfigurationController.addCallback(mConfigurationListener);
             mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
             // Theme might have changed between inflating this view and attaching it to the
@@ -3725,7 +3685,6 @@
         public void onViewDetachedFromWindow(View v) {
             FragmentHostManager.get(mView).removeTagListener(QS.TAG, mFragmentListener);
             mStatusBarStateController.removeCallback(mStatusBarStateListener);
-            mZenModeController.removeCallback(mZenModeControllerCallback);
             mConfigurationController.removeCallback(mConfigurationListener);
             mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 5882879..2986727 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -699,7 +699,6 @@
             SysuiStatusBarStateController statusBarStateController,
             VibratorHelper vibratorHelper,
             BubbleController bubbleController,
-            NotificationGroupManager groupManager,
             VisualStabilityManager visualStabilityManager,
             DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
@@ -780,7 +779,6 @@
         mStatusBarStateController = statusBarStateController;
         mVibratorHelper = vibratorHelper;
         mBubbleController = bubbleController;
-        mGroupManager = groupManager;
         mVisualStabilityManager = visualStabilityManager;
         mDeviceProvisionedController = deviceProvisionedController;
         mNavigationBarController = navigationBarController;
@@ -1159,7 +1157,6 @@
 
         mNotificationPanelViewController.initDependencies(
                 this,
-                mGroupManager,
                 mNotificationShelfController);
 
         BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
@@ -1506,9 +1503,8 @@
         mStatusBarKeyguardViewManager.registerStatusBar(
                 /* statusBar= */ this, getBouncerContainer(),
                 mNotificationPanelViewController, mBiometricUnlockController,
-                mDismissCallbackRegistry,
                 mNotificationShadeWindowView.findViewById(R.id.lock_icon_container),
-                mStackScroller, mKeyguardBypassController, mFalsingManager);
+                mStackScroller, mKeyguardBypassController);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
@@ -4084,8 +4080,6 @@
     // all notifications
     protected NotificationStackScrollLayout mStackScroller;
 
-    private final NotificationGroupManager mGroupManager;
-
     // handling reordering
     private final VisualStabilityManager mVisualStabilityManager;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 777bf3f7..b56993b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -44,15 +44,13 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
@@ -106,6 +104,7 @@
     private final NavigationModeController mNavigationModeController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final Optional<FaceAuthScreenBrightnessController> mFaceAuthScreenBrightnessController;
+    private final KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
     private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
         @Override
         public void onFullyShown() {
@@ -216,7 +215,8 @@
             NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController,
             Optional<FaceAuthScreenBrightnessController> faceAuthScreenBrightnessController,
-            NotificationMediaManager notificationMediaManager) {
+            NotificationMediaManager notificationMediaManager,
+            KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) {
         mContext = context;
         mViewMediatorCallback = callback;
         mLockPatternUtils = lockPatternUtils;
@@ -229,6 +229,7 @@
         mStatusBarStateController = sysuiStatusBarStateController;
         mDockManager = dockManager;
         mFaceAuthScreenBrightnessController = faceAuthScreenBrightnessController;
+        mKeyguardBouncerComponentFactory = keyguardBouncerComponentFactory;
     }
 
     @Override
@@ -236,9 +237,8 @@
             ViewGroup container,
             NotificationPanelViewController notificationPanelViewController,
             BiometricUnlockController biometricUnlockController,
-            DismissCallbackRegistry dismissCallbackRegistry,
             ViewGroup lockIconContainer, View notificationContainer,
-            KeyguardBypassController bypassController, FalsingManager falsingManager) {
+            KeyguardBypassController bypassController) {
         mStatusBar = statusBar;
         mContainer = container;
         mLockIconContainer = lockIconContainer;
@@ -246,9 +246,9 @@
             mLastLockVisible = mLockIconContainer.getVisibility() == View.VISIBLE;
         }
         mBiometricUnlockController = biometricUnlockController;
-        mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
-                mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
-                mExpansionCallback, mKeyguardStateController, falsingManager, bypassController);
+        mBouncer = mKeyguardBouncerComponentFactory
+                .build(container, mExpansionCallback)
+                .createKeyguardBouncer();
         mNotificationPanelViewController = notificationPanelViewController;
         notificationPanelViewController.addExpansionListener(this);
         mBypassController = bypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index de11c90..f806567 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -66,10 +66,10 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 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;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -96,7 +96,6 @@
 
     private final NotificationEntryManager mEntryManager;
     private final NotifPipeline mNotifPipeline;
-    private final NotifCollection mNotifCollection;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final ActivityStarter mActivityStarter;
     private final NotificationClickNotifier mClickNotifier;
@@ -107,7 +106,7 @@
     private final BubbleController mBubbleController;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final NotificationRemoteInputManager mRemoteInputManager;
-    private final NotificationGroupManager mGroupManager;
+    private final GroupMembershipManager mGroupMembershipManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final ShadeController mShadeController;
     private final KeyguardStateController mKeyguardStateController;
@@ -135,7 +134,6 @@
             Executor uiBgExecutor,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
-            NotifCollection notifCollection,
             HeadsUpManagerPhone headsUpManager,
             ActivityStarter activityStarter,
             NotificationClickNotifier clickNotifier,
@@ -146,7 +144,7 @@
             BubbleController bubbleController,
             Lazy<AssistManager> assistManagerLazy,
             NotificationRemoteInputManager remoteInputManager,
-            NotificationGroupManager groupManager,
+            GroupMembershipManager groupMembershipManager,
             NotificationLockscreenUserManager lockscreenUserManager,
             ShadeController shadeController,
             KeyguardStateController keyguardStateController,
@@ -170,7 +168,6 @@
         mUiBgExecutor = uiBgExecutor;
         mEntryManager = entryManager;
         mNotifPipeline = notifPipeline;
-        mNotifCollection = notifCollection;
         mHeadsUpManager = headsUpManager;
         mActivityStarter = activityStarter;
         mClickNotifier = clickNotifier;
@@ -181,7 +178,7 @@
         mBubbleController = bubbleController;
         mAssistManagerLazy = assistManagerLazy;
         mRemoteInputManager = remoteInputManager;
-        mGroupManager = groupManager;
+        mGroupMembershipManager = groupMembershipManager;
         mLockscreenUserManager = lockscreenUserManager;
         mShadeController = shadeController;
         mKeyguardStateController = keyguardStateController;
@@ -228,8 +225,9 @@
     public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
         mLogger.logStartingActivityFromClick(sbn.getKey());
 
+        final NotificationEntry entry = row.getEntry();
         RemoteInputController controller = mRemoteInputManager.getController();
-        if (controller.isRemoteInputActive(row.getEntry())
+        if (controller.isRemoteInputActive(entry)
                 && !TextUtils.isEmpty(row.getActiveRemoteInputText())) {
             // We have an active remote input typed and the user clicked on the notification.
             // this was probably unintentional, so we're closing the edit text instead.
@@ -240,7 +238,7 @@
         final PendingIntent intent = notification.contentIntent != null
                 ? notification.contentIntent
                 : notification.fullScreenIntent;
-        final boolean isBubble = row.getEntry().isBubble();
+        final boolean isBubble = entry.isBubble();
 
         // This code path is now executed for notification without a contentIntent.
         // The only valid case is Bubble notifications. Guard against other cases
@@ -260,7 +258,7 @@
                 mLockscreenUserManager.getCurrentUserId());
         ActivityStarter.OnDismissAction postKeyguardAction =
                 () -> handleNotificationClickAfterKeyguardDismissed(
-                        sbn, row, controller, intent,
+                        entry, row, controller, intent,
                         isActivityIntent, wasOccluded, showOverLockscreen);
         if (showOverLockscreen) {
             mIsCollapsingToShowActivityOverLockscreen = true;
@@ -272,27 +270,27 @@
     }
 
     private boolean handleNotificationClickAfterKeyguardDismissed(
-            StatusBarNotification sbn,
+            NotificationEntry entry,
             ExpandableNotificationRow row,
             RemoteInputController controller,
             PendingIntent intent,
             boolean isActivityIntent,
             boolean wasOccluded,
             boolean showOverLockscreen) {
-        mLogger.logHandleClickAfterKeyguardDismissed(sbn.getKey());
+        mLogger.logHandleClickAfterKeyguardDismissed(entry.getKey());
 
         // TODO: Some of this code may be able to move to NotificationEntryManager.
         removeHUN(row);
         NotificationEntry parentToCancel = null;
-        if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
-            NotificationEntry summarySbn = mGroupManager.getLogicalGroupSummary(sbn);
+        if (shouldAutoCancel(entry.getSbn()) && mGroupMembershipManager.isOnlyChildInGroup(entry)) {
+            NotificationEntry summarySbn = mGroupMembershipManager.getLogicalGroupSummary(entry);
             if (shouldAutoCancel(summarySbn.getSbn())) {
                 parentToCancel = summarySbn;
             }
         }
         final NotificationEntry parentToCancelFinal = parentToCancel;
         final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
-                sbn, row, controller, intent,
+                entry, row, controller, intent,
                 isActivityIntent, wasOccluded, parentToCancelFinal);
 
         if (showOverLockscreen) {
@@ -309,16 +307,16 @@
     }
 
     private void handleNotificationClickAfterPanelCollapsed(
-            StatusBarNotification sbn,
+            NotificationEntry entry,
             ExpandableNotificationRow row,
             RemoteInputController controller,
             PendingIntent intent,
             boolean isActivityIntent,
             boolean wasOccluded,
             NotificationEntry parentToCancelFinal) {
-        mLogger.logHandleClickAfterPanelCollapsed(sbn.getKey());
+        String notificationKey = entry.getKey();
+        mLogger.logHandleClickAfterPanelCollapsed(notificationKey);
 
-        String notificationKey = sbn.getKey();
         try {
             // The intent we are sending is for the application, which
             // won't have permission to immediately start an activity after
@@ -346,7 +344,6 @@
             }
         }
         Intent fillInIntent = null;
-        NotificationEntry entry = row.getEntry();
         CharSequence remoteInputText = null;
         if (!TextUtils.isEmpty(entry.remoteInputText)) {
             remoteInputText = entry.remoteInputText;
@@ -385,7 +382,7 @@
                 // necessary in the new pipeline due to group pruning in ShadeListBuilder.
                 removeNotification(parentToCancelFinal);
             }
-            if (shouldAutoCancel(sbn)
+            if (shouldAutoCancel(entry.getSbn())
                     || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
                     notificationKey)) {
                 // Automatically remove all notifications that we may have kept around longer
@@ -605,7 +602,6 @@
         private final Executor mUiBgExecutor;
         private final NotificationEntryManager mEntryManager;
         private final NotifPipeline mNotifPipeline;
-        private final NotifCollection mNotifCollection;
         private final HeadsUpManagerPhone mHeadsUpManager;
         private final ActivityStarter mActivityStarter;
         private final NotificationClickNotifier mClickNotifier;
@@ -616,7 +612,7 @@
         private final BubbleController mBubbleController;
         private final Lazy<AssistManager> mAssistManagerLazy;
         private final NotificationRemoteInputManager mRemoteInputManager;
-        private final NotificationGroupManager mGroupManager;
+        private final GroupMembershipManager mGroupMembershipManager;
         private final NotificationLockscreenUserManager mLockscreenUserManager;
         private final ShadeController mShadeController;
         private final KeyguardStateController mKeyguardStateController;
@@ -643,7 +639,6 @@
                 @UiBackground Executor uiBgExecutor,
                 NotificationEntryManager entryManager,
                 NotifPipeline notifPipeline,
-                NotifCollection notifCollection,
                 HeadsUpManagerPhone headsUpManager,
                 ActivityStarter activityStarter,
                 NotificationClickNotifier clickNotifier,
@@ -654,7 +649,7 @@
                 BubbleController bubbleController,
                 Lazy<AssistManager> assistManagerLazy,
                 NotificationRemoteInputManager remoteInputManager,
-                NotificationGroupManager groupManager,
+                GroupMembershipManager groupMembershipManager,
                 NotificationLockscreenUserManager lockscreenUserManager,
                 ShadeController shadeController,
                 KeyguardStateController keyguardStateController,
@@ -674,7 +669,6 @@
             mUiBgExecutor = uiBgExecutor;
             mEntryManager = entryManager;
             mNotifPipeline = notifPipeline;
-            mNotifCollection = notifCollection;
             mHeadsUpManager = headsUpManager;
             mActivityStarter = activityStarter;
             mClickNotifier = clickNotifier;
@@ -685,7 +679,7 @@
             mBubbleController = bubbleController;
             mAssistManagerLazy = assistManagerLazy;
             mRemoteInputManager = remoteInputManager;
-            mGroupManager = groupManager;
+            mGroupMembershipManager = groupMembershipManager;
             mLockscreenUserManager = lockscreenUserManager;
             mShadeController = shadeController;
             mKeyguardStateController = keyguardStateController;
@@ -731,7 +725,6 @@
                     mUiBgExecutor,
                     mEntryManager,
                     mNotifPipeline,
-                    mNotifCollection,
                     mHeadsUpManager,
                     mActivityStarter,
                     mClickNotifier,
@@ -742,7 +735,7 @@
                     mBubbleController,
                     mAssistManagerLazy,
                     mRemoteInputManager,
-                    mGroupManager,
+                    mGroupMembershipManager,
                     mLockscreenUserManager,
                     mShadeController,
                     mKeyguardStateController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 8a89429..36519ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager.Callback;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -65,7 +66,7 @@
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final ShadeController mShadeController;
     private final ActivityIntentHelper mActivityIntentHelper;
-    private final NotificationGroupManager mGroupManager;
+    private final GroupExpansionManager mGroupExpansionManager;
     private View mPendingWorkRemoteInputView;
     private View mPendingRemoteInputView;
     private KeyguardManager mKeyguardManager;
@@ -78,12 +79,15 @@
     /**
      */
     @Inject
-    public StatusBarRemoteInputCallback(Context context, NotificationGroupManager groupManager,
+    public StatusBarRemoteInputCallback(
+            Context context,
+            GroupExpansionManager groupExpansionManager,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            ActivityStarter activityStarter, ShadeController shadeController,
+            ActivityStarter activityStarter,
+            ShadeController shadeController,
             CommandQueue commandQueue,
             ActionClickLogger clickLogger) {
         mContext = context;
@@ -101,7 +105,7 @@
         mCommandQueue.addCallback(this);
         mActionClickLogger = clickLogger;
         mActivityIntentHelper = new ActivityIntentHelper(mContext);
-        mGroupManager = groupManager;
+        mGroupExpansionManager = groupExpansionManager;
     }
 
     @Override
@@ -182,7 +186,7 @@
         } else {
             if (row.isChildInGroup() && !row.areChildrenExpanded()) {
                 // The group isn't expanded, let's make sure it's visible!
-                mGroupManager.toggleGroupExpansion(row.getEntry().getSbn());
+                mGroupExpansionManager.toggleGroupExpansion(row.getEntry());
             }
             row.setUserExpanded(true);
             row.getPrivateLayout().setOnExpandedVisibleListener(clickedView::performClick);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 72067d3..16c3dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -80,7 +80,6 @@
 import com.android.systemui.statusbar.phone.LightsOutNotifController;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -158,7 +157,6 @@
             SysuiStatusBarStateController statusBarStateController,
             VibratorHelper vibratorHelper,
             BubbleController bubbleController,
-            NotificationGroupManager groupManager,
             VisualStabilityManager visualStabilityManager,
             DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
@@ -238,7 +236,6 @@
                 statusBarStateController,
                 vibratorHelper,
                 bubbleController,
-                groupManager,
                 visualStabilityManager,
                 deviceProvisionedController,
                 navigationBarController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
index 9b4e165..485b1b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -15,8 +15,6 @@
 package com.android.systemui.statusbar.policy;
 
 import android.app.ActivityManager;
-import android.content.ContentResolver;
-import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
@@ -30,6 +28,8 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
 
@@ -43,8 +43,8 @@
 
     protected static final String TAG = DeviceProvisionedControllerImpl.class.getSimpleName();
     protected final ArrayList<DeviceProvisionedListener> mListeners = new ArrayList<>();
-    private final ContentResolver mContentResolver;
-    private final Context mContext;
+    private final GlobalSettings mGlobalSettings;
+    private final SecureSettings mSecureSettings;
     private final Uri mDeviceProvisionedUri;
     private final Uri mUserSetupUri;
     protected final ContentObserver mSettingsObserver;
@@ -52,13 +52,14 @@
     /**
      */
     @Inject
-    public DeviceProvisionedControllerImpl(Context context, @Main Handler mainHandler,
-            BroadcastDispatcher broadcastDispatcher) {
+    public DeviceProvisionedControllerImpl(@Main Handler mainHandler,
+            BroadcastDispatcher broadcastDispatcher, GlobalSettings globalSettings,
+            SecureSettings secureSettings) {
         super(broadcastDispatcher);
-        mContext = context;
-        mContentResolver = context.getContentResolver();
-        mDeviceProvisionedUri = Global.getUriFor(Global.DEVICE_PROVISIONED);
-        mUserSetupUri = Secure.getUriFor(Secure.USER_SETUP_COMPLETE);
+        mGlobalSettings = globalSettings;
+        mSecureSettings = secureSettings;
+        mDeviceProvisionedUri = mGlobalSettings.getUriFor(Global.DEVICE_PROVISIONED);
+        mUserSetupUri = mSecureSettings.getUriFor(Secure.USER_SETUP_COMPLETE);
         mSettingsObserver = new ContentObserver(mainHandler) {
             @Override
             public void onChange(boolean selfChange, Uri uri, int flags) {
@@ -74,13 +75,12 @@
 
     @Override
     public boolean isDeviceProvisioned() {
-        return Global.getInt(mContentResolver, Global.DEVICE_PROVISIONED, 0) != 0;
+        return mGlobalSettings.getInt(Global.DEVICE_PROVISIONED, 0) != 0;
     }
 
     @Override
     public boolean isUserSetup(int currentUser) {
-        return Secure.getIntForUser(mContentResolver, Secure.USER_SETUP_COMPLETE, 0, currentUser)
-                != 0;
+        return mSecureSettings.getIntForUser(Secure.USER_SETUP_COMPLETE, 0, currentUser) != 0;
     }
 
     @Override
@@ -107,24 +107,24 @@
     }
 
     protected void startListening(int user) {
-        mContentResolver.registerContentObserver(mDeviceProvisionedUri, true,
+        mGlobalSettings.registerContentObserverForUser(mDeviceProvisionedUri, true,
                 mSettingsObserver, 0);
-        mContentResolver.registerContentObserver(mUserSetupUri, true,
+        mSecureSettings.registerContentObserverForUser(mUserSetupUri, true,
                 mSettingsObserver, user);
         startTracking();
     }
 
     protected void stopListening() {
         stopTracking();
-        mContentResolver.unregisterContentObserver(mSettingsObserver);
+        mGlobalSettings.unregisterContentObserver(mSettingsObserver);
     }
 
     @Override
     public void onUserSwitched(int newUserId) {
-        mContentResolver.unregisterContentObserver(mSettingsObserver);
-        mContentResolver.registerContentObserver(mDeviceProvisionedUri, true,
+        mGlobalSettings.unregisterContentObserver(mSettingsObserver);
+        mGlobalSettings.registerContentObserverForUser(mDeviceProvisionedUri, true,
                 mSettingsObserver, 0);
-        mContentResolver.registerContentObserver(mUserSetupUri, true,
+        mSecureSettings.registerContentObserverForUser(mUserSetupUri, true,
                 mSettingsObserver, newUserId);
         notifyUserChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index f9ac760..17fcb1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -206,7 +206,7 @@
             @Override
             protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) {
                 final SparseArray<Bitmap> bitmaps = params[0];
-                List<UserInfo> infos = mUserManager.getUsers(true);
+                List<UserInfo> infos = mUserManager.getAliveUsers();
                 if (infos == null) {
                     return null;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index e7c10f1..d727bfb 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -47,11 +47,11 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.ShadeControllerImpl;
@@ -136,7 +136,7 @@
             Context context,
             StatusBarStateController statusBarStateController,
             KeyguardBypassController bypassController,
-            NotificationGroupManager groupManager,
+            NotificationGroupManagerLegacy groupManager,
             ConfigurationController configurationController) {
         return new HeadsUpManagerPhone(context, statusBarStateController, bypassController,
                 groupManager, configurationController);
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
new file mode 100644
index 0000000..5946af3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.concurrency;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.concurrent.Executor;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger Module for classes found within the concurrent package.
+ */
+@Module
+public abstract class GlobalConcurrencyModule {
+
+    /**
+     * Binds {@link ThreadFactoryImpl} to {@link ThreadFactory}.
+     */
+    @Binds
+    public abstract ThreadFactory bindExecutorFactory(ThreadFactoryImpl impl);
+
+     /** Main Looper */
+    @Provides
+    @Main
+    public static  Looper provideMainLooper() {
+        return Looper.getMainLooper();
+    }
+
+    /**
+     * Main Handler.
+     *
+     * Prefer the Main Executor when possible.
+     */
+    @Provides
+    @Main
+    public static Handler provideMainHandler(@Main Looper mainLooper) {
+        return new Handler(mainLooper);
+    }
+
+    /**
+     * Provide a Main-Thread Executor.
+     */
+    @Provides
+    @Main
+    public static Executor provideMainExecutor(Context context) {
+        return context.getMainExecutor();
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
rename to packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index 628c808..b9b20c7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.util.concurrency;
 
-import android.content.Context;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -31,7 +30,6 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
-import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
@@ -39,7 +37,7 @@
  * Dagger Module for classes found within the concurrent package.
  */
 @Module
-public abstract class ConcurrencyModule {
+public abstract class SysUIConcurrencyModule {
     /** Background Looper */
     @Provides
     @SysUISingleton
@@ -62,13 +60,6 @@
         return thread.getLooper();
     }
 
-    /** Main Looper */
-    @Provides
-    @Main
-    public static  Looper provideMainLooper() {
-        return Looper.getMainLooper();
-    }
-
     /**
      * Background Handler.
      *
@@ -81,17 +72,6 @@
     }
 
     /**
-     * Main Handler.
-     *
-     * Prefer the Main Executor when possible.
-     */
-    @Provides
-    @Main
-    public static Handler provideMainHandler(@Main Looper mainLooper) {
-        return new Handler(mainLooper);
-    }
-
-    /**
      * Provide a Background-Thread Executor by default.
      */
     @Provides
@@ -121,15 +101,6 @@
     }
 
     /**
-     * Provide a Main-Thread Executor.
-     */
-    @Provides
-    @Main
-    public static Executor provideMainExecutor(Context context) {
-        return context.getMainExecutor();
-    }
-
-    /**
      * Provide a Background-Thread Executor by default.
      */
     @Provides
@@ -199,10 +170,4 @@
     public static Executor provideUiBackgroundExecutor() {
         return Executors.newSingleThreadExecutor();
     }
-
-    /**
-     * Binds {@link ThreadFactoryImpl} to {@link ThreadFactory}.
-     */
-    @Binds
-    public abstract ThreadFactory bindExecutorFactory(ThreadFactoryImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
index 5c37f79..5aaf7f6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
@@ -67,7 +67,35 @@
      * Implicitly calls {@link #getUriFor(String)} on the passed in name.
      */
     default void registerContentObserver(String name, ContentObserver settingsObserver) {
-        registerContentObserverForUser(name, settingsObserver, getUserId());
+        registerContentObserver(getUriFor(name), settingsObserver);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+     */
+    default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
+        registerContentObserverForUser(uri, settingsObserver, getUserId());
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserver(String name, boolean notifyForDescendents,
+            ContentObserver settingsObserver) {
+        registerContentObserver(getUriFor(name), notifyForDescendents, settingsObserver);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+     */
+    default void registerContentObserver(Uri uri, boolean notifyForDescendents,
+            ContentObserver settingsObserver) {
+        registerContentObserverForUser(uri, notifyForDescendents, settingsObserver, getUserId());
     }
 
     /**
@@ -78,8 +106,42 @@
      */
     default void registerContentObserverForUser(
             String name, ContentObserver settingsObserver, int userHandle) {
+        registerContentObserverForUser(
+                getUriFor(name), settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     */
+    default void registerContentObserverForUser(
+            Uri uri, ContentObserver settingsObserver, int userHandle) {
+        registerContentObserverForUser(
+                uri, false, settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserverForUser(
+            String name, boolean notifyForDescendents, ContentObserver settingsObserver,
+            int userHandle) {
+        registerContentObserverForUser(
+                getUriFor(name), notifyForDescendents, settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     */
+    default void registerContentObserverForUser(
+            Uri uri, boolean notifyForDescendents, ContentObserver settingsObserver,
+            int userHandle) {
         getContentResolver().registerContentObserver(
-                getUriFor(name), false, settingsObserver, userHandle);
+                uri, notifyForDescendents, settingsObserver, userHandle);
     }
 
     /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 51ad30e..78f83d3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -49,6 +49,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
+import android.graphics.Region;
 import android.graphics.drawable.ColorDrawable;
 import android.media.AudioManager;
 import android.media.AudioSystem;
@@ -71,6 +72,7 @@
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.view.ViewStub;
+import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
@@ -109,7 +111,8 @@
  * Methods ending in "H" must be called on the (ui) handler.
  */
 public class VolumeDialogImpl implements VolumeDialog,
-        ConfigurationController.ConfigurationListener {
+        ConfigurationController.ConfigurationListener,
+        ViewTreeObserver.OnComputeInternalInsetsListener {
     private static final String TAG = Util.logTag(VolumeDialogImpl.class);
 
     private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
@@ -126,6 +129,7 @@
     private final H mHandler = new H();
     private final VolumeDialogController mController;
     private final DeviceProvisionedController mDeviceProvisionedController;
+    private final Region mTouchableRegion = new Region();
 
     private Window mWindow;
     private CustomDialog mDialog;
@@ -204,6 +208,33 @@
         Dependency.get(ConfigurationController.class).removeCallback(this);
     }
 
+    @Override
+    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo internalInsetsInfo) {
+        // Set touchable region insets on the root dialog view. This tells WindowManager that
+        // touches outside of this region should not be delivered to the volume window, and instead
+        // go to the window below. This is the only way to do this - returning false in
+        // onDispatchTouchEvent results in the event being ignored entirely, rather than passed to
+        // the next window.
+        internalInsetsInfo.setTouchableInsets(
+                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+
+        mTouchableRegion.setEmpty();
+
+        // Set the touchable region to the union of all child view bounds. We don't use touches on
+        // the volume dialog container itself, so this is fine.
+        for (int i = 0; i < mDialogView.getChildCount(); i++) {
+            final View view = mDialogView.getChildAt(i);
+            mTouchableRegion.op(
+                    view.getLeft(),
+                    view.getTop(),
+                    view.getRight(),
+                    view.getBottom(),
+                    Region.Op.UNION);
+        }
+
+        internalInsetsInfo.touchableRegion.set(mTouchableRegion);
+    }
+
     private void initDialog() {
         mDialog = new CustomDialog(mContext);
 
@@ -235,6 +266,7 @@
         mDialogView.setAlpha(0);
         mDialog.setCanceledOnTouchOutside(true);
         mDialog.setOnShowListener(dialog -> {
+            mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
             if (!isLandscape()) mDialogView.setTranslationX(mDialogView.getWidth() / 2.0f);
             mDialogView.setAlpha(0);
             mDialogView.animate()
@@ -253,6 +285,11 @@
                     .start();
         });
 
+        mDialog.setOnDismissListener(dialogInterface ->
+                mDialogView
+                        .getViewTreeObserver()
+                        .removeOnComputeInternalInsetsListener(VolumeDialogImpl.this));
+
         mDialogView.setOnHoverListener((v, event) -> {
             int action = event.getActionMasked();
             mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
@@ -1369,6 +1406,11 @@
             super(context, R.style.volume_dialog_theme);
         }
 
+        /**
+         * NOTE: This will only be called for touches within the touchable region of the volume
+         * dialog, as returned by {@link #onComputeInternalInsets}. Other touches, even if they are
+         * within the bounds of the volume dialog, will fall through to the window below.
+         */
         @Override
         public boolean dispatchTouchEvent(MotionEvent ev) {
             rescheduleTimeoutH();
@@ -1387,6 +1429,12 @@
             mHandler.sendEmptyMessage(H.RECHECK_ALL);
         }
 
+        /**
+         * NOTE: This will be called with ACTION_OUTSIDE MotionEvents for touches that occur outside
+         * of the touchable region of the volume dialog (as returned by
+         * {@link #onComputeInternalInsets}) even if those touches occurred within the bounds of the
+         * volume dialog.
+         */
         @Override
         public boolean onTouchEvent(MotionEvent event) {
             if (mShowing) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index a7808ad..f65c2c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -77,13 +77,13 @@
 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.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -118,7 +118,7 @@
     @Mock
     private NotificationEntryManager mNotificationEntryManager;
     @Mock
-    private NotificationGroupManager mNotificationGroupManager;
+    private NotificationGroupManagerLegacy mNotificationGroupManager;
     @Mock
     private WindowManager mWindowManager;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 4936360..dd191e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -75,6 +75,7 @@
 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.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -82,7 +83,6 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -116,7 +116,7 @@
     @Mock
     private NotificationEntryManager mNotificationEntryManager;
     @Mock
-    private NotificationGroupManager mNotificationGroupManager;
+    private NotificationGroupManagerLegacy mNotificationGroupManager;
     @Mock
     private BubbleController.NotifCallback mNotifCallback;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
index 51ca2a4..58b27f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -30,8 +30,8 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -52,7 +52,7 @@
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager lockscreenUserManager,
-            NotificationGroupManager groupManager,
+            NotificationGroupManagerLegacy groupManager,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
             FeatureFlags featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index d2bf483..d041ee0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -53,7 +54,6 @@
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 
 import com.google.android.collect.Lists;
 
@@ -76,7 +76,7 @@
     // Dependency mocks:
     @Mock private NotificationEntryManager mEntryManager;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
-    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private NotificationGroupManagerLegacy mGroupManager;
     @Mock private VisualStabilityManager mVisualStabilityManager;
 
     private TestableLooper mTestableLooper;
@@ -95,7 +95,7 @@
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
                 mLockscreenUserManager);
-        mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
+        mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
         mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
         when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true);
         when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true);
@@ -136,11 +136,11 @@
                 Lists.newArrayList(entry0, entry1, entry2));
 
         // Set up group manager to report that they should be bundled now.
-        when(mGroupManager.isChildInGroupWithSummary(entry0.getSbn())).thenReturn(false);
-        when(mGroupManager.isChildInGroupWithSummary(entry1.getSbn())).thenReturn(true);
-        when(mGroupManager.isChildInGroupWithSummary(entry2.getSbn())).thenReturn(true);
-        when(mGroupManager.getGroupSummary(entry1.getSbn())).thenReturn(entry0);
-        when(mGroupManager.getGroupSummary(entry2.getSbn())).thenReturn(entry0);
+        when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
+        when(mGroupManager.isChildInGroup(entry1)).thenReturn(true);
+        when(mGroupManager.isChildInGroup(entry2)).thenReturn(true);
+        when(mGroupManager.getGroupSummary(entry1)).thenReturn(entry0);
+        when(mGroupManager.getGroupSummary(entry2)).thenReturn(entry0);
 
         // Run updateNotifications - the view hierarchy should be reorganized.
         mViewHierarchyManager.updateNotificationViews();
@@ -165,9 +165,9 @@
                 Lists.newArrayList(entry0, entry1, entry2));
 
         // Set up group manager to report that they should not be bundled now.
-        when(mGroupManager.isChildInGroupWithSummary(entry0.getSbn())).thenReturn(false);
-        when(mGroupManager.isChildInGroupWithSummary(entry1.getSbn())).thenReturn(false);
-        when(mGroupManager.isChildInGroupWithSummary(entry2.getSbn())).thenReturn(false);
+        when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
+        when(mGroupManager.isChildInGroup(entry1)).thenReturn(false);
+        when(mGroupManager.isChildInGroup(entry2)).thenReturn(false);
 
         // Run updateNotifications - the view hierarchy should be reorganized.
         mViewHierarchyManager.updateNotificationViews();
@@ -194,8 +194,8 @@
                 Lists.newArrayList(entry0, entry1));
 
         // Set up group manager to report a suppressed summary now.
-        when(mGroupManager.isChildInGroupWithSummary(entry0.getSbn())).thenReturn(false);
-        when(mGroupManager.isChildInGroupWithSummary(entry1.getSbn())).thenReturn(false);
+        when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
+        when(mGroupManager.isChildInGroup(entry1)).thenReturn(false);
         when(mGroupManager.isSummaryOfSuppressedGroup(entry0.getSbn())).thenReturn(true);
 
         // Run updateNotifications - the view hierarchy should be reorganized.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index fc0201a..3e1616c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -75,13 +75,13 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationEntryManagerInflationTest;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.leak.LeakDetector;
@@ -119,7 +119,7 @@
     @Mock private NotificationRemoveInterceptor mRemoveInterceptor;
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private RankingMap mRankingMap;
-    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private NotificationGroupManagerLegacy mGroupManager;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private RowInflaterTask mAsyncInflationTask;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index 5a81d36..dfe006d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -48,10 +48,10 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 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.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 
 import org.junit.After;
@@ -109,8 +109,8 @@
                 eq(UID_ALLOW_DURING_SETUP)))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
         mDependency.injectTestDependency(ForegroundServiceController.class, mFsc);
-        mDependency.injectTestDependency(NotificationGroupManager.class,
-                new NotificationGroupManager(
+        mDependency.injectTestDependency(NotificationGroupManagerLegacy.class,
+                new NotificationGroupManagerLegacy(
                         mock(StatusBarStateController.class),
                         () -> mock(PeopleNotificationIdentifier.class)));
         mDependency.injectMockDependency(ShadeController.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
index 386c866c..14877ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -37,8 +37,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -48,12 +48,13 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class HighPriorityProviderTest extends SysuiTestCase {
     @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
-    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private GroupMembershipManager mGroupMembershipManager;
     private HighPriorityProvider mHighPriorityProvider;
 
     @Before
@@ -61,7 +62,7 @@
         MockitoAnnotations.initMocks(this);
         mHighPriorityProvider = new HighPriorityProvider(
                 mPeopleNotificationIdentifier,
-                mGroupManager);
+                mGroupMembershipManager);
     }
 
     @Test
@@ -71,7 +72,7 @@
                 .setImportance(IMPORTANCE_HIGH)
                 .build();
         when(mPeopleNotificationIdentifier
-                .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+                .getPeopleNotificationType(entry))
                 .thenReturn(TYPE_NON_PERSON);
 
         // THEN it has high priority
@@ -88,7 +89,7 @@
                 .setImportance(IMPORTANCE_LOW)
                 .build();
         when(mPeopleNotificationIdentifier
-                .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+                .getPeopleNotificationType(entry))
                 .thenReturn(TYPE_PERSON);
 
         // THEN it has high priority
@@ -105,7 +106,7 @@
                 .setNotification(notification)
                 .build();
         when(mPeopleNotificationIdentifier
-                .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+                .getPeopleNotificationType(entry))
                 .thenReturn(TYPE_NON_PERSON);
 
         // THEN it has high priority
@@ -123,7 +124,7 @@
                 .setImportance(IMPORTANCE_LOW)
                 .build();
         when(mPeopleNotificationIdentifier
-                .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+                .getPeopleNotificationType(entry))
                 .thenReturn(TYPE_NON_PERSON);
 
         // THEN it has high priority
@@ -141,7 +142,7 @@
                 .setImportance(IMPORTANCE_MIN)
                 .build();
         when(mPeopleNotificationIdentifier
-                .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+                .getPeopleNotificationType(entry))
                 .thenReturn(TYPE_NON_PERSON);
 
         // THEN it does NOT have high priority
@@ -165,7 +166,7 @@
                 .setChannel(channel)
                 .build();
         when(mPeopleNotificationIdentifier
-                .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+                .getPeopleNotificationType(entry))
                 .thenReturn(TYPE_PERSON);
 
         // THEN it does NOT have high priority
@@ -173,13 +174,13 @@
     }
 
     @Test
-    public void testIsHighPriority_checkChildrenToCalculatePriority() {
+    public void testIsHighPriority_checkChildrenToCalculatePriority_legacy() {
         // GIVEN: a summary with low priority has a highPriorityChild and a lowPriorityChild
         final NotificationEntry summary = createNotifEntry(false);
         final NotificationEntry lowPriorityChild = createNotifEntry(false);
         final NotificationEntry highPriorityChild = createNotifEntry(true);
-        when(mGroupManager.isGroupSummary(summary.getSbn())).thenReturn(true);
-        when(mGroupManager.getChildren(summary.getSbn())).thenReturn(
+        when(mGroupMembershipManager.isGroupSummary(summary)).thenReturn(true);
+        when(mGroupMembershipManager.getChildren(summary)).thenReturn(
                 new ArrayList<>(Arrays.asList(lowPriorityChild, highPriorityChild)));
 
         // THEN the summary is high priority since it has a high priority child
@@ -210,16 +211,20 @@
     }
 
     @Test
-    public void testIsHighPriority_checkChildrenToCalculatePriorityOf() {
+    public void testIsHighPriority_checkChildrenToCalculatePriority() {
         // GIVEN:
-        // GroupEntry = parentEntry, summary = lowPrioritySummary
+        // parent with summary = lowPrioritySummary
         //      NotificationEntry = lowPriorityChild
         //      NotificationEntry = highPriorityChild
+        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
         final GroupEntry parentEntry = new GroupEntryBuilder()
-                .setSummary(createNotifEntry(false))
-                .addChild(createNotifEntry(false))
-                .addChild(createNotifEntry(true))
+                .setSummary(lowPrioritySummary)
                 .build();
+        when(mGroupMembershipManager.getChildren(parentEntry)).thenReturn(
+                new ArrayList<>(
+                        List.of(
+                                createNotifEntry(false),
+                                createNotifEntry(true))));
 
         // THEN the GroupEntry parentEntry is high priority since it has a high priority child
         assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
@@ -228,13 +233,15 @@
     @Test
     public void testIsHighPriority_childEntryRankingUpdated() {
         // GIVEN:
-        // GroupEntry = parentEntry, summary = lowPrioritySummary
+        // parent with summary = lowPrioritySummary
         //      NotificationEntry = lowPriorityChild
-        final NotificationEntry lowPriorityChild = createNotifEntry(false);
+        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
         final GroupEntry parentEntry = new GroupEntryBuilder()
-                .setSummary(createNotifEntry(false))
-                .addChild(lowPriorityChild)
+                .setSummary(lowPrioritySummary)
                 .build();
+        final NotificationEntry lowPriorityChild = createNotifEntry(false);
+        when(mGroupMembershipManager.getChildren(parentEntry)).thenReturn(
+                new ArrayList<>(List.of(lowPriorityChild)));
 
         // WHEN the child entry ranking changes to high priority
         lowPriorityChild.setRanking(
@@ -243,9 +250,8 @@
                         .setImportance(IMPORTANCE_HIGH)
                         .build());
 
-        // THEN the parent entry's high priority value is updated - but not the parent's summary
+        // THEN the parent entry's high priority value is updated
         assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
-        assertFalse(mHighPriorityProvider.isHighPriority(parentEntry.getSummary()));
     }
 
     private NotificationEntry createNotifEntry(boolean highPriority) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index 82a7774..c832fe4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -40,7 +40,7 @@
 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
 import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
-import com.android.systemui.statusbar.phone.NotificationGroupManager
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
@@ -71,14 +71,14 @@
         notificationFilter = mock(NotificationFilter::class.java)
         rankingManager = TestableNotificationRankingManager(
                 lazyMedia,
-                mock(NotificationGroupManager::class.java),
+                mock(NotificationGroupManagerLegacy::class.java),
                 mock(HeadsUpManager::class.java),
                 notificationFilter,
                 mock(NotificationEntryManagerLogger::class.java),
                 sectionsManager,
                 personNotificationIdentifier,
                 HighPriorityProvider(personNotificationIdentifier,
-                    mock(NotificationGroupManager::class.java))
+                    mock(NotificationGroupManagerLegacy::class.java))
         )
     }
 
@@ -174,7 +174,7 @@
                 .setOverrideGroupKey("")
                 .build()
 
-        whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
+        whenever(personNotificationIdentifier.getPeopleNotificationType(a))
                 .thenReturn(TYPE_IMPORTANT_PERSON)
 
         val bN = Notification.Builder(mContext, "test")
@@ -194,7 +194,7 @@
             whenever(it.isHeadsUp).thenReturn(true)
         }
 
-        whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
+        whenever(personNotificationIdentifier.getPeopleNotificationType(a))
                 .thenReturn(TYPE_PERSON)
 
         assertEquals(listOf(b, a), rankingManager.updateRanking(null, listOf(a, b), "test"))
@@ -216,7 +216,7 @@
                 .setUser(mContext.user)
                 .setOverrideGroupKey("")
                 .build()
-        whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
+        whenever(personNotificationIdentifier.getPeopleNotificationType(a))
                 .thenReturn(TYPE_PERSON)
 
         val bN = Notification.Builder(mContext, "test")
@@ -232,7 +232,7 @@
                 .setUser(mContext.user)
                 .setOverrideGroupKey("")
                 .build()
-        whenever(personNotificationIdentifier.getPeopleNotificationType(b.sbn, b.ranking))
+        whenever(personNotificationIdentifier.getPeopleNotificationType(b))
                 .thenReturn(TYPE_IMPORTANT_PERSON)
 
         whenever(personNotificationIdentifier.compareTo(TYPE_PERSON, TYPE_IMPORTANT_PERSON))
@@ -261,7 +261,7 @@
                 .setUser(mContext.user)
                 .setOverrideGroupKey("")
                 .build()
-        whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
+        whenever(personNotificationIdentifier.getPeopleNotificationType(a))
                 .thenReturn(TYPE_PERSON)
 
         val bN = Notification.Builder(mContext, "test")
@@ -277,7 +277,7 @@
                 .setUser(mContext.user)
                 .setOverrideGroupKey("")
                 .build()
-        whenever(personNotificationIdentifier.getPeopleNotificationType(b.sbn, b.ranking))
+        whenever(personNotificationIdentifier.getPeopleNotificationType(b))
                 .thenReturn(TYPE_FULL_PERSON)
 
         whenever(personNotificationIdentifier.compareTo(TYPE_PERSON, TYPE_FULL_PERSON))
@@ -400,7 +400,7 @@
                 .setUser(mContext.user)
                 .setOverrideGroupKey("")
                 .build()
-        whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
+        whenever(personNotificationIdentifier.getPeopleNotificationType(a))
                 .thenReturn(TYPE_IMPORTANT_PERSON)
 
         assertThat(rankingManager.updateRanking(null, listOf(a, b, c), "test"))
@@ -410,7 +410,7 @@
 
     internal class TestableNotificationRankingManager(
         mediaManager: Lazy<NotificationMediaManager>,
-        groupManager: NotificationGroupManager,
+        groupManager: NotificationGroupManagerLegacy,
         headsUpManager: HeadsUpManager,
         filter: NotificationFilter,
         logger: NotificationEntryManagerLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index c49393d..09c9bcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -84,8 +84,8 @@
 
     @Test
     fun testInPeopleSection() {
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(
-            entry.sbn, entry.ranking)).thenReturn(TYPE_PERSON)
+        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
+            .thenReturn(TYPE_PERSON)
 
         // only put people notifications in this section
         assertTrue(peopleSectioner.isInSection(entry))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 7a0a19b..aff8ade 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -67,6 +67,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.icon.IconBuilder;
 import com.android.systemui.statusbar.notification.icon.IconManager;
@@ -77,7 +78,6 @@
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -130,7 +130,8 @@
     @Mock private KeyguardBypassController mKeyguardBypassController;
     @Mock private StatusBarStateController mStatusBarStateController;
 
-    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private NotificationGroupManagerLegacy mGroupMembershipManager;
+    @Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private LeakDetector mLeakDetector;
 
@@ -170,10 +171,10 @@
 
         mEntryManager = new NotificationEntryManager(
                 mock(NotificationEntryManagerLogger.class),
-                mGroupManager,
+                mGroupMembershipManager,
                 new NotificationRankingManager(
                         () -> mock(NotificationMediaManager.class),
-                        mGroupManager,
+                        mGroupMembershipManager,
                         mHeadsUpManager,
                         mock(NotificationFilter.class),
                         mock(NotificationEntryManagerLogger.class),
@@ -228,7 +229,8 @@
                                 new FakeSystemClock(),
                                 "FOOBAR", "FOOBAR",
                                 mKeyguardBypassController,
-                                mGroupManager,
+                                mGroupMembershipManager,
+                                mGroupExpansionManager,
                                 mRowContentBindStage,
                                 mock(NotificationLogger.class),
                                 mHeadsUpManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index df26c5b..b952c05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -57,6 +57,7 @@
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 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.icon.IconBuilder;
@@ -68,7 +69,6 @@
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
 
 import org.mockito.ArgumentCaptor;
@@ -96,7 +96,8 @@
     private final Context mContext;
     private final TestableLooper mTestLooper;
     private int mId;
-    private final NotificationGroupManager mGroupManager;
+    private final NotificationGroupManagerLegacy mGroupMembershipManager;
+    private final NotificationGroupManagerLegacy mGroupExpansionManager;
     private ExpandableNotificationRow mRow;
     private HeadsUpManagerPhone mHeadsUpManager;
     private final NotifBindPipeline mBindPipeline;
@@ -116,13 +117,14 @@
         dependency.injectMockDependency(BubbleController.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
-        mGroupManager = new NotificationGroupManager(
+        mGroupMembershipManager = new NotificationGroupManagerLegacy(
                 mStatusBarStateController,
                 () -> mock(PeopleNotificationIdentifier.class));
+        mGroupExpansionManager = mGroupMembershipManager;
         mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController,
-                mock(KeyguardBypassController.class), mock(NotificationGroupManager.class),
+                mock(KeyguardBypassController.class), mock(NotificationGroupManagerLegacy.class),
                 mock(ConfigurationControllerImpl.class));
-        mGroupManager.setHeadsUpManager(mHeadsUpManager);
+        mGroupMembershipManager.setHeadsUpManager(mHeadsUpManager);
         mIconManager = new IconManager(
                 mock(CommonNotifCollection.class),
                 mock(LauncherApps.class),
@@ -416,7 +418,8 @@
                 entry.getKey(),
                 mock(ExpansionLogger.class),
                 mock(KeyguardBypassController.class),
-                mGroupManager,
+                mGroupMembershipManager,
+                mGroupExpansionManager,
                 mHeadsUpManager,
                 mBindStage,
                 mock(OnExpandClickListener.class),
@@ -434,7 +437,7 @@
         // This would be done as part of onAsyncInflationFinished, but we skip large amounts of
         // the callback chain, so we need to make up for not adding it to the group manager
         // here.
-        mGroupManager.onEntryAdded(entry);
+        mGroupMembershipManager.onEntryAdded(entry);
         return row;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 62b741c..5264458 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -47,9 +47,6 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -72,6 +69,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -80,8 +78,6 @@
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.KeyguardBypassEnabledProvider;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.leak.LeakDetector;
@@ -115,7 +111,8 @@
     @Mock private SysuiStatusBarStateController mBarState;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationBlockingHelperManager mBlockingHelperManager;
-    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private NotificationGroupManagerLegacy mGroupMembershipManger;
+    @Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
     @Mock private ExpandHelper mExpandHelper;
     @Mock private EmptyShadeView mEmptyShadeView;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
@@ -128,6 +125,7 @@
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private NotificationSwipeHelper mNotificationSwipeHelper;
+    @Mock NotificationStackScrollLayoutController mStackScrollLayoutController;
     private NotificationEntryManager mEntryManager;
     private int mOriginalInterruptionModelSetting;
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
@@ -159,10 +157,10 @@
 
         mEntryManager = new NotificationEntryManager(
                 mock(NotificationEntryManagerLogger.class),
-                mock(NotificationGroupManager.class),
+                mock(NotificationGroupManagerLegacy.class),
                 new NotificationRankingManager(
                         () -> mock(NotificationMediaManager.class),
-                        mGroupManager,
+                        mGroupMembershipManger,
                         mHeadsUpManager,
                         mock(NotificationFilter.class),
                         mock(NotificationEntryManagerLogger.class),
@@ -207,15 +205,19 @@
                 mock(NotifPipeline.class),
                 mEntryManager,
                 mock(NotifCollection.class),
-                mUiEventLoggerFake
+                mUiEventLoggerFake,
+                mGroupMembershipManger,
+                mGroupExpansionManager
         );
         mStackScrollerInternal.initView(getContext(), mKeyguardBypassEnabledProvider,
                 mNotificationSwipeHelper);
         mStackScroller = spy(mStackScrollerInternal);
         mStackScroller.setShelfController(notificationShelfController);
         mStackScroller.setStatusBar(mBar);
-        mStackScroller.setGroupManager(mGroupManager);
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
+        when(mStackScrollLayoutController.getNoticationRoundessManager())
+                .thenReturn(mock(NotificationRoundnessManager.class));
+        mStackScroller.setController(mStackScrollLayoutController);
 
         // Stub out functionality that isn't necessary to test.
         doNothing().when(mBar)
@@ -224,7 +226,7 @@
                         anyBoolean(),
                         anyBoolean(),
                         anyBoolean());
-        doNothing().when(mGroupManager).collapseAllGroups();
+        doNothing().when(mGroupExpansionManager).collapseGroups();
         doNothing().when(mExpandHelper).cancelImmediately();
         doNothing().when(notificationShelf).setAnimationsEnabled(anyBoolean());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
index f5d9fa0..32c6828 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -45,11 +48,11 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -62,6 +65,7 @@
 import org.mockito.Answers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -114,6 +118,11 @@
     private StatusBar mStatusBar;
     @Mock
     private ScrimController mScrimController;
+    @Mock
+    private NotificationGroupManagerLegacy mLegacyGroupManager;
+
+    @Captor
+    ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
 
     private NotificationStackScrollLayoutController mController;
 
@@ -143,7 +152,9 @@
                 mResources,
                 mNotificationSwipeHelperBuilder,
                 mStatusBar,
-                mScrimController
+                mScrimController,
+                mLegacyGroupManager,
+                mLegacyGroupManager
         );
 
         when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
@@ -181,32 +192,49 @@
     }
 
     @Test
-    public void testUpdateEmptyShadeView_notificationsVisible() {
+    public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
         mController.attach(mNotificationStackScrollLayout);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
 
-        mController.updateEmptyShadeView(true /* visible */);
+        setupShowEmptyShadeViewState(stateListener, true);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 true /* visible */,
+
                 true /* notifVisibleInShade */);
+
+        setupShowEmptyShadeViewState(stateListener, false);
         reset(mNotificationStackScrollLayout);
-        mController.updateEmptyShadeView(false /* visible */);
+        mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 false /* visible */,
                 true /* notifVisibleInShade */);
     }
 
     @Test
-    public void testUpdateEmptyShadeView_notificationsHidden() {
+    public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         mController.attach(mNotificationStackScrollLayout);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
 
-        mController.updateEmptyShadeView(true /* visible */);
+        setupShowEmptyShadeViewState(stateListener, true);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 true /* visible */,
                 false /* notifVisibleInShade */);
+
+        setupShowEmptyShadeViewState(stateListener, false);
         reset(mNotificationStackScrollLayout);
-        mController.updateEmptyShadeView(false /* visible */);
+        mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 false /* visible */,
                 false /* notifVisibleInShade */);
@@ -234,15 +262,12 @@
     public void testOnStatePostChange_verifyIfProfileIsPublic() {
         when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
 
-        ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
-                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
-
         mController.attach(mNotificationStackScrollLayout);
         verify(mSysuiStatusBarStateController).addCallback(
-                stateListenerArgumentCaptor.capture(), anyInt());
+                mStateListenerArgumentCaptor.capture(), anyInt());
 
         StatusBarStateController.StateListener stateListener =
-                stateListenerArgumentCaptor.getValue();
+                mStateListenerArgumentCaptor.getValue();
 
         stateListener.onStatePostChange();
         verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
@@ -299,6 +324,20 @@
         return argThat(new LogMatcher(category, type));
     }
 
+    private void setupShowEmptyShadeViewState(
+            StatusBarStateController.StateListener statusBarStateListener,
+            boolean toShow) {
+        if (toShow) {
+            statusBarStateListener.onStateChanged(SHADE);
+            mController.setQsExpanded(false);
+            mController.getView().removeAllViews();
+        } else {
+            statusBarStateListener.onStateChanged(KEYGUARD);
+            mController.setQsExpanded(true);
+            mController.getView().addContainerView(mock(ExpandableNotificationRow.class));
+        }
+    }
+
     static class LogMatcher implements ArgumentMatcher<LogMaker> {
         private int mCategory, mType;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 2239b1b..57020eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -36,6 +36,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 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.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -57,7 +58,7 @@
 
     private HeadsUpManagerPhone mHeadsUpManager;
 
-    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private NotificationGroupManagerLegacy mGroupManager;
     @Mock private View mNotificationShadeWindowView;
     @Mock private VisualStabilityManager mVSManager;
     @Mock private StatusBar mBar;
@@ -69,7 +70,7 @@
     private final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
         TestableHeadsUpManagerPhone(
                 Context context,
-                NotificationGroupManager groupManager,
+                NotificationGroupManagerLegacy groupManager,
                 VisualStabilityManager vsManager,
                 StatusBarStateController statusBarStateController,
                 KeyguardBypassController keyguardBypassController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index 885dff3..2ece8be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
 import com.android.systemui.statusbar.notification.row.RowContentBindParams;
@@ -68,7 +69,7 @@
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
     private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper;
-    private NotificationGroupManager mGroupManager;
+    private NotificationGroupManagerLegacy mGroupManager;
     private HeadsUpManager mHeadsUpManager;
     @Mock private NotificationEntryManager mNotificationEntryManager;
     @Mock private RowContentBindStage mBindStage;
@@ -88,10 +89,10 @@
         when(mNotificationEntryManager.getPendingNotificationsIterator())
                 .thenReturn(mPendingEntries.values());
 
-        mGroupManager = new NotificationGroupManager(
+        mGroupManager = new NotificationGroupManagerLegacy(
                 mock(StatusBarStateController.class),
                 () -> mock(PeopleNotificationIdentifier.class));
-        mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
+        mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
 
         when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
similarity index 87%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
index 5a6f74a..0aa0091 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
@@ -33,6 +33,7 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -47,11 +48,11 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class NotificationGroupManagerTest extends SysuiTestCase {
+public class NotificationGroupManagerLegacyTest extends SysuiTestCase {
     @Rule
     public MockitoRule rule = MockitoJUnit.rule();
 
-    private NotificationGroupManager mGroupManager;
+    private NotificationGroupManagerLegacy mGroupManager;
     private final NotificationGroupTestHelper mGroupTestHelper =
             new NotificationGroupTestHelper(mContext);
 
@@ -64,7 +65,7 @@
     }
 
     private void initializeGroupManager() {
-        mGroupManager = new NotificationGroupManager(
+        mGroupManager = new NotificationGroupManagerLegacy(
                 mock(StatusBarStateController.class),
                 () -> mock(PeopleNotificationIdentifier.class));
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
@@ -78,7 +79,7 @@
         mGroupManager.onEntryAdded(summaryEntry);
         mGroupManager.onEntryAdded(childEntry);
 
-        assertTrue(mGroupManager.isOnlyChildInGroup(childEntry.getSbn()));
+        assertTrue(mGroupManager.isOnlyChildInGroup(childEntry));
     }
 
     @Test
@@ -90,7 +91,7 @@
         mGroupManager.onEntryAdded(childEntry);
         mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
 
-        assertTrue(mGroupManager.isChildInGroupWithSummary(childEntry.getSbn()));
+        assertTrue(mGroupManager.isChildInGroup(childEntry));
     }
 
     @Test
@@ -102,8 +103,8 @@
         mGroupManager.onEntryAdded(childEntry);
         mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
 
-        assertTrue(mGroupManager.isSummaryOfGroup(summaryEntry.getSbn()));
-        assertEquals(summaryEntry, mGroupManager.getGroupSummary(childEntry.getSbn()));
+        assertTrue(mGroupManager.isGroupSummary(summaryEntry));
+        assertEquals(summaryEntry, mGroupManager.getGroupSummary(childEntry));
     }
 
     @Test
@@ -116,7 +117,7 @@
 
         mGroupManager.onEntryRemoved(childEntry);
 
-        assertFalse(mGroupManager.isChildInGroupWithSummary(childEntry.getSbn()));
+        assertFalse(mGroupManager.isChildInGroup(childEntry));
     }
 
     @Test
@@ -129,8 +130,8 @@
 
         mGroupManager.onEntryRemoved(summaryEntry);
 
-        assertNull(mGroupManager.getGroupSummary(childEntry.getSbn()));
-        assertFalse(mGroupManager.isSummaryOfGroup(summaryEntry.getSbn()));
+        assertNull(mGroupManager.getGroupSummary(childEntry));
+        assertFalse(mGroupManager.isGroupSummary(summaryEntry));
     }
 
     @Test
@@ -146,7 +147,7 @@
 
         // Child entries that are heads upped should be considered separate groups visually even if
         // they are the same group logically
-        assertEquals(childEntry, mGroupManager.getGroupSummary(childEntry.getSbn()));
-        assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(childEntry.getSbn()));
+        assertEquals(childEntry, mGroupManager.getGroupSummary(childEntry));
+        assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(childEntry));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 453baa5..cf64ff2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -74,12 +74,12 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.InjectionInflationController;
 
 import org.junit.Before;
@@ -118,7 +118,7 @@
     @Mock
     private NotificationShelfController mNotificationShelfController;
     @Mock
-    private NotificationGroupManager mGroupManager;
+    private NotificationGroupManagerLegacy mGroupManager;
     @Mock
     private KeyguardStatusBarView mKeyguardStatusBar;
     @Mock
@@ -174,8 +174,6 @@
     private KeyguardClockSwitch mKeyguardClockSwitch;
     private PanelViewController.TouchHandler mTouchHandler;
     @Mock
-    private ZenModeController mZenModeController;
-    @Mock
     private ConfigurationController mConfigurationController;
     @Mock
     private MediaHierarchyManager mMediaHiearchyManager;
@@ -259,16 +257,16 @@
                 mKeyguardStateController, mStatusBarStateController, mDozeLog,
                 mDozeParameters, mCommandQueue, mVibratorHelper,
                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
-                mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
+                mMetricsLogger, mActivityManager, mConfigurationController,
                 flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
                 mConversationNotificationManager, mMediaHiearchyManager,
                 mBiometricUnlockController, mStatusBarKeyguardViewManager,
                 mNotificationStackScrollLayoutController,
-                mNotificationAreaController,
-                mKeyguardStatusViewComponentFactory);
+                mKeyguardStatusViewComponentFactory,
+                mGroupManager,
+                mNotificationAreaController);
         mNotificationPanelViewController.initDependencies(
                 mStatusBar,
-                mGroupManager,
                 mNotificationShelfController);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
         mNotificationPanelViewController.setBar(mPanelBar);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 2f45113..1083273 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.eq;
@@ -27,7 +28,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -39,14 +39,13 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -71,8 +70,6 @@
     @Mock
     private LockPatternUtils mLockPatternUtils;
     @Mock
-    private KeyguardBouncer mBouncer;
-    @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private StatusBar mStatusBar;
@@ -94,6 +91,13 @@
     private KeyguardBypassController mBypassController;
     @Mock
     private FaceAuthScreenBrightnessController mFaceAuthScreenBrightnessController;
+    @Mock
+    private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
+    @Mock
+    private KeyguardBouncerComponent mKeyguardBouncerComponent;
+    @Mock
+    private KeyguardBouncer mBouncer;
+
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     @Before
@@ -102,7 +106,14 @@
         when(mLockIconContainer.getParent()).thenReturn(mock(ViewGroup.class));
         when(mLockIconContainer.animate()).thenReturn(mock(ViewPropertyAnimator.class,
                 RETURNS_DEEP_STUBS));
-        mStatusBarKeyguardViewManager = new TestableStatusBarKeyguardViewManager(
+
+        when(mKeyguardBouncerComponentFactory.build(
+                any(ViewGroup.class),
+                any(KeyguardBouncer.BouncerExpansionCallback.class)))
+                .thenReturn(mKeyguardBouncerComponent);
+        when(mKeyguardBouncerComponent.createKeyguardBouncer()).thenReturn(mBouncer);
+
+        mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager(
                 getContext(),
                 mViewMediatorCallback,
                 mLockPatternUtils,
@@ -113,12 +124,12 @@
                 mock(DockManager.class),
                 mock(NotificationShadeWindowController.class),
                 mKeyguardStateController,
-                mFaceAuthScreenBrightnessController,
-                mock(NotificationMediaManager.class));
+                Optional.of(mFaceAuthScreenBrightnessController),
+                mock(NotificationMediaManager.class),
+                mKeyguardBouncerComponentFactory);
         mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, mContainer,
-                mNotificationPanelView, mBiometrucUnlockController, mDismissCallbackRegistry,
-                mLockIconContainer, mNotificationContainer, mBypassController,
-                new FalsingManagerFake());
+                mNotificationPanelView, mBiometrucUnlockController,
+                mLockIconContainer, mNotificationContainer, mBypassController);
         mStatusBarKeyguardViewManager.show(null);
     }
 
@@ -267,38 +278,4 @@
         verify(action).onDismiss();
         verify(cancelAction, never()).run();
     }
-
-    private class TestableStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager {
-
-        public TestableStatusBarKeyguardViewManager(Context context,
-                ViewMediatorCallback callback,
-                LockPatternUtils lockPatternUtils,
-                SysuiStatusBarStateController sysuiStatusBarStateController,
-                ConfigurationController configurationController,
-                KeyguardUpdateMonitor keyguardUpdateMonitor,
-                NavigationModeController navigationModeController,
-                DockManager dockManager,
-                NotificationShadeWindowController notificationShadeWindowController,
-                KeyguardStateController keyguardStateController,
-                FaceAuthScreenBrightnessController faceAuthScreenBrightnessController,
-                NotificationMediaManager notificationMediaManager) {
-            super(context, callback, lockPatternUtils, sysuiStatusBarStateController,
-                    configurationController, keyguardUpdateMonitor, navigationModeController,
-                    dockManager, notificationShadeWindowController, keyguardStateController,
-                    Optional.of(faceAuthScreenBrightnessController), notificationMediaManager);
-        }
-
-        @Override
-        public void registerStatusBar(StatusBar statusBar, ViewGroup container,
-                NotificationPanelViewController notificationPanelViewController,
-                BiometricUnlockController fingerprintUnlockController,
-                DismissCallbackRegistry dismissCallbackRegistry,
-                ViewGroup lockIconContainer, View notificationContainer,
-                KeyguardBypassController bypassController, FalsingManager falsingManager) {
-            super.registerStatusBar(statusBar, container, notificationPanelViewController,
-                    fingerprintUnlockController, dismissCallbackRegistry, lockIconContainer,
-                    notificationContainer, bypassController, falsingManager);
-            mBouncer = StatusBarKeyguardViewManagerTest.this.mBouncer;
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 3f631b1..792637d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -67,9 +67,9 @@
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 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;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -122,8 +122,6 @@
     private FeatureFlags mFeatureFlags;
     @Mock
     private NotifPipeline mNotifPipeline;
-    @Mock
-    private NotifCollection mNotifCollection;
 
     @Mock
     private ActivityIntentHelper mActivityIntentHelper;
@@ -187,7 +185,6 @@
                         mUiBgExecutor,
                         mEntryManager,
                         mNotifPipeline,
-                        mNotifCollection,
                         mock(HeadsUpManagerPhone.class),
                         mActivityStarter,
                         mClickNotifier,
@@ -198,7 +195,7 @@
                         mBubbleController,
                         () -> mAssistManager,
                         mRemoteInputManager,
-                        mock(NotificationGroupManager.class),
+                        mock(NotificationGroupManagerLegacy.class),
                         mock(NotificationLockscreenUserManager.class),
                         mShadeController,
                         mKeyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index bf2bd38..6fbbee2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -36,6 +36,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -72,7 +73,7 @@
                 mNotificationLockscreenUserManager);
 
         mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
-                mock(NotificationGroupManager.class), mNotificationLockscreenUserManager,
+                mock(NotificationGroupManagerLegacy.class), mNotificationLockscreenUserManager,
                 mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
                 mActivityStarter, mShadeController, new CommandQueue(mContext),
                 mock(ActionClickLogger.class)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 87aee3f..5143596 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -220,7 +220,6 @@
     @Mock private NetworkController mNetworkController;
     @Mock private VibratorHelper mVibratorHelper;
     @Mock private BubbleController mBubbleController;
-    @Mock private NotificationGroupManager mGroupManager;
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private NotificationIconAreaController mNotificationIconAreaController;
     @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
@@ -376,7 +375,6 @@
                 mStatusBarStateController,
                 mVibratorHelper,
                 mBubbleController,
-                mGroupManager,
                 mVisualStabilityManager,
                 mDeviceProvisionedController,
                 mNavigationBarController,
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
index 07e8211..5b46cb4 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
@@ -294,7 +294,7 @@
                                 + Float.toString(mGestureDetectionThresholdPixels));
             }
             if (getState() == STATE_CLEAR) {
-                if (moveDelta < mTouchSlop) {
+                if (moveDelta < (mTargetFingerCount * mTouchSlop)) {
                     // This still counts as a touch not a swipe.
                     continue;
                 } else if (mStrokeBuffers[pointerIndex].size() == 0) {
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index e9c6882..8305be3 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -608,7 +608,7 @@
                                 mReceivedPointerTracker.getReceivedPointerDownY(id)
                                         - rawEvent.getY(index);
                         final double moveDelta = Math.hypot(deltaX, deltaY);
-                        if (moveDelta < mTouchSlop) {
+                        if (moveDelta < (2 * mTouchSlop)) {
                             return;
                         }
                     }
diff --git a/services/companion/TEST_MAPPING b/services/companion/TEST_MAPPING
new file mode 100644
index 0000000..63f54fa
--- /dev/null
+++ b/services/companion/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsOsTestCases",
+      "options": [
+        {
+          "include-filter": "android.os.cts.CompanionDeviceManagerTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 35e88eb..7d81d41 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2114,7 +2114,7 @@
                  * Owner or system user account was renamed, rename the account for
                  * those users with which the account was shared.
                  */
-                    List<UserInfo> users = getUserManager().getUsers(true);
+                    List<UserInfo> users = getUserManager().getAliveUsers();
                     for (UserInfo user : users) {
                         if (user.isRestricted()
                                 && (user.restrictedProfileParentId == parentUserId)) {
@@ -2373,7 +2373,7 @@
             int parentUserId = accounts.userId;
             if (canHaveProfile(parentUserId)) {
                 // Remove from any restricted profiles that are sharing this account.
-                List<UserInfo> users = getUserManager().getUsers(true);
+                List<UserInfo> users = getUserManager().getAliveUsers();
                 for (UserInfo user : users) {
                     if (user.isRestricted() && parentUserId == (user.restrictedProfileParentId)) {
                         removeSharedAccountAsUser(account, user.id, callingUid);
@@ -4267,7 +4267,7 @@
      */
     @NonNull
     public AccountAndUser[] getAllAccounts() {
-        final List<UserInfo> users = getUserManager().getUsers(true);
+        final List<UserInfo> users = getUserManager().getAliveUsers();
         final int[] userIds = new int[users.size()];
         for (int i = 0; i < userIds.length; i++) {
             userIds[i] = users.get(i).id;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2d803437..1502b7e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17144,8 +17144,7 @@
         @Override
         public int checkContentProviderUriPermission(Uri uri, int userId,
                 int callingUid, int modeFlags) {
-            return mCpHelper.checkContentProviderUriPermission(uri,
-                    userId, callingUid, modeFlags);
+            return mCpHelper.checkContentProviderUriPermission(uri, userId, callingUid, modeFlags);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index bfba4af..05a731d 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -198,22 +198,10 @@
 
             if (providerRunning) {
                 cpi = cpr.info;
-                String msg;
 
                 if (r != null && cpr.canRunHere(r)) {
-                    if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
-                        throw new SecurityException("Content provider lookup "
-                                + cpr.name.flattenToShortString()
-                                + " failed: association not allowed with package " + msg);
-                    }
-                    checkTime(startTime,
-                            "getContentProviderImpl: before checkContentProviderPermission");
-                    if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
-                            != null) {
-                        throw new SecurityException(msg);
-                    }
-                    checkTime(startTime,
-                            "getContentProviderImpl: after checkContentProviderPermission");
+                    checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
+                            cpr.name.flattenToShortString(), startTime);
 
                     // This provider has been published or is in the process
                     // of being published...  but it is also allowed to run
@@ -234,26 +222,14 @@
                 } catch (RemoteException e) {
                 }
 
-                if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
-                    throw new SecurityException(
-                            "Content provider lookup " + cpr.name.flattenToShortString()
-                                    + " failed: association not allowed with package " + msg);
-                }
-                checkTime(startTime,
-                        "getContentProviderImpl: before checkContentProviderPermission");
-                if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
-                        != null) {
-                    throw new SecurityException(msg);
-                }
-                checkTime(startTime,
-                        "getContentProviderImpl: after checkContentProviderPermission");
+                checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
+                        cpr.name.flattenToShortString(), startTime);
 
                 final long origId = Binder.clearCallingIdentity();
 
                 checkTime(startTime, "getContentProviderImpl: incProviderCountLocked");
 
-                // In this case the provider instance already exists, so we can
-                // return it right away.
+                // In this case the provider instance already exists so we can return it right away.
                 conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag,
                         stable, true, startTime, mService.mProcessList);
 
@@ -328,19 +304,8 @@
                 cpi.applicationInfo = mService.getAppInfoForUser(cpi.applicationInfo, userId);
                 checkTime(startTime, "getContentProviderImpl: got app info for user");
 
-                String msg;
-                if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
-                    throw new SecurityException("Content provider lookup " + name
-                            + " failed: association not allowed with package " + msg);
-                }
-                checkTime(startTime,
-                        "getContentProviderImpl: before checkContentProviderPermission");
-                if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))
-                        != null) {
-                    throw new SecurityException(msg);
-                }
-                checkTime(startTime,
-                        "getContentProviderImpl: after checkContentProviderPermission");
+                checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, !singleton,
+                        name, startTime);
 
                 if (!mService.mProcessesReady && !cpi.processName.equals("system")) {
                     // If this content provider does not run in the system
@@ -352,10 +317,12 @@
 
                 // If system providers are not installed yet we aggressively crash to avoid
                 // creating multiple instance of these providers and then bad things happen!
-                if (!mSystemProvidersInstalled && cpi.applicationInfo.isSystemApp()
-                        && "system".equals(cpi.processName)) {
-                    throw new IllegalStateException("Cannot access system provider: '"
-                            + cpi.authority + "' before system providers are installed!");
+                synchronized (this) {
+                    if (!mSystemProvidersInstalled && cpi.applicationInfo.isSystemApp()
+                            && "system".equals(cpi.processName)) {
+                        throw new IllegalStateException("Cannot access system provider: '"
+                                + cpi.authority + "' before system providers are installed!");
+                    }
                 }
 
                 // Make sure that the user who owns this provider is running.  If not,
@@ -605,6 +572,23 @@
         return cpr.newHolder(conn, false);
     }
 
+    private void checkAssociationAndPermissionLocked(ProcessRecord callingApp, ProviderInfo cpi,
+            int callingUid, int userId, boolean checkUser, String cprName, long startTime) {
+        String msg;
+        if ((msg = checkContentProviderAssociation(callingApp, callingUid, cpi)) != null) {
+            throw new SecurityException("Content provider lookup " + cprName
+                    + " failed: association not allowed with package " + msg);
+        }
+        checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
+        if ((msg = checkContentProviderPermission(
+                    cpi, Binder.getCallingPid(), Binder.getCallingUid(), userId, checkUser,
+                    callingApp != null ? callingApp.toString() : null))
+                != null) {
+            throw new SecurityException(msg);
+        }
+        checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");
+    }
+
     void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) {
         if (providers == null) {
             return;
@@ -623,7 +607,7 @@
             }
 
             final long origId = Binder.clearCallingIdentity();
-
+            boolean providersPublished = false;
             for (int i = 0, size = providers.size(); i < size; i++) {
                 ContentProviderHolder src = providers.get(i);
                 if (src == null || src.info == null || src.provider == null) {
@@ -636,6 +620,7 @@
                 if (DEBUG_MU) {
                     Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid);
                 }
+                providersPublished = true;
 
                 ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
                 mProviderMap.putProviderByClass(comp, dst);
@@ -673,8 +658,19 @@
                     dst.onProviderPublishStatusLocked(true);
                 }
                 dst.mRestartCount = 0;
+            }
+
+            // update the app's oom adj value and each provider's usage stats
+            if (providersPublished) {
                 mService.updateOomAdjLocked(r, true, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
-                maybeUpdateProviderUsageStatsLocked(r, src.info.packageName, src.info.authority);
+                for (int i = 0, size = providers.size(); i < size; i++) {
+                    ContentProviderHolder src = providers.get(i);
+                    if (src == null || src.info == null || src.provider == null) {
+                        continue;
+                    }
+                    maybeUpdateProviderUsageStatsLocked(r,
+                            src.info.packageName, src.info.authority);
+                }
             }
 
             Binder.restoreCallingIdentity(origId);
@@ -997,17 +993,19 @@
                     + "; expected to find a valid ContentProvider for this authority";
         }
 
+        final int callingPid = Binder.getCallingPid();
         ProcessRecord r;
+        final String appName;
         synchronized (mService.mPidsSelfLocked) {
-            r = mService.mPidsSelfLocked.get(Binder.getCallingPid());
-        }
-        if (r == null) {
-            return "Failed to find PID " + Binder.getCallingPid();
+            r = mService.mPidsSelfLocked.get(callingPid);
+            if (r == null) {
+                return "Failed to find PID " + callingPid;
+            }
+            appName = r.toString();
         }
 
-        synchronized (mService) {
-            return checkContentProviderPermissionLocked(cpi, r, userId, true);
-        }
+        return checkContentProviderPermission(cpi, callingPid, Binder.getCallingUid(),
+                userId, true, appName);
     }
 
     int checkContentProviderUriPermission(Uri uri, int userId, int callingUid, int modeFlags) {
@@ -1163,13 +1161,14 @@
                 }
             }
         }
-        if (providers != null) {
-            mService.mSystemThread.installSystemProviders(providers);
-        }
 
-        synchronized (mService) {
+        synchronized (this) {
+            if (providers != null) {
+                mService.mSystemThread.installSystemProviders(providers);
+            }
             mSystemProvidersInstalled = true;
         }
+
         mService.mConstants.start(mService.mContext.getContentResolver());
         mService.mCoreSettingsObserver = new CoreSettingsObserver(mService);
         mService.mActivityTaskManager.installSystemProviders();
@@ -1305,10 +1304,8 @@
      * given {@link ProviderInfo}. Final permission checking is always done
      * in {@link ContentProvider}.
      */
-    private String checkContentProviderPermissionLocked(ProviderInfo cpi, ProcessRecord r,
-            int userId, boolean checkUser) {
-        final int callingPid = (r != null) ? r.pid : Binder.getCallingPid();
-        final int callingUid = (r != null) ? r.uid : Binder.getCallingUid();
+    private String checkContentProviderPermission(ProviderInfo cpi, int callingPid, int callingUid,
+            int userId, boolean checkUser, String appName) {
         boolean checkedGrants = false;
         if (checkUser) {
             // Looking for cross-user grants before enforcing the typical cross-users permissions
@@ -1376,8 +1373,8 @@
             suffix = " requires " + cpi.readPermission + " or " + cpi.writePermission;
         }
         final String msg = "Permission Denial: opening provider " + cpi.name
-                + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
-                + ", uid=" + callingUid + ")" + suffix;
+                + " from " + (appName != null ? appName : "(null)")
+                + " (pid=" + callingPid + ", uid=" + callingUid + ")" + suffix;
         Slog.w(TAG, msg);
         return msg;
     }
@@ -1398,18 +1395,17 @@
     }
 
     ProviderInfo getProviderInfoLocked(String authority, @UserIdInt int userId, int pmFlags) {
-        ProviderInfo pi = null;
         ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userId);
         if (cpr != null) {
-            pi = cpr.info;
+            return cpr.info;
         } else {
             try {
-                pi = AppGlobals.getPackageManager().resolveContentProvider(
+                return AppGlobals.getPackageManager().resolveContentProvider(
                         authority, PackageManager.GET_URI_PERMISSION_PATTERNS | pmFlags, userId);
             } catch (RemoteException ex) {
+                return null;
             }
         }
-        return pi;
     }
 
     private void maybeUpdateProviderUsageStatsLocked(ProcessRecord app, String providerPkgName,
@@ -1419,7 +1415,6 @@
             return;
         }
 
-
         UserState userState = mService.mUserController.getStartedUserState(app.userId);
         if (userState == null) return;
         final long now = SystemClock.elapsedRealtime();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d59780d..3f29eb5 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6528,8 +6528,8 @@
                                     CHECK_MODE_FOR_UID_PERIOD_MS);
                             break;
                         }
-                        // For now just log the fact that an app is hogging the audio mode.
-                        // TODO(b/160260850): remove abusive app from audio mode stack.
+                        setModeInt(AudioSystem.MODE_NORMAL, h.getBinder(), h.getPid(), h.getUid(),
+                                h.isPrivileged(), "MSG_CHECK_MODE_FOR_UID");
                         mModeLogger.log(new PhoneStateEvent(h.getPackage(), h.getPid()));
                     }
                     break;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
index 32bb2db..d9c62df 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
@@ -29,7 +29,6 @@
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
 import android.hardware.face.Face;
-import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Build;
@@ -383,7 +382,7 @@
         //    is safe because authenticatorIds only change when A) new template has been enrolled,
         //    or B) all templates are removed.
         mHandler.post(() -> {
-            for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+            for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
                 final int targetUserId = user.id;
                 if (!mAuthenticatorIds.containsKey(targetUserId)) {
                     scheduleUpdateActiveUserWithoutHandler(targetUserId);
@@ -480,7 +479,8 @@
      *    notifying the previous caller that the interrupting operation is complete (e.g. the
      *    interrupting client's challenge has been revoked, so that the interrupted client can
      *    start retry logic if necessary). See
-     *    {@link FaceManager.GenerateChallengeCallback#onChallengeInterruptFinished(int)}
+     *    {@link
+     *android.hardware.face.FaceManager.GenerateChallengeCallback#onChallengeInterruptFinished(int)}
      * The only case of conflicting challenges is currently resetLockout --> enroll. So, the second
      * option seems better as it prioritizes the new operation, which is user-facing.
      */
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
index c5c2822..3754bd7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
@@ -440,7 +440,7 @@
         //    is safe because authenticatorIds only change when A) new template has been enrolled,
         //    or B) all templates are removed.
         mHandler.post(() -> {
-            for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+            for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
                 final int targetUserId = user.id;
                 if (!mAuthenticatorIds.containsKey(targetUserId)) {
                     scheduleUpdateActiveUserWithoutHandler(targetUserId);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index ec12a97..b33aa0a 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -367,7 +367,7 @@
     }
 
     private void removeStaleAccounts() {
-        for (UserInfo user : mUserManager.getUsers(true)) {
+        for (UserInfo user : mUserManager.getAliveUsers()) {
             // Skip any partially created/removed users
             if (user.partial) continue;
             Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(
@@ -777,7 +777,7 @@
         if (!mSyncStorageEngine.shouldGrantSyncAdaptersAccountAccess()) {
             return;
         }
-        List<UserInfo> users = mUserManager.getUsers(true);
+        List<UserInfo> users = mUserManager.getAliveUsers();
         final int userCount = users.size();
         for (int i = 0; i < userCount; i++) {
             UserHandle userHandle = users.get(i).getUserHandle();
diff --git a/services/core/java/com/android/server/inputmethod/TEST_MAPPING b/services/core/java/com/android/server/inputmethod/TEST_MAPPING
new file mode 100644
index 0000000..0ccd75d
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+    {
+      "path": "frameworks/base/core/java/android/view/inputmethod"
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index e9a05a8..715e41c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -746,7 +746,7 @@
 
     public void dump(IndentingPrintWriter pw) {
         final UserManager um = UserManager.get(mContext);
-        for (UserInfo user : um.getUsers(false)) {
+        for (UserInfo user : um.getUsers()) {
             File userPath = getSyntheticPasswordDirectoryForUser(user.id);
             pw.println(String.format("User %d [%s]:", user.id, userPath.getAbsolutePath()));
             pw.increaseIndent();
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index a604625..74b7bd7 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1554,7 +1554,7 @@
             if (!isEnabledForCurrentProfiles()) {
                 return false;
             }
-            return this.userid == userId;
+            return userId == USER_ALL || userId == this.userid;
         }
 
         public boolean enabledAndUserMatches(int nid) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0465855..12419a9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -262,7 +262,6 @@
 import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
 import com.android.server.UiThread;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
@@ -966,8 +965,7 @@
                     nv.recycle();
                 }
                 reportUserInteraction(r);
-                mAssistants.notifyAssistantActionClicked(
-                        r.getSbn(), actionIndex, action, generatedByAssistant);
+                mAssistants.notifyAssistantActionClicked(r.getSbn(), action, generatedByAssistant);
             }
         }
 
@@ -8629,12 +8627,25 @@
                 ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
     }
 
-    private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
+    @VisibleForTesting
+    boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
         if (!listener.enabledAndUserMatches(sbn.getUserId())) {
             return false;
         }
-        // TODO: remove this for older listeners.
-        return true;
+        return isInteractionVisibleToListener(listener, sbn.getUserId());
+    }
+
+    /**
+     * Returns whether the given assistant should be informed about interactions on the given user.
+     *
+     * Normally an assistant would be able to see all interactions on the current user and any
+     * associated profiles because they are notification listeners, but since NASes have one
+     * instance per user, we want to filter out interactions that are not for the user that the
+     * given NAS is bound in.
+     */
+    private boolean isInteractionVisibleToListener(ManagedServiceInfo info, int userId) {
+        boolean isAssistantService = mAssistants.isServiceTokenValidLocked(info.service);
+        return !isAssistantService || info.isSameUser(userId);
     }
 
     private boolean isPackageSuspendedForUser(String pkg, int uid) {
@@ -8856,8 +8867,6 @@
         }
 
         protected void onNotificationsSeenLocked(ArrayList<NotificationRecord> records) {
-            // There should be only one, but it's a list, so while we enforce
-            // singularity elsewhere, we keep it general here, to avoid surprises.
             for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
                 ArrayList<String> keys = new ArrayList<>(records.size());
                 for (NotificationRecord r : records) {
@@ -8875,6 +8884,8 @@
         }
 
         protected void onPanelRevealed(int items) {
+            // send to all currently bounds NASes since notifications from both users will appear in
+            // the panel
             for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
                 mHandler.post(() -> {
                     final INotificationListener assistant = (INotificationListener) info.service;
@@ -8888,6 +8899,8 @@
         }
 
         protected void onPanelHidden() {
+            // send to all currently bounds NASes since notifications from both users will appear in
+            // the panel
             for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
                 mHandler.post(() -> {
                     final INotificationListener assistant = (INotificationListener) info.service;
@@ -8976,7 +8989,7 @@
             }
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onNotificationVisibilityChanged(key, isVisible);
@@ -8994,7 +9007,7 @@
             final String key = sbn.getKey();
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onNotificationExpansionChanged(key, isUserAction, isExpanded);
@@ -9010,7 +9023,7 @@
             final String key = sbn.getKey();
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onNotificationDirectReply(key);
@@ -9026,7 +9039,7 @@
             final String key = sbn.getKey();
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onSuggestedReplySent(
@@ -9043,12 +9056,12 @@
 
         @GuardedBy("mNotificationLock")
         void notifyAssistantActionClicked(
-                final StatusBarNotification sbn, int actionIndex, Notification.Action action,
+                final StatusBarNotification sbn, Notification.Action action,
                 boolean generatedByAssistant) {
             final String key = sbn.getKey();
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onActionClicked(
@@ -9072,7 +9085,7 @@
                 final StatusBarNotification sbn, final String snoozeCriterionId) {
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onNotificationSnoozedUntilContext(
@@ -9129,7 +9142,7 @@
         }
 
         protected void resetDefaultAssistantsIfNecessary() {
-            final List<UserInfo> activeUsers = mUm.getUsers(true);
+            final List<UserInfo> activeUsers = mUm.getAliveUsers();
             for (UserInfo userInfo : activeUsers) {
                 int userId = userInfo.getUserHandle().getIdentifier();
                 if (!hasUserSet(userId)) {
@@ -9293,10 +9306,12 @@
         }
 
         public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) {
+            // send to all currently bounds NASes since notifications from both users will appear in
+            // the status bar
             for (final ManagedServiceInfo info : getServices()) {
                 mHandler.post(() -> {
                     final INotificationListener listener = (INotificationListener) info.service;
-                     try {
+                    try {
                         listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
                     } catch (RemoteException ex) {
                         Slog.e(TAG, "unable to notify listener "
@@ -9470,7 +9485,8 @@
                     && changedHiddenNotifications.size() > 0;
 
             for (final ManagedServiceInfo serviceInfo : getServices()) {
-                if (!serviceInfo.isEnabledForCurrentProfiles()) {
+                if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+                        serviceInfo, ActivityManager.getCurrentUser())) {
                     continue;
                 }
 
@@ -9489,12 +9505,7 @@
                     final NotificationRankingUpdate update = makeRankingUpdateLocked(
                             serviceInfo);
 
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            notifyRankingUpdate(serviceInfo, update);
-                        }
-                    });
+                    mHandler.post(() -> notifyRankingUpdate(serviceInfo, update));
                 }
             }
         }
@@ -9502,15 +9513,11 @@
         @GuardedBy("mNotificationLock")
         public void notifyListenerHintsChangedLocked(final int hints) {
             for (final ManagedServiceInfo serviceInfo : getServices()) {
-                if (!serviceInfo.isEnabledForCurrentProfiles()) {
+                if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+                        serviceInfo, ActivityManager.getCurrentUser())) {
                     continue;
                 }
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        notifyListenerHintsChanged(serviceInfo, hints);
-                    }
-                });
+                mHandler.post(() -> notifyListenerHintsChanged(serviceInfo, hints));
             }
         }
 
@@ -9562,15 +9569,12 @@
 
         public void notifyInterruptionFilterChanged(final int interruptionFilter) {
             for (final ManagedServiceInfo serviceInfo : getServices()) {
-                if (!serviceInfo.isEnabledForCurrentProfiles()) {
+                if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+                        serviceInfo, ActivityManager.getCurrentUser())) {
                     continue;
                 }
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        notifyInterruptionFilterChanged(serviceInfo, interruptionFilter);
-                    }
-                });
+                mHandler.post(
+                        () -> notifyInterruptionFilterChanged(serviceInfo, interruptionFilter));
             }
         }
 
@@ -9579,15 +9583,16 @@
             if (channel == null) {
                 return;
             }
-            for (final ManagedServiceInfo serviceInfo : getServices()) {
-                if (!serviceInfo.enabledAndUserMatches(UserHandle.getCallingUserId())) {
+            for (final ManagedServiceInfo info : getServices()) {
+                if (!info.enabledAndUserMatches(UserHandle.getCallingUserId())
+                        || !isInteractionVisibleToListener(info, UserHandle.getCallingUserId())) {
                     continue;
                 }
 
                 BackgroundThread.getHandler().post(() -> {
-                    if (serviceInfo.isSystem || hasCompanionDevice(serviceInfo)) {
+                    if (info.isSystem || hasCompanionDevice(info)) {
                         notifyNotificationChannelChanged(
-                                serviceInfo, pkg, user, channel, modificationType);
+                                info, pkg, user, channel, modificationType);
                     }
                 });
             }
@@ -9599,15 +9604,16 @@
             if (group == null) {
                 return;
             }
-            for (final ManagedServiceInfo serviceInfo : getServices()) {
-                if (!serviceInfo.enabledAndUserMatches(UserHandle.getCallingUserId())) {
+            for (final ManagedServiceInfo info : getServices()) {
+                if (!info.enabledAndUserMatches(UserHandle.getCallingUserId())
+                        || !isInteractionVisibleToListener(info, UserHandle.getCallingUserId())) {
                     continue;
                 }
 
                 BackgroundThread.getHandler().post(() -> {
-                    if (serviceInfo.isSystem || hasCompanionDevice(serviceInfo)) {
+                    if (info.isSystem || hasCompanionDevice(info)) {
                         notifyNotificationChannelGroupChanged(
-                                serviceInfo, pkg, user, group, modificationType);
+                                info, pkg, user, group, modificationType);
                     }
                 });
             }
@@ -9626,9 +9632,6 @@
 
         private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
                 NotificationRankingUpdate rankingUpdate, NotificationStats stats, int reason) {
-            if (!info.enabledAndUserMatches(sbn.getUserId())) {
-                return;
-            }
             final INotificationListener listener = (INotificationListener) info.service;
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index a4debc1..d7a1ba2 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -288,7 +288,7 @@
 
     private void initIfNeeded() {
         final UserManager um = getContext().getSystemService(UserManager.class);
-        final List<UserInfo> users = um.getUsers(true /*excludeDying*/);
+        final List<UserInfo> users = um.getAliveUsers();
         synchronized (mLock) {
             final int userCount = users.size();
             for (int i = 0; i < userCount; i++) {
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index d8745ab..0338ed8 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
 import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA256;
 import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA512;
 import static android.content.pm.PackageManager.WHOLE_MD5;
@@ -27,11 +28,20 @@
 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
 import android.content.pm.FileChecksum;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.incremental.IncrementalManager;
+import android.os.incremental.IncrementalStorage;
 import android.util.ArrayMap;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.apk.ApkSignatureSchemeV2Verifier;
 import android.util.apk.ApkSignatureSchemeV3Verifier;
@@ -43,6 +53,7 @@
 import android.util.apk.SignatureNotFoundException;
 import android.util.apk.VerityBuilder;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.security.VerityUtils;
 
 import java.io.BufferedInputStream;
@@ -72,32 +83,163 @@
     static final String ALGO_SHA512 = "SHA512";
 
     /**
-     * Fetch or calculate checksums for the specific file.
+     * Check back in 1 second after we detected we needed to wait for the APK to be fully available.
+     */
+    private static final long PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS = 1000;
+
+    /**
+     * 24 hours timeout to wait till all files are loaded.
+     */
+    private static final long PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS = 1000 * 3600 * 24;
+
+    /**
+     * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
      *
-     * @param split             split name, null for base
-     * @param file              to fetch checksums for
+     * NOTE: All getters should return the same instance for every call.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    static class Injector {
+
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+        interface Producer<T> {
+            /** Produce an instance of type {@link T} */
+            T produce();
+        }
+
+        private final Producer<Context> mContext;
+        private final Producer<Handler> mHandlerProducer;
+        private final Producer<IncrementalManager> mIncrementalManagerProducer;
+
+        Injector(Producer<Context> context, Producer<Handler> handlerProducer,
+                Producer<IncrementalManager> incrementalManagerProducer) {
+            mContext = context;
+            mHandlerProducer = handlerProducer;
+            mIncrementalManagerProducer = incrementalManagerProducer;
+        }
+
+        public Context getContext() {
+            return mContext.produce();
+        }
+
+        public Handler getHandler() {
+            return mHandlerProducer.produce();
+        }
+
+        public IncrementalManager getIncrementalManager() {
+            return mIncrementalManagerProducer.produce();
+        }
+    }
+
+    /**
+     * Fetch or calculate checksums for the collection of files.
+     *
+     * @param filesToChecksum   split name, null for base and File to fetch checksums for
      * @param optional          mask to fetch readily available checksums
      * @param required          mask to forcefully calculate if not available
      * @param trustedInstallers array of certificate to trust, two specific cases:
      *                          null - trust anybody,
      *                          [] - trust nobody.
+     * @param statusReceiver    to receive the resulting checksums
      */
-    public static List<FileChecksum> getFileChecksums(String split, File file,
+    public static void getChecksums(List<Pair<String, File>> filesToChecksum,
             @PackageManager.FileChecksumKind int optional,
             @PackageManager.FileChecksumKind int required,
-            @Nullable Certificate[] trustedInstallers) {
+            @Nullable Certificate[] trustedInstallers,
+            @NonNull IntentSender statusReceiver,
+            @NonNull Injector injector) {
+        List<Map<Integer, FileChecksum>> result = new ArrayList<>(filesToChecksum.size());
+        for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
+            final String split = filesToChecksum.get(i).first;
+            final File file = filesToChecksum.get(i).second;
+            Map<Integer, FileChecksum> checksums = new ArrayMap<>();
+            result.add(checksums);
+
+            try {
+                getAvailableFileChecksums(split, file, optional | required, trustedInstallers,
+                        checksums);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Preferred checksum calculation error", e);
+            }
+        }
+
+        long startTime = SystemClock.uptimeMillis();
+        processRequiredChecksums(filesToChecksum, result, required, statusReceiver, injector,
+                startTime);
+    }
+
+    private static void processRequiredChecksums(List<Pair<String, File>> filesToChecksum,
+            List<Map<Integer, FileChecksum>> result,
+            @PackageManager.FileChecksumKind int required,
+            @NonNull IntentSender statusReceiver,
+            @NonNull Injector injector,
+            long startTime) {
+        final boolean timeout =
+                SystemClock.uptimeMillis() - startTime >= PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS;
+        List<FileChecksum> allChecksums = new ArrayList<>();
+        for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
+            final String split = filesToChecksum.get(i).first;
+            final File file = filesToChecksum.get(i).second;
+            Map<Integer, FileChecksum> checksums = result.get(i);
+
+            try {
+                if (!timeout || required != 0) {
+                    if (needToWait(file, required, checksums, injector)) {
+                        // Not ready, come back later.
+                        injector.getHandler().postDelayed(() -> {
+                            processRequiredChecksums(filesToChecksum, result, required,
+                                    statusReceiver, injector, startTime);
+                        }, PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS);
+                        return;
+                    }
+
+                    getRequiredFileChecksums(split, file, required, checksums);
+                }
+                allChecksums.addAll(checksums.values());
+            } catch (Throwable e) {
+                Slog.e(TAG, "Required checksum calculation error", e);
+            }
+        }
+
+        final Intent intent = new Intent();
+        intent.putExtra(EXTRA_CHECKSUMS,
+                allChecksums.toArray(new FileChecksum[allChecksums.size()]));
+
+        try {
+            statusReceiver.sendIntent(injector.getContext(), 1, intent, null, null);
+        } catch (IntentSender.SendIntentException e) {
+            Slog.w(TAG, e);
+        }
+    }
+
+    /**
+     * Fetch readily available checksums - enforced by kernel or provided by Installer.
+     *
+     * @param split             split name, null for base
+     * @param file              to fetch checksums for
+     * @param kinds             mask to fetch checksums
+     * @param trustedInstallers array of certificate to trust, two specific cases:
+     *                          null - trust anybody,
+     *                          [] - trust nobody.
+     * @param checksums         resulting checksums
+     */
+    private static void getAvailableFileChecksums(String split, File file,
+            @PackageManager.FileChecksumKind int kinds,
+            @Nullable Certificate[] trustedInstallers,
+            Map<Integer, FileChecksum> checksums) {
         final String filePath = file.getAbsolutePath();
-        Map<Integer, FileChecksum> checksums = new ArrayMap<>();
-        final int kinds = (optional | required);
-        // System enforced: FSI or v2/v3/v4 signatures.
-        if ((kinds & WHOLE_MERKLE_ROOT_4K_SHA256) != 0) {
+
+        // Always available: FSI or IncFs.
+        if (isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)) {
             // Hashes in fs-verity and IncFS are always verified.
             FileChecksum checksum = extractHashFromFS(split, filePath);
             if (checksum != null) {
                 checksums.put(checksum.getKind(), checksum);
             }
         }
-        if ((kinds & (PARTIAL_MERKLE_ROOT_1M_SHA256 | PARTIAL_MERKLE_ROOT_1M_SHA512)) != 0) {
+
+        // System enforced: v2/v3.
+        if (isRequired(PARTIAL_MERKLE_ROOT_1M_SHA256, kinds, checksums) || isRequired(
+                PARTIAL_MERKLE_ROOT_1M_SHA512, kinds, checksums)) {
             Map<Integer, FileChecksum> v2v3checksums = extractHashFromV2V3Signature(
                     split, filePath, kinds);
             if (v2v3checksums != null) {
@@ -106,11 +248,58 @@
         }
 
         // TODO(b/160605420): Installer provided.
-        // TODO(b/160605420): Wait for Incremental to be fully loaded.
+    }
+
+    /**
+     * Whether the file is available for checksumming or we need to wait.
+     */
+    private static boolean needToWait(File file,
+            @PackageManager.FileChecksumKind int kinds,
+            Map<Integer, FileChecksum> checksums,
+            @NonNull Injector injector) throws IOException {
+        if (!isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)
+                && !isRequired(WHOLE_MD5, kinds, checksums)
+                && !isRequired(WHOLE_SHA1, kinds, checksums)
+                && !isRequired(WHOLE_SHA256, kinds, checksums)
+                && !isRequired(WHOLE_SHA512, kinds, checksums)
+                && !isRequired(PARTIAL_MERKLE_ROOT_1M_SHA256, kinds, checksums)
+                && !isRequired(PARTIAL_MERKLE_ROOT_1M_SHA512, kinds, checksums)) {
+            return false;
+        }
+
+        final String filePath = file.getAbsolutePath();
+        if (!IncrementalManager.isIncrementalPath(filePath)) {
+            return false;
+        }
+
+        IncrementalManager manager = injector.getIncrementalManager();
+        if (manager == null) {
+            throw new IllegalStateException("IncrementalManager is missing.");
+        }
+        IncrementalStorage storage = manager.openStorage(filePath);
+        if (storage == null) {
+            throw new IllegalStateException(
+                    "IncrementalStorage is missing for a path on IncFs: " + filePath);
+        }
+
+        return !storage.isFileFullyLoaded(filePath);
+    }
+
+    /**
+     * Fetch or calculate checksums for the specific file.
+     *
+     * @param split     split name, null for base
+     * @param file      to fetch checksums for
+     * @param kinds     mask to forcefully calculate if not available
+     * @param checksums resulting checksums
+     */
+    private static void getRequiredFileChecksums(String split, File file,
+            @PackageManager.FileChecksumKind int kinds,
+            Map<Integer, FileChecksum> checksums) {
+        final String filePath = file.getAbsolutePath();
 
         // Manually calculating required checksums if not readily available.
-        if ((required & WHOLE_MERKLE_ROOT_4K_SHA256) != 0 && !checksums.containsKey(
-                WHOLE_MERKLE_ROOT_4K_SHA256)) {
+        if (isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)) {
             try {
                 byte[] generatedRootHash = VerityBuilder.generateFsVerityRootHash(
                         filePath, /*salt=*/null,
@@ -127,14 +316,23 @@
             }
         }
 
-        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_MD5);
-        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA1);
-        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA256);
-        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA512);
+        calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_MD5);
+        calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA1);
+        calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA256);
+        calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA512);
 
-        calculatePartialChecksumsIfRequested(checksums, split, file, required);
+        calculatePartialChecksumsIfRequested(checksums, split, file, kinds);
+    }
 
-        return new ArrayList<>(checksums.values());
+    private static boolean isRequired(@PackageManager.FileChecksumKind int kind,
+            @PackageManager.FileChecksumKind int kinds, Map<Integer, FileChecksum> checksums) {
+        if ((kinds & kind) == 0) {
+            return false;
+        }
+        if (checksums.containsKey(kind)) {
+            return false;
+        }
+        return true;
     }
 
     private static FileChecksum extractHashFromFS(String split, String filePath) {
@@ -170,7 +368,9 @@
                     PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
                     false).contentDigests;
         } catch (PackageParser.PackageParserException e) {
-            Slog.e(TAG, "Signature verification error", e);
+            if (!(e.getCause() instanceof SignatureNotFoundException)) {
+                Slog.e(TAG, "Signature verification error", e);
+            }
         }
 
         if (contentDigests == null) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 4b246c3..162bfee 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -743,9 +743,8 @@
             mStagingManager.createSession(session);
         }
 
-        if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) {
-            mCallbacks.notifySessionCreated(session.sessionId, session.userId);
-        }
+        mCallbacks.notifySessionCreated(session.sessionId, session.userId);
+
         writeSessionsAsync();
         return sessionId;
     }
@@ -1355,25 +1354,18 @@
 
     class InternalCallback {
         public void onSessionBadgingChanged(PackageInstallerSession session) {
-            if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) {
-                mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId);
-            }
-
+            mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId);
             writeSessionsAsync();
         }
 
         public void onSessionActiveChanged(PackageInstallerSession session, boolean active) {
-            if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) {
-                mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId,
-                        active);
-            }
+            mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId,
+                    active);
         }
 
         public void onSessionProgressChanged(PackageInstallerSession session, float progress) {
-            if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) {
-                mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId,
-                        progress);
-            }
+            mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId,
+                    progress);
         }
 
         public void onStagedSessionChanged(PackageInstallerSession session) {
@@ -1389,17 +1381,13 @@
         }
 
         public void onSessionFinished(final PackageInstallerSession session, boolean success) {
-            if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) {
-                mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
-            }
+            mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
 
             mInstallHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    if (session.isStaged()) {
-                        if (!success) {
-                            mStagingManager.abortSession(session);
-                        }
+                    if (session.isStaged() && !success) {
+                        mStagingManager.abortSession(session);
                     }
                     synchronized (mSessions) {
                         if (!session.isStaged() || !success) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ed62362..51164ba 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1561,19 +1561,20 @@
         dispatchSessionFinished(error, detailMessage, null);
     }
 
-    private void onSessionVerificationFailure(int error, String detailMessage) {
-        Slog.e(TAG, "Failed to verify session " + sessionId + " [" + detailMessage + "]");
+    private void onSessionVerificationFailure(int error, String msg) {
+        final String msgWithErrorCode = PackageManager.installStatusToString(error, msg);
+        Slog.e(TAG, "Failed to verify session " + sessionId + " [" + msgWithErrorCode + "]");
         // Session is sealed and committed but could not be verified, we need to destroy it.
         destroyInternal();
         if (isStaged()) {
             setStagedSessionFailed(
-                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, detailMessage);
+                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, msgWithErrorCode);
             // TODO(b/136257624): Remove this once all verification logic has been transferred out
             //  of StagingManager.
             mStagingManager.notifyVerificationComplete(sessionId);
         } else {
             // Dispatch message to remove session from PackageInstallerService.
-            dispatchSessionFinished(error, detailMessage, null);
+            dispatchSessionFinished(error, msg, null);
         }
     }
 
@@ -3306,8 +3307,7 @@
         // Send broadcast to default launcher only if it's a new install
         // TODO(b/144270665): Secure the usage of this broadcast.
         final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING);
-        if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()
-                && (params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) {
+        if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()) {
             mPm.sendSessionCommitBroadcast(generateInfoScrubbed(true /*icon*/), userId);
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 344f9cf..b6b7e93 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -40,7 +40,6 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
@@ -169,7 +168,6 @@
 import android.content.pm.DataLoaderType;
 import android.content.pm.FallbackCategoryProvider;
 import android.content.pm.FeatureInfo;
-import android.content.pm.FileChecksum;
 import android.content.pm.IDexModuleRegisterCallback;
 import android.content.pm.IPackageChangeObserver;
 import android.content.pm.IPackageDataObserver;
@@ -933,6 +931,7 @@
         private final Object mLock;
         private final Installer mInstaller;
         private final Object mInstallLock;
+        private final Handler mBackgroundHandler;
         private final Executor mBackgroundExecutor;
 
         // ----- producers -----
@@ -955,7 +954,7 @@
 
         Injector(Context context, Object lock, Installer installer,
                 Object installLock, PackageAbiHelper abiHelper,
-                Executor backgroundExecutor,
+                Handler backgroundHandler,
                 Producer<ComponentResolver> componentResolverProducer,
                 Producer<PermissionManagerServiceInternal> permissionManagerProducer,
                 Producer<UserManagerService> userManagerProducer,
@@ -977,7 +976,8 @@
             mInstaller = installer;
             mAbiHelper = abiHelper;
             mInstallLock = installLock;
-            mBackgroundExecutor = backgroundExecutor;
+            mBackgroundHandler = backgroundHandler;
+            mBackgroundExecutor = new HandlerExecutor(backgroundHandler);
             mComponentResolverProducer = new Singleton<>(componentResolverProducer);
             mPermissionManagerProducer = new Singleton<>(permissionManagerProducer);
             mUserManagerProducer = new Singleton<>(userManagerProducer);
@@ -1092,6 +1092,10 @@
             return mPlatformCompatProducer.get(this, mPackageManager);
         }
 
+        public Handler getBackgroundHandler() {
+            return mBackgroundHandler;
+        }
+
         public Executor getBackgroundExecutor() {
             return mBackgroundExecutor;
         }
@@ -2489,29 +2493,14 @@
 
         final Certificate[] trustedCerts = (trustedInstallers != null) ? decodeCertificates(
                 trustedInstallers) : null;
-        final Context context = mContext;
 
         mInjector.getBackgroundExecutor().execute(() -> {
-            final Intent intent = new Intent();
-            List<FileChecksum> result = new ArrayList<>();
-            for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
-                final String split = filesToChecksum.get(i).first;
-                final File file = filesToChecksum.get(i).second;
-                try {
-                    result.addAll(ApkChecksums.getFileChecksums(split, file, optional, required,
-                            trustedCerts));
-                } catch (Throwable e) {
-                    Slog.e(TAG, "Checksum calculation error", e);
-                }
-            }
-            intent.putExtra(EXTRA_CHECKSUMS,
-                    result.toArray(new FileChecksum[result.size()]));
-
-            try {
-                statusReceiver.sendIntent(context, 1, intent, null, null);
-            } catch (SendIntentException e) {
-                Slog.w(TAG, e);
-            }
+            ApkChecksums.Injector injector = new ApkChecksums.Injector(
+                    () -> mContext,
+                    () -> mInjector.getBackgroundHandler(),
+                    () -> mContext.getSystemService(IncrementalManager.class));
+            ApkChecksums.getChecksums(filesToChecksum, optional, required, trustedCerts,
+                    statusReceiver, injector);
         });
     }
 
@@ -2684,7 +2673,7 @@
 
         Injector injector = new Injector(
                 context, lock, installer, installLock, new PackageAbiHelperImpl(),
-                new HandlerExecutor(backgroundHandler),
+                backgroundHandler,
                 (i, pm) ->
                         new ComponentResolver(i.getUserManagerService(), pm.mPmInternal, lock),
                 (i, pm) ->
@@ -14541,13 +14530,11 @@
             Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package=" + res.pkg);
         }
 
-        // A restore should be performed at this point if (a) the install
-        // succeeded, (b) the operation is not an update, and (c) the new
-        // package has not opted out of backup participation.
+        // A restore should be requested at this point if (a) the install
+        // succeeded, (b) the operation is not an update.
         final boolean update = res.removedInfo != null
                 && res.removedInfo.removedPackage != null;
-        boolean allowBackup = res.pkg != null && res.pkg.isAllowBackup();
-        boolean doRestore = !update && allowBackup;
+        boolean doRestore = !update;
 
         // Set up the post-install work request bookkeeping.  This will be used
         // and cleaned up by the post-install event handling regardless of whether
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index dbe96e6..4858682 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -33,6 +33,9 @@
       "name": "CtsContentTestCases",
       "options": [
         {
+          "include-filter": "android.content.pm.cts.ChecksumsTest"
+        },
+        {
           "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest"
         },
         {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d137fd0..e44c8ab 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -760,6 +760,8 @@
         return null;
     }
 
+    // TODO(b/157921703): replace by getAliveUsers() or remove (so callers
+    // explicitly call the 3-booleans version)
     public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
         return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
                 true);
diff --git a/services/core/java/com/android/server/slice/SliceFullAccessList.java b/services/core/java/com/android/server/slice/SliceFullAccessList.java
index 6f5afa2..d25ddf8 100644
--- a/services/core/java/com/android/server/slice/SliceFullAccessList.java
+++ b/services/core/java/com/android/server/slice/SliceFullAccessList.java
@@ -101,7 +101,7 @@
     public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
         // upgrade xml
         int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
-        final List<UserInfo> activeUsers = UserManager.get(mContext).getUsers(true);
+        final List<UserInfo> activeUsers = UserManager.get(mContext).getAliveUsers();
         for (UserInfo userInfo : activeUsers) {
             upgradeXml(xmlVersion, userInfo.getUserHandle().getIdentifier());
         }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 0c85387..386f390 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -379,7 +379,7 @@
     }
 
     private void updateTrustAll() {
-        List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */);
+        List<UserInfo> userInfos = mUserManager.getAliveUsers();
         for (UserInfo userInfo : userInfos) {
             updateTrust(userInfo.id, 0);
         }
@@ -485,7 +485,7 @@
 
         List<UserInfo> userInfos;
         if (userIdOrAll == UserHandle.USER_ALL) {
-            userInfos = mUserManager.getUsers(true /* excludeDying */);
+            userInfos = mUserManager.getAliveUsers();
         } else {
             userInfos = new ArrayList<>();
             userInfos.add(mUserManager.getUserInfo(userIdOrAll));
@@ -644,7 +644,7 @@
         }
         List<UserInfo> userInfos;
         if (userId == UserHandle.USER_ALL) {
-            userInfos = mUserManager.getUsers(true /* excludeDying */);
+            userInfos = mUserManager.getAliveUsers();
         } else {
             userInfos = new ArrayList<>();
             userInfos.add(mUserManager.getUserInfo(userId));
@@ -1171,7 +1171,7 @@
                 fout.println("disabled because the third-party apps can't run yet.");
                 return;
             }
-            final List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */);
+            final List<UserInfo> userInfos = mUserManager.getAliveUsers();
             mHandler.runWithScissors(new Runnable() {
                 @Override
                 public void run() {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 56261c4..7d4d562 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -401,7 +401,6 @@
 
     final ActivityTaskManagerService mAtmService;
     final ActivityInfo info; // activity info provided by developer in AndroidManifest
-    // Non-null only for application tokens.
     // TODO: rename to mActivityToken
     final ActivityRecord.Token appToken;
     // Which user is this running for?
@@ -5480,10 +5479,6 @@
     }
 
     void updateReportedVisibilityLocked() {
-        if (appToken == null) {
-            return;
-        }
-
         if (DEBUG_VISIBILITY) Slog.v(TAG, "Update reported visibility: " + this);
         final int count = mChildren.size();
 
@@ -6330,8 +6325,7 @@
     }
 
     private void setOrientation(int requestedOrientation, boolean freezeScreenIfNeeded) {
-        final IBinder binder =
-                (freezeScreenIfNeeded && appToken != null) ? appToken.asBinder() : null;
+        final IBinder binder = freezeScreenIfNeeded ? appToken.asBinder() : null;
         setOrientation(requestedOrientation, binder, this);
 
         // Push the new configuration to the requested app in case where it's not pushed, e.g. when
@@ -7713,9 +7707,7 @@
     }
 
     void writeNameToProto(ProtoOutputStream proto, long fieldId) {
-        if (appToken != null) {
-            proto.write(fieldId, appToken.getName());
-        }
+        proto.write(fieldId, appToken.getName());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index be7a6ae..f206259 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1710,8 +1710,9 @@
         mRootWindowContainer.startPowerModeLaunchIfNeeded(
                 false /* forceSend */, mStartActivity);
 
-        mTargetStack.startActivityLocked(mStartActivity, topStack.getTopNonFinishingActivity(),
-                newTask, mKeepCurTransition, mOptions);
+        mTargetStack.startActivityLocked(mStartActivity,
+                topStack != null ? topStack.getTopNonFinishingActivity() : null, newTask,
+                mKeepCurTransition, mOptions);
         if (mDoResume) {
             final ActivityRecord topTaskActivity =
                     mStartActivity.getTask().topRunningActivityLocked();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6a8cbfb..c58b5b5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -125,6 +125,7 @@
 import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 
 import android.Manifest;
 import android.annotation.IntDef;
@@ -5465,8 +5466,11 @@
         updateResumedAppTrace(r);
         mLastResumedActivity = r;
 
-        r.getDisplay().setFocusedApp(r, true);
-
+        final boolean changed = r.getDisplay().setFocusedApp(r);
+        if (changed) {
+            mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+                    true /*updateInputWindows*/);
+        }
         if (prevTask == null || task != prevTask) {
             if (prevTask != null) {
                 mTaskChangeNotificationController.notifyTaskFocusChanged(prevTask.mTaskId, false);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2f7cc69..aa8069a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -115,13 +115,11 @@
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.REPORT_FOCUS_CHANGE;
 import static com.android.server.wm.WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE;
 import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS;
 import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
 import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION;
-import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
@@ -649,8 +647,9 @@
 
     private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
         final ActivityRecord focusedApp = mFocusedApp;
-        ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b",
-                w, w.mAttrs.flags, w.canReceiveKeys());
+        ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
+                w, w.mAttrs.flags, w.canReceiveKeys(),
+                w.canReceiveKeysReason(false /* fromUserTouch */));
 
         if (!w.canReceiveKeys()) {
             return false;
@@ -3072,7 +3071,7 @@
      */
     WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {
         return (mWmService.mPerDisplayFocusEnabled || topFocusedDisplayId == INVALID_DISPLAY)
-                ? findFocusedWindow() : null;
+                    ? findFocusedWindow() : null;
     }
 
     WindowState findFocusedWindow() {
@@ -3081,7 +3080,8 @@
         forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);
 
         if (mTmpWindow == null) {
-            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: No focusable windows.");
+            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: No focusable windows, display=%d",
+                    getDisplayId());
             return null;
         }
         return mTmpWindow;
@@ -3116,18 +3116,15 @@
                     && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
                 assignWindowLayers(false /* setLayoutNeeded */);
             }
+
+            if (imWindowChanged) {
+                mWmService.mWindowsChanged = true;
+                setLayoutNeeded();
+                newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
+            }
         }
 
-        if (imWindowChanged) {
-            mWmService.mWindowsChanged = true;
-            setLayoutNeeded();
-            newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
-        }
-        if (mCurrentFocus != newFocus) {
-            mWmService.mH.obtainMessage(REPORT_FOCUS_CHANGE, this).sendToTarget();
-        }
-
-        ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Changing focus from %s to %s displayId=%d Callers=%s",
+        ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "Changing focus from %s to %s displayId=%d Callers=%s",
                 mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));
         final WindowState oldFocus = mCurrentFocus;
         mCurrentFocus = newFocus;
@@ -3185,9 +3182,25 @@
         if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
             pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
         }
+
+        // Notify the accessibility manager for the change so it has the windows before the newly
+        // focused one starts firing events.
+        // TODO(b/151179149) investigate what info accessibility service needs before input can
+        // dispatch focus to clients.
+        if (mWmService.mAccessibilityController != null) {
+            mWmService.mH.sendMessage(PooledLambda.obtainMessage(
+                    this::updateAccessibilityOnWindowFocusChanged,
+                    mWmService.mAccessibilityController));
+        }
+
+        mLastFocus = mCurrentFocus;
         return true;
     }
 
+    void updateAccessibilityOnWindowFocusChanged(AccessibilityController accessibilityController) {
+        accessibilityController.onWindowFocusChangedNotLocked(getDisplayId());
+    }
+
     private static void onWindowFocusChanged(WindowState oldFocus, WindowState newFocus) {
         final Task focusedTask = newFocus != null ? newFocus.getTask() : null;
         final Task unfocusedTask = oldFocus != null ? oldFocus.getTask() : null;
@@ -3219,6 +3232,8 @@
         if (mFocusedApp == newFocus) {
             return false;
         }
+        ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "setFocusedApp %s displayId=%d Callers=%s",
+                newFocus, getDisplayId(), Debug.getCallers(4));
         mFocusedApp = newFocus;
         getInputMonitor().setFocusedAppLw(newFocus);
         updateTouchExcludeRegion();
@@ -4708,7 +4723,7 @@
         // Traverse all windows top down to assemble the gesture exclusion rects.
         // For each window, we only take the rects that fall within its touchable region.
         forAllWindows(w -> {
-            if (w.cantReceiveTouchInput() || !w.isVisible()
+            if (!w.canReceiveTouchInput() || !w.isVisible()
                     || (w.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0
                     || unhandled.isEmpty()) {
                 return;
@@ -5225,30 +5240,6 @@
                 && (mAtmService.mRunningVoice == null);
     }
 
-    void setFocusedApp(ActivityRecord r, boolean moveFocusNow) {
-        final ActivityRecord newFocus;
-        final IBinder token = r.appToken;
-        if (token == null) {
-            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Clearing focused app, displayId=%d",
-                    mDisplayId);
-            newFocus = null;
-        } else {
-            newFocus = mWmService.mRoot.getActivityRecord(token);
-            if (newFocus == null) {
-                Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token
-                        + ", displayId=" + mDisplayId);
-            }
-            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
-                    "Set focused app to: %s moveFocusNow=%b displayId=%d", newFocus,
-                    moveFocusNow, mDisplayId);
-        }
-
-        final boolean changed = setFocusedApp(newFocus);
-        if (moveFocusNow && changed) {
-            mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
-                    true /*updateInputWindows*/);
-        }
-    }
 
     void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
             boolean preserveWindows, boolean notifyClients) {
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 999aab9..ec62ed4 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -114,7 +114,7 @@
 
                     final WindowState callingWin = mService.windowForClientLocked(
                             null, window, false);
-                    if (callingWin == null || callingWin.cantReceiveTouchInput()) {
+                    if (callingWin == null || !callingWin.canReceiveTouchInput()) {
                         Slog.w(TAG_WM, "Bad requesting window " + window);
                         return null;  // !!! TODO: throw here?
                     }
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index e166bfc..0978636 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -24,6 +24,7 @@
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
+import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.input.InputManagerService;
 import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow;
@@ -252,7 +253,7 @@
         // All the calls below need to happen without the WM lock held since they call into AM.
         mService.mAtmInternal.saveANRState(reason);
 
-        if (activity != null && activity.appToken != null) {
+        if (activity != null) {
             // Notify the activity manager about the timeout and let it decide whether
             // to abort dispatching or keep waiting.
             final boolean abort = activity.keyDispatchingTimedOut(reason, windowPid);
@@ -410,6 +411,8 @@
             requestRefreshConfiguration = dispatchPointerCaptureChanged(focusedWindow, false);
         }
         mFocusedWindow.set(newFocusedWindow);
+        mService.mH.sendMessage(PooledLambda.obtainMessage(mService::reportFocusChanged,
+                oldToken, newToken));
         return requestRefreshConfiguration;
     }
 
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index fb511e0..4efd687 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -370,7 +370,8 @@
      * Layer assignment is assumed to be complete by the time this is called.
      */
     public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
-        ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "Input focus has changed to %s", newWindow);
+        ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Input focus has changed to %s display=%d",
+                newWindow, mDisplayId);
 
         if (newWindow != mInputFocus) {
             if (newWindow != null && newWindow.canReceiveKeys()) {
@@ -493,7 +494,7 @@
             final int type = w.mAttrs.type;
             final boolean isVisible = w.isVisibleLw();
             if (inputChannel == null || inputWindowHandle == null || w.mRemoved
-                    || (w.cantReceiveTouchInput() && !shouldApplyRecentsInputConsumer)) {
+                    || (!w.canReceiveTouchInput() && !shouldApplyRecentsInputConsumer)) {
                 if (w.mWinAnimator.hasSurface()) {
                     // Assign an InputInfo with type to the overlay window which can't receive input
                     // event. This is used to omit Surfaces from occlusion detection.
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 21e30ce..6182a55 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -40,7 +40,6 @@
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
 import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
-
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
@@ -90,7 +89,6 @@
 import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
 import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
-
 import static java.lang.Integer.MAX_VALUE;
 
 import android.annotation.IntDef;
@@ -129,6 +127,7 @@
 import android.os.storage.StorageManager;
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionSession;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.IntArray;
@@ -162,7 +161,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -214,7 +212,7 @@
     private int mTopFocusedDisplayId = INVALID_DISPLAY;
 
     // Map from the PID to the top most app which has a focused window of the process.
-    final HashMap<Integer, ActivityRecord> mTopFocusedAppByProcess = new HashMap<>();
+    final ArrayMap<Integer, ActivityRecord> mTopFocusedAppByProcess = new ArrayMap<>();
 
     // Only a separate transaction until we separate the apply surface changes
     // transaction from the global transaction.
@@ -480,8 +478,7 @@
             mTopFocusedDisplayId = topFocusedDisplayId;
             mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
             mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
-            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d",
-                    topFocusedDisplayId);
+            ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId);
         }
         return changed;
     }
@@ -2289,10 +2286,6 @@
 
         for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
             final DisplayContent display = getChildAt(displayNdx);
-            if (display.shouldSleep()) {
-                continue;
-            }
-
             final boolean curResult = result;
             boolean resumedOnDisplay = display.reduceOnAllTaskDisplayAreas(
                     (taskDisplayArea, resumed) -> {
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 33935d6..7df2b40 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -26,6 +26,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.DebugUtils;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
@@ -429,7 +430,8 @@
 
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("mLeash="); pw.print(mLeash);
-        pw.print(" mAnimationType=" + mAnimationType);
+        pw.print(" mAnimationType=" + DebugUtils.valueToString(SurfaceAnimator.class,
+                "ANIMATION_TYPE_", mAnimationType));
         pw.println(mAnimationStartDelayed ? " mAnimationStartDelayed=true" : "");
         pw.print(prefix); pw.print("Animation: "); pw.println(mAnimation);
         if (mAnimation != null) {
@@ -442,56 +444,56 @@
      * No animation is specified.
      * @hide
      */
-    static final int ANIMATION_TYPE_NONE = 0;
+    public static final int ANIMATION_TYPE_NONE = 0;
 
     /**
      * Animation for an app transition.
      * @hide
      */
-    static final int ANIMATION_TYPE_APP_TRANSITION = 1;
+    public static final int ANIMATION_TYPE_APP_TRANSITION = 1;
 
     /**
      * Animation for screen rotation.
      * @hide
      */
-    static final int ANIMATION_TYPE_SCREEN_ROTATION = 1 << 1;
+    public static final int ANIMATION_TYPE_SCREEN_ROTATION = 1 << 1;
 
     /**
      * Animation for dimming.
      * @hide
      */
-    static final int ANIMATION_TYPE_DIMMER = 1 << 2;
+    public static final int ANIMATION_TYPE_DIMMER = 1 << 2;
 
     /**
      * Animation for recent apps.
      * @hide
      */
-    static final int ANIMATION_TYPE_RECENTS = 1 << 3;
+    public static final int ANIMATION_TYPE_RECENTS = 1 << 3;
 
     /**
      * Animation for a {@link WindowState} without animating the activity.
      * @hide
      */
-    static final int ANIMATION_TYPE_WINDOW_ANIMATION = 1 << 4;
+    public static final int ANIMATION_TYPE_WINDOW_ANIMATION = 1 << 4;
 
     /**
      * Animation to control insets. This is actually not an animation, but is used to give the
      * client a leash over the system window causing insets.
      * @hide
      */
-    static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5;
+    public static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5;
 
     /**
      * Animation when a fixed rotation transform is applied to a window token.
      * @hide
      */
-    static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6;
+    public static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6;
 
     /**
      * Bitmask to include all animation types. This is NOT an {@link AnimationType}
      * @hide
      */
-    static final int ANIMATION_TYPE_ALL = -1;
+    public static final int ANIMATION_TYPE_ALL = -1;
 
     /**
      * The type of the animation.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 19bf451..bfaaf46 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -6348,7 +6348,7 @@
         return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayArea());
     }
 
-    void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
+    void startActivityLocked(ActivityRecord r, @Nullable ActivityRecord focusedTopActivity,
             boolean newTask, boolean keepCurTransition, ActivityOptions options) {
         Task rTask = r.getTask();
         final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
@@ -7585,7 +7585,11 @@
         // Do not sleep activities in this stack if we're marked as focused and the keyguard
         // is in the process of going away.
         if (isFocusedStackOnDisplay()
-                && mStackSupervisor.getKeyguardController().isKeyguardGoingAway()) {
+                && mStackSupervisor.getKeyguardController().isKeyguardGoingAway()
+                // Avoid resuming activities on secondary displays since we don't want bubble
+                // activities to be resumed while bubble is still collapsed.
+                // TODO(b/113840485): Having keyguard going away state for secondary displays.
+                && display.isDefaultDisplay) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 1b779c6..63a595e 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -38,6 +38,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 import android.view.SurfaceControl;
 import android.window.ITaskOrganizer;
 import android.window.ITaskOrganizerController;
@@ -50,6 +51,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
@@ -206,7 +208,6 @@
         private final DeathRecipient mDeathRecipient;
         private final ArrayList<Task> mOrganizedTasks = new ArrayList<>();
         private final int mUid;
-        private boolean mInterceptBackPressedOnTaskRoot;
 
         TaskOrganizerState(ITaskOrganizer organizer, int uid) {
             final Consumer<Runnable> deferTaskOrgCallbacksConsumer =
@@ -224,10 +225,6 @@
             mUid = uid;
         }
 
-        void setInterceptBackPressedOnTaskRoot(boolean interceptBackPressed) {
-            mInterceptBackPressedOnTaskRoot = interceptBackPressed;
-        }
-
         void addTask(Task t) {
             if (t.mTaskAppearedSent) return;
 
@@ -247,6 +244,7 @@
                 mOrganizer.onTaskVanished(t);
             }
             mOrganizedTasks.remove(t);
+            mInterceptBackPressedOnRootTasks.remove(t.mTaskId);
         }
 
         void dispose() {
@@ -278,6 +276,8 @@
     private final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>();
     private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
     private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>();
+    // Set of organized tasks (by taskId) that dispatch back pressed to their organizers
+    private final HashSet<Integer> mInterceptBackPressedOnRootTasks = new HashSet();
 
     private final ActivityTaskManagerService mService;
 
@@ -623,7 +623,7 @@
     }
 
     @Override
-    public void setInterceptBackPressedOnTaskRoot(ITaskOrganizer organizer,
+    public void setInterceptBackPressedOnTaskRoot(WindowContainerToken token,
             boolean interceptBackPressed) {
         enforceStackPermission("setInterceptBackPressedOnTaskRoot()");
         final long origId = Binder.clearCallingIdentity();
@@ -631,9 +631,15 @@
             synchronized (mGlobalLock) {
                 ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Set intercept back pressed on root=%b",
                         interceptBackPressed);
-                final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
-                if (state != null) {
-                    state.setInterceptBackPressedOnTaskRoot(interceptBackPressed);
+                final Task task = WindowContainer.fromBinder(token.asBinder()).asTask();
+                if (task == null) {
+                    Slog.w(TAG, "Could not resolve task from token");
+                    return;
+                }
+                if (interceptBackPressed) {
+                    mInterceptBackPressedOnRootTasks.add(task.mTaskId);
+                } else {
+                    mInterceptBackPressedOnRootTasks.remove(task.mTaskId);
                 }
             }
         } finally {
@@ -642,15 +648,12 @@
     }
 
     public boolean handleInterceptBackPressedOnTaskRoot(Task task) {
-        if (task == null || !task.isOrganized()) {
+        if (task == null || !task.isOrganized()
+                || !mInterceptBackPressedOnRootTasks.contains(task.mTaskId)) {
             return false;
         }
 
         final TaskOrganizerState state = mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder());
-        if (!state.mInterceptBackPressedOnTaskRoot) {
-            return false;
-        }
-
         state.mOrganizer.onBackPressedOnTaskRoot(task);
         return true;
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c45ccb6..19179a8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -376,8 +376,11 @@
     private static final String BOOT_ANIMATION_SERVICE = "bootanim";
 
     static final int UPDATE_FOCUS_NORMAL = 0;
+    /** Caller will assign layers */
     static final int UPDATE_FOCUS_WILL_ASSIGN_LAYERS = 1;
+    /** Caller is performing surface placement */
     static final int UPDATE_FOCUS_PLACING_SURFACES = 2;
+    /** Caller will performSurfacePlacement */
     static final int UPDATE_FOCUS_WILL_PLACE_SURFACES = 3;
     /** Indicates we are removing the focused window when updating the focus. */
     static final int UPDATE_FOCUS_REMOVING_FOCUS = 4;
@@ -4730,12 +4733,30 @@
         return false;
     }
 
+    void reportFocusChanged(IBinder oldToken, IBinder newToken) {
+        WindowState lastFocus;
+        WindowState newFocus;
+        synchronized (mGlobalLock) {
+            lastFocus = mInputToWindowMap.get(oldToken);
+            newFocus = mInputToWindowMap.get(newToken);
+            ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastFocus, newFocus);
+        }
+
+        if (newFocus != null) {
+            newFocus.reportFocusChangedSerialized(true);
+            notifyFocusChanged();
+        }
+
+        if (lastFocus != null) {
+            lastFocus.reportFocusChangedSerialized(false);
+        }
+    }
+
     // -------------------------------------------------------------
     // Async Handler
     // -------------------------------------------------------------
 
     final class H extends android.os.Handler {
-        public static final int REPORT_FOCUS_CHANGE = 2;
         public static final int WINDOW_FREEZE_TIMEOUT = 11;
 
         public static final int PERSIST_ANIMATION_SCALE = 14;
@@ -4788,50 +4809,6 @@
                 Slog.v(TAG_WM, "handleMessage: entry what=" + msg.what);
             }
             switch (msg.what) {
-                case REPORT_FOCUS_CHANGE: {
-                    final DisplayContent displayContent = (DisplayContent) msg.obj;
-                    WindowState lastFocus;
-                    WindowState newFocus;
-
-                    AccessibilityController accessibilityController = null;
-
-                    synchronized (mGlobalLock) {
-                        if (mAccessibilityController != null) {
-                            accessibilityController = mAccessibilityController;
-                        }
-
-                        lastFocus = displayContent.mLastFocus;
-                        newFocus = displayContent.mCurrentFocus;
-                        if (lastFocus == newFocus) {
-                            // Focus is not changing, so nothing to do.
-                            return;
-                        }
-                        displayContent.mLastFocus = newFocus;
-                        ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus moving from %s"
-                                        + " to %s displayId=%d", lastFocus, newFocus,
-                                displayContent.getDisplayId());
-                    }
-
-                    // First notify the accessibility manager for the change so it has
-                    // the windows before the newly focused one starts firing events.
-                    if (accessibilityController != null) {
-                        accessibilityController.onWindowFocusChangedNotLocked(
-                                displayContent.getDisplayId());
-                    }
-
-                    if (newFocus != null) {
-                        ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Gaining focus: %s", newFocus);
-                        newFocus.reportFocusChangedSerialized(true);
-                        notifyFocusChanged();
-                    }
-
-                    if (lastFocus != null) {
-                        ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing focus: %s", lastFocus);
-                        lastFocus.reportFocusChangedSerialized(false);
-                    }
-                    break;
-                }
-
                 case WINDOW_FREEZE_TIMEOUT: {
                     final DisplayContent displayContent = (DisplayContent) msg.obj;
                     synchronized (mGlobalLock) {
@@ -7994,6 +7971,8 @@
             return;
         }
 
+        ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "onPointerDownOutsideFocusLocked called on %s",
+                touchedWindow);
         final DisplayContent displayContent = touchedWindow.getDisplayContent();
         if (!displayContent.isOnTop()) {
             displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent,
@@ -8022,10 +8001,7 @@
             }
         }
 
-        try {
-            mActivityTaskManager.setFocusedTask(task.mTaskId);
-        } catch (RemoteException e) {
-        }
+        mAtmService.setFocusedTask(task.mTaskId);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9ff33b1..84a9c75 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2884,12 +2884,25 @@
         return canReceiveKeys(false /* fromUserTouch */);
     }
 
+    public String canReceiveKeysReason(boolean fromUserTouch) {
+        return "fromTouch= " + fromUserTouch
+                + " isVisibleOrAdding=" + isVisibleOrAdding()
+                + " mViewVisibility=" + mViewVisibility
+                + " mRemoveOnExit=" + mRemoveOnExit
+                + " flags=" + mAttrs.flags
+                + " appWindowsAreFocusable="
+                + (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
+                + " canReceiveTouchInput=" + canReceiveTouchInput()
+                + " displayIsOnTop=" + getDisplayContent().isOnTop()
+                + " displayIsTrusted=" + getDisplayContent().isTrusted();
+    }
+
     public boolean canReceiveKeys(boolean fromUserTouch) {
         final boolean canReceiveKeys = isVisibleOrAdding()
                 && (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
                 && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
                 && (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
-                && !cantReceiveTouchInput();
+                && canReceiveTouchInput();
         if (!canReceiveKeys) {
             return false;
         }
@@ -2907,15 +2920,18 @@
         return showBecauseOfActivity || showBecauseOfWindow;
     }
 
-    /** @return {@code false} if this window desires touch events. */
-    boolean cantReceiveTouchInput() {
-        if (mActivityRecord == null || mActivityRecord.getTask() == null) {
-            return false;
+    /**
+     * @return {@code true} if this window can receive touches based on among other things,
+     * windowing state and recents animation state.
+     **/
+    boolean canReceiveTouchInput() {
+        if (mActivityRecord == null  || mActivityRecord.getTask() == null) {
+            return true;
         }
 
-        return mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
-                || !mActivityRecord.mVisibleRequested
-                || isRecentsAnimationConsumingAppInput();
+        return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
+                && mActivityRecord.mVisibleRequested
+                && !isRecentsAnimationConsumingAppInput();
     }
 
     /**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 22e309c..8045583 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5782,9 +5782,6 @@
         if (!mHasFeature) {
             return;
         }
-        final CallerIdentity identity = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isSystemUid(identity) || isRootUid(identity)
-                || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL));
 
         final ActiveAdmin admin;
         synchronized (getLockObject()) {
@@ -9438,8 +9435,7 @@
         Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
-            final List<UserInfo> userInfos = mInjector.getUserManager().getUsers(true
-                    /*excludeDying*/);
+            final List<UserInfo> userInfos = mInjector.getUserManager().getAliveUsers();
             final List<UserHandle> userHandles = new ArrayList<>();
             for (UserInfo userInfo : userInfos) {
                 UserHandle userHandle = userInfo.getUserHandle();
@@ -10362,7 +10358,7 @@
 
     private void maybeClearLockTaskPolicyLocked() {
         mInjector.binderWithCleanCallingIdentity(() -> {
-            final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
+            final List<UserInfo> userInfos = mUserManager.getAliveUsers();
             for (int i = userInfos.size() - 1; i >= 0; i--) {
                 int userId = userInfos.get(i).id;
                 if (canUserUseLockTaskLocked(userId)) {
@@ -10849,7 +10845,7 @@
      * them.
      */
     void updateUserSetupCompleteAndPaired() {
-        List<UserInfo> users = mUserManager.getUsers(true);
+        List<UserInfo> users = mUserManager.getAliveUsers();
         final int N = users.size();
         for (int i = 0; i < N; i++) {
             int userHandle = users.get(i).id;
@@ -12052,14 +12048,6 @@
     }
 
     @Override
-    public boolean isSystemOnlyUser(ComponentName admin) {
-        Objects.requireNonNull(admin, "ComponentName is null");
-        final CallerIdentity identity = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
-        return UserManager.isSplitSystemUser() && identity.getUserId() == UserHandle.USER_SYSTEM;
-    }
-
-    @Override
     public void reboot(ComponentName admin) {
         Objects.requireNonNull(admin, "ComponentName is null");
         final CallerIdentity identity = getCallerIdentity(admin);
@@ -12579,7 +12567,7 @@
 
     private boolean areAllUsersAffiliatedWithDeviceLocked() {
         return mInjector.binderWithCleanCallingIdentity(() -> {
-            final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
+            final List<UserInfo> userInfos = mUserManager.getAliveUsers();
             for (int i = 0; i < userInfos.size(); i++) {
                 int userId = userInfos.get(i).id;
                 if (!isUserAffiliatedWithDeviceLocked(userId)) {
@@ -13048,7 +13036,7 @@
                     }
                 } else {
                     // Caller is the device owner: Look for profile owners that it can bind to.
-                    final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
+                    final List<UserInfo> userInfos = mUserManager.getAliveUsers();
                     for (int i = 0; i < userInfos.size(); i++) {
                         final int userId = userInfos.get(i).id;
                         if (userId != callingUserId && canUserBindToDeviceOwnerLocked(userId)) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 3cdd482..7649af4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -169,7 +169,7 @@
             // First, try to read from the legacy file.
             final File legacy = getLegacyConfigFile();
 
-            final List<UserInfo> users = mUserManager.getUsers(true);
+            final List<UserInfo> users = mUserManager.getAliveUsers();
 
             if (readLegacyOwnerFileLocked(legacy)) {
                 if (DEBUG) {
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 41945a2..87ae4d7 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -237,6 +237,13 @@
     return ok();
 }
 
+binder::Status BinderIncrementalService::isFileFullyLoaded(int32_t storageId,
+                                                           const std::string& path,
+                                                           int32_t* _aidl_return) {
+    *_aidl_return = mImpl.isFileFullyLoaded(storageId, path);
+    return ok();
+}
+
 binder::Status BinderIncrementalService::getLoadingProgress(int32_t storageId,
                                                             float* _aidl_return) {
     *_aidl_return = mImpl.getLoadingProgress(storageId);
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 8b40350..8478142 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -66,6 +66,8 @@
                             int32_t destStorageId, const std::string& destPath,
                             int32_t* _aidl_return) final;
     binder::Status unlink(int32_t storageId, const std::string& path, int32_t* _aidl_return) final;
+    binder::Status isFileFullyLoaded(int32_t storageId, const std::string& path,
+                                     int32_t* _aidl_return) final;
     binder::Status getLoadingProgress(int32_t storageId, float* _aidl_return) final;
     binder::Status getMetadataByPath(int32_t storageId, const std::string& path,
                                      std::vector<uint8_t>* _aidl_return) final;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 9836262e..447ee55 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1603,7 +1603,8 @@
 
     const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId);
     if (!writeFd.ok()) {
-        LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd;
+        LOG(ERROR) << "Failed to open write fd for: " << targetLibPath
+                   << " errno: " << writeFd.get();
         return;
     }
 
@@ -1673,6 +1674,37 @@
     return mRunning;
 }
 
+int IncrementalService::isFileFullyLoaded(StorageId storage, const std::string& path) const {
+    std::unique_lock l(mLock);
+    const auto ifs = getIfsLocked(storage);
+    if (!ifs) {
+        LOG(ERROR) << "isFileFullyLoaded failed, invalid storageId: " << storage;
+        return -EINVAL;
+    }
+    const auto storageInfo = ifs->storages.find(storage);
+    if (storageInfo == ifs->storages.end()) {
+        LOG(ERROR) << "isFileFullyLoaded failed, no storage: " << storage;
+        return -EINVAL;
+    }
+    l.unlock();
+    return isFileFullyLoadedFromPath(*ifs, path);
+}
+
+int IncrementalService::isFileFullyLoadedFromPath(const IncFsMount& ifs,
+                                                  std::string_view filePath) const {
+    const auto [filledBlocks, totalBlocks] = mIncFs->countFilledBlocks(ifs.control, filePath);
+    if (filledBlocks < 0) {
+        LOG(ERROR) << "isFileFullyLoadedFromPath failed to get filled blocks count for: "
+                   << filePath << " errno: " << filledBlocks;
+        return filledBlocks;
+    }
+    if (totalBlocks < filledBlocks) {
+        LOG(ERROR) << "isFileFullyLoadedFromPath failed to get total num of blocks";
+        return -EINVAL;
+    }
+    return totalBlocks - filledBlocks;
+}
+
 float IncrementalService::getLoadingProgress(StorageId storage) const {
     std::unique_lock l(mLock);
     const auto ifs = getIfsLocked(storage);
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index cd6bfed..267458d 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -132,6 +132,7 @@
              std::string_view newPath);
     int unlink(StorageId storage, std::string_view path);
 
+    int isFileFullyLoaded(StorageId storage, const std::string& path) const;
     float getLoadingProgress(StorageId storage) const;
 
     RawMetadata getMetadata(StorageId storage, std::string_view path) const;
@@ -339,6 +340,7 @@
     int makeDirs(const IncFsMount& ifs, StorageId storageId, std::string_view path, int mode);
     binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs);
 
+    int isFileFullyLoadedFromPath(const IncFsMount& ifs, std::string_view filePath) const;
     float getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const;
 
     void registerAppOpsCallback(const std::string& packageName);
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 1ed46c4..f6d89c5 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -195,8 +195,8 @@
     ErrorCode unlink(const Control& control, std::string_view path) const final {
         return incfs::unlink(control, path);
     }
-    base::unique_fd openForSpecialOps(const Control& control, FileId id) const final {
-        return base::unique_fd{incfs::openForSpecialOps(control, id).release()};
+    incfs::UniqueFd openForSpecialOps(const Control& control, FileId id) const final {
+        return incfs::openForSpecialOps(control, id);
     }
     ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final {
         return incfs::writeBlocks({blocks.data(), size_t(blocks.size())});
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 82a1704..6376d86 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -74,6 +74,7 @@
     using Control = incfs::Control;
     using FileId = incfs::FileId;
     using ErrorCode = incfs::ErrorCode;
+    using UniqueFd = incfs::UniqueFd;
     using WaitResult = incfs::WaitResult;
 
     using ExistingMountCallback =
@@ -96,7 +97,7 @@
     virtual ErrorCode link(const Control& control, std::string_view from,
                            std::string_view to) const = 0;
     virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0;
-    virtual base::unique_fd openForSpecialOps(const Control& control, FileId id) const = 0;
+    virtual UniqueFd openForSpecialOps(const Control& control, FileId id) const = 0;
     virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0;
     virtual WaitResult waitForPendingReads(
             const Control& control, std::chrono::milliseconds timeout,
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index d1000e56..a290a17 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -289,7 +289,7 @@
                        ErrorCode(const Control& control, std::string_view from,
                                  std::string_view to));
     MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path));
-    MOCK_CONST_METHOD2(openForSpecialOps, base::unique_fd(const Control& control, FileId id));
+    MOCK_CONST_METHOD2(openForSpecialOps, UniqueFd(const Control& control, FileId id));
     MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks));
     MOCK_CONST_METHOD3(waitForPendingReads,
                        WaitResult(const Control& control, std::chrono::milliseconds timeout,
@@ -304,6 +304,10 @@
         ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(1, 2)));
     }
 
+    void countFilledBlocksFullyLoaded() {
+        ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(10000, 10000)));
+    }
+
     void countFilledBlocksFails() {
         ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(-1, -1)));
     }
@@ -1069,6 +1073,53 @@
     ASSERT_EQ(res, 0);
 }
 
+TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithNoFile) {
+    mIncFs->countFilledBlocksFails();
+    mFs->hasNoFile();
+
+    TemporaryDir tempDir;
+    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                                       IncrementalService::CreateOptions::CreateNew,
+                                                       {}, {}, {});
+    ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
+}
+
+TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithFailedRanges) {
+    mIncFs->countFilledBlocksFails();
+    mFs->hasFiles();
+
+    TemporaryDir tempDir;
+    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                                       IncrementalService::CreateOptions::CreateNew,
+                                                       {}, {}, {});
+    EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
+    ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
+}
+
+TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccessWithEmptyRanges) {
+    mIncFs->countFilledBlocksEmpty();
+    mFs->hasFiles();
+
+    TemporaryDir tempDir;
+    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                                       IncrementalService::CreateOptions::CreateNew,
+                                                       {}, {}, {});
+    EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
+    ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
+}
+
+TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccess) {
+    mIncFs->countFilledBlocksFullyLoaded();
+    mFs->hasFiles();
+
+    TemporaryDir tempDir;
+    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                                       IncrementalService::CreateOptions::CreateNew,
+                                                       {}, {}, {});
+    EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
+    ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
+}
+
 TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithNoFile) {
     mIncFs->countFilledBlocksSuccess();
     mFs->hasNoFile();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index b306ff0..431cc27 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -16,7 +16,6 @@
 package com.android.server.devicepolicy;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -236,7 +235,7 @@
         }
         mUserInfos.add(uh);
         when(userManager.getUsers()).thenReturn(mUserInfos);
-        when(userManager.getUsers(anyBoolean())).thenReturn(mUserInfos);
+        when(userManager.getAliveUsers()).thenReturn(mUserInfos);
         when(userManager.isUserRunning(eq(new UserHandle(userId)))).thenReturn(true);
         when(userManager.getProfileParent(anyInt())).thenAnswer(
                 invocation -> {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 99433a6..d7e431f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -978,6 +978,7 @@
 
         assertFalse(services.isSameUser(service, 0));
         assertTrue(services.isSameUser(service, 10));
+        assertTrue(services.isSameUser(service, UserHandle.USER_ALL));
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index ab4dc47..5796e84 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -103,7 +103,7 @@
             when(mUm.getUserInfo(eq(user.id))).thenReturn(user);
         }
         when(mUm.getUsers()).thenReturn(users);
-        when(mUm.getUsers(anyBoolean())).thenReturn(users);
+        when(mUm.getAliveUsers()).thenReturn(users);
         IntArray profileIds = new IntArray();
         profileIds.add(0);
         profileIds.add(11);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9319bea..8644719 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5058,7 +5058,7 @@
                 10, 10, r.getKey(), actionIndex, action, notificationVisibility,
                 generatedByAssistant);
         verify(mAssistants).notifyAssistantActionClicked(
-                eq(r.getSbn()), eq(actionIndex), eq(action), eq(generatedByAssistant));
+                eq(r.getSbn()), eq(action), eq(generatedByAssistant));
 
         assertEquals(1, mNotificationRecordLogger.numCalls());
         assertEquals(
@@ -5082,7 +5082,7 @@
                 10, 10, r.getKey(), actionIndex, action, notificationVisibility,
                 generatedByAssistant);
         verify(mAssistants).notifyAssistantActionClicked(
-                eq(r.getSbn()), eq(actionIndex), eq(action), eq(generatedByAssistant));
+                eq(r.getSbn()), eq(action), eq(generatedByAssistant));
 
         assertEquals(1, mNotificationRecordLogger.numCalls());
         assertEquals(
@@ -6948,4 +6948,63 @@
         assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1,
                 mService.getNotificationRecordCount());
     }
+
+    @Test
+    public void testIsVisibleToListener_notEnabled() {
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        when(sbn.getUserId()).thenReturn(10);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        ManagedServices.ManagedServiceInfo assistant = mock(ManagedServices.ManagedServiceInfo.class);
+        info.userid = 10;
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        when(assistant.isSameUser(anyInt())).thenReturn(true);
+        when(info.enabledAndUserMatches(info.userid)).thenReturn(false);
+        when(mAssistants.checkServiceTokenLocked(any())).thenReturn(assistant);
+
+        assertFalse(mService.isVisibleToListener(sbn, info));
+    }
+
+    @Test
+    public void testIsVisibleToListener_noAssistant() {
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        when(sbn.getUserId()).thenReturn(10);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        info.userid = 10;
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        when(info.enabledAndUserMatches(info.userid)).thenReturn(true);
+        when(mAssistants.checkServiceTokenLocked(any())).thenReturn(null);
+
+        assertTrue(mService.isVisibleToListener(sbn, info));
+    }
+
+    @Test
+    public void testIsVisibleToListener_assistant_differentUser() {
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        when(sbn.getUserId()).thenReturn(10);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        ManagedServices.ManagedServiceInfo assistant = mock(ManagedServices.ManagedServiceInfo.class);
+        info.userid = 0;
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        when(assistant.isSameUser(anyInt())).thenReturn(true);
+        when(info.enabledAndUserMatches(info.userid)).thenReturn(true);
+        when(mAssistants.checkServiceTokenLocked(any())).thenReturn(assistant);
+
+        assertFalse(mService.isVisibleToListener(sbn, info));
+    }
+
+    @Test
+    public void testIsVisibleToListener_assistant_sameUser() {
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        when(sbn.getUserId()).thenReturn(10);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        ManagedServices.ManagedServiceInfo assistant = mock(ManagedServices.ManagedServiceInfo.class);
+        info.userid = 10;
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        when(assistant.isSameUser(anyInt())).thenReturn(true);
+        when(info.enabledAndUserMatches(info.userid)).thenReturn(true);
+        when(mAssistants.checkServiceTokenLocked(any())).thenReturn(assistant);
+
+        assertTrue(mService.isVisibleToListener(sbn, info));
+    }
+
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index e2948a7..4cad397 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -1202,19 +1202,22 @@
     @Test
     public void testShouldSleepActivities() {
         // When focused activity and keyguard is going away, we should not sleep regardless
-        // of the display state
+        // of the display state, but keyguard-going-away should only take effects on default
+        // display since there is no keyguard on secondary displays (yet).
         verifyShouldSleepActivities(true /* focusedStack */, true /*keyguardGoingAway*/,
-                true /* displaySleeping */, false /* expected*/);
+                true /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
+        verifyShouldSleepActivities(true /* focusedStack */, true /*keyguardGoingAway*/,
+                true /* displaySleeping */, false /* isDefaultDisplay */, true /* expected */);
 
         // When not the focused stack, defer to display sleeping state.
         verifyShouldSleepActivities(false /* focusedStack */, true /*keyguardGoingAway*/,
-                true /* displaySleeping */, true /* expected*/);
+                true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
 
         // If keyguard is going away, defer to the display sleeping state.
         verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/,
-                true /* displaySleeping */, true /* expected*/);
+                true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
         verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/,
-                false /* displaySleeping */, false /* expected*/);
+                false /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
     }
 
     @Test
@@ -1423,9 +1426,11 @@
     }
 
     private void verifyShouldSleepActivities(boolean focusedStack,
-            boolean keyguardGoingAway, boolean displaySleeping, boolean expected) {
+            boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
+            boolean expected) {
         final DisplayContent display = mock(DisplayContent.class);
         final KeyguardController keyguardController = mSupervisor.getKeyguardController();
+        display.isDefaultDisplay = isDefaultDisplay;
 
         doReturn(display).when(mStack).getDisplay();
         doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index b89d168..26b0bfb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -915,24 +915,6 @@
         assertEquals(taskDisplayArea.getTopStack(), taskDisplayArea.getRootHomeTask());
     }
 
-    @Test
-    public void testResumeFocusedStackOnSleepingDisplay() {
-        // Create an activity on secondary display.
-        final TestDisplayContent secondDisplay = addNewDisplayContentAt(
-                DisplayContent.POSITION_TOP);
-        final Task stack = secondDisplay.getDefaultTaskDisplayArea()
-                .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final ActivityRecord activity = new ActivityBuilder(mAtm).setStack(stack).build();
-        spyOn(activity);
-        spyOn(stack);
-
-        // Cannot resumed activities on secondary display if the display should sleep.
-        doReturn(true).when(secondDisplay).shouldSleep();
-        mRootWindowContainer.resumeFocusedStacksTopActivities();
-        verify(stack, never()).resumeTopActivityUncheckedLocked(any(), any());
-        verify(activity, never()).makeActiveIfNeeded(any());
-    }
-
     /**
      * Mock {@link RootWindowContainer#resolveHomeActivity} for returning consistent activity
      * info for test cases.
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 289d54e..46a6a82 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -930,23 +930,36 @@
         final Task stack = createStack();
         final Task task = createTask(stack);
         final ActivityRecord activity = createActivityRecordInTask(stack.mDisplayContent, task);
+        final Task stack2 = createStack();
+        final Task task2 = createTask(stack2);
+        final ActivityRecord activity2 = createActivityRecordInTask(stack.mDisplayContent, task2);
         final ITaskOrganizer organizer = registerMockOrganizer();
 
         // Setup the task to be controlled by the MW mode organizer
         stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         assertTrue(stack.isOrganized());
+        assertTrue(stack2.isOrganized());
 
         // Verify a back pressed does not call the organizer
         mWm.mAtmService.onBackPressedOnTaskRoot(activity.token);
         verify(organizer, never()).onBackPressedOnTaskRoot(any());
 
         // Enable intercepting back
-        mWm.mAtmService.mTaskOrganizerController.setInterceptBackPressedOnTaskRoot(organizer,
-                true);
+        mWm.mAtmService.mTaskOrganizerController.setInterceptBackPressedOnTaskRoot(
+                stack.mRemoteToken.toWindowContainerToken(), true);
 
         // Verify now that the back press does call the organizer
         mWm.mAtmService.onBackPressedOnTaskRoot(activity.token);
         verify(organizer, times(1)).onBackPressedOnTaskRoot(any());
+
+        // Disable intercepting back
+        mWm.mAtmService.mTaskOrganizerController.setInterceptBackPressedOnTaskRoot(
+                stack.mRemoteToken.toWindowContainerToken(), false);
+
+        // Verify now that the back press no longer calls the organizer
+        mWm.mAtmService.onBackPressedOnTaskRoot(activity.token);
+        verify(organizer, times(1)).onBackPressedOnTaskRoot(any());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 9603d28..3106ca2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -661,14 +661,14 @@
         RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
         when(recentsController.shouldApplyInputConsumer(win0.mActivityRecord)).thenReturn(true);
         mWm.setRecentsAnimationController(recentsController);
-        assertTrue(win0.cantReceiveTouchInput());
+        assertFalse(win0.canReceiveTouchInput());
     }
 
     @Test
     public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
         final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
         win0.mActivityRecord.mVisibleRequested = false;
-        assertTrue(win0.cantReceiveTouchInput());
+        assertFalse(win0.canReceiveTouchInput());
     }
 
     @Test
@@ -676,7 +676,7 @@
         final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
         win0.mActivityRecord.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         win0.mActivityRecord.getStack().setFocusable(false);
-        assertTrue(win0.cantReceiveTouchInput());
+        assertFalse(win0.canReceiveTouchInput());
     }
 
     @UseTestDisplay(addWindows = W_ACTIVITY)
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 81aad97..f151d9c 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -183,6 +183,7 @@
     private static class ActivityData {
         private final String mTaskRootPackage;
         private final String mTaskRootClass;
+        public int lastEvent = Event.NONE;
         private ActivityData(String taskRootPackage, String taskRootClass) {
             mTaskRootPackage = taskRootPackage;
             mTaskRootClass = taskRootClass;
@@ -785,6 +786,7 @@
         switch (event.mEventType) {
             case Event.ACTIVITY_RESUMED:
             case Event.ACTIVITY_PAUSED:
+            case Event.ACTIVITY_STOPPED:
                 uid = mPackageManagerInternal.getPackageUid(event.mPackage, 0, userId);
                 break;
             default:
@@ -817,8 +819,10 @@
                                     .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_FOREGROUND);
                     // check if this activity has already been resumed
                     if (mVisibleActivities.get(event.mInstanceId) != null) break;
-                    mVisibleActivities.put(event.mInstanceId,
-                            new ActivityData(event.mTaskRootPackage, event.mTaskRootClass));
+                    final ActivityData resumedData = new ActivityData(event.mTaskRootPackage,
+                            event.mTaskRootClass);
+                    resumedData.lastEvent = Event.ACTIVITY_RESUMED;
+                    mVisibleActivities.put(event.mInstanceId, resumedData);
                     try {
                         switch(mUsageSource) {
                             case USAGE_SOURCE_CURRENT_ACTIVITY:
@@ -834,16 +838,17 @@
                     }
                     break;
                 case Event.ACTIVITY_PAUSED:
-                    if (event.mTaskRootPackage == null) {
-                        // Task Root info is missing. Repair the event based on previous data
-                        final ActivityData prevData = mVisibleActivities.get(event.mInstanceId);
-                        if (prevData == null) {
-                            Slog.w(TAG, "Unexpected activity event reported! (" + event.mPackage
-                                    + "/" + event.mClass + " event : " + event.mEventType
-                                    + " instanceId : " + event.mInstanceId + ")");
-                        } else {
-                            event.mTaskRootPackage = prevData.mTaskRootPackage;
-                            event.mTaskRootClass = prevData.mTaskRootClass;
+                    final ActivityData pausedData = mVisibleActivities.get(event.mInstanceId);
+                    if (pausedData == null) {
+                        Slog.w(TAG, "Unexpected activity event reported! (" + event.mPackage
+                                + "/" + event.mClass + " event : " + event.mEventType
+                                + " instanceId : " + event.mInstanceId + ")");
+                    } else {
+                        pausedData.lastEvent = Event.ACTIVITY_PAUSED;
+                        if (event.mTaskRootPackage == null) {
+                            // Task Root info is missing. Repair the event based on previous data
+                            event.mTaskRootPackage = pausedData.mTaskRootPackage;
+                            event.mTaskRootClass = pausedData.mTaskRootClass;
                         }
                     }
                     FrameworkStatsLog.write(
@@ -866,6 +871,16 @@
                         return;
                     }
 
+                    if (prevData.lastEvent != Event.ACTIVITY_PAUSED) {
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED,
+                                uid,
+                                event.mPackage,
+                                event.mClass,
+                                FrameworkStatsLog
+                                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_BACKGROUND);
+                    }
+
                     ArraySet<String> tokens;
                     synchronized (mUsageReporters) {
                         tokens = mUsageReporters.removeReturnOld(event.mInstanceId);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a229efb..470d4be 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1678,6 +1678,15 @@
             "hide_lte_plus_data_icon_bool";
 
     /**
+     * The combined channel bandwidth threshold (non-inclusive) in KHz required to display the
+     * LTE+ data icon. It is 20000 by default, meaning the LTE+ icon will be shown if the device is
+     * using carrier aggregation and the combined channel bandwidth is strictly greater than 20 MHz.
+     * @hide
+     */
+    public static final String KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT =
+            "lte_plus_threshold_bandwidth_khz_int";
+
+    /**
      * The string is used to filter redundant string from PLMN Network Name that's supplied by
      * specific carrier.
      *
@@ -4259,6 +4268,7 @@
         sDefaults.putString(KEY_OPERATOR_NAME_FILTER_PATTERN_STRING, "");
         sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, "");
         sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
+        sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
         sDefaults.putBoolean(KEY_NR_ENABLED_BOOL, true);
         sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_TDSCDMA_BOOL, false);
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 4f5a305..7dd003e 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -15,6 +15,7 @@
 android_test {
     name: "RollbackTest",
     manifest: "RollbackTest/AndroidManifest.xml",
+    platform_apis: true,
     srcs: ["RollbackTest/src/**/*.java"],
     static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
     test_suites: ["general-tests"],
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index de51c5c..0db2b2a 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -175,7 +175,7 @@
             assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
 
             UserManager um = (UserManager) context.getSystemService(context.USER_SERVICE);
-            List<Integer> userIds = um.getUsers(true)
+            List<Integer> userIds = um.getAliveUsers()
                     .stream().map(user -> user.id).collect(Collectors.toList());
             assertThat(InstallUtils.isOnlyInstalledForUser(TestApp.A,
                     context.getUserId(), userIds)).isTrue();
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index aa3a139..9302f78 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -81,6 +81,12 @@
     public String capabilities;
 
     /**
+     * The interface name on which the scan result was received.
+     * @hide
+     */
+    public String ifaceName;
+
+    /**
      * @hide
      * No security protocol.
      */
@@ -939,6 +945,7 @@
             flags = source.flags;
             radioChainInfos = source.radioChainInfos;
             this.mWifiStandard = source.mWifiStandard;
+            this.ifaceName = source.ifaceName;
         }
     }
 
@@ -977,6 +984,7 @@
         sb.append(", 80211mcResponder: ");
         sb.append(((flags & FLAG_80211mc_RESPONDER) != 0) ? "is supported" : "is not supported");
         sb.append(", Radio Chain Infos: ").append(Arrays.toString(radioChainInfos));
+        sb.append(", interface name: ").append(ifaceName);
         return sb.toString();
     }
 
@@ -1056,6 +1064,7 @@
         } else {
             dest.writeInt(0);
         }
+        dest.writeString((ifaceName != null) ? ifaceName.toString() : "");
     }
 
     /** Implement the Parcelable interface */
@@ -1134,6 +1143,7 @@
                         sr.radioChainInfos[i].level = in.readInt();
                     }
                 }
+                sr.ifaceName = in.readString();
                 return sr;
             }
 
diff --git a/wifi/tests/src/android/net/wifi/ScanResultTest.java b/wifi/tests/src/android/net/wifi/ScanResultTest.java
index 5516f43..4a35868 100644
--- a/wifi/tests/src/android/net/wifi/ScanResultTest.java
+++ b/wifi/tests/src/android/net/wifi/ScanResultTest.java
@@ -44,6 +44,7 @@
     public static final long TEST_TSF = 04660l;
     public static final @WifiAnnotations.WifiStandard int TEST_WIFI_STANDARD =
             ScanResult.WIFI_STANDARD_11AC;
+    public static final String TEST_IFACE_NAME = "test_ifname";
 
     /**
      * Frequency to channel map. This include some frequencies used outside the US.
@@ -219,7 +220,7 @@
                 + "passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, "
                 + "standard: 11ac, "
                 + "80211mcResponder: is not supported, "
-                + "Radio Chain Infos: null", scanResult.toString());
+                + "Radio Chain Infos: null, interface name: test_ifname", scanResult.toString());
     }
 
     /**
@@ -242,7 +243,8 @@
                 + "standard: 11ac, "
                 + "80211mcResponder: is not supported, "
                 + "Radio Chain Infos: [RadioChainInfo: id=0, level=-45, "
-                + "RadioChainInfo: id=1, level=-54]", scanResult.toString());
+                + "RadioChainInfo: id=1, level=-54], interface name: test_ifname",
+                scanResult.toString());
     }
 
     /**
@@ -283,6 +285,8 @@
         result.frequency = TEST_FREQUENCY;
         result.timestamp = TEST_TSF;
         result.setWifiStandard(TEST_WIFI_STANDARD);
+        result.ifaceName = TEST_IFACE_NAME;
+
         return result;
     }