Merge "Decouple BubbleController from more classes" into main
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 7a4ad0a..da530d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -53,8 +53,6 @@
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
 import com.android.wm.shell.common.bubbles.BubbleInfo;
-import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -403,13 +401,9 @@
      * Returns the existing {@link #mBubbleTaskView} if it's not {@code null}. Otherwise a new
      * instance of {@link BubbleTaskView} is created.
      */
-    public BubbleTaskView getOrCreateBubbleTaskView(Context context, BubbleController controller) {
+    public BubbleTaskView getOrCreateBubbleTaskView(BubbleTaskViewFactory taskViewFactory) {
         if (mBubbleTaskView == null) {
-            TaskViewTaskController taskViewTaskController = new TaskViewTaskController(context,
-                    controller.getTaskOrganizer(),
-                    controller.getTaskViewTransitions(), controller.getSyncTransactionQueue());
-            TaskView taskView = new TaskView(context, taskViewTaskController);
-            mBubbleTaskView = new BubbleTaskView(taskView, controller.getMainExecutor());
+            mBubbleTaskView = taskViewFactory.create();
         }
         return mBubbleTaskView;
     }
@@ -514,14 +508,18 @@
      *
      * @param callback the callback to notify one the bubble is ready to be displayed.
      * @param context the context for the bubble.
-     * @param controller the bubble controller.
+     * @param expandedViewManager the bubble expanded view manager.
+     * @param taskViewFactory the task view factory used to create the task view for the bubble.
+     * @param positioner the bubble positioner.
      * @param stackView the view the bubble is added to, iff showing as floating.
      * @param layerView the layer the bubble is added to, iff showing in the bubble bar.
-     * @param iconFactory the icon factory use to create images for the bubble.
+     * @param iconFactory the icon factory used to create images for the bubble.
      */
     void inflate(BubbleViewInfoTask.Callback callback,
             Context context,
-            BubbleController controller,
+            BubbleExpandedViewManager expandedViewManager,
+            BubbleTaskViewFactory taskViewFactory,
+            BubblePositioner positioner,
             @Nullable BubbleStackView stackView,
             @Nullable BubbleBarLayerView layerView,
             BubbleIconFactory iconFactory,
@@ -531,7 +529,9 @@
         }
         mInflationTask = new BubbleViewInfoTask(this,
                 context,
-                controller,
+                expandedViewManager,
+                taskViewFactory,
+                positioner,
                 stackView,
                 layerView,
                 iconFactory,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 0aa8959..5c6f73f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -113,6 +113,7 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
 import com.android.wm.shell.taskview.TaskViewTransitions;
 import com.android.wm.shell.transition.Transitions;
 
@@ -190,6 +191,8 @@
     private final ShellCommandHandler mShellCommandHandler;
     private final IWindowManager mWmService;
     private final BubbleProperties mBubbleProperties;
+    private final BubbleTaskViewFactory mBubbleTaskViewFactory;
+    private final BubbleExpandedViewManager mExpandedViewManager;
 
     // Used to post to main UI thread
     private final ShellExecutor mMainExecutor;
@@ -333,6 +336,16 @@
         mWmService = wmService;
         mBubbleProperties = bubbleProperties;
         shellInit.addInitCallback(this::onInit, this);
+        mBubbleTaskViewFactory = new BubbleTaskViewFactory() {
+            @Override
+            public BubbleTaskView create() {
+                TaskViewTaskController taskViewTaskController = new TaskViewTaskController(
+                        context, organizer, taskViewTransitions, syncQueue);
+                TaskView taskView = new TaskView(context, taskViewTaskController);
+                return new BubbleTaskView(taskView, mainExecutor);
+            }
+        };
+        mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
     }
 
     private void registerOneHandedState(OneHandedController oneHanded) {
@@ -802,7 +815,13 @@
         try {
             mAddedToWindowManager = true;
             registerBroadcastReceiver();
-            mBubbleData.getOverflow().initialize(this, isShowingAsBubbleBar());
+            if (isShowingAsBubbleBar()) {
+                mBubbleData.getOverflow().initializeForBubbleBar(
+                        mExpandedViewManager, mBubblePositioner);
+            } else {
+                mBubbleData.getOverflow().initialize(
+                        mExpandedViewManager, mStackView, mBubblePositioner);
+            }
             // (TODO: b/273314541) some duplication in the inset listener
             if (isShowingAsBubbleBar()) {
                 mWindowManager.addView(mLayerView, mWmLayoutParams);
@@ -984,7 +1003,9 @@
         for (Bubble b : mBubbleData.getBubbles()) {
             b.inflate(null /* callback */,
                     mContext,
-                    this,
+                    mExpandedViewManager,
+                    mBubbleTaskViewFactory,
+                    mBubblePositioner,
                     mStackView,
                     mLayerView,
                     mBubbleIconFactory,
@@ -993,7 +1014,9 @@
         for (Bubble b : mBubbleData.getOverflowBubbles()) {
             b.inflate(null /* callback */,
                     mContext,
-                    this,
+                    mExpandedViewManager,
+                    mBubbleTaskViewFactory,
+                    mBubblePositioner,
                     mStackView,
                     mLayerView,
                     mBubbleIconFactory,
@@ -1377,7 +1400,9 @@
                 bubble.inflate(
                         (b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble),
                         mContext,
-                        this,
+                        mExpandedViewManager,
+                        mBubbleTaskViewFactory,
+                        mBubblePositioner,
                         mStackView,
                         mLayerView,
                         mBubbleIconFactory,
@@ -1431,7 +1456,9 @@
             Bubble bubble = mBubbleData.getBubbles().get(i);
             bubble.inflate(callback,
                     mContext,
-                    this,
+                    mExpandedViewManager,
+                    mBubbleTaskViewFactory,
+                    mBubblePositioner,
                     mStackView,
                     mLayerView,
                     mBubbleIconFactory,
@@ -1506,8 +1533,14 @@
         // Lazy init stack view when a bubble is created
         ensureBubbleViewsAndWindowCreated();
         bubble.setInflateSynchronously(mInflateSynchronously);
-        bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
-                mContext, this, mStackView,  mLayerView,
+        bubble.inflate(
+                b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
+                mContext,
+                mExpandedViewManager,
+                mBubbleTaskViewFactory,
+                mBubblePositioner,
+                mStackView,
+                mLayerView,
                 mBubbleIconFactory,
                 false /* skipInflation */);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 6d3f0c3..6c2f925 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -37,10 +37,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubbles.DismissReason;
-import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
 import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.common.bubbles.RemovedBubble;
 
@@ -180,7 +178,7 @@
      * This interface reports changes to the state and appearance of bubbles which should be applied
      * as necessary to the UI.
      */
-    interface Listener {
+    public interface Listener {
         /** Reports changes have have occurred as a result of the most recent operation. */
         void applyUpdate(Update update);
     }
@@ -419,8 +417,10 @@
 
     /**
      * When this method is called it is expected that all info in the bubble has completed loading.
-     * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
-     * BubbleBarLayerView, BubbleIconFactory, boolean)
+     * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager,
+     * BubbleTaskViewFactory, BubblePositioner, BubbleStackView,
+     * com.android.wm.shell.bubbles.bar.BubbleBarLayerView,
+     * com.android.launcher3.icons.BubbleIconFactory, boolean)
      */
     void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
         mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 088660e..df9ba63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -184,7 +184,7 @@
     private boolean mIsOverflow;
     private boolean mIsClipping;
 
-    private BubbleController mController;
+    private BubbleExpandedViewManager mManager;
     private BubbleStackView mStackView;
     private BubblePositioner mPositioner;
 
@@ -261,7 +261,7 @@
                     // the bubble again so we'll just remove it.
                     Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
                             + ", " + e.getMessage() + "; removing bubble");
-                    mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+                    mManager.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
                 }
             });
             mInitialized = true;
@@ -281,7 +281,7 @@
 
             if (mBubble != null && mBubble.isAppBubble()) {
                 // Let the controller know sooner what the taskId is.
-                mController.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
+                mManager.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
             }
 
             // With the task org, the taskAppeared callback will only happen once the task has
@@ -301,7 +301,7 @@
             ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
                     taskId, getBubbleKey());
             if (mBubble != null) {
-                mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+                mManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
             }
             if (mTaskView != null) {
                 // Release the surface
@@ -421,17 +421,20 @@
      * Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need
      * to be called after view inflate.
      */
-    void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow,
+    void initialize(BubbleExpandedViewManager expandedViewManager,
+            BubbleStackView stackView,
+            BubblePositioner positioner,
+            boolean isOverflow,
             @Nullable BubbleTaskView bubbleTaskView) {
-        mController = controller;
+        mManager = expandedViewManager;
         mStackView = stackView;
         mIsOverflow = isOverflow;
-        mPositioner = mController.getPositioner();
+        mPositioner = positioner;
 
         if (mIsOverflow) {
             mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
                     R.layout.bubble_overflow_container, null /* root */);
-            mOverflowView.setBubbleController(mController);
+            mOverflowView.initialize(expandedViewManager, positioner);
             FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
             mExpandedViewContainer.addView(mOverflowView, lp);
             mExpandedViewContainer.setLayoutParams(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
new file mode 100644
index 0000000..b0d3cc4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+/** Manager interface for bubble expanded views. */
+interface BubbleExpandedViewManager {
+
+    val overflowBubbles: List<Bubble>
+    fun setOverflowListener(listener: BubbleData.Listener)
+    fun collapseStack()
+    fun updateWindowFlagsForBackpress(intercept: Boolean)
+    fun promoteBubbleFromOverflow(bubble: Bubble)
+    fun removeBubble(key: String, reason: Int)
+    fun dismissBubble(bubble: Bubble, reason: Int)
+    fun setAppBubbleTaskId(key: String, taskId: Int)
+    fun isStackExpanded(): Boolean
+    fun isShowingAsBubbleBar(): Boolean
+
+    companion object {
+        /**
+         * Convenience function for creating a [BubbleExpandedViewManager] that delegates to the
+         * given `controller`.
+         */
+        @JvmStatic
+        fun fromBubbleController(controller: BubbleController): BubbleExpandedViewManager {
+            return object : BubbleExpandedViewManager {
+
+                override val overflowBubbles: List<Bubble>
+                    get() = controller.overflowBubbles
+
+                override fun setOverflowListener(listener: BubbleData.Listener) {
+                    controller.setOverflowListener(listener)
+                }
+
+                override fun collapseStack() {
+                    controller.collapseStack()
+                }
+
+                override fun updateWindowFlagsForBackpress(intercept: Boolean) {
+                    controller.updateWindowFlagsForBackpress(intercept)
+                }
+
+                override fun promoteBubbleFromOverflow(bubble: Bubble) {
+                    controller.promoteBubbleFromOverflow(bubble)
+                }
+
+                override fun removeBubble(key: String, reason: Int) {
+                    controller.removeBubble(key, reason)
+                }
+
+                override fun dismissBubble(bubble: Bubble, reason: Int) {
+                    controller.dismissBubble(bubble, reason)
+                }
+
+                override fun setAppBubbleTaskId(key: String, taskId: Int) {
+                    controller.setAppBubbleTaskId(key, taskId)
+                }
+
+                override fun isStackExpanded(): Boolean = controller.isStackExpanded
+
+                override fun isShowingAsBubbleBar(): Boolean = controller.isShowingAsBubbleBar
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index e5d9ace..f32974e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -56,19 +56,32 @@
     }
 
     /** Call before use and again if cleanUpExpandedState was called. */
-    fun initialize(controller: BubbleController, forBubbleBar: Boolean) {
-        if (forBubbleBar) {
-            createBubbleBarExpandedView()
-                .initialize(controller, /* isOverflow= */ true, /* bubbleTaskView= */ null)
-        } else {
-            createExpandedView()
+    fun initialize(
+        expandedViewManager: BubbleExpandedViewManager,
+        stackView: BubbleStackView,
+        positioner: BubblePositioner
+    ) {
+        createExpandedView()
                 .initialize(
-                    controller,
-                    controller.stackView,
+                        expandedViewManager,
+                    stackView,
+                    positioner,
                     /* isOverflow= */ true,
                     /* bubbleTaskView= */ null
                 )
-        }
+    }
+
+    fun initializeForBubbleBar(
+        expandedViewManager: BubbleExpandedViewManager,
+        positioner: BubblePositioner
+    ) {
+        createBubbleBarExpandedView()
+            .initialize(
+                expandedViewManager,
+                positioner,
+                /* isOverflow= */ true,
+                /* bubbleTaskView= */ null
+            )
     }
 
     fun cleanUpExpandedState() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index 70cdc82..b06de4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -62,7 +62,8 @@
     private ImageView mEmptyStateImage;
     private int mHorizontalMargin;
     private int mVerticalMargin;
-    private BubbleController mController;
+    private BubbleExpandedViewManager mExpandedViewManager;
+    private BubblePositioner mPositioner;
     private BubbleOverflowAdapter mAdapter;
     private RecyclerView mRecyclerView;
     private List<Bubble> mOverflowBubbles = new ArrayList<>();
@@ -70,7 +71,7 @@
     private View.OnKeyListener mKeyListener = (view, i, keyEvent) -> {
         if (keyEvent.getAction() == KeyEvent.ACTION_UP
                 && keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) {
-            mController.collapseStack();
+            mExpandedViewManager.collapseStack();
             return true;
         }
         return false;
@@ -126,8 +127,11 @@
         setFocusableInTouchMode(true);
     }
 
-    public void setBubbleController(BubbleController controller) {
-        mController = controller;
+    /** Initializes the view. Must be called after creation. */
+    public void initialize(BubbleExpandedViewManager expandedViewManager,
+            BubblePositioner positioner) {
+        mExpandedViewManager = expandedViewManager;
+        mPositioner = positioner;
     }
 
     public void show() {
@@ -149,9 +153,9 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        if (mController != null) {
+        if (mExpandedViewManager != null) {
             // For the overflow to get key events (e.g. back press) we need to adjust the flags
-            mController.updateWindowFlagsForBackpress(true);
+            mExpandedViewManager.updateWindowFlagsForBackpress(true);
         }
         setOnKeyListener(mKeyListener);
     }
@@ -159,8 +163,8 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        if (mController != null) {
-            mController.updateWindowFlagsForBackpress(false);
+        if (mExpandedViewManager != null) {
+            mExpandedViewManager.updateWindowFlagsForBackpress(false);
         }
         setOnKeyListener(null);
     }
@@ -177,15 +181,15 @@
             mRecyclerView.addItemDecoration(new OverflowItemDecoration());
         }
         mAdapter = new BubbleOverflowAdapter(getContext(), mOverflowBubbles,
-                mController::promoteBubbleFromOverflow,
-                mController.getPositioner());
+                mExpandedViewManager::promoteBubbleFromOverflow,
+                mPositioner);
         mRecyclerView.setAdapter(mAdapter);
 
         mOverflowBubbles.clear();
-        mOverflowBubbles.addAll(mController.getOverflowBubbles());
+        mOverflowBubbles.addAll(mExpandedViewManager.getOverflowBubbles());
         mAdapter.notifyDataSetChanged();
 
-        mController.setOverflowListener(mDataListener);
+        mExpandedViewManager.setOverflowListener(mDataListener);
         updateEmptyStateVisibility();
         updateTheme();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt
new file mode 100644
index 0000000..230626f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+/** Factory for creating [BubbleTaskView]s. */
+fun interface BubbleTaskViewFactory {
+    /** Creates a new instance of [BubbleTaskView]. */
+    fun create(): BubbleTaskView
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 530ec5a..21b70b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -62,7 +62,7 @@
     }
 
     private final Context mContext;
-    private final BubbleController mController;
+    private final BubbleExpandedViewManager mExpandedViewManager;
     private final BubbleTaskViewHelper.Listener mListener;
     private final View mParentView;
 
@@ -142,7 +142,8 @@
                     // the bubble again so we'll just remove it.
                     Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
                             + ", " + e.getMessage() + "; removing bubble");
-                    mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+                    mExpandedViewManager.removeBubble(
+                            getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
                 }
                 mInitialized = true;
             });
@@ -175,7 +176,7 @@
             ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
                     taskId, getBubbleKey());
             if (mBubble != null) {
-                mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+                mExpandedViewManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
             }
             if (mTaskView != null) {
                 mTaskView.release();
@@ -186,19 +187,19 @@
 
         @Override
         public void onBackPressedOnTaskRoot(int taskId) {
-            if (mTaskId == taskId && mController.isStackExpanded()) {
+            if (mTaskId == taskId && mExpandedViewManager.isStackExpanded()) {
                 mListener.onBackPressed();
             }
         }
     };
 
     public BubbleTaskViewHelper(Context context,
-            BubbleController controller,
+            BubbleExpandedViewManager expandedViewManager,
             BubbleTaskViewHelper.Listener listener,
             BubbleTaskView bubbleTaskView,
             View parent) {
         mContext = context;
-        mController = controller;
+        mExpandedViewManager = expandedViewManager;
         mListener = listener;
         mParentView = parent;
         mTaskView = bubbleTaskView.getTaskView();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 5fc10a9..69119cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -70,7 +70,9 @@
 
     private Bubble mBubble;
     private WeakReference<Context> mContext;
-    private WeakReference<BubbleController> mController;
+    private WeakReference<BubbleExpandedViewManager> mExpandedViewManager;
+    private WeakReference<BubbleTaskViewFactory> mTaskViewFactory;
+    private WeakReference<BubblePositioner> mPositioner;
     private WeakReference<BubbleStackView> mStackView;
     private WeakReference<BubbleBarLayerView> mLayerView;
     private BubbleIconFactory mIconFactory;
@@ -84,7 +86,9 @@
      */
     BubbleViewInfoTask(Bubble b,
             Context context,
-            BubbleController controller,
+            BubbleExpandedViewManager expandedViewManager,
+            BubbleTaskViewFactory taskViewFactory,
+            BubblePositioner positioner,
             @Nullable BubbleStackView stackView,
             @Nullable BubbleBarLayerView layerView,
             BubbleIconFactory factory,
@@ -93,7 +97,9 @@
             Executor mainExecutor) {
         mBubble = b;
         mContext = new WeakReference<>(context);
-        mController = new WeakReference<>(controller);
+        mExpandedViewManager = new WeakReference<>(expandedViewManager);
+        mTaskViewFactory = new WeakReference<>(taskViewFactory);
+        mPositioner = new WeakReference<>(positioner);
         mStackView = new WeakReference<>(stackView);
         mLayerView = new WeakReference<>(layerView);
         mIconFactory = factory;
@@ -109,11 +115,13 @@
             return null;
         }
         if (mLayerView.get() != null) {
-            return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(),
-                    mLayerView.get(), mIconFactory, mBubble, mSkipInflation);
+            return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(),
+                    mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory,
+                    mBubble, mSkipInflation);
         } else {
-            return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(),
-                    mIconFactory, mBubble, mSkipInflation);
+            return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(),
+                    mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory,
+                    mBubble, mSkipInflation);
         }
     }
 
@@ -135,7 +143,7 @@
     }
 
     private boolean verifyState() {
-        if (mController.get().isShowingAsBubbleBar()) {
+        if (mExpandedViewManager.get().isShowingAsBubbleBar()) {
             return mLayerView.get() != null;
         } else {
             return mStackView.get() != null;
@@ -167,18 +175,23 @@
         Bitmap badgeBitmap;
 
         @Nullable
-        public static BubbleViewInfo populateForBubbleBar(Context c, BubbleController controller,
-                BubbleBarLayerView layerView, BubbleIconFactory iconFactory, Bubble b,
+        public static BubbleViewInfo populateForBubbleBar(Context c,
+                BubbleExpandedViewManager expandedViewManager,
+                BubbleTaskViewFactory taskViewFactory,
+                BubblePositioner positioner,
+                BubbleBarLayerView layerView,
+                BubbleIconFactory iconFactory,
+                Bubble b,
                 boolean skipInflation) {
             BubbleViewInfo info = new BubbleViewInfo();
 
             if (!skipInflation && !b.isInflated()) {
-                BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(c, controller);
+                BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
                 LayoutInflater inflater = LayoutInflater.from(c);
                 info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
                         R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
                 info.bubbleBarExpandedView.initialize(
-                        controller, false /* isOverflow */, bubbleTaskView);
+                        expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView);
             }
 
             if (!populateCommonInfo(info, c, b, iconFactory)) {
@@ -191,8 +204,13 @@
 
         @VisibleForTesting
         @Nullable
-        public static BubbleViewInfo populate(Context c, BubbleController controller,
-                BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b,
+        public static BubbleViewInfo populate(Context c,
+                BubbleExpandedViewManager expandedViewManager,
+                BubbleTaskViewFactory taskViewFactory,
+                BubblePositioner positioner,
+                BubbleStackView stackView,
+                BubbleIconFactory iconFactory,
+                Bubble b,
                 boolean skipInflation) {
             BubbleViewInfo info = new BubbleViewInfo();
 
@@ -201,13 +219,14 @@
                 LayoutInflater inflater = LayoutInflater.from(c);
                 info.imageView = (BadgedImageView) inflater.inflate(
                         R.layout.bubble_view, stackView, false /* attachToRoot */);
-                info.imageView.initialize(controller.getPositioner());
+                info.imageView.initialize(positioner);
 
-                BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(c, controller);
+                BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
                 info.expandedView = (BubbleExpandedView) inflater.inflate(
                         R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
                 info.expandedView.initialize(
-                        controller, stackView, false /* isOverflow */, bubbleTaskView);
+                        expandedViewManager, stackView, positioner, false /* isOverflow */,
+                        bubbleTaskView);
             }
 
             if (!populateCommonInfo(info, c, b, iconFactory)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 73a9cf4..ebb8e3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -36,8 +36,9 @@
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleExpandedViewManager;
 import com.android.wm.shell.bubbles.BubbleOverflowContainerView;
+import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleTaskView;
 import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -45,11 +46,7 @@
 
 import java.util.function.Supplier;
 
-/**
- * Expanded view of a bubble when it's part of the bubble bar.
- *
- * {@link BubbleController#isShowingAsBubbleBar()}
- */
+/** Expanded view of a bubble when it's part of the bubble bar. */
 public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener {
     /**
      * The expanded view listener notifying the {@link BubbleBarLayerView} about the internal
@@ -67,7 +64,7 @@
     private static final String TAG = BubbleBarExpandedView.class.getSimpleName();
     private static final int INVALID_TASK_ID = -1;
 
-    private BubbleController mController;
+    private BubbleExpandedViewManager mManager;
     private boolean mIsOverflow;
     private BubbleTaskViewHelper mBubbleTaskViewHelper;
     private BubbleBarMenuViewController mMenuViewController;
@@ -133,20 +130,22 @@
         mMenuViewController.hideMenu(false /* animated */);
     }
 
-    /** Set the BubbleController on the view, must be called before doing anything else. */
-    public void initialize(BubbleController controller, boolean isOverflow,
+    /** Initializes the view, must be called before doing anything else. */
+    public void initialize(BubbleExpandedViewManager expandedViewManager,
+            BubblePositioner positioner,
+            boolean isOverflow,
             @Nullable BubbleTaskView bubbleTaskView) {
-        mController = controller;
+        mManager = expandedViewManager;
         mIsOverflow = isOverflow;
 
         if (mIsOverflow) {
             mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
                     R.layout.bubble_overflow_container, null /* root */);
-            mOverflowView.setBubbleController(mController);
+            mOverflowView.initialize(expandedViewManager, positioner);
             addView(mOverflowView);
         } else {
             mTaskView = bubbleTaskView.getTaskView();
-            mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
+            mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, expandedViewManager,
                     /* listener= */ this, bubbleTaskView,
                     /* viewParent= */ this);
             if (mTaskView.getParent() != null) {
@@ -178,13 +177,13 @@
 
             @Override
             public void onOpenAppSettings(Bubble bubble) {
-                mController.collapseStack();
+                mManager.collapseStack();
                 mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser());
             }
 
             @Override
             public void onDismissBubble(Bubble bubble) {
-                mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
+                mManager.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
             }
         });
         mHandleView.setOnClickListener(view -> {
@@ -279,7 +278,7 @@
         if (mMenuViewController.isMenuVisible()) {
             mMenuViewController.hideMenu(/* animated = */ true);
         } else {
-            mController.collapseStack();
+            mManager.collapseStack();
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
index f5b0174..094af96 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
@@ -45,18 +44,22 @@
 
     private TestableBubblePositioner mPositioner;
     private BubbleOverflow mOverflow;
+    private BubbleExpandedViewManager mExpandedViewManager;
 
     @Mock
     private BubbleController mBubbleController;
+    @Mock
+    private BubbleStackView mBubbleStackView;
 
     @Before
-    public void setUp() throws Exception {
+    public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(mBubbleController);
         mPositioner = new TestableBubblePositioner(mContext,
                 mContext.getSystemService(WindowManager.class));
         when(mBubbleController.getPositioner()).thenReturn(mPositioner);
-        when(mBubbleController.getStackView()).thenReturn(mock(BubbleStackView.class));
+        when(mBubbleController.getStackView()).thenReturn(mBubbleStackView);
 
         mOverflow = new BubbleOverflow(mContext, mPositioner);
     }
@@ -65,7 +68,7 @@
     public void test_initialize_forStack() {
         assertThat(mOverflow.getExpandedView()).isNull();
 
-        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
+        mOverflow.initialize(mExpandedViewManager, mBubbleStackView, mPositioner);
 
         assertThat(mOverflow.getExpandedView()).isNotNull();
         assertThat(mOverflow.getExpandedView().getBubbleKey()).isEqualTo(BubbleOverflow.KEY);
@@ -74,7 +77,7 @@
 
     @Test
     public void test_initialize_forBubbleBar() {
-        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ true);
+        mOverflow.initializeForBubbleBar(mExpandedViewManager, mPositioner);
 
         assertThat(mOverflow.getBubbleBarExpandedView()).isNotNull();
         assertThat(mOverflow.getExpandedView()).isNull();
@@ -82,11 +85,10 @@
 
     @Test
     public void test_cleanUpExpandedState() {
-        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
+        mOverflow.initialize(mExpandedViewManager, mBubbleStackView, mPositioner);
         assertThat(mOverflow.getExpandedView()).isNotNull();
 
         mOverflow.cleanUpExpandedState();
         assertThat(mOverflow.getExpandedView()).isNull();
     }
-
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 1668e37..ae39fbc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -44,6 +44,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskView
 import com.android.wm.shell.taskview.TaskViewTransitions
 import com.android.wm.shell.transition.Transitions
 import com.google.common.truth.Truth.assertThat
@@ -55,6 +56,7 @@
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
+import java.util.concurrent.Executor
 
 /** Tests for loading / inflating views & icons for a bubble. */
 @SmallTest
@@ -65,11 +67,16 @@
     private lateinit var metadataFlagListener: Bubbles.BubbleMetadataFlagListener
     private lateinit var iconFactory: BubbleIconFactory
     private lateinit var bubble: Bubble
-
     private lateinit var bubbleController: BubbleController
     private lateinit var mainExecutor: ShellExecutor
     private lateinit var bubbleStackView: BubbleStackView
     private lateinit var bubbleBarLayerView: BubbleBarLayerView
+    private lateinit var bubblePositioner: BubblePositioner
+    private lateinit var expandedViewManager: BubbleExpandedViewManager
+
+    private val bubbleTaskViewFactory = BubbleTaskViewFactory {
+        BubbleTaskView(mock<TaskView>(), mock<Executor>())
+    }
 
     @Before
     fun setup() {
@@ -88,7 +95,7 @@
         val shellInit = ShellInit(mainExecutor)
         val shellCommandHandler = ShellCommandHandler()
         val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
-        val bubblePositioner = BubblePositioner(context, windowManager)
+        bubblePositioner = BubblePositioner(context, windowManager)
         val bubbleData =
             BubbleData(
                 context,
@@ -143,6 +150,7 @@
                 bubbleController,
                 mainExecutor
             )
+        expandedViewManager = BubbleExpandedViewManager.fromBubbleController(bubbleController)
         bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
     }
 
@@ -152,7 +160,9 @@
         val info =
             BubbleViewInfoTask.BubbleViewInfo.populate(
                 context,
-                bubbleController,
+                expandedViewManager,
+                bubbleTaskViewFactory,
+                bubblePositioner,
                 bubbleStackView,
                 iconFactory,
                 bubble,
@@ -178,7 +188,9 @@
         val info =
             BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
                 context,
-                bubbleController,
+                expandedViewManager,
+                bubbleTaskViewFactory,
+                bubblePositioner,
                 bubbleBarLayerView,
                 iconFactory,
                 bubble,
@@ -212,7 +224,9 @@
         val info =
             BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
                 context,
-                bubbleController,
+                expandedViewManager,
+                bubbleTaskViewFactory,
+                bubblePositioner,
                 bubbleBarLayerView,
                 iconFactory,
                 bubble,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index d45a9a9..8d933dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -176,9 +176,11 @@
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleEducationController;
 import com.android.wm.shell.bubbles.BubbleEntry;
+import com.android.wm.shell.bubbles.BubbleExpandedViewManager;
 import com.android.wm.shell.bubbles.BubbleLogger;
 import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubbleStackView;
+import com.android.wm.shell.bubbles.BubbleTaskView;
 import com.android.wm.shell.bubbles.BubbleViewInfoTask;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -195,6 +197,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskView;
 import com.android.wm.shell.taskview.TaskViewTransitions;
 import com.android.wm.shell.transition.Transitions;
 
@@ -214,6 +217,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import kotlinx.coroutines.test.TestScope;
 
@@ -1416,7 +1420,9 @@
                 .thenReturn(userContext);
 
         BubbleViewInfoTask.BubbleViewInfo info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
-                mBubbleController,
+                BubbleExpandedViewManager.fromBubbleController(mBubbleController),
+                () -> new BubbleTaskView(mock(TaskView.class), mock(Executor.class)),
+                mPositioner,
                 mBubbleController.getStackView(),
                 new BubbleIconFactory(mContext,
                         mContext.getResources().getDimensionPixelSize(com.android.wm.shell.R.dimen.bubble_size),