Merge "Do not use shared lib resources" into sc-v2-dev
diff --git a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
index a630873..e1c13f7 100644
--- a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
+++ b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
@@ -29,4 +29,6 @@
         in String callingPackage,
         in IFindDeviceCallback findCallback,
         in AndroidFuture<Association> serviceCallback);
+
+    void onAssociationCreated();
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 42b4380..20515e7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -516,12 +516,8 @@
                 // Skipping containers that do not have any activities to report.
                 continue;
             }
-            ActivityStack primaryContainer =
-                    new ActivityStack(
-                            container.getPrimaryContainer().collectActivities());
-            ActivityStack secondaryContainer =
-                    new ActivityStack(
-                            container.getSecondaryContainer().collectActivities());
+            ActivityStack primaryContainer = container.getPrimaryContainer().toActivityStack();
+            ActivityStack secondaryContainer = container.getSecondaryContainer().toActivityStack();
             SplitInfo splitState = new SplitInfo(primaryContainer,
                     secondaryContainer,
                     // Splits that are not showing side-by-side are reported as having 0 split
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 54e44a7..80d9c2c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -109,6 +109,10 @@
         return allActivities;
     }
 
+    ActivityStack toActivityStack() {
+        return new ActivityStack(collectActivities(), mInfo.getRunningActivityCount() == 0);
+    }
+
     void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
         mPendingAppearedActivities.add(pendingAppearedActivity);
     }
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 4f36c9c..830d13d 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index b4cafd8..edbfd2a 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -254,8 +254,14 @@
         Log.i(LOG_TAG, "onDeviceConfirmed(selectedDevice = " + selectedDevice + ")");
         getService().onDeviceSelected(
                 getCallingPackage(), getDeviceMacAddress(selectedDevice.device));
+    }
+
+    void setResultAndFinish() {
+        Log.i(LOG_TAG, "setResultAndFinish(selectedDevice = "
+                + getService().mSelectedDevice.device + ")");
         setResult(RESULT_OK,
-                new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice.device));
+                new Intent().putExtra(
+                        CompanionDeviceManager.EXTRA_DEVICE, getService().mSelectedDevice.device));
         finish();
     }
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index c24782e..5df8e3c 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -117,6 +117,11 @@
                     CompanionDeviceDiscoveryService::startDiscovery,
                     CompanionDeviceDiscoveryService.this, request));
         }
+
+        @Override
+        public void onAssociationCreated() {
+            Handler.getMain().post(CompanionDeviceDiscoveryService.this::onAssociationCreated);
+        }
     };
 
     private ScanCallback mBLEScanCallback;
@@ -222,6 +227,11 @@
                 SCAN_TIMEOUT);
     }
 
