[1/3] Add DragToBubbleController
Added DragToBubbleController class implementation to handle shell drags.
Bug: 411505605
Flag: com.android.wm.shell.enable_create_any_bubble
Test: tree hugger
Change-Id: Ia1a2b1dc7a16dde05deb3d66d3c122a3572ef38a
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
index 8b50b9c..0d99a23 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
@@ -693,10 +693,10 @@
/** Bubble bar properties for generating a drop target. */
interface BubbleBarPropertiesProvider {
- fun getHeight(): Int
+ fun getHeight(): Int = 0
- fun getWidth(): Int
+ fun getWidth(): Int = 0
- fun getBottomPadding(): Int
+ fun getBottomPadding(): Int = 0
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DraggedObject.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DraggedObject.kt
index fdd2f57..b296ee5 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DraggedObject.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DraggedObject.kt
@@ -25,6 +25,7 @@
data class ExpandedView(val initialLocation: BubbleBarLocation) : DraggedObject
+ // TODO(b/411505605) Remove onDropAction
data class LauncherIcon(val bubbleBarHasBubbles: Boolean, val onDropAction: Runnable) :
DraggedObject
}
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 e8975a6..cd4ad40 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
@@ -941,6 +941,16 @@
}
}
+ /**
+ * Show bubble bar pin view given location.
+ */
+ public void showBubbleBarPinAtLocation(BubbleBarLocation bubbleBarLocation) {
+ if (isShowingAsBubbleBar() && mBubbleStateListener != null) {
+ // TODO(b/411505605) show bubble bar drop target in launcher since taskbar window is
+ // layered on top of the shell drag window
+ }
+ }
+
@Override
public void onDragItemOverBubbleBarDragZone(@Nullable BubbleBarLocation bubbleBarLocation) {
if (bubbleBarLocation == null) return;
@@ -1435,7 +1445,6 @@
* Whether or not there are bubbles present, regardless of them being visible on the
* screen (e.g. if on AOD).
*/
- @VisibleForTesting
public boolean hasBubbles() {
if (mStackView == null && mLayerView == null) {
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/DragToBubbleController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/DragToBubbleController.kt
new file mode 100644
index 0000000..f770e86
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/DragToBubbleController.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2025 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.bar
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import com.android.wm.shell.bubbles.BubbleController
+import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.draganddrop.DragAndDropController.DragAndDropListener
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.DragZone
+import com.android.wm.shell.shared.bubbles.DragZoneFactory
+import com.android.wm.shell.shared.bubbles.DragZoneFactory.BubbleBarPropertiesProvider
+import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode
+import com.android.wm.shell.shared.bubbles.DraggedObject
+import com.android.wm.shell.shared.bubbles.DraggedObject.LauncherIcon
+import com.android.wm.shell.shared.bubbles.DropTargetManager
+
+/** Handles scenarios when launcher icon is being dragged to the bubble bar drop zones. */
+class DragToBubbleController(
+ val context: Context,
+ val bubblePositioner: BubblePositioner,
+ val bubbleController: BubbleController,
+) : DragAndDropListener {
+
+ private val containerView: FrameLayout =
+ FrameLayout(context).apply {
+ layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ )
+ }
+
+ private val isRtl: Boolean
+ get() = containerView.isLayoutRtl
+
+ private val dropTargetManager: DropTargetManager =
+ DropTargetManager(context, containerView, createDragZoneListener())
+
+ private val dragZoneFactory = createDragZoneFactory()
+ private var lastDragZone: DragZone? = null
+
+ /** Returns the container view in which drop targets are added. */
+ fun getDropTargetContainer(): ViewGroup = containerView
+
+ /** Called when the drag is tarted. */
+ override fun onDragStarted() {
+ if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
+ return
+ }
+ val draggedObject = LauncherIcon(bubbleBarHasBubbles = true) {}
+ val dragZones = dragZoneFactory.createSortedDragZones(draggedObject)
+ dropTargetManager.onDragStarted(draggedObject, dragZones)
+ }
+
+ /**
+ * Called when drag position is updated.
+ *
+ * @return true if drag is over any bubble bar drop zones
+ */
+ fun onDragUpdate(x: Int, y: Int): Boolean {
+ lastDragZone = dropTargetManager.onDragUpdated(x, y)
+ return lastDragZone != null
+ }
+
+ /** Called when the item with the [ShortcutInfo] is dropped over the bubble bar drop target. */
+ fun onItemDropped(shortcutInfo: ShortcutInfo) {
+ val dropLocation = lastDragZone?.getBubbleBarLocation() ?: return
+ bubbleController.expandStackAndSelectBubble(shortcutInfo, dropLocation)
+ }
+
+ /**
+ * Called when the item with the [PendingIntent] and the [UserHandle] is dropped over the
+ * bubble bar drop target.
+ */
+ fun onItemDropped(pendingIntent: PendingIntent, userHandle: UserHandle) {
+ val dropLocation = lastDragZone?.getBubbleBarLocation() ?: return
+ bubbleController.expandStackAndSelectBubble(pendingIntent, userHandle, dropLocation)
+ }
+
+ /** Called when the drag is ended. */
+ override fun onDragEnded() {
+ dropTargetManager.onDragEnded()
+ }
+
+ private fun createDragZoneFactory(): DragZoneFactory {
+ return DragZoneFactory(
+ context,
+ bubblePositioner.currentConfig,
+ { SplitScreenMode.UNSUPPORTED },
+ { false },
+ object : BubbleBarPropertiesProvider {},
+ )
+ }
+
+ private fun createDragZoneListener() = object : DropTargetManager.DragZoneChangedListener {
+
+ private var lastUpdateLocation: BubbleBarLocation? = null
+ private val isLocationChangedFromOriginal: Boolean
+ get() = lastUpdateLocation != null
+ && isDifferentSides(lastUpdateLocation, bubbleController.bubbleBarLocation)
+
+ override fun onInitialDragZoneSet(dragZone: DragZone?) {}
+
+ override fun onDragZoneChanged(
+ draggedObject: DraggedObject,
+ from: DragZone?,
+ to: DragZone?,
+ ) {
+ val updateLocation = getBarLocation(to)
+ updateBubbleBarLocation(updateLocation)
+ lastUpdateLocation = updateLocation
+ }
+
+ override fun onDragEnded(zone: DragZone?) {
+ updateBubbleBarLocation(updateLocation = null)
+ }
+
+ fun updateBubbleBarLocation(updateLocation: BubbleBarLocation?) {
+ val updatedBefore = lastUpdateLocation != null
+ val originalLocation = bubbleController.bubbleBarLocation
+ val isLocationUpdated = isDifferentSides(lastUpdateLocation, updateLocation)
+ if (!bubbleController.hasBubbles()) {
+ // has no bubbles, so showing the pin view
+ if (updateLocation == null || !updatedBefore || isLocationUpdated) {
+ bubbleController.showBubbleBarPinAtLocation(updateLocation)
+ }
+ return
+ }
+ if (updateLocation == null && isLocationChangedFromOriginal) {
+ bubbleController.animateBubbleBarLocation(originalLocation)
+ return
+ }
+ if (updatedBefore && isLocationUpdated) {
+ // updated before and location updated - update to new location
+ bubbleController.animateBubbleBarLocation(updateLocation)
+ return
+ }
+ if (!updatedBefore && isDifferentSides(originalLocation, updateLocation)) {
+ // not updated before and location changed from original
+ bubbleController.animateBubbleBarLocation(updateLocation)
+ }
+ }
+
+ fun getBarLocation(dragZone: DragZone?): BubbleBarLocation? {
+ return when (dragZone) {
+ is DragZone.Bubble.Left -> BubbleBarLocation.LEFT
+ is DragZone.Bubble.Right -> BubbleBarLocation.RIGHT
+ else -> null
+ }
+ }
+
+ fun isDifferentSides(f: BubbleBarLocation?, s: BubbleBarLocation?): Boolean {
+ return f != null && s != null && f.isOnLeft(isRtl) != s.isOnLeft(isRtl)
+ }
+ }
+
+ private fun DragZone.getBubbleBarLocation(): BubbleBarLocation? =
+ when (this) {
+ is DragZone.Bubble.Left -> BubbleBarLocation.LEFT
+ is DragZone.Bubble.Right -> BubbleBarLocation.RIGHT
+ else -> null
+ }
+}