diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index 29c34a07bc..cc73776d80 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -565,6 +565,7 @@ public class BubbleBarViewController { mBarView.showDropTarget(showingDropTarget); } + //TODO(b/411505605) remove unused IPC calls and code /** * Notifies the controller that a drag event is over the Bubble Bar drop zone. The controller * will display the appropriate drop target and enter drop target mode. The controller will also diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java index 0a68478fc0..796644d4d1 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java @@ -140,6 +140,7 @@ public class BubbleControllers { return -(int) bubbleStashController.getBubbleBarTranslationY(); } }, + bubbleBarLocationListeners, SystemUiProxy.INSTANCE.get(taskbarControllers.taskbarActivityContext)); mPostInitRunnables.executeAllAndDestroy(); } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/DragToBubbleController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/DragToBubbleController.kt index c1c82584cf..0c879b82c4 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/DragToBubbleController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/DragToBubbleController.kt @@ -25,6 +25,7 @@ import com.android.launcher3.dragndrop.DragController import com.android.launcher3.dragndrop.DragOptions import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.taskbar.bubbles.BubbleBarController.BubbleBarLocationListener import com.android.launcher3.taskbar.bubbles.BubbleBarLocationDropTarget.BubbleBarDropTargetController import com.android.quickstep.SystemUiProxy import com.android.wm.shell.shared.bubbles.BubbleBarLocation @@ -47,10 +48,12 @@ class DragToBubbleController(private val context: Context, bubbleBarContainer: F DragController.DragListener { @VisibleForTesting val dropTargetManager: DropTargetManager - @VisibleForTesting lateinit var bubbleBarLeftDropTarget: BubbleBarLocationDropTarget @VisibleForTesting lateinit var bubbleBarRightDropTarget: BubbleBarLocationDropTarget @VisibleForTesting lateinit var dragZoneFactory: DragZoneFactory + // If item drop is handled the next sysui update will set the bubble bar location + @VisibleForTesting var isItemDropHandled = false + private lateinit var bubbleBarLocationListener: BubbleBarLocationListener private lateinit var systemUiProxy: SystemUiProxy private lateinit var bubbleBarViewController: BubbleBarViewController @@ -61,10 +64,12 @@ class DragToBubbleController(private val context: Context, bubbleBarContainer: F fun init( bubbleBarViewController: BubbleBarViewController, bubbleBarPropertiesProvider: BubbleBarPropertiesProvider, + bubbleBarLocationListener: BubbleBarLocationListener, systemUiProxy: SystemUiProxy, ) { this.bubbleBarViewController = bubbleBarViewController this.systemUiProxy = systemUiProxy + this.bubbleBarLocationListener = bubbleBarLocationListener val dropController: BubbleBarDropTargetController = createDropController() dragZoneFactory = createDragZoneFactory(bubbleBarPropertiesProvider) bubbleBarLeftDropTarget = createDropTarget(dropController, isLeftDropTarget = true) @@ -95,6 +100,7 @@ class DragToBubbleController(private val context: Context, bubbleBarContainer: F } override fun onDragStart(dragObject: DragObject, options: DragOptions) { + isItemDropHandled = false val launcherIcon: DraggedObject = LauncherIcon(bubbleBarViewController.hasBubbles()) {} val dragZones: List = dragZoneFactory.createSortedDragZones(launcherIcon) dropTargetManager.onDragStarted(launcherIcon, dragZones) @@ -110,8 +116,17 @@ class DragToBubbleController(private val context: Context, bubbleBarContainer: F context.isRtl, object : DragToBubblesZoneChangeListener.Callback { + private var currentBarLocation: BubbleBarLocation? = null + override fun onDragEnteredLocation(bubbleBarLocation: BubbleBarLocation?) { bubbleBarViewController.isShowingDropTarget = bubbleBarLocation != null + if (isItemDropHandled) return + val updatedLocation = bubbleBarLocation ?: getStartingBubbleBarLocation() + currentBarLocation = currentBarLocation ?: getStartingBubbleBarLocation() + if (updatedLocation != currentBarLocation) { + currentBarLocation = updatedLocation + bubbleBarLocationListener.onBubbleBarLocationAnimated(updatedLocation) + } } override fun getStartingBubbleBarLocation(): BubbleBarLocation { @@ -122,14 +137,9 @@ class DragToBubbleController(private val context: Context, bubbleBarContainer: F override fun hasBubbles(): Boolean = bubbleBarViewController.hasBubbles() override fun animateBubbleBarLocation(bubbleBarLocation: BubbleBarLocation) { + if (isItemDropHandled) return bubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation) } - - override fun bubbleBarPillowShownAtLocation( - bubbleBarLocation: BubbleBarLocation? - ) { - // TODO(b/411506181) adjust taskbar - } }, ) return DropTargetManager(context, bubbleBarContainer, listener) @@ -154,6 +164,18 @@ class DragToBubbleController(private val context: Context, bubbleBarContainer: F private fun createDropController(): BubbleBarDropTargetController { return object : BubbleBarDropTargetController { override fun onDrop(itemInfo: ItemInfo, isLeftDropTarget: Boolean) { + isItemDropHandled = handleDrop(itemInfo, isLeftDropTarget) + } + + override fun acceptDrop(itemInfo: ItemInfo): Boolean { + return hasShortcutInfo(itemInfo) || itemInfo.intent?.component != null + } + + fun hasShortcutInfo(itemInfo: ItemInfo): Boolean { + return itemInfo is WorkspaceItemInfo && itemInfo.deepShortcutInfo != null + } + + private fun handleDrop(itemInfo: ItemInfo, isLeftDropTarget: Boolean): Boolean { val location = if (isLeftDropTarget) { BubbleBarLocation.LEFT @@ -163,20 +185,13 @@ class DragToBubbleController(private val context: Context, bubbleBarContainer: F if (hasShortcutInfo(itemInfo)) { val si = (itemInfo as WorkspaceItemInfo).deepShortcutInfo systemUiProxy.showShortcutBubble(si, location) - return + return true } - val itemIntent: Intent = itemInfo.intent ?: return - val packageName = itemIntent.component?.packageName ?: return + val itemIntent: Intent = itemInfo.intent ?: return false + val packageName = itemIntent.component?.packageName ?: return false itemIntent.setPackage(packageName) systemUiProxy.showAppBubble(itemIntent, itemInfo.user, location) - } - - override fun acceptDrop(itemInfo: ItemInfo): Boolean { - return hasShortcutInfo(itemInfo) || itemInfo.intent?.component != null - } - - fun hasShortcutInfo(itemInfo: ItemInfo): Boolean { - return itemInfo is WorkspaceItemInfo && itemInfo.deepShortcutInfo != null + return true } } } diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/DragToBubbleControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/DragToBubbleControllerTest.kt index f8534fbe84..17283c37ee 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/DragToBubbleControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/DragToBubbleControllerTest.kt @@ -31,6 +31,7 @@ import com.android.launcher3.DropTarget import com.android.launcher3.DropTarget.DragObject import com.android.launcher3.dragndrop.DragOptions import com.android.launcher3.model.data.AppInfo +import com.android.launcher3.taskbar.bubbles.BubbleBarController.BubbleBarLocationListener import com.android.quickstep.SystemUiProxy import com.android.wm.shell.shared.bubbles.BubbleBarLocation import com.android.wm.shell.shared.bubbles.DeviceConfig @@ -44,7 +45,9 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any +import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.doReturn +import org.mockito.kotlin.inOrder import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.stub @@ -61,6 +64,7 @@ class DragToBubbleControllerTest { private val container = FrameLayout(context) private val bubbleBarViewController: BubbleBarViewController = mock() private val systemUiProxy: SystemUiProxy = mock() + private val bubbleBarLocationListener: BubbleBarLocationListener = mock() private val bubbleBarPropertiesProvider = FakeBubbleBarPropertiesProvider() private val testDragZonesFactory = createTestDragZoneFactory() private val dragObject = DragObject(context) @@ -86,10 +90,12 @@ class DragToBubbleControllerTest { @Before fun setUp() { + prepareBubbleBarViewController() dragToBubbleController = DragToBubbleController(context, container) dragToBubbleController.init( bubbleBarViewController, bubbleBarPropertiesProvider, + bubbleBarLocationListener, systemUiProxy, ) dragToBubbleController.dragZoneFactory = testDragZonesFactory @@ -104,6 +110,7 @@ class DragToBubbleControllerTest { assertThat(secondDropTargetView!!.parent).isEqualTo(container) assertThat(dropTargetView.alpha).isEqualTo(0f) assertThat(secondDropTargetView!!.alpha).isEqualTo(0f) + assertThat(dragToBubbleController.isItemDropHandled).isFalse() } @Test @@ -235,13 +242,168 @@ class DragToBubbleControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync { dragObject.updateXYToCenterOf(leftDropTargetRect) bubbleBarLeftDropTarget.onDragEnter(dragObject) - bubbleBarLeftDropTarget.onDragExit(dragObject) bubbleBarLeftDropTarget.onDrop(dragObject, DragOptions()) + assertThat(dragToBubbleController.isItemDropHandled).isTrue() + bubbleBarLeftDropTarget.onDragExit(dragObject) } verify(systemUiProxy).showAppBubble(itemIntent, appInfo.user, BubbleBarLocation.LEFT) } + @Test + fun dragExitRightZone_noBubbles_listenerNotNotified() { + // Scenario: No bubbles. Drag enters RIGHT, then exits to no particular zone. + // This is distinct as it starts on the default side. + prepareBubbleBarViewController( + hasBubbles = false, + bubbleBarLocation = BubbleBarLocation.RIGHT, + ) + dragToBubbleController.onDragStart(dragObject, DragOptions()) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + dragObject.updateXYToCenterOf(rightDropTargetRect) + bubbleBarRightDropTarget.onDragEnter(dragObject) // Location is the same + } + verify(bubbleBarLocationListener, never()) + .onBubbleBarLocationAnimated(BubbleBarLocation.RIGHT) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + dragObject.updateXY(0, 0) // Move out of all zones + bubbleBarRightDropTarget.onDragExit(dragObject) + } + + // Exiting the RIGHT zone (which is the default) should not re-notify of RIGHT + verify(bubbleBarLocationListener, never()) + .onBubbleBarLocationAnimated(BubbleBarLocation.RIGHT) + } + + @Test + fun onDragEnd_noBubbles_wasDraggingLeft_listenerNotifiedWithDefaultRightLocationAnimated() { + val startingLocation = BubbleBarLocation.RIGHT + // Scenario: No bubbles. Drag was over LEFT zone. Drag ends. + prepareBubbleBarViewController(hasBubbles = false, bubbleBarLocation = startingLocation) + dragToBubbleController.onDragStart(dragObject, DragOptions()) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + dragObject.updateXYToCenterOf(leftDropTargetRect) + bubbleBarLeftDropTarget.onDragEnter(dragObject) + } + // Notifies onBubbleBarLocationAnimated(LEFT) + verify(bubbleBarLocationListener).onBubbleBarLocationAnimated(BubbleBarLocation.LEFT) + clearInvocations(bubbleBarLocationListener) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + dragToBubbleController.onDragEnd() + } + assertThat(dragToBubbleController.isItemDropHandled).isFalse() + + // After drag ends (and no bubbles), the listener should be notified of the default location + verify(bubbleBarLocationListener).onBubbleBarLocationAnimated(startingLocation) + } + + @Test + fun onDragEnd_noBubbles_wasDraggingRight_listenerNotifiedWithDefaultRightLocationAnimated() { + // Scenario: No bubbles. Drag was over RIGHT zone (default side). Drag ends. + prepareBubbleBarViewController(hasBubbles = false) + dragToBubbleController.onDragStart(dragObject, DragOptions()) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + dragObject.updateXYToCenterOf(rightDropTargetRect) + } + verify(bubbleBarLocationListener, never()) + .onBubbleBarLocationAnimated(BubbleBarLocation.RIGHT) + clearInvocations(bubbleBarLocationListener) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + dragObject.updateXY(0, 0) + bubbleBarLeftDropTarget.onDragExit(dragObject) + dragToBubbleController.onDragEnd() + } + + // After drag ends (and no bubbles), listener should not be notified of the default + // location. + verify(bubbleBarLocationListener, never()) + .onBubbleBarLocationAnimated(BubbleBarLocation.RIGHT) + } + + @Test + fun dragEnterLeftZone_bubblesOnLeft_listenerNotNotified() { + // Scenario: Bubbles on LEFT. Drag enters LEFT zone. + prepareBubbleBarViewController( + hasBubbles = true, + bubbleBarLocation = BubbleBarLocation.LEFT, + ) + dragToBubbleController.onDragStart(dragObject, DragOptions()) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + dragObject.updateXYToCenterOf(leftDropTargetRect) + bubbleBarLeftDropTarget.onDragEnter(dragObject) + } + + // Bubbles are already on the LEFT, and drag enters LEFT. + // No new animation to LEFT should be triggered by the zone entry itself. + verify(bubbleBarLocationListener, never()) + .onBubbleBarLocationAnimated(BubbleBarLocation.LEFT) + } + + @Test + fun onDragEnd_bubblesOnLeft_defaultIsLeft_wasDraggingRight_listenerNotifiedLeftAnimated() { + // Scenario: Bubbles on LEFT. Drag was over RIGHT zone. Drag ends. + prepareBubbleBarViewController( + hasBubbles = true, + bubbleBarLocation = BubbleBarLocation.LEFT, + ) + dragToBubbleController.onDragStart(dragObject, DragOptions()) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + dragObject.updateXYToCenterOf(rightDropTargetRect) + bubbleBarRightDropTarget.onDragEnter(dragObject) // Notifies Animated(RIGHT) + } + verify(bubbleBarLocationListener).onBubbleBarLocationAnimated(BubbleBarLocation.RIGHT) + clearInvocations(bubbleBarLocationListener) // Clear the Animated(RIGHT) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + dragObject.updateXY(0, 0) + bubbleBarLeftDropTarget.onDragExit(dragObject) + dragToBubbleController.onDragEnd() + } + + // Bubble bar's final animated location should be LEFT. + verify(bubbleBarLocationListener).onBubbleBarLocationAnimated(BubbleBarLocation.LEFT) + } + + @Test + fun dragEnterLeftThenExitToNoZoneThenEnterRight_noBubbles_listenerSequenceCorrectAnimated() { + // Scenario: No bubbles. Complex drag path: Left -> None -> Right + prepareBubbleBarViewController(hasBubbles = false) + dragToBubbleController.onDragStart(dragObject, DragOptions()) + clearInvocations(bubbleBarLocationListener) + + val inOrder = inOrder(bubbleBarLocationListener) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + // 1. Enter Left + dragObject.updateXYToCenterOf(leftDropTargetRect) + bubbleBarLeftDropTarget.onDragEnter(dragObject) + + // 2. Exit Left to no zone + dragObject.updateXY(0, 0) + bubbleBarLeftDropTarget.onDragExit(dragObject) + + // 3. Enter Right + dragObject.updateXYToCenterOf(rightDropTargetRect) + bubbleBarRightDropTarget.onDragEnter(dragObject) + } + + inOrder + .verify(bubbleBarLocationListener) + .onBubbleBarLocationAnimated(BubbleBarLocation.LEFT) + // Revert to default, following enter of the same zone should not trigger updated + inOrder + .verify(bubbleBarLocationListener) + .onBubbleBarLocationAnimated(BubbleBarLocation.RIGHT) + } + private fun prepareBubbleBarViewController( hasBubbles: Boolean = false, bubbleBarLocation: BubbleBarLocation = BubbleBarLocation.RIGHT,