+    @MainThread
+    private void onAssociationCreated() {
+        mActivity.setResultAndFinish();
+    }
+
     private boolean shouldScan(List<? extends DeviceFilter> mediumSpecificFilters) {
         return !isEmpty(mediumSpecificFilters) || isEmpty(mFilters);
     }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c6cca5a..721b432 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -52,12 +52,18 @@
 
 filegroup {
     name: "ReleaseJavaFiles",
-    srcs: ["src/com/android/systemui/flags/FeatureFlagManager.java"],
+    srcs: [
+        "src-release/**/*.kt",
+        "src-release/**/*.java",
+    ],
 }
 
 filegroup {
     name: "DebugJavaFiles",
-    srcs: ["src-debug/com/android/systemui/flags/FeatureFlagManager.java"],
+    srcs: [
+        "src-debug/**/*.kt",
+        "src-debug/**/*.java",
+    ],
 }
 
 android_library {
@@ -66,6 +72,8 @@
         "src/**/*.kt",
         "src/**/*.java",
         "src/**/I*.aidl",
+        "src-release/**/*.kt",
+        "src-release/**/*.java",
     ],
     product_variables: {
         debuggable: {
@@ -159,6 +167,8 @@
         "src/**/*.kt",
         "src/**/*.java",
         "src/**/I*.aidl",
+        "src-release/**/*.kt",
+        "src-release/**/*.java",
     ],
     static_libs: [
         "SystemUIAnimationLib",
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_call_chip.xml
index 5389d9b..c949ba0 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_call_chip.xml
@@ -21,6 +21,7 @@
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:layout_gravity="center_vertical|start"
+    android:layout_marginStart="5dp"
 >
     <LinearLayout
         android:id="@+id/ongoing_call_chip_background"
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index ab159e1..040df86 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -32,4 +32,6 @@
 
     <!-- Notifications are sized to match the width of two (of 4) qs tiles in landscape. -->
     <bool name="config_skinnyNotifsInLandscape">false</bool>
+
+    <dimen name="keyguard_indication_margin_bottom">25dp</dimen>
 </resources>
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
index 3a8ee29..1eeb516 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
@@ -75,6 +75,7 @@
     }
 
     /** Return a {@link BooleanFlag}'s value. */
+    @Override
     public boolean isEnabled(int id, boolean defaultValue) {
         if (!mBooleanFlagCache.containsKey(id)) {
             Boolean result = isEnabledInternal(id);
@@ -105,6 +106,7 @@
     }
 
     /** Set whether a given {@link BooleanFlag} is enabled or not. */
+    @Override
     public void setEnabled(int id, boolean value) {
         Boolean currentValue = isEnabledInternal(id);
         if (currentValue != null && currentValue == value) {
@@ -136,8 +138,10 @@
         Log.i(TAG, "Erase id " + id);
     }
 
+    @Override
     public void addListener(Listener run) {}
 
+    @Override
     public void removeListener(Listener run) {}
 
     private void restartSystemUI() {
@@ -198,6 +202,7 @@
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("can override: true");
         ArrayList<String> flagStrings = new ArrayList<>(mBooleanFlagCache.size());
         for (Map.Entry<Integer, Boolean> entry : mBooleanFlagCache.entrySet()) {
             flagStrings.add("  sysui_flag_" + entry.getKey() + ": " + entry.getValue());
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
rename to packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java
index 78f0b5f..e501a07 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.flags;
 
+import android.content.Context;
 import android.util.SparseBooleanArray;
 
 import androidx.annotation.NonNull;
@@ -39,21 +40,21 @@
 public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
     SparseBooleanArray mAccessedFlags = new SparseBooleanArray();
     @Inject
-    public FeatureFlagManager(DumpManager dumpManager) {
+    public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context,
+            DumpManager dumpManager) {
         dumpManager.registerDumpable("SysUIFlags", this);
     }
-    public boolean isEnabled(String key, boolean defaultValue) {
-        return defaultValue;
-    }
+    @Override
     public boolean isEnabled(int key, boolean defaultValue) {
         mAccessedFlags.append(key, defaultValue);
         return defaultValue;
     }
-    public void setEnabled(String key, boolean value) {}
+    @Override
     public void setEnabled(int key, boolean value) {}
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("can override: false");
         int size = mAccessedFlags.size();
         for (int i = 0; i < size; i++) {
             pw.println("  sysui_flag_" + mAccessedFlags.keyAt(i)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index 947a39a..48bb281 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 import android.util.FeatureFlagUtils;
+import android.util.Log;
+import android.widget.Toast;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
@@ -86,6 +88,22 @@
         }
     }
 
+    public void assertLegacyPipelineEnabled() {
+        if (isNewNotifPipelineRenderingEnabled()) {
+            throw new IllegalStateException("Old pipeline code running w/ new pipeline enabled");
+        }
+    }
+
+    public boolean checkLegacyPipelineEnabled() {
+        if (!isNewNotifPipelineRenderingEnabled()) {
+            return true;
+        }
+        Log.d("NotifPipeline", "Old pipeline code running w/ new pipeline enabled",
+                new Exception());
+        Toast.makeText(mContext, "Old pipeline code running!", Toast.LENGTH_SHORT).show();
+        return false;
+    }
+
     public boolean isNewNotifPipelineEnabled() {
         return isEnabled(Flags.NEW_NOTIFICATION_PIPELINE);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 4f87cad..98b9146 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -72,12 +72,6 @@
     private var listening: Boolean = false
 
     var expanded = false
-        set(value) {
-            if (field != value) {
-                field = value
-                updateView()
-            }
-        }
 
     private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button)
     private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container)
@@ -176,8 +170,7 @@
     }
 
     private fun updateView() {
-        mView.updateEverything(buttonsVisible(), isTunerEnabled(),
-                multiUserSwitchController.isMultiUserEnabled)
+        mView.updateEverything(isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
     }
 
     override fun onViewDetached() {
@@ -191,14 +184,14 @@
         this.listening = listening
         if (this.listening) {
             userInfoController.addCallback(onUserInfoChangedListener)
+            updateView()
         } else {
             userInfoController.removeCallback(onUserInfoChangedListener)
         }
     }
 
     fun disable(state2: Int) {
-        mView.disable(buttonsVisible(), state2, isTunerEnabled(),
-                multiUserSwitchController.isMultiUserEnabled)
+        mView.disable(state2, isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
     }
 
     fun setExpansion(headerExpansionFraction: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
index 941e54a..f81f7bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
@@ -107,7 +107,6 @@
     }
 
     fun disable(
-        buttonsVisible: Boolean,
         state2: Int,
         isTunerEnabled: Boolean,
         multiUserEnabled: Boolean
@@ -115,16 +114,15 @@
         val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
         if (disabled == qsDisabled) return
         qsDisabled = disabled
-        updateEverything(buttonsVisible, isTunerEnabled, multiUserEnabled)
+        updateEverything(isTunerEnabled, multiUserEnabled)
     }
 
     fun updateEverything(
-        buttonsVisible: Boolean,
         isTunerEnabled: Boolean,
         multiUserEnabled: Boolean
     ) {
         post {
-            updateVisibilities(buttonsVisible, isTunerEnabled, multiUserEnabled)
+            updateVisibilities(isTunerEnabled, multiUserEnabled)
             updateClickabilities()
             isClickable = false
         }
@@ -137,15 +135,14 @@
     }
 
     private fun updateVisibilities(
-        buttonsVisible: Boolean,
         isTunerEnabled: Boolean,
         multiUserEnabled: Boolean
     ) {
         settingsContainer.visibility = if (qsDisabled) GONE else VISIBLE
         tunerIcon.visibility = if (isTunerEnabled) VISIBLE else INVISIBLE
-        multiUserSwitch.visibility = if (buttonsVisible && multiUserEnabled) VISIBLE else GONE
+        multiUserSwitch.visibility = if (multiUserEnabled) VISIBLE else GONE
         val isDemo = UserManager.isDeviceInDemoMode(context)
-        settingsButton.visibility = if (isDemo || !buttonsVisible) INVISIBLE else VISIBLE
+        settingsButton.visibility = if (isDemo) INVISIBLE else VISIBLE
     }
 
     fun onUserInfoChanged(picture: Drawable?, isGuestUser: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 7e5ff8a..1784f73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -112,6 +112,16 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
+        // Pass configuration change to non-attached pages as well. Some config changes will cause
+        // QS to recreate itself (as determined in FragmentHostManager), but in order to minimize
+        // those, make sure that all get passed to all pages.
+        int numPages = mPages.size();
+        for (int i = 0; i < numPages; i++) {
+            View page = mPages.get(i);
+            if (page.getParent() == null) {
+                page.dispatchConfigurationChanged(newConfig);
+            }
+        }
         if (mLayoutOrientation != newConfig.orientation) {
             mLayoutOrientation = newConfig.orientation;
             mDistributeTiles = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 396d86b..464b2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -27,6 +27,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
@@ -72,6 +73,7 @@
 
     // Dependencies:
     private final DynamicChildBindController mDynamicChildBindController;
+    private final FeatureFlags mFeatureFlags;
     protected final NotificationLockscreenUserManager mLockscreenUserManager;
     protected final NotificationGroupManagerLegacy mGroupManager;
     protected final VisualStabilityManager mVisualStabilityManager;
@@ -107,6 +109,7 @@
     public NotificationViewHierarchyManager(
             Context context,
             @Main Handler mainHandler,
+            FeatureFlags featureFlags,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             NotificationGroupManagerLegacy groupManager,
             VisualStabilityManager visualStabilityManager,
@@ -121,6 +124,7 @@
             AssistantFeedbackController assistantFeedbackController) {
         mContext = context;
         mHandler = mainHandler;
+        mFeatureFlags = featureFlags;
         mLockscreenUserManager = notificationLockscreenUserManager;
         mBypassController = bypassController;
         mGroupManager = groupManager;
@@ -142,7 +146,9 @@
             NotificationListContainer listContainer) {
         mPresenter = presenter;
         mListContainer = listContainer;
-        mDynamicPrivacyController.addListener(this);
+        if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mDynamicPrivacyController.addListener(this);
+        }
     }
 
     /**
@@ -151,6 +157,10 @@
     //TODO: Rewrite this to focus on Entries, or some other data object instead of views
     public void updateNotificationViews() {
         Assert.isMainThread();
+        if (!mFeatureFlags.checkLegacyPipelineEnabled()) {
+            return;
+        }
+
         beginUpdate();
 
         List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
@@ -425,6 +435,10 @@
      */
     public void updateRowStates() {
         Assert.isMainThread();
+        if (!mFeatureFlags.checkLegacyPipelineEnabled()) {
+            return;
+        }
+
         beginUpdate();
         updateRowStatesInternal();
         endUpdate();
@@ -510,6 +524,7 @@
 
     @Override
     public void onDynamicPrivacyChanged() {
+        mFeatureFlags.assertLegacyPipelineEnabled();
         if (mPerformingUpdate) {
             Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call");
         }
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 d297d95..1c9174a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -184,6 +184,7 @@
     static NotificationViewHierarchyManager provideNotificationViewHierarchyManager(
             Context context,
             @Main Handler mainHandler,
+            FeatureFlags featureFlags,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             NotificationGroupManagerLegacy groupManager,
             VisualStabilityManager visualStabilityManager,
@@ -199,6 +200,7 @@
         return new NotificationViewHierarchyManager(
                 context,
                 mainHandler,
+                featureFlags,
                 notificationLockscreenUserManager,
                 groupManager,
                 visualStabilityManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index dfdc548..b36b7c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -48,6 +48,7 @@
 import android.annotation.UserIdInt;
 import android.app.Notification;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
@@ -512,6 +513,7 @@
     }
 
     private void dispatchEventsAndRebuildList() {
+        Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList");
         mAmDispatchingToOtherCode = true;
         while (!mEventQueue.isEmpty()) {
             mEventQueue.remove().dispatchTo(mNotifCollectionListeners);
@@ -521,6 +523,7 @@
         if (mBuildListener != null) {
             mBuildListener.onBuildList(mReadOnlyNotificationSet);
         }
+        Trace.endSection();
     }
 
     private void onEndLifetimeExtension(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index 47939f0..5777925 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
@@ -216,6 +217,11 @@
         mShadeListBuilder.addOnBeforeRenderListListener(listener);
     }
 
+    /** Registers an invalidator that can be used to invalidate the entire notif list. */
+    public void addPreRenderInvalidator(Invalidator invalidator) {
+        mShadeListBuilder.addPreRenderInvalidator(invalidator);
+    }
+
     /**
      * Returns a read-only view in to the current shade list, i.e. the list of notifications that
      * are currently present in the shade. If this method is called during pipeline execution it
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 3730524..6d38389 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -30,6 +30,7 @@
 
 import android.annotation.MainThread;
 import android.annotation.Nullable;
+import android.os.Trace;
 import android.util.ArrayMap;
 
 import androidx.annotation.NonNull;
@@ -45,6 +46,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState;
 import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
@@ -174,6 +176,13 @@
         mOnBeforeRenderListListeners.add(listener);
     }
 
+    void addPreRenderInvalidator(Invalidator invalidator) {
+        Assert.isMainThread();
+
+        mPipelineState.requireState(STATE_IDLE);
+        invalidator.setInvalidationListener(this::onPreRenderInvalidated);
+    }
+
     void addPreGroupFilter(NotifFilter filter) {
         Assert.isMainThread();
         mPipelineState.requireState(STATE_IDLE);
@@ -256,6 +265,14 @@
                 }
             };
 
+    private void onPreRenderInvalidated(Invalidator invalidator) {
+        Assert.isMainThread();
+
+        mLogger.logPreRenderInvalidated(invalidator.getName(), mPipelineState.getState());
+
+        rebuildListIfBefore(STATE_FINALIZING);
+    }
+
     private void onPreGroupFilterInvalidated(NotifFilter filter) {
         Assert.isMainThread();
 
@@ -316,6 +333,7 @@
      * if we detect that behavior, we should crash instantly.
      */
     private void buildList() {
+        Trace.beginSection("ShadeListBuilder.buildList");
         mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
         mPipelineState.setState(STATE_BUILD_STARTED);
 
@@ -369,9 +387,11 @@
 
         // Step 8: Dispatch the new list, first to any listeners and then to the view layer
         dispatchOnBeforeRenderList(mReadOnlyNotifList);
+        Trace.beginSection("ShadeListBuilder.onRenderList");
         if (mOnRenderListListener != null) {
             mOnRenderListListener.onRenderList(mReadOnlyNotifList);
         }
+        Trace.endSection();
 
         // Step 9: We're done!
         mLogger.logEndBuildList(
@@ -383,9 +403,11 @@
         }
         mPipelineState.setState(STATE_IDLE);
         mIterationCount++;
+        Trace.endSection();
     }
 
     private void notifySectionEntriesUpdated() {
+        Trace.beginSection("ShadeListBuilder.notifySectionEntriesUpdated");
         NotifSection currentSection = null;
         mTempSectionMembers.clear();
         for (int i = 0; i < mNotifList.size(); i++) {
@@ -399,6 +421,7 @@
             }
             mTempSectionMembers.add(currentEntry);
         }
+        Trace.endSection();
     }
 
     /**
@@ -440,6 +463,7 @@
             Collection<? extends ListEntry> entries,
             List<ListEntry> out,
             List<NotifFilter> filters) {
+        Trace.beginSection("ShadeListBuilder.filterNotifs");
         final long now = mSystemClock.uptimeMillis();
         for (ListEntry entry : entries)  {
             if (entry instanceof GroupEntry) {
@@ -471,9 +495,11 @@
                 }
             }
         }
+        Trace.endSection();
     }
 
     private void groupNotifs(List<ListEntry> entries, List<ListEntry> out) {
+        Trace.beginSection("ShadeListBuilder.groupNotifs");
         for (ListEntry listEntry : entries) {
             // since grouping hasn't happened yet, all notifs are NotificationEntries
             NotificationEntry entry = (NotificationEntry) listEntry;
@@ -529,12 +555,14 @@
                 }
             }
         }
+        Trace.endSection();
     }
 
     private void stabilizeGroupingNotifs(List<ListEntry> topLevelList) {
         if (mNotifStabilityManager == null) {
             return;
         }
+        Trace.beginSection("ShadeListBuilder.stabilizeGroupingNotifs");
 
         for (int i = 0; i < topLevelList.size(); i++) {
             final ListEntry tle = topLevelList.get(i);
@@ -560,6 +588,7 @@
                 }
             }
         }
+        Trace.endSection();
     }
 
     /**
@@ -592,6 +621,7 @@
     }
 
     private void promoteNotifs(List<ListEntry> list) {
+        Trace.beginSection("ShadeListBuilder.promoteNotifs");
         for (int i = 0; i < list.size(); i++) {
             final ListEntry tle = list.get(i);
 
@@ -610,9 +640,11 @@
                 });
             }
         }
+        Trace.endSection();
     }
 
     private void pruneIncompleteGroups(List<ListEntry> shadeList) {
+        Trace.beginSection("ShadeListBuilder.pruneIncompleteGroups");
         for (int i = 0; i < shadeList.size(); i++) {
             final ListEntry tle = shadeList.get(i);
 
@@ -667,6 +699,7 @@
                 }
             }
         }
+        Trace.endSection();
     }
 
     /**
@@ -733,6 +766,7 @@
     }
 
     private void sortListAndNotifySections() {
+        Trace.beginSection("ShadeListBuilder.sortListAndNotifySections");
         // Assign sections to top-level elements and sort their children
         for (ListEntry entry : mNotifList) {
             NotifSection section = applySections(entry);
@@ -750,6 +784,7 @@
 
         // notify sections since the list is sorted now
         notifySectionEntriesUpdated();
+        Trace.endSection();
     }
 
     private void freeEmptyGroups() {
@@ -1000,27 +1035,35 @@
     }
 
     private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
+        Trace.beginSection("ShadeListBuilder.dispatchOnBeforeTransformGroups");
         for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
             mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
         }
+        Trace.endSection();
     }
 
     private void dispatchOnBeforeSort(List<ListEntry> entries) {
+        Trace.beginSection("ShadeListBuilder.dispatchOnBeforeSort");
         for (int i = 0; i < mOnBeforeSortListeners.size(); i++) {
             mOnBeforeSortListeners.get(i).onBeforeSort(entries);
         }
+        Trace.endSection();
     }
 
     private void dispatchOnBeforeFinalizeFilter(List<ListEntry> entries) {
+        Trace.beginSection("ShadeListBuilder.dispatchOnBeforeFinalizeFilter");
         for (int i = 0; i < mOnBeforeFinalizeFilterListeners.size(); i++) {
             mOnBeforeFinalizeFilterListeners.get(i).onBeforeFinalizeFilter(entries);
         }
+        Trace.endSection();
     }
 
     private void dispatchOnBeforeRenderList(List<ListEntry> entries) {
+        Trace.beginSection("ShadeListBuilder.dispatchOnBeforeRenderList");
         for (int i = 0; i < mOnBeforeRenderListListeners.size(); i++) {
             mOnBeforeRenderListListeners.get(i).onBeforeRenderList(entries);
         }
+        Trace.endSection();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
index 301b185..3a39c39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
@@ -23,11 +23,11 @@
 
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 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.dagger.CoordinatorScope;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
@@ -48,7 +48,7 @@
  *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
  *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
  */
-@SysUISingleton
+@CoordinatorScope
 public class AppOpsCoordinator implements Coordinator {
     private static final String TAG = "AppOpsCoordinator";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 29a030f..15f0d88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -16,10 +16,10 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
-import com.android.systemui.dagger.SysUISingleton;
 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.coordinator.dagger.CoordinatorScope;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
@@ -53,7 +53,7 @@
  * respond to app-cancellations (ie: remove the bubble if the app cancels the notification).
  *
  */
-@SysUISingleton
+@CoordinatorScope
 public class BubbleCoordinator implements Coordinator {
     private static final String TAG = "BubbleCoordinator";
 
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 c385836..e59f4a6 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
@@ -16,10 +16,10 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.render.NodeController
@@ -34,7 +34,7 @@
  * - Elevates important conversation notifications
  * - Puts conversations into its own people section. @see [NotifCoordinators] for section ordering.
  */
-@SysUISingleton
+@CoordinatorScope
 class ConversationCoordinator @Inject constructor(
     private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
     @PeopleHeader peopleHeaderController: NodeController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 47928b4..e865249 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -23,9 +23,9 @@
 import android.os.RemoteException;
 import android.service.notification.StatusBarNotification;
 
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
@@ -36,7 +36,7 @@
  * Special notifications with extra permissions and tags won't be filtered out even when the
  * device is unprovisioned.
  */
-@SysUISingleton
+@CoordinatorScope
 public class DeviceProvisionedCoordinator implements Coordinator {
     private static final String TAG = "DeviceProvisionedCoordinator";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
index 8948969..dbecf1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
@@ -17,17 +17,17 @@
 
 import android.util.ArraySet
 import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 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.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
-import com.android.systemui.statusbar.notification.row.NotificationGuts
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager
+import com.android.systemui.statusbar.notification.row.NotificationGuts
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -38,7 +38,7 @@
  * Coordinates the guts displayed by the [NotificationGutsManager] with the pipeline.
  * Specifically, this just adds the lifetime extension necessary to keep guts from disappearing.
  */
-@SysUISingleton
+@CoordinatorScope
 class GutsCoordinator @Inject constructor(
     private val notifGutsViewManager: NotifGutsViewManager,
     private val logger: GutsCoordinatorLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
index 8c8a8a9..f8b4274 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
@@ -22,11 +22,11 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 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.dagger.CoordinatorScope;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
@@ -57,7 +57,7 @@
  *
  * Note: The inflation callback in {@link PreparationCoordinator} handles showing HUNs.
  */
-@SysUISingleton
+@CoordinatorScope
 public class HeadsUpCoordinator implements Coordinator {
     private static final String TAG = "HeadsUpCoordinator";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java
index 0059e7b..6684237 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java
@@ -20,13 +20,21 @@
 
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 
+import javax.inject.Inject;
+
 /**
  * Filters out notifications that have been dismissed locally (by the user) but that system server
  * hasn't yet confirmed the removal of.
  */
+@CoordinatorScope
 public class HideLocallyDismissedNotifsCoordinator implements Coordinator {
+
+    @Inject
+    HideLocallyDismissedNotifsCoordinator() { }
+
     @Override
     public void attach(NotifPipeline pipeline) {
         pipeline.addPreGroupFilter(mFilter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
index e595dd4..7b5cf85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 
 import javax.inject.Inject;
@@ -37,6 +38,7 @@
  * TODO: The NotificationLockscreenUserManager currently maintains the list of active user profiles.
  *  We should spin that off into a standalone section at some point.
  */
+@CoordinatorScope
 public class HideNotifsForOtherUsersCoordinator implements Coordinator {
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final SharedCoordinatorLogger mLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 23d5369..fe1cd7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -34,13 +34,13 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 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.dagger.CoordinatorScope;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -50,7 +50,7 @@
 /**
  * Filters low priority and privacy-sensitive notifications from the lockscreen.
  */
-@SysUISingleton
+@CoordinatorScope
 public class KeyguardCoordinator implements Coordinator {
     private static final String TAG = "KeyguardCoordinator";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 026a3ff..8769969 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -21,6 +21,7 @@
 import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 
 import javax.inject.Inject;
@@ -28,6 +29,7 @@
 /**
  * Coordinates hiding (filtering) of media notifications.
  */
+@CoordinatorScope
 public class MediaCoordinator implements Coordinator {
     private static final String TAG = "MediaCoordinator";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
deleted file mode 100644
index 9305900..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.statusbar.notification.collection.coordinator;
-
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Handles the attachment of {@link Coordinator}s to the {@link NotifPipeline} so that the
- * Coordinators can register their respective callbacks.
- */
-@SysUISingleton
-public class NotifCoordinators implements Dumpable {
-    private static final String TAG = "NotifCoordinators";
-    private final List<Coordinator> mCoordinators = new ArrayList<>();
-    private final List<NotifSectioner> mOrderedSections = new ArrayList<>();
-
-    /**
-     * Creates all the coordinators.
-     */
-    @Inject
-    public NotifCoordinators(
-            DumpManager dumpManager,
-            FeatureFlags featureFlags,
-            HideNotifsForOtherUsersCoordinator hideNotifsForOtherUsersCoordinator,
-            KeyguardCoordinator keyguardCoordinator,
-            RankingCoordinator rankingCoordinator,
-            AppOpsCoordinator appOpsCoordinator,
-            DeviceProvisionedCoordinator deviceProvisionedCoordinator,
-            BubbleCoordinator bubbleCoordinator,
-            HeadsUpCoordinator headsUpCoordinator,
-            GutsCoordinator gutsCoordinator,
-            ConversationCoordinator conversationCoordinator,
-            PreparationCoordinator preparationCoordinator,
-            MediaCoordinator mediaCoordinator,
-            ShadeEventCoordinator shadeEventCoordinator,
-            SmartspaceDedupingCoordinator smartspaceDedupingCoordinator,
-            ViewConfigCoordinator viewConfigCoordinator,
-            VisualStabilityCoordinator visualStabilityCoordinator) {
-        dumpManager.registerDumpable(TAG, this);
-
-        mCoordinators.add(new HideLocallyDismissedNotifsCoordinator());
-        mCoordinators.add(hideNotifsForOtherUsersCoordinator);
-        mCoordinators.add(keyguardCoordinator);
-        mCoordinators.add(rankingCoordinator);
-        mCoordinators.add(appOpsCoordinator);
-        mCoordinators.add(deviceProvisionedCoordinator);
-        mCoordinators.add(bubbleCoordinator);
-        mCoordinators.add(conversationCoordinator);
-        mCoordinators.add(mediaCoordinator);
-        mCoordinators.add(shadeEventCoordinator);
-        mCoordinators.add(viewConfigCoordinator);
-        mCoordinators.add(visualStabilityCoordinator);
-
-        if (featureFlags.isSmartspaceDedupingEnabled()) {
-            mCoordinators.add(smartspaceDedupingCoordinator);
-        }
-
-        if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
-            mCoordinators.add(headsUpCoordinator);
-            mCoordinators.add(gutsCoordinator);
-            mCoordinators.add(preparationCoordinator);
-        }
-
-        // Manually add Ordered Sections
-        // HeadsUp > FGS > People > Alerting > Silent > Unknown/Default
-        if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
-            mOrderedSections.add(headsUpCoordinator.getSectioner()); // HeadsUp
-        }
-        mOrderedSections.add(appOpsCoordinator.getSectioner()); // ForegroundService
-        mOrderedSections.add(conversationCoordinator.getSectioner()); // People
-        mOrderedSections.add(rankingCoordinator.getAlertingSectioner()); // Alerting
-        mOrderedSections.add(rankingCoordinator.getSilentSectioner()); // Silent
-    }
-
-    /**
-     * Sends the pipeline to each coordinator when the pipeline is ready to accept
-     * {@link Pluggable}s, {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s.
-     */
-    public void attach(NotifPipeline pipeline) {
-        for (Coordinator c : mCoordinators) {
-            c.attach(pipeline);
-        }
-
-        pipeline.setSections(mOrderedSections);
-    }
-
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println();
-        pw.println(TAG + ":");
-        for (Coordinator c : mCoordinators) {
-            pw.println("\t" + c.getClass());
-        }
-
-        for (NotifSectioner s : mOrderedSections) {
-            pw.println("\t" + s.getName());
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
new file mode 100644
index 0000000..66290bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.ArrayList
+import javax.inject.Inject
+
+/**
+ * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the
+ * Coordinators can register their respective callbacks.
+ */
+interface NotifCoordinators : Coordinator, Dumpable
+
+@CoordinatorScope
+class NotifCoordinatorsImpl @Inject constructor(
+    dumpManager: DumpManager,
+    featureFlags: FeatureFlags,
+    hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+    hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+    keyguardCoordinator: KeyguardCoordinator,
+    rankingCoordinator: RankingCoordinator,
+    appOpsCoordinator: AppOpsCoordinator,
+    deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+    bubbleCoordinator: BubbleCoordinator,
+    headsUpCoordinator: HeadsUpCoordinator,
+    gutsCoordinator: GutsCoordinator,
+    conversationCoordinator: ConversationCoordinator,
+    preparationCoordinator: PreparationCoordinator,
+    mediaCoordinator: MediaCoordinator,
+    shadeEventCoordinator: ShadeEventCoordinator,
+    smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+    viewConfigCoordinator: ViewConfigCoordinator,
+    visualStabilityCoordinator: VisualStabilityCoordinator,
+    sensitiveContentCoordinator: SensitiveContentCoordinator
+) : NotifCoordinators {
+
+    private val mCoordinators: MutableList<Coordinator> = ArrayList()
+    private val mOrderedSections: MutableList<NotifSectioner> = ArrayList()
+
+    /**
+     * Creates all the coordinators.
+     */
+    init {
+        dumpManager.registerDumpable(TAG, this)
+        mCoordinators.add(hideLocallyDismissedNotifsCoordinator)
+        mCoordinators.add(hideNotifsForOtherUsersCoordinator)
+        mCoordinators.add(keyguardCoordinator)
+        mCoordinators.add(rankingCoordinator)
+        mCoordinators.add(appOpsCoordinator)
+        mCoordinators.add(deviceProvisionedCoordinator)
+        mCoordinators.add(bubbleCoordinator)
+        mCoordinators.add(conversationCoordinator)
+        mCoordinators.add(mediaCoordinator)
+        mCoordinators.add(shadeEventCoordinator)
+        mCoordinators.add(viewConfigCoordinator)
+        mCoordinators.add(visualStabilityCoordinator)
+        mCoordinators.add(sensitiveContentCoordinator)
+        if (featureFlags.isSmartspaceDedupingEnabled) {
+            mCoordinators.add(smartspaceDedupingCoordinator)
+        }
+        if (featureFlags.isNewNotifPipelineRenderingEnabled) {
+            mCoordinators.add(headsUpCoordinator)
+            mCoordinators.add(gutsCoordinator)
+            mCoordinators.add(preparationCoordinator)
+        }
+
+        // Manually add Ordered Sections
+        // HeadsUp > FGS > People > Alerting > Silent > Unknown/Default
+        if (featureFlags.isNewNotifPipelineRenderingEnabled) {
+            mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
+        }
+        mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
+        mOrderedSections.add(conversationCoordinator.sectioner) // People
+        mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
+        mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent
+    }
+
+    /**
+     * Sends the pipeline to each coordinator when the pipeline is ready to accept
+     * [Pluggable]s, [NotifCollectionListener]s and [NotifLifetimeExtender]s.
+     */
+    override fun attach(pipeline: NotifPipeline) {
+        for (c in mCoordinators) {
+            c.attach(pipeline)
+        }
+        pipeline.setSections(mOrderedSections)
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
+        pw.println()
+        pw.println("$TAG:")
+        for (c in mCoordinators) {
+            pw.println("\t${c.javaClass}")
+        }
+        for (s in mOrderedSections) {
+            pw.println("\t${s.name}")
+        }
+    }
+
+    companion object {
+        private const val TAG = "NotifCoordinators"
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 31826c7..afdfb3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -57,6 +57,7 @@
  * If a notification was uninflated, this coordinator will filter the notification out from the
  * {@link ShadeListBuilder} until it is inflated.
  */
+// TODO(b/204468557): Move to @CoordinatorScope
 @SysUISingleton
 public class PreparationCoordinator implements Coordinator {
     private static final String TAG = "PreparationCoordinator";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 1a6a63a..2ab2dd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -19,11 +19,11 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 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.dagger.CoordinatorScope;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
@@ -44,7 +44,7 @@
  *  - whether the notification's app is suspended or hiding its notifications
  *  - whether DND settings are hiding notifications from ambient display or the notification list
  */
-@SysUISingleton
+@CoordinatorScope
 public class RankingCoordinator implements Coordinator {
     public static final boolean SHOW_ALL_SECTIONS = false;
     private final StatusBarStateController mStatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
new file mode 100644
index 0000000..a115e04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.os.UserHandle
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.notification.DynamicPrivacyController
+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.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import dagger.Module
+import dagger.Provides
+
+@Module
+object SensitiveContentCoordinatorModule {
+    @Provides
+    @JvmStatic
+    @CoordinatorScope
+    fun provideCoordinator(
+        dynamicPrivacyController: DynamicPrivacyController,
+        lockscreenUserManager: NotificationLockscreenUserManager
+    ): SensitiveContentCoordinator =
+            SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager)
+}
+
+/** Coordinates re-inflation and post-processing of sensitive notification content. */
+interface SensitiveContentCoordinator : Coordinator
+
+private class SensitiveContentCoordinatorImpl(
+    private val dynamicPrivacyController: DynamicPrivacyController,
+    private val lockscreenUserManager: NotificationLockscreenUserManager
+) : Invalidator("SensitiveContentInvalidator"),
+        SensitiveContentCoordinator,
+        DynamicPrivacyController.Listener,
+        OnBeforeRenderListListener {
+
+    override fun attach(pipeline: NotifPipeline) {
+        dynamicPrivacyController.addListener(this)
+        pipeline.addOnBeforeRenderListListener(this)
+        pipeline.addPreRenderInvalidator(this)
+    }
+
+    override fun onDynamicPrivacyChanged(): Unit = invalidateList()
+
+    override fun onBeforeRenderList(entries: List<ListEntry>) {
+        val currentUserId = lockscreenUserManager.currentUserId
+        val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
+        val deviceSensitive = devicePublic &&
+                !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)
+        val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
+        for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
+            val notifUserId = entry.sbn.user.identifier
+            val userLockscreen = devicePublic ||
+                    lockscreenUserManager.isLockscreenPublicMode(notifUserId)
+            val userPublic = when {
+                // if we're not on the lockscreen, we're definitely private
+                !userLockscreen -> false
+                // we are on the lockscreen, so unless we're dynamically unlocked, we're
+                // definitely public
+                !dynamicallyUnlocked -> true
+                // we're dynamically unlocked, but check if the notification needs
+                // a separate challenge if it's from a work profile
+                else -> when (notifUserId) {
+                    currentUserId -> false
+                    UserHandle.USER_ALL -> false
+                    else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
+                }
+            }
+            val needsRedaction = lockscreenUserManager.needsRedaction(entry)
+            val isSensitive = userPublic && needsRedaction
+            entry.setSensitive(isSensitive, deviceSensitive)
+        }
+    }
+}
+
+private fun extractAllRepresentativeEntries(
+    entries: List<ListEntry>
+): Sequence<NotificationEntry> =
+    entries.asSequence().flatMap(::extractAllRepresentativeEntries)
+
+private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> =
+    sequence {
+        listEntry.representativeEntry?.let { yield(it) }
+        if (listEntry is GroupEntry) {
+            yieldAll(extractAllRepresentativeEntries(listEntry.children))
+        }
+    }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
index f9648a0..2d5c331 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
@@ -29,6 +29,7 @@
  * A coordinator which provides callbacks to a view surfaces for various events relevant to the
  * shade, such as when the user removes a notification, or when the shade is emptied.
  */
+// TODO(b/204468557): Move to @CoordinatorScope
 @SysUISingleton
 class ShadeEventCoordinator @Inject internal constructor(
     private val mLogger: ShadeEventCoordinatorLogger
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
index 442d9d2..519d75f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
@@ -18,7 +18,6 @@
 
 import android.app.smartspace.SmartspaceTarget
 import android.os.Parcelable
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -28,6 +27,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -45,7 +45,7 @@
  */
 // This class is a singleton so that the same instance can be accessed by both the old and new
 // pipelines
-@SysUISingleton
+@CoordinatorScope
 class SmartspaceDedupingCoordinator @Inject constructor(
     private val statusBarStateController: SysuiStatusBarStateController,
     private val smartspaceController: LockscreenSmartspaceController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index df1132b..5b86de2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -19,11 +19,11 @@
 import com.android.internal.widget.MessagingGroup
 import com.android.internal.widget.MessagingMessage
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import javax.inject.Inject
@@ -33,7 +33,7 @@
  * for the current uiMode and screen properties; additionally deferring those changes when a user
  * change is in progress until that process has completed.
  */
-@SysUISingleton
+@CoordinatorScope
 class ViewConfigCoordinator @Inject internal constructor(
     configurationController: ConfigurationController,
     lockscreenUserManager: NotificationLockscreenUserManagerImpl,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 5d6c043..5ba4c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -50,6 +50,7 @@
  * This is now integrated in the data-layer via
  * {@link com.android.systemui.statusbar.notification.collection.ShadeListBuilder}.
  */
+// TODO(b/204468557): Move to @CoordinatorScope
 @SysUISingleton
 public class VisualStabilityCoordinator implements Coordinator {
     private final DelayableExecutor mDelayableExecutor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
new file mode 100644
index 0000000..a26d50d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators
+import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinatorsImpl
+import com.android.systemui.statusbar.notification.collection.coordinator.SensitiveContentCoordinatorModule
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import javax.inject.Qualifier
+import javax.inject.Scope
+
+@Module(subcomponents = [CoordinatorsSubcomponent::class])
+object CoordinatorsModule {
+    @SysUISingleton
+    @JvmStatic
+    @Provides
+    fun notifCoordinators(factory: CoordinatorsSubcomponent.Factory): NotifCoordinators =
+            factory.create().notifCoordinators
+}
+
+@CoordinatorScope
+@Subcomponent(modules = [InternalCoordinatorsModule::class])
+interface CoordinatorsSubcomponent {
+    @get:Internal val notifCoordinators: NotifCoordinators
+
+    @Subcomponent.Factory
+    interface Factory {
+        fun create(): CoordinatorsSubcomponent
+    }
+}
+
+@Module(includes = [SensitiveContentCoordinatorModule::class])
+private abstract class InternalCoordinatorsModule {
+    @Binds
+    @Internal
+    abstract fun bindNotifCoordinators(impl: NotifCoordinatorsImpl): NotifCoordinators
+}
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+private annotation class Internal
+
+@Scope
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CoordinatorScope
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 5a35127..8fff905 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -47,6 +47,15 @@
         })
     }
 
+    fun logPreRenderInvalidated(filterName: String, pipelineState: Int) {
+        buffer.log(TAG, DEBUG, {
+            str1 = filterName
+            int1 = pipelineState
+        }, {
+            """Pre-render Invalidator "$str1" invalidated; pipeline state is $int1"""
+        })
+    }
+
     fun logPreGroupFilterInvalidated(filterName: String, pipelineState: Int) {
         buffer.log(TAG, DEBUG, {
             str1 = filterName
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java
new file mode 100644
index 0000000..d7092ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
+
+/** A {@link Pluggable} that can only invalidate. */
+public abstract class Invalidator extends Pluggable<Invalidator> {
+    protected Invalidator(String name) {
+        super(name);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
index 8e4fb75..b981a96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
 
 import android.annotation.Nullable;
+import android.os.Trace;
 
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 
@@ -50,7 +51,9 @@
      */
     public final void invalidateList() {
         if (mListener != null) {
+            Trace.beginSection("Pluggable<" + mName + ">.invalidateList");
             mListener.onPluggableInvalidated((This) this);
+            Trace.endSection();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
index 9b8ac72..010b6f80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.util.traceSection
 
 /**
  * Converts a notif list (the output of the ShadeListBuilder) into a NodeSpec, an abstract
@@ -36,7 +37,7 @@
     fun buildNodeSpec(
         rootController: NodeController,
         notifList: List<ListEntry>
-    ): NodeSpec {
+    ): NodeSpec = traceSection("NodeSpecBuilder.buildNodeSpec") {
         val root = NodeSpecImpl(null, rootController)
         var currentSection: NotifSection? = null
         val prevSections = mutableSetOf<NotifSection?>()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 7babbb4..6d4ae4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -19,6 +19,7 @@
 import android.annotation.MainThread
 import android.view.View
 import com.android.systemui.util.kotlin.transform
+import com.android.systemui.util.traceSection
 
 /**
  * Given a "spec" that describes a "tree" of views, adds and removes views from the
@@ -47,7 +48,7 @@
      * provided [spec]. The root node of the spec must match the root controller passed to the
      * differ's constructor.
      */
-    fun applySpec(spec: NodeSpec) {
+    fun applySpec(spec: NodeSpec) = traceSection("ShadeViewDiffer.applySpec") {
         val specMap = treeToMap(spec)
 
         if (spec.controller != rootNode.controller) {
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 a2c7aa5..b582a24 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
@@ -18,10 +18,13 @@
 
 import android.content.Context
 import android.view.View
+import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.util.traceSection
 import javax.inject.Inject
 
 /**
@@ -32,7 +35,7 @@
     context: Context,
     listContainer: NotificationListContainer,
     logger: ShadeViewDifferLogger,
-    viewBarn: NotifViewBarn,
+    private val viewBarn: NotifViewBarn,
     private val notificationIconAreaController: NotificationIconAreaController
 ) {
     // We pass a shim view here because the listContainer may not actually have a view associated
@@ -45,8 +48,21 @@
             listBuilder.setOnRenderListListener(::onNewNotifTree)
 
     private fun onNewNotifTree(notifList: List<ListEntry>) {
-        viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
-        notificationIconAreaController.updateNotificationIcons(notifList)
+        traceSection("ShadeViewManager.onNewNotifTree") {
+            viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
+            updateGroupCounts(notifList)
+            notificationIconAreaController.updateNotificationIcons(notifList)
+        }
+    }
+
+    private fun updateGroupCounts(notifList: List<ListEntry>) {
+        traceSection("ShadeViewManager.updateGroupCounts") {
+            notifList.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
+                val controller = viewBarn.requireView(checkNotNull(groupEntry.summary))
+                val row = controller.view as ExpandableNotificationRow
+                row.setUntruncatedChildCount(groupEntry.untruncatedChildCount)
+            }
+        }
     }
 }
 
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 540216c..1eb007e 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
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator;
 import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorsModule;
 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;
@@ -93,7 +94,10 @@
 /**
  * Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
  */
-@Module(includes = {NotificationSectionHeadersModule.class})
+@Module(includes = {
+        NotificationSectionHeadersModule.class,
+        CoordinatorsModule.class
+})
 public interface NotificationsModule {
     @Binds
     StackScrollAlgorithm.SectionProvider bindSectionProvider(
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 02b1210..ccd4843 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
@@ -107,6 +107,7 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
+import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.wmshell.BubblesManager;
 
 import java.io.FileDescriptor;
@@ -3305,40 +3306,45 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        super.dump(fd, pw, args);
-        pw.println("  Notification: " + mEntry.getKey());
-        pw.print("    visibility: " + getVisibility());
-        pw.print(", alpha: " + getAlpha());
-        pw.print(", translation: " + getTranslation());
-        pw.print(", removed: " + isRemoved());
-        pw.print(", expandAnimationRunning: " + mExpandAnimationRunning);
-        NotificationContentView showingLayout = getShowingLayout();
-        pw.print(", privateShowing: " + (showingLayout == mPrivateLayout));
-        pw.println();
-        showingLayout.dump(fd, pw, args);
-        pw.print("    ");
-        if (getViewState() != null) {
-            getViewState().dump(fd, pw, args);
-        } else {
-            pw.print("no viewState!!!");
-        }
-        pw.println();
-        pw.println();
-        if (mIsSummaryWithChildren) {
-            pw.print("  ChildrenContainer");
-            pw.print(" visibility: " + mChildrenContainer.getVisibility());
-            pw.print(", alpha: " + mChildrenContainer.getAlpha());
-            pw.print(", translationY: " + mChildrenContainer.getTranslationY());
-            pw.println();
-            List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();
-            pw.println("  Children: " + notificationChildren.size());
-            pw.println("  {");
-            for(ExpandableNotificationRow child : notificationChildren) {
-                child.dump(fd, pw, args);
+        // Skip super call; dump viewState ourselves
+        pw.println("Notification: " + mEntry.getKey());
+        DumpUtilsKt.withIndenting(pw, ipw -> {
+            ipw.print("visibility: " + getVisibility());
+            ipw.print(", alpha: " + getAlpha());
+            ipw.print(", translation: " + getTranslation());
+            ipw.print(", removed: " + isRemoved());
+            ipw.print(", expandAnimationRunning: " + mExpandAnimationRunning);
+            NotificationContentView showingLayout = getShowingLayout();
+            ipw.print(", privateShowing: " + (showingLayout == mPrivateLayout));
+            ipw.println();
+            showingLayout.dump(fd, ipw, args);
+
+            if (getViewState() != null) {
+                getViewState().dump(fd, ipw, args);
+                ipw.println();
+            } else {
+                ipw.println("no viewState!!!");
             }
-            pw.println("  }");
-            pw.println();
-        }
+
+            if (mIsSummaryWithChildren) {
+                ipw.println();
+                ipw.print("ChildrenContainer");
+                ipw.print(" visibility: " + mChildrenContainer.getVisibility());
+                ipw.print(", alpha: " + mChildrenContainer.getAlpha());
+                ipw.print(", translationY: " + mChildrenContainer.getTranslationY());
+                ipw.println();
+                List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();
+                ipw.println("Children: " + notificationChildren.size());
+                ipw.print("{");
+                ipw.increaseIndent();
+                for (ExpandableNotificationRow child : notificationChildren) {
+                    ipw.println();
+                    child.dump(fd, ipw, args);
+                }
+                ipw.decreaseIndent();
+                ipw.println("}");
+            }
+        });
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 8b0764b..fa2c1ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.util.DumpUtilsKt;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -743,6 +744,16 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(getClass().getSimpleName());
+        DumpUtilsKt.withIndenting(pw, ipw -> {
+            ExpandableViewState viewState = getViewState();
+            if (viewState == null) {
+                ipw.println("no viewState!!!");
+            } else {
+                viewState.dump(fd, ipw, args);
+                ipw.println();
+            }
+        });
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 9eb95c4..b27a40a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -25,6 +25,10 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.ViewState;
+import com.android.systemui.util.DumpUtilsKt;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 
 public class FooterView extends StackScrollerDecorView {
     private FooterViewButton mDismissButton;
@@ -45,6 +49,19 @@
     }
 
     @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dump(fd, pw, args);
+        DumpUtilsKt.withIndenting(pw, ipw -> {
+            ipw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility()));
+            ipw.println("manageButton showHistory: " + mShowHistory);
+            ipw.println("manageButton visibility: "
+                    + DumpUtilsKt.visibilityString(mDismissButton.getVisibility()));
+            ipw.println("dismissButton visibility: "
+                    + DumpUtilsKt.visibilityString(mDismissButton.getVisibility()));
+        });
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mDismissButton = (FooterViewButton) findSecondaryView();
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 4f54e4f..df484dd 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
@@ -1253,7 +1253,7 @@
             }
             if (hasRemoteInput) {
                 existing.setWrapper(wrapper);
-                existing.setOnVisibilityChangedListener(this::setRemoteInputVisible);
+                existing.addOnVisibilityChangedListener(this::setRemoteInputVisible);
 
                 if (existingPendingIntent != null || existing.isActive()) {
                     // The current action could be gone, or the pending intent no longer valid.
@@ -1938,7 +1938,6 @@
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.print("    ");
         pw.print("contentView visibility: " + getVisibility());
         pw.print(", alpha: " + getAlpha());
         pw.print(", clipBounds: " + getClipBounds());
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 6aa5f07..6a12710 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
@@ -109,6 +109,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.DumpUtilsKt;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -4899,54 +4900,42 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println(String.format("[%s: pulsing=%s visibility=%s"
-                        + " alpha=%f scrollY:%d maxTopPadding=%d showShelfOnly=%s"
-                        + " qsExpandFraction=%f"
-                        + " hideAmount=%f]",
-                this.getClass().getSimpleName(),
-                mPulsing ? "T" : "f",
-                getVisibility() == View.VISIBLE ? "visible"
-                        : getVisibility() == View.GONE ? "gone"
-                                : "invisible",
-                getAlpha(),
-                mAmbientState.getScrollY(),
-                mMaxTopPadding,
-                mShouldShowShelfOnly ? "T" : "f",
-                mQsExpansionFraction,
-                mAmbientState.getHideAmount()));
-        int childCount = getChildCount();
-        pw.println("  Number of children: " + childCount);
-        pw.println();
+        StringBuilder sb = new StringBuilder("[")
+                .append(this.getClass().getSimpleName()).append(":")
+                .append(" pulsing=").append(mPulsing ? "T" : "f")
+                .append(" visibility=").append(DumpUtilsKt.visibilityString(getVisibility()))
+                .append(" alpha=").append(getAlpha())
+                .append(" scrollY=").append(mAmbientState.getScrollY())
+                .append(" maxTopPadding=").append(mMaxTopPadding)
+                .append(" showShelfOnly=").append(mShouldShowShelfOnly ? "T" : "f")
+                .append(" qsExpandFraction=").append(mQsExpansionFraction)
+                .append(" isCurrentUserSetup=").append(mIsCurrentUserSetup)
+                .append(" hideAmount=").append(mAmbientState.getHideAmount())
+                .append("]");
+        pw.println(sb.toString());
+        DumpUtilsKt.withIndenting(pw, ipw -> {
+            int childCount = getChildCount();
+            ipw.println("Number of children: " + childCount);
+            ipw.println();
 
-        for (int i = 0; i < childCount; i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
-            child.dump(fd, pw, args);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                pw.println("  " + child.getClass().getSimpleName());
-                // Notifications dump it's viewstate as part of their dump to support children
-                ExpandableViewState viewState = child.getViewState();
-                if (viewState == null) {
-                    pw.println("    no viewState!!!");
-                } else {
-                    pw.print("    ");
-                    viewState.dump(fd, pw, args);
-                    pw.println();
-                    pw.println();
-                }
+            for (int i = 0; i < childCount; i++) {
+                ExpandableView child = (ExpandableView) getChildAt(i);
+                child.dump(fd, ipw, args);
+                ipw.println();
             }
-        }
-        int transientViewCount = getTransientViewCount();
-        pw.println("  Transient Views: " + transientViewCount);
-        for (int i = 0; i < transientViewCount; i++) {
-            ExpandableView child = (ExpandableView) getTransientView(i);
-            child.dump(fd, pw, args);
-        }
-        View swipedView = mSwipeHelper.getSwipedView();
-        pw.println("  Swiped view: " + swipedView);
-        if (swipedView instanceof ExpandableView) {
-            ExpandableView expandableView = (ExpandableView) swipedView;
-            expandableView.dump(fd, pw, args);
-        }
+            int transientViewCount = getTransientViewCount();
+            pw.println("Transient Views: " + transientViewCount);
+            for (int i = 0; i < transientViewCount; i++) {
+                ExpandableView child = (ExpandableView) getTransientView(i);
+                child.dump(fd, pw, args);
+            }
+            View swipedView = mSwipeHelper.getSwipedView();
+            pw.println("Swiped view: " + swipedView);
+            if (swipedView instanceof ExpandableView) {
+                ExpandableView expandableView = (ExpandableView) swipedView;
+                expandableView.dump(fd, pw, args);
+            }
+        });
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 6516abd..fbe59a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -6,6 +6,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Trace;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -336,12 +337,14 @@
     }
 
     private void updateNotificationIcons() {
+        Trace.beginSection("NotificationIconAreaController.updateNotificationIcons");
         updateStatusBarIcons();
         updateShelfIcons();
         updateCenterIcon();
         updateAodNotificationIcons();
 
         applyNotificationIconsTint();
+        Trace.endSection();
     }
 
     private void updateShelfIcons() {
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 4ad2af4..711e941 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -129,7 +129,6 @@
 import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.AutoReinflateContainer;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.InitController;
 import com.android.systemui.Prefs;
@@ -235,6 +234,7 @@
 import com.android.systemui.unfold.UnfoldTransitionWallpaperController;
 import com.android.systemui.unfold.config.UnfoldTransitionConfig;
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
+import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.WallpaperController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.MessageRouter;
@@ -936,8 +936,6 @@
                 data -> mCommandQueueCallbacks.animateExpandSettingsPanel(data.mSubpanel));
         mMessageRouter.subscribeTo(MSG_LAUNCH_TRANSITION_TIMEOUT,
                 id -> onLaunchTransitionTimeout());
-
-        dumpManager.registerDumpable(this);
     }
 
     @Override
@@ -2445,8 +2443,14 @@
         }
         pw.println("  mStackScroller: ");
         if (mStackScroller != null) {
-            pw.print  ("      ");
-            ((Dumpable) mStackScroller).dump(fd, pw, args);
+            DumpUtilsKt.withIndenting(pw, ipw -> {
+                // Triple indent until we rewrite the rest of this dump()
+                ipw.increaseIndent();
+                ipw.increaseIndent();
+                mStackScroller.dump(fd, ipw, args);
+                ipw.decreaseIndent();
+                ipw.decreaseIndent();
+            });
         }
         pw.println("  Theme:");
         String nightMode = mUiModeManager == null ? "null" : mUiModeManager.getNightMode() + "";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index cf9b2c6..ecd5c98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -314,9 +314,7 @@
             mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
             return;
         }
-
         mViewHierarchyManager.updateNotificationViews();
-
         mNotificationPanel.updateNotificationViews(reason);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5d7d480..aa8d95f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -108,36 +108,36 @@
 
     private final SendButtonTextWatcher mTextWatcher;
     private final TextView.OnEditorActionListener mEditorActionHandler;
-    private final UiEventLogger mUiEventLogger;
-    private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
-    private final List<OnFocusChangeListener> mEditTextFocusChangeListeners = new ArrayList<>();
-    private final List<OnSendRemoteInputListener> mOnSendListeners = new ArrayList<>();
+    private final ArrayList<OnSendRemoteInputListener> mOnSendListeners = new ArrayList<>();
+    private final ArrayList<Consumer<Boolean>> mOnVisibilityChangedListeners = new ArrayList<>();
+    private final ArrayList<OnFocusChangeListener> mEditTextFocusChangeListeners =
+            new ArrayList<>();
+
     private RemoteEditText mEditText;
     private ImageButton mSendButton;
     private GradientDrawable mContentBackground;
     private ProgressBar mProgressBar;
-    private PendingIntent mPendingIntent;
-    private RemoteInput[] mRemoteInputs;
-    private RemoteInput mRemoteInput;
-    private RemoteInputController mController;
-
-    private NotificationEntry mEntry;
-
-    private boolean mRemoved;
-
+    private ImageView mDelete;
+    private ImageView mDeleteBg;
+    // TODO(b/193539698): remove reveal param fields, turn them into parameters where needed
     private int mRevealCx;
     private int mRevealCy;
     private int mRevealR;
-
     private boolean mColorized;
     private int mTint;
-
     private boolean mResetting;
-    private NotificationViewWrapper mWrapper;
-    private Consumer<Boolean> mOnVisibilityChangedListener;
+
+    // TODO(b/193539698): move these to a Controller
+    private RemoteInputController mController;
+    private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
+    private final UiEventLogger mUiEventLogger;
+    private NotificationEntry mEntry;
+    private PendingIntent mPendingIntent;
+    private RemoteInput mRemoteInput;
+    private RemoteInput[] mRemoteInputs;
     private NotificationRemoteInputManager.BouncerChecker mBouncerChecker;
-    private ImageView mDelete;
-    private ImageView mDeleteBg;
+    private boolean mRemoved;
+    private NotificationViewWrapper mWrapper;
 
     /**
      * Enum for logged notification remote input UiEvents.
@@ -382,7 +382,7 @@
     private void sendRemoteInput(Intent intent) {
         if (mBouncerChecker != null && mBouncerChecker.showBouncerIfNecessary()) {
             mEditText.hideIme();
-            for (OnSendRemoteInputListener listener : mOnSendListeners) {
+            for (OnSendRemoteInputListener listener : new ArrayList<>(mOnSendListeners)) {
                 listener.onSendRequestBounced();
             }
             return;
@@ -399,7 +399,7 @@
         mController.remoteInputSent(mEntry);
         mEntry.setHasSentReply();
 
-        for (OnSendRemoteInputListener listener : mOnSendListeners) {
+        for (OnSendRemoteInputListener listener : new ArrayList<>(mOnSendListeners)) {
             listener.onSendRemoteInput();
         }
 
@@ -760,15 +760,32 @@
         mWrapper = wrapper;
     }
 
-    public void setOnVisibilityChangedListener(Consumer<Boolean> visibilityChangedListener) {
-        mOnVisibilityChangedListener = visibilityChangedListener;
+    /**
+     * Register a listener to be notified when this view's visibility changes.
+     *
+     * Specifically, the passed {@link Consumer} will receive {@code true} when
+     * {@link #getVisibility()} would return {@link View#VISIBLE}, and {@code false} it would return
+     * any other value.
+     */
+    public void addOnVisibilityChangedListener(Consumer<Boolean> listener) {
+        mOnVisibilityChangedListeners.add(listener);
+    }
+
+    /**
+     * Unregister a listener previously registered via
+     * {@link #addOnVisibilityChangedListener(Consumer)}.
+     */
+    public void removeOnVisibilityChangedListener(Consumer<Boolean> listener) {
+        mOnVisibilityChangedListeners.remove(listener);
     }
 
     @Override
     protected void onVisibilityChanged(View changedView, int visibility) {
         super.onVisibilityChanged(changedView, visibility);
-        if (changedView == this && mOnVisibilityChangedListener != null) {
-            mOnVisibilityChangedListener.accept(visibility == VISIBLE);
+        if (changedView == this) {
+            for (Consumer<Boolean> listener : new ArrayList<>(mOnVisibilityChangedListeners)) {
+                listener.accept(visibility == VISIBLE);
+            }
             // Hide soft-keyboard when the input view became invisible
             // (i.e. The notification shade collapsed by pressing the home key)
             if (visibility != VISIBLE && !mEditText.isVisibleToUser()
diff --git a/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt b/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt
new file mode 100644
index 0000000..9f33c27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.util.IndentingPrintWriter
+import android.view.View
+import java.io.PrintWriter
+import java.util.function.Consumer
+
+/**
+ * Run some code that will print to an [IndentingPrintWriter] that wraps the given [PrintWriter].
+ *
+ * If the given [PrintWriter] is an [IndentingPrintWriter], the block will be passed that same
+ * instance with [IndentingPrintWriter.increaseIndent] having been called, and calling
+ * [IndentingPrintWriter.decreaseIndent] after completion of the block, so the passed [PrintWriter]
+ * should not be used before the block completes.
+ */
+inline fun PrintWriter.withIndenting(block: (IndentingPrintWriter) -> Unit) {
+    if (this is IndentingPrintWriter) {
+        this.withIncreasedIndent { block(this) }
+    } else {
+        block(IndentingPrintWriter(this))
+    }
+}
+
+/**
+ * Run some code that will print to an [IndentingPrintWriter] that wraps the given [PrintWriter].
+ *
+ * If the given [PrintWriter] is an [IndentingPrintWriter], the block will be passed that same
+ * instance with [IndentingPrintWriter.increaseIndent] having been called, and calling
+ * [IndentingPrintWriter.decreaseIndent] after completion of the block, so the passed [PrintWriter]
+ * should not be used before the block completes.
+ */
+fun PrintWriter.withIndenting(consumer: Consumer<IndentingPrintWriter>) {
+    if (this is IndentingPrintWriter) {
+        this.withIncreasedIndent { consumer.accept(this) }
+    } else {
+        consumer.accept(IndentingPrintWriter(this))
+    }
+}
+
+/**
+ * Run some code inside a block, with [IndentingPrintWriter.increaseIndent] having been called on
+ * the given argument, and calling [IndentingPrintWriter.decreaseIndent] after completion.
+ */
+inline fun IndentingPrintWriter.withIncreasedIndent(block: () -> Unit) {
+    this.increaseIndent()
+    try {
+        block()
+    } finally {
+        this.decreaseIndent()
+    }
+}
+
+/** Return a readable string for the visibility */
+fun visibilityString(@View.Visibility visibility: Int): String = when (visibility) {
+    View.GONE -> "gone"
+    View.VISIBLE -> "visible"
+    View.INVISIBLE -> "invisible"
+    else -> "unknown:$visibility"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
new file mode 100644
index 0000000..5b16ae9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.os.Trace
+
+/**
+ * Run a block within a [Trace] section.
+ * Calls [Trace.beginSection] before and [Trace.endSection] after the passed block.
+ */
+inline fun <T> traceSection(tag: String, block: () -> T): T {
+    Trace.beginSection(tag)
+    try {
+        return block()
+    } finally {
+        Trace.endSection()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
index e639313a..5568f64 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
@@ -17,6 +17,7 @@
 package com.android.systemui.util.sensors;
 
 import android.hardware.SensorManager;
+import android.os.Build;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -56,7 +57,7 @@
  */
 class ProximitySensorImpl implements ProximitySensor {
     private static final String TAG = "ProxSensor";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE;
     private static final long SECONDARY_PING_INTERVAL_MS = 5000;
 
     ThresholdSensor mPrimaryThresholdSensor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
index b3c098c..8243be8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
@@ -20,13 +20,21 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.Context;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -36,17 +44,33 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 
+/**
+ * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
+ * overriding, and should never return any value other than the one provided as the default.
+ */
 @SmallTest
 public class FeatureFlagManagerTest extends SysuiTestCase {
     FeatureFlagManager mFeatureFlagManager;
 
+    @Mock private SystemPropertiesHelper mProps;
+    @Mock private Context mContext;
     @Mock private DumpManager mDumpManager;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mFeatureFlagManager = new FeatureFlagManager(mDumpManager);
+        mFeatureFlagManager = new FeatureFlagManager(mProps, mContext, mDumpManager);
+    }
+
+    @After
+    public void onFinished() {
+        // SystemPropertiesHelper and Context are provided for constructor consistency with the
+        // debug version of the FeatureFlagManager, but should never be used.
+        verifyZeroInteractions(mProps, mContext);
+        // The dump manager should be registered with even for the release version, but that's it.
+        verify(mDumpManager).registerDumpable(anyString(), any());
+        verifyNoMoreInteractions(mDumpManager);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index e54a6ec..d2bba36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -1,7 +1,9 @@
 package com.android.systemui.qs
 
-import com.android.systemui.R
 import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.ViewUtils
 import android.view.LayoutInflater
 import android.view.View
 import androidx.test.filters.SmallTest
@@ -9,6 +11,7 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.logging.testing.FakeMetricsLogger
 import com.android.systemui.Dependency
+import com.android.systemui.R
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
@@ -19,8 +22,11 @@
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.utils.leaks.FakeTunerService
 import com.android.systemui.utils.leaks.LeakCheckedTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Mock
@@ -30,6 +36,8 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
 class FooterActionsControllerTest : LeakCheckedTest() {
     @Mock
     private lateinit var userManager: UserManager
@@ -53,10 +61,12 @@
     private val metricsLogger: MetricsLogger = FakeMetricsLogger()
     private lateinit var view: FooterActionsView
     private val falsingManager: FalsingManagerFake = FalsingManagerFake()
+    private lateinit var testableLooper: TestableLooper
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
         injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES)
         val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService
 
@@ -69,7 +79,14 @@
                 globalActionsDialog, uiEventLogger, showPMLiteButton = true,
                 buttonsVisibleState = ExpansionState.EXPANDED)
         controller.init()
-        controller.onViewAttached()
+        ViewUtils.attachView(view)
+        // View looper is the testable looper associated with the test
+        testableLooper.processAllMessages()
+    }
+
+    @After
+    fun tearDown() {
+        ViewUtils.detachView(view)
     }
 
     @Test
@@ -90,4 +107,19 @@
         // Verify Settings wasn't launched.
         verify<ActivityStarter>(activityStarter, Mockito.never()).startActivity(any(), anyBoolean())
     }
+
+    @Test
+    fun testMultiUserSwitchUpdatedWhenExpansionStarts() {
+        // When expansion starts, listening is set to true
+        val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
+
+        assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
+
+        whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
+
+        controller.setListening(true)
+        testableLooper.processAllMessages()
+
+        assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
+    }
 }
\ No newline at end of file
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 7fb7b86..cf58c63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -36,6 +36,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.DynamicChildBindController;
@@ -75,6 +76,7 @@
     @Spy private FakeListContainer mListContainer = new FakeListContainer();
 
     // Dependency mocks:
+    @Mock private FeatureFlags mFeatureFlags;
     @Mock private NotificationEntryManager mEntryManager;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotificationGroupManagerLegacy mGroupManager;
@@ -101,10 +103,14 @@
         when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true);
         when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true);
 
+        when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
+        when(mFeatureFlags.checkLegacyPipelineEnabled()).thenReturn(true);
+
         mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
 
         mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
-                mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
+                mHandler, mFeatureFlags, mLockscreenUserManager, mGroupManager,
+                mVisualStabilityManager,
                 mock(StatusBarStateControllerImpl.class), mEntryManager,
                 mock(KeyguardBypassController.class),
                 Optional.of(mock(Bubbles.class)),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index e9e6718..190c352 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -56,6 +56,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
@@ -847,11 +848,13 @@
         NotifPromoter idPromoter = new IdPromoter(4);
         NotifSectioner section = new PackageSectioner(PACKAGE_1);
         NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
+        Invalidator preRenderInvalidator = new Invalidator("PreRenderInvalidator") {};
 
         mListBuilder.addPreGroupFilter(packageFilter);
         mListBuilder.addPromoter(idPromoter);
         mListBuilder.setSectioners(singletonList(section));
         mListBuilder.setComparators(singletonList(hypeComparator));
+        mListBuilder.addPreRenderInvalidator(preRenderInvalidator);
 
         // GIVEN a set of random notifs
         addNotif(0, PACKAGE_1);
@@ -876,6 +879,10 @@
         clearInvocations(mOnRenderListListener);
         hypeComparator.invalidateList();
         verify(mOnRenderListListener).onRenderList(anyList());
+
+        clearInvocations(mOnRenderListListener);
+        preRenderInvalidator.invalidateList();
+        verify(mOnRenderListListener).onRenderList(anyList());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
new file mode 100644
index 0000000..5fd4174
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.notification.DynamicPrivacyController
+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.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.mockito.mock
+import org.junit.Test
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+class SensitiveContentCoordinatorTest : SysuiTestCase() {
+
+    val dynamicPrivacyController: DynamicPrivacyController = mock()
+    val lockscreenUserManager: NotificationLockscreenUserManager = mock()
+    val pipeline: NotifPipeline = mock()
+
+    val coordinator: SensitiveContentCoordinator = SensitiveContentCoordinatorModule
+            .provideCoordinator(dynamicPrivacyController, lockscreenUserManager)
+
+    @Test
+    fun onDynamicPrivacyChanged_invokeInvalidationListener() {
+        coordinator.attach(pipeline)
+        val invalidator = withArgCaptor<Invalidator> {
+            verify(pipeline).addPreRenderInvalidator(capture())
+        }
+        val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> {
+            verify(dynamicPrivacyController).addListener(capture())
+        }
+
+        val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>()
+        invalidator.setInvalidationListener(invalidationListener)
+
+        dynamicPrivacyListener.onDynamicPrivacyChanged()
+
+        verify(invalidationListener).onPluggableInvalidated(invalidator)
+    }
+
+    @Test
+    fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, false)
+    }
+
+    @Test
+    fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, false)
+    }
+
+    @Test
+    fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, false)
+    }
+
+    @Test
+    fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        val entry = fakeNotification(1, true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
+
+        val entry = fakeNotification(2, true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
+        val mockUserHandle = mock<UserHandle>().apply {
+            whenever(identifier).thenReturn(notifUserId)
+        }
+        val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply {
+            whenever(user).thenReturn(mockUserHandle)
+        }
+        val mockEntry = mock<NotificationEntry>().apply {
+            whenever(sbn).thenReturn(mockSbn)
+        }
+        whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction)
+        whenever(mockEntry.rowExists()).thenReturn(true)
+        return object : ListEntry("key", 0) {
+            override fun getRepresentativeEntry(): NotificationEntry = mockEntry
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index dd8354d..97e1edb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -205,7 +205,7 @@
         ExpandableNotificationRow row = helper.createRow();
         RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
 
-        view.setOnVisibilityChangedListener(null);
+        view.addOnVisibilityChangedListener(null);
         view.setVisibility(View.INVISIBLE);
         view.setVisibility(View.VISIBLE);
     }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 4748a86..2028766 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -457,6 +457,9 @@
             }, FgThread.getExecutor()).whenComplete(uncheckExceptions((association, err) -> {
                 if (err == null) {
                     addAssociation(association, userId);
+                    mServiceConnectors.forUser(userId).post(service -> {
+                        service.onAssociationCreated();
+                    });
                 } else {
                     Slog.e(LOG_TAG, "Failed to discover device(s)", err);
                     callback.onFailure("No devices found: " + err.getMessage());
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index e890acb..9572154 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -36,6 +36,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -152,16 +153,9 @@
                     throw new RuntimeException(
                         "Failed to find NetworkScanInfo with id " + message.arg2);
                 }
-                NetworkScanCallback callback = nsi.mCallback;
-                Executor executor = nsi.mExecutor;
-                if (callback == null) {
-                    throw new RuntimeException(
-                        "Failed to find NetworkScanCallback with id " + message.arg2);
-                }
-                if (executor == null) {
-                    throw new RuntimeException(
-                        "Failed to find Executor with id " + message.arg2);
-                }
+
+                final NetworkScanCallback callback = nsi.mCallback;
+                final Executor executor = nsi.mExecutor;
 
                 switch (message.what) {
                     case CALLBACK_RESTRICTED_SCAN_RESULTS:
@@ -246,17 +240,24 @@
             NetworkScanRequest request, Executor executor, NetworkScanCallback callback,
             String callingPackage, @Nullable String callingFeatureId) {
         try {
+            Objects.requireNonNull(request, "Request was null");
+            Objects.requireNonNull(callback, "Callback was null");
+            Objects.requireNonNull(executor, "Executor was null");
             final ITelephony telephony = getITelephony();
             if (telephony == null) return null;
 
-            int scanId = telephony.requestNetworkScan(
-                    subId, request, mMessenger, new Binder(), callingPackage,
-                    callingFeatureId);
-            if (scanId == INVALID_SCAN_ID) {
-                Rlog.e(TAG, "Failed to initiate network scan");
-                return null;
-            }
+            // The lock must be taken before calling requestNetworkScan because the resulting
+            // scanId can be invoked asynchronously on another thread at any time after
+            // requestNetworkScan invoked, leaving a critical section between that call and adding
+            // the record to the ScanInfo cache.
             synchronized (mScanInfo) {
+                int scanId = telephony.requestNetworkScan(
+                        subId, request, mMessenger, new Binder(), callingPackage,
+                        callingFeatureId);
+                if (scanId == INVALID_SCAN_ID) {
+                    Rlog.e(TAG, "Failed to initiate network scan");
+                    return null;
+                }
                 // We link to death whenever a scan is started to ensure that we are linked
                 // at the point that phone process death might matter.
                 // We never unlink because: