Merge "Decouple BubbleController from some bubble classes" into main
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 945b8ac..5f392a8 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
@@ -173,7 +173,7 @@
     private final Context mContext;
     private final BubblesImpl mImpl = new BubblesImpl();
     private Bubbles.BubbleExpandListener mExpandListener;
-    @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
+    @Nullable private final BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
     private final FloatingContentCoordinator mFloatingContentCoordinator;
     private final BubbleDataRepository mDataRepository;
     private final WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -197,12 +197,12 @@
     private final Handler mMainHandler;
     private final ShellExecutor mBackgroundExecutor;
 
-    private BubbleLogger mLogger;
-    private BubbleData mBubbleData;
+    private final BubbleLogger mLogger;
+    private final BubbleData mBubbleData;
     @Nullable private BubbleStackView mStackView;
     @Nullable private BubbleBarLayerView mLayerView;
     private BubbleIconFactory mBubbleIconFactory;
-    private BubblePositioner mBubblePositioner;
+    private final BubblePositioner mBubblePositioner;
     private Bubbles.SysuiProxy mSysuiProxy;
 
     // Tracks the id of the current (foreground) user.
@@ -232,13 +232,17 @@
     /** Whether or not the BubbleStackView has been added to the WindowManager. */
     private boolean mAddedToWindowManager = false;
 
-    /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */
+    /**
+     * Saved screen density, used to detect display size changes in {@link #onConfigurationChanged}.
+     */
     private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
 
-    /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/
-    private Rect mScreenBounds = new Rect();
+    /**
+     * Saved screen bounds, used to detect screen size changes in {@link #onConfigurationChanged}.
+     */
+    private final Rect mScreenBounds = new Rect();
 
-    /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */
+    /** Saved font scale, used to detect font size changes in {@link #onConfigurationChanged}. */
     private float mFontScale = 0;
 
     /** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
@@ -253,9 +257,9 @@
     private boolean mIsStatusBarShade = true;
 
     /** One handed mode controller to register transition listener. */
-    private Optional<OneHandedController> mOneHandedOptional;
+    private final Optional<OneHandedController> mOneHandedOptional;
     /** Drag and drop controller to register listener for onDragStarted. */
-    private Optional<DragAndDropController> mDragAndDropController;
+    private final Optional<DragAndDropController> mDragAndDropController;
     /** Used to send bubble events to launcher. */
     private Bubbles.BubbleStateListener mBubbleStateListener;
 
@@ -731,9 +735,11 @@
             }
         } else {
             if (mStackView == null) {
+                BubbleStackViewManager bubbleStackViewManager =
+                        BubbleStackViewManager.fromBubbleController(this);
                 mStackView = new BubbleStackView(
-                        mContext, this, mBubbleData, mSurfaceSynchronizer,
-                        mFloatingContentCoordinator, this, mMainExecutor);
+                        mContext, bubbleStackViewManager, mBubblePositioner, mBubbleData,
+                        mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor);
                 mStackView.onOrientationChanged();
                 if (mExpandListener != null) {
                     mStackView.setExpandListener(mExpandListener);
@@ -893,7 +899,6 @@
      * Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
      * added in the meantime.
      */
-    @VisibleForTesting
     public void onAllBubblesAnimatedOut() {
         if (mStackView != null) {
             mStackView.setVisibility(INVISIBLE);
@@ -1047,7 +1052,6 @@
         return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
     }
 
-    @VisibleForTesting
     public boolean isStackExpanded() {
         return mBubbleData.isExpanded();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index a619401..9facef3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -204,7 +204,7 @@
                     Choreographer.getInstance().postFrameCallback(frameCallback);
                 }
             };
-    private final BubbleController mBubbleController;
+    private final BubbleStackViewManager mManager;
     private final BubbleData mBubbleData;
     private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider;
     private StackViewState mStackViewState = new StackViewState();
@@ -858,6 +858,7 @@
 
     private BubbleOverflow mBubbleOverflow;
     private StackEducationView mStackEduView;
+    private StackEducationView.Manager mStackEducationViewManager;
     private ManageEducationView mManageEduView;
     private DismissView mDismissView;
 
@@ -873,15 +874,16 @@
     private BubblePositioner mPositioner;
 
     @SuppressLint("ClickableViewAccessibility")
-    public BubbleStackView(Context context, BubbleController bubbleController,
-            BubbleData data, @Nullable SurfaceSynchronizer synchronizer,
+    public BubbleStackView(Context context, BubbleStackViewManager bubbleStackViewManager,
+            BubblePositioner bubblePositioner, BubbleData data,
+            @Nullable SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
             Bubbles.SysuiProxy.Provider sysuiProxyProvider,
             ShellExecutor mainExecutor) {
         super(context);
 
         mMainExecutor = mainExecutor;
-        mBubbleController = bubbleController;
+        mManager = bubbleStackViewManager;
         mBubbleData = data;
         mSysuiProxyProvider = sysuiProxyProvider;
 
@@ -893,7 +895,7 @@
         mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
         int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
 
-        mPositioner = mBubbleController.getPositioner();
+        mPositioner = bubblePositioner;
 
         final TypedArray ta = mContext.obtainStyledAttributes(
                 new int[]{android.R.attr.dialogCornerRadius});
@@ -903,7 +905,7 @@
         final Runnable onBubbleAnimatedOut = () -> {
             if (getBubbleCount() == 0) {
                 mExpandedViewTemporarilyHidden = false;
-                mBubbleController.onAllBubblesAnimatedOut();
+                mManager.onAllBubblesAnimatedOut();
             }
         };
         mStackAnimationController = new StackAnimationController(
@@ -1383,7 +1385,9 @@
             return false;
         }
         if (mStackEduView == null) {
-            mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+            mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+            mStackEduView =
+                    new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
             addView(mStackEduView);
         }
         return showStackEdu();
@@ -1412,7 +1416,9 @@
     private void updateUserEdu() {
         if (isStackEduVisible() && !mStackEduView.isHiding()) {
             removeView(mStackEduView);
-            mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+            mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+            mStackEduView =
+                    new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
             addView(mStackEduView);
             showStackEdu();
         }
@@ -2106,7 +2112,7 @@
             logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
             logBubbleEvent(mExpandedBubble,
                     FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
-            mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
+            mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> {
                 if (!notifPanelExpanded && mIsExpanded) {
                     startMonitoringSwipeUpGesture();
                 }
@@ -2227,7 +2233,7 @@
      */
     void hideCurrentInputMethod() {
         mPositioner.setImeVisible(false, 0);
-        mBubbleController.hideCurrentInputMethod();
+        mManager.hideCurrentInputMethod();
     }
 
     /** Set the stack position to whatever the positioner says. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
new file mode 100644
index 0000000..fb597a0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
@@ -0,0 +1,60 @@
+/*
+ * 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
+
+import java.util.function.Consumer
+
+/** Defines callbacks from [BubbleStackView] to its manager. */
+interface BubbleStackViewManager {
+
+    /** Notifies that all bubbles animated out. */
+    fun onAllBubblesAnimatedOut()
+
+    /** Notifies whether backpress should be intercepted. */
+    fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+
+    /**
+     * Checks the current expansion state of the notification panel, and invokes [callback] with the
+     * result.
+     */
+    fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>)
+
+    /** Requests to hide the current input method. */
+    fun hideCurrentInputMethod()
+
+    companion object {
+
+        @JvmStatic
+        fun fromBubbleController(controller: BubbleController) = object : BubbleStackViewManager {
+            override fun onAllBubblesAnimatedOut() {
+                controller.onAllBubblesAnimatedOut()
+            }
+
+            override fun updateWindowFlagsForBackpress(interceptBack: Boolean) {
+                controller.updateWindowFlagsForBackpress(interceptBack)
+            }
+
+            override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {
+                controller.isNotificationPanelExpanded(callback)
+            }
+
+            override fun hideCurrentInputMethod() {
+                controller.hideCurrentInputMethod()
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 95f1017..c4108c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -35,7 +35,7 @@
 class StackEducationView(
     context: Context,
     private val positioner: BubblePositioner,
-    private val controller: BubbleController
+    private val manager: Manager
 ) : LinearLayout(context) {
 
     companion object {
@@ -44,6 +44,12 @@
         private const val ANIMATE_DURATION_SHORT: Long = 40
     }
 
+    /** Callbacks to notify managers of [StackEducationView] about events. */
+    interface Manager {
+        /** Notifies whether backpress should be intercepted. */
+        fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+    }
+
     private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
     private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
     private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) }
@@ -93,7 +99,7 @@
     override fun onDetachedFromWindow() {
         super.onDetachedFromWindow()
         setOnKeyListener(null)
-        controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+        manager.updateWindowFlagsForBackpress(false /* interceptBack */)
     }
 
     private fun setTextColor() {
@@ -124,7 +130,7 @@
         isHiding = false
         if (visibility == VISIBLE) return false
 
-        controller.updateWindowFlagsForBackpress(true /* interceptBack */)
+        manager.updateWindowFlagsForBackpress(true /* interceptBack */)
         layoutParams.width =
                 if (positioner.isLargeScreen || positioner.isLandscape)
                     context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
@@ -185,7 +191,7 @@
         if (visibility != VISIBLE || isHiding) return
         isHiding = true
 
-        controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+        manager.updateWindowFlagsForBackpress(false /* interceptBack */)
         animate()
             .alpha(0f)
             .setDuration(if (isExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
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 4878df8..75965d6 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
@@ -56,9 +56,7 @@
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
-/**
- * Tests for loading / inflating views & icons for a bubble.
- */
+/** Tests for loading / inflating views & icons for a bubble. */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
@@ -76,25 +74,33 @@
     @Before
     fun setup() {
         metadataFlagListener = Bubbles.BubbleMetadataFlagListener {}
-        iconFactory = BubbleIconFactory(context,
+        iconFactory =
+            BubbleIconFactory(
+                context,
                 60,
                 30,
                 Color.RED,
-                mContext.resources.getDimensionPixelSize(
-                        R.dimen.importance_ring_stroke_width))
+                mContext.resources.getDimensionPixelSize(R.dimen.importance_ring_stroke_width)
+            )
 
         mainExecutor = TestShellExecutor()
         val windowManager = context.getSystemService(WindowManager::class.java)
         val shellInit = ShellInit(mainExecutor)
         val shellCommandHandler = ShellCommandHandler()
-        val shellController = ShellController(context, shellInit, shellCommandHandler,
-                mainExecutor)
+        val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
         val bubblePositioner = BubblePositioner(context, windowManager)
-        val bubbleData = BubbleData(context, mock<BubbleLogger>(), bubblePositioner,
-                BubbleEducationController(context), mainExecutor)
+        val bubbleData =
+            BubbleData(
+                context,
+                mock<BubbleLogger>(),
+                bubblePositioner,
+                BubbleEducationController(context),
+                mainExecutor
+            )
         val surfaceSynchronizer = { obj: Runnable -> obj.run() }
 
-        bubbleController = BubbleController(
+        bubbleController =
+            BubbleController(
                 context,
                 shellInit,
                 shellCommandHandler,
@@ -122,18 +128,36 @@
                 mock<Transitions>(),
                 mock<SyncTransactionQueue>(),
                 mock<IWindowManager>(),
-                mock<BubbleProperties>())
+                mock<BubbleProperties>()
+            )
 
-        bubbleStackView = BubbleStackView(context, bubbleController, bubbleData,
-                surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor)
+        val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController)
+        bubbleStackView =
+            BubbleStackView(
+                context,
+                bubbleStackViewManager,
+                bubblePositioner,
+                bubbleData,
+                surfaceSynchronizer,
+                FloatingContentCoordinator(),
+                bubbleController,
+                mainExecutor
+            )
         bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
     }
 
     @Test
     fun testPopulate() {
         bubble = createBubbleWithShortcut()
-        val info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
-                bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */)
+        val info =
+            BubbleViewInfoTask.BubbleViewInfo.populate(
+                context,
+                bubbleController,
+                bubbleStackView,
+                iconFactory,
+                bubble,
+                false /* skipInflation */
+            )
         assertThat(info!!).isNotNull()
 
         assertThat(info.imageView).isNotNull()
@@ -151,9 +175,15 @@
     @Test
     fun testPopulateForBubbleBar() {
         bubble = createBubbleWithShortcut()
-        val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
-                bubbleController, bubbleBarLayerView, iconFactory, bubble,
-                false /* skipInflation */)
+        val info =
+            BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
+                context,
+                bubbleController,
+                bubbleBarLayerView,
+                iconFactory,
+                bubble,
+                false /* skipInflation */
+            )
         assertThat(info!!).isNotNull()
 
         assertThat(info.imageView).isNull()
@@ -176,12 +206,18 @@
         // exception here if the app has an issue loading the shortcut icon; we default to
         // the app icon in that case / none of the icons will be null.
         val mockIconFactory = mock<BubbleIconFactory>()
-        whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo),
-                any())).doThrow(RuntimeException())
+        whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo), any()))
+            .doThrow(RuntimeException())
 
-        val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
-                bubbleController, bubbleBarLayerView, iconFactory, bubble,
-                true /* skipInflation */)
+        val info =
+            BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
+                context,
+                bubbleController,
+                bubbleBarLayerView,
+                iconFactory,
+                bubble,
+                true /* skipInflation */
+            )
         assertThat(info).isNotNull()
 
         assertThat(info?.shortcutInfo).isNotNull()
@@ -194,8 +230,17 @@
 
     private fun createBubbleWithShortcut(): Bubble {
         val shortcutInfo = ShortcutInfo.Builder(mContext, "mockShortcutId").build()
-        return Bubble("mockKey", shortcutInfo, 1000, Resources.ID_NULL,
-                "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
-                mainExecutor, metadataFlagListener)
+        return Bubble(
+            "mockKey",
+            shortcutInfo,
+            1000,
+            Resources.ID_NULL,
+            "mockTitle",
+            0 /* taskId */,
+            "mockLocus",
+            true /* isDismissible */,
+            mainExecutor,
+            metadataFlagListener
+        )
     }
-}
\ No newline at end of file
+}