Loading libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/DragToBubbleControllerTest.kt +24 −5 Original line number Diff line number Diff line Loading @@ -129,7 +129,7 @@ class DragToBubbleControllerTest { @Test fun droppedItemWithIntentAtTheLeftDropZone_noBubblesOnTheRight_bubbleCreationRequested() { val bubbleBarOriginalLocation = BubbleBarLocation.RIGHT prepareBubbleController(hasBubbles = false, bubbleBarLocation = bubbleBarOriginalLocation) prepareBubbleController(bubbleBarLocation = bubbleBarOriginalLocation) val pendingIntent = PendingIntent(mock<IIntentSender>()) val userHandle = UserHandle(0) Loading @@ -148,7 +148,7 @@ class DragToBubbleControllerTest { @Test fun droppedItemWithShortcutInfoAtTheLeftDropZone_noBubblesOnTheRight_bubbleCreationRequested() { val bubbleBarOriginalLocation = BubbleBarLocation.RIGHT prepareBubbleController(hasBubbles = false, bubbleBarLocation = bubbleBarOriginalLocation) prepareBubbleController(bubbleBarLocation = bubbleBarOriginalLocation) val shortcutInfo = ShortcutInfo.Builder(context, "id").setLongLabel("Shortcut").build() dragToBubbleController.onDragStarted() Loading @@ -165,7 +165,7 @@ class DragToBubbleControllerTest { @Test fun droppedItem_afterNewDragStartedOnItemDropCleared() { val bubbleBarOriginalLocation = BubbleBarLocation.RIGHT prepareBubbleController(hasBubbles = false, bubbleBarLocation = bubbleBarOriginalLocation) prepareBubbleController(bubbleBarLocation = bubbleBarOriginalLocation) val shortcutInfo = ShortcutInfo.Builder(context, "id").setLongLabel("Shortcut").build() runOnMainSync { dragToBubbleController.onDragStarted() Loading @@ -191,16 +191,35 @@ class DragToBubbleControllerTest { .expandStackAndSelectBubble(any<PendingIntent>(), any(), any()) } @Test fun hideDropTargets_dragEnteredDropZone_dropTargetsHidden() { // Given dragToBubbleController.onDragStarted() runOnMainSync { dragToBubbleController.onDragUpdate(rightDropRect.centerX(), rightDropRect.centerY()) animatorTestRule.advanceTimeBy(250) } assertThat(dropTargetContainer.childCount).isEqualTo(1) assertThat(dropTargetView.alpha).isEqualTo(1f) // When runOnMainSync { dragToBubbleController.hideDropTargets() animatorTestRule.advanceTimeBy(250) } // Then assertThat(dropTargetView.alpha).isEqualTo(0f) } private fun runOnMainSync(action: () -> Unit) { InstrumentationRegistry.getInstrumentation().runOnMainSync { action() } } private fun prepareBubbleController( hasBubbles: Boolean = false, bubbleBarLocation: BubbleBarLocation = BubbleBarLocation.RIGHT, ) { bubbleController.stub { on { hasBubbles() } doReturn hasBubbles on { getBubbleBarLocation() } doReturn bubbleBarLocation } } Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt +23 −1 Original line number Diff line number Diff line Loading @@ -26,6 +26,8 @@ import androidx.annotation.VisibleForTesting import androidx.core.animation.Animator import androidx.core.animation.AnimatorListenerAdapter import androidx.core.animation.ValueAnimator import com.android.wm.shell.shared.bubbles.DragZone.Bounds.CircleZone import com.android.wm.shell.shared.bubbles.DragZone.Bounds.RectZone import com.android.wm.shell.shared.bubbles.DragZone.DropTargetRect import com.android.wm.shell.shared.bubbles.DraggedObject.Bubble import com.android.wm.shell.shared.bubbles.DraggedObject.BubbleBar Loading Loading @@ -110,6 +112,26 @@ class DropTargetManager( return newDragZone } /** * Called when the drop target views should be hidden. This method will not remove the drop * target views from the container. * * It is mandatory to call [onDragEnded] when the drag operation ends, ensuring that the drop * target views are removed from the container. */ fun hideDropTargets() { val dragZones = state?.dragZones if (dragZones.isNullOrEmpty()) return val lowestBottom = dragZones.map { it.bounds }.maxOf { dragZone -> when (dragZone) { is RectZone -> dragZone.rect.bottom // rect bottom is CircleZone -> dragZone.y - dragZone.radius // circle bottom } } // use coordinate that will not hit any drag zone, so drop targets will be hidden onDragUpdated(0, lowestBottom + 1) } /** Called when the drag ended. */ fun onDragEnded() { val dropState = state ?: return Loading Loading @@ -219,7 +241,7 @@ class DropTargetManager( /** Stores the current drag state. */ private inner class DragState( private val dragZones: List<DragZone>, val dragZones: List<DragZone>, val draggedObject: DraggedObject, ) { val initialDragZone = Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/DragToBubbleController.kt +5 −0 Original line number Diff line number Diff line Loading @@ -91,6 +91,11 @@ class DragToBubbleController( return lastDragZone != null } /** Called when drop targets should be hidden. */ fun hideDropTargets() { dropTargetManager.hideDropTargets() } /** Called when the item with the [ShortcutInfo] is dropped over the bubble bar drop target. */ fun onItemDropped(shortcutInfo: ShortcutInfo) { val dropLocation = lastDragZone?.getBubbleBarLocation() ?: return Loading libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +2 −2 Original line number Diff line number Diff line Loading @@ -637,9 +637,9 @@ public class DragLayout extends LinearLayout } }); if (mIsOverBubblesDropZone) { // bubble bar is still showing drop target, notify bubbles of drag cancel // bubble bar is still showing drop target, notify bubbles to hide drop targets mIsOverBubblesDropZone = false; Objects.requireNonNull(mDragToBubbleController).onDragEnded(); Objects.requireNonNull(mDragToBubbleController).hideDropTargets(); } // Reset the state if we previously force-ignore the bottom margin mDropZoneView1.setForceIgnoreBottomMargin(false); Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt +143 −0 Original line number Diff line number Diff line Loading @@ -655,6 +655,149 @@ class DropTargetManagerTest { assertThat(container.childCount).isEqualTo(randomViewsCount) // All managers views removed } @Test fun hideDropTargets_whenInAZone_notifiesAndHidesDropTarget() { dropTargetManager.onDragStarted( DraggedObject.LauncherIcon(bubbleBarHasBubbles = true) {}, listOf(bubbleLeftDragZone, bubbleRightDragZone) ) // Initially, drag into the left zone InstrumentationRegistry.getInstrumentation().runOnMainSync { val dragZone = dropTargetManager.onDragUpdated( bubbleLeftDragZone.bounds.rect.centerX(), bubbleLeftDragZone.bounds.rect.centerY() ) assertThat(dragZone).isEqualTo(bubbleLeftDragZone) animatorTestRule.advanceTimeBy(250) } assertThat(dropTargetView.alpha).isEqualTo(1f) assertThat(dragZoneChangedListener.toDragZone).isEqualTo(bubbleLeftDragZone) // Call hideDropTargets InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.hideDropTargets() animatorTestRule.advanceTimeBy(250) } // Verify listener was notified of leaving the zone assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone) assertThat(dragZoneChangedListener.toDragZone).isNull() // Verify drop target is hidden and container still has the view assertThat(dropTargetView.alpha).isEqualTo(0f) assertThat(container.childCount).isEqualTo(DROP_VIEWS_COUNT) } @Test fun hideDropTargets_whenInAZoneWithSecondDropTarget_notifiesAndHidesBothDropTargets() { dropTargetManager.onDragStarted( DraggedObject.LauncherIcon(bubbleBarHasBubbles = false) {}, listOf(bubbleLeftDragZoneWithSecondDropTarget, bubbleRightDragZoneWithSecondDropTarget) ) // Initially, drag into the left zone InstrumentationRegistry.getInstrumentation().runOnMainSync { val dragZone = dropTargetManager.onDragUpdated( bubbleLeftDragZoneWithSecondDropTarget.bounds.rect.centerX(), bubbleLeftDragZoneWithSecondDropTarget.bounds.rect.centerY() ) assertThat(dragZone).isEqualTo(bubbleLeftDragZoneWithSecondDropTarget) animatorTestRule.advanceTimeBy(250) } assertThat(dropTargetView.alpha).isEqualTo(1f) assertThat(secondDropTargetView!!.alpha).isEqualTo(1f) assertThat(dragZoneChangedListener.toDragZone).isEqualTo( bubbleLeftDragZoneWithSecondDropTarget ) // Call hideDropTargets InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.hideDropTargets() animatorTestRule.advanceTimeBy(250) } // Verify listener was notified of leaving the zone assertThat(dragZoneChangedListener.fromDragZone).isEqualTo( bubbleLeftDragZoneWithSecondDropTarget ) assertThat(dragZoneChangedListener.toDragZone).isNull() // Verify drop targets are hidden and container still has the views assertThat(dropTargetView.alpha).isEqualTo(0f) assertThat(secondDropTargetView!!.alpha).isEqualTo(0f) assertThat(container.childCount).isEqualTo(DROP_VIEWS_COUNT_FOR_TWO_DROP_TARGETS) } @Test fun hideDropTargets_whenAlreadyOutsideZones_doesNothingAndDoesNotNotify() { dropTargetManager.onDragStarted( DraggedObject.Bubble(BubbleBarLocation.LEFT), listOf(bubbleLeftDragZone, bubbleRightDragZone) ) // Initially, drag outside all zones InstrumentationRegistry.getInstrumentation().runOnMainSync { val dragZone = dropTargetManager.onDragUpdated( 500, // outside any defined zone 500 ) assertThat(dragZone).isNull() animatorTestRule.advanceTimeBy(250) } assertThat(dropTargetView.alpha).isEqualTo(0f) assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone) assertThat(dragZoneChangedListener.toDragZone).isNull() // Reset listener state dragZoneChangedListener.fromDragZone = null dragZoneChangedListener.toDragZone = null // Call hideDropTargets InstrumentationRegistry.getInstrumentation().runOnMainSync { // No animation should occur as it's already hidden dropTargetManager.hideDropTargets() } // Verify listener was NOT notified again (already outside) assertThat(dragZoneChangedListener.fromDragZone).isNull() assertThat(dragZoneChangedListener.toDragZone).isNull() // Verify drop target remains hidden assertThat(dropTargetView.alpha).isEqualTo(0f) assertThat(container.childCount).isEqualTo(DROP_VIEWS_COUNT) } @Test fun hideDropTargets_dragNotStarted_doesNothing() { // Call hideDropTargets InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.hideDropTargets() } // Verify no listener calls assertThat(dragZoneChangedListener.initialDragZone).isNull() assertThat(dragZoneChangedListener.fromDragZone).isNull() assertThat(dragZoneChangedListener.toDragZone).isNull() assertThat(container.childCount).isEqualTo(0) } @Test fun hideDropTargets_afterDragEnded_doesNothing() { dropTargetManager.onDragStarted( DraggedObject.Bubble(BubbleBarLocation.LEFT), listOf(bubbleLeftDragZone, bubbleRightDragZone) ) InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.onDragEnded() animatorTestRule.advanceTimeBy(250) } assertThat(container.childCount).isEqualTo(0) // Views removed on drag end // Call hideDropTargets InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.hideDropTargets() } // Drop target alpha is irrelevant as views are removed. // Listener should not be affected further. assertThat(dragZoneChangedListener.endedDragZone).isEqualTo(bubbleLeftDragZone) // From onDragEnded } private fun verifyDropTargetPosition(rect: Rect) { verifyDropTargetPosition(dropTargetView, rect) } Loading Loading
libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/DragToBubbleControllerTest.kt +24 −5 Original line number Diff line number Diff line Loading @@ -129,7 +129,7 @@ class DragToBubbleControllerTest { @Test fun droppedItemWithIntentAtTheLeftDropZone_noBubblesOnTheRight_bubbleCreationRequested() { val bubbleBarOriginalLocation = BubbleBarLocation.RIGHT prepareBubbleController(hasBubbles = false, bubbleBarLocation = bubbleBarOriginalLocation) prepareBubbleController(bubbleBarLocation = bubbleBarOriginalLocation) val pendingIntent = PendingIntent(mock<IIntentSender>()) val userHandle = UserHandle(0) Loading @@ -148,7 +148,7 @@ class DragToBubbleControllerTest { @Test fun droppedItemWithShortcutInfoAtTheLeftDropZone_noBubblesOnTheRight_bubbleCreationRequested() { val bubbleBarOriginalLocation = BubbleBarLocation.RIGHT prepareBubbleController(hasBubbles = false, bubbleBarLocation = bubbleBarOriginalLocation) prepareBubbleController(bubbleBarLocation = bubbleBarOriginalLocation) val shortcutInfo = ShortcutInfo.Builder(context, "id").setLongLabel("Shortcut").build() dragToBubbleController.onDragStarted() Loading @@ -165,7 +165,7 @@ class DragToBubbleControllerTest { @Test fun droppedItem_afterNewDragStartedOnItemDropCleared() { val bubbleBarOriginalLocation = BubbleBarLocation.RIGHT prepareBubbleController(hasBubbles = false, bubbleBarLocation = bubbleBarOriginalLocation) prepareBubbleController(bubbleBarLocation = bubbleBarOriginalLocation) val shortcutInfo = ShortcutInfo.Builder(context, "id").setLongLabel("Shortcut").build() runOnMainSync { dragToBubbleController.onDragStarted() Loading @@ -191,16 +191,35 @@ class DragToBubbleControllerTest { .expandStackAndSelectBubble(any<PendingIntent>(), any(), any()) } @Test fun hideDropTargets_dragEnteredDropZone_dropTargetsHidden() { // Given dragToBubbleController.onDragStarted() runOnMainSync { dragToBubbleController.onDragUpdate(rightDropRect.centerX(), rightDropRect.centerY()) animatorTestRule.advanceTimeBy(250) } assertThat(dropTargetContainer.childCount).isEqualTo(1) assertThat(dropTargetView.alpha).isEqualTo(1f) // When runOnMainSync { dragToBubbleController.hideDropTargets() animatorTestRule.advanceTimeBy(250) } // Then assertThat(dropTargetView.alpha).isEqualTo(0f) } private fun runOnMainSync(action: () -> Unit) { InstrumentationRegistry.getInstrumentation().runOnMainSync { action() } } private fun prepareBubbleController( hasBubbles: Boolean = false, bubbleBarLocation: BubbleBarLocation = BubbleBarLocation.RIGHT, ) { bubbleController.stub { on { hasBubbles() } doReturn hasBubbles on { getBubbleBarLocation() } doReturn bubbleBarLocation } } Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt +23 −1 Original line number Diff line number Diff line Loading @@ -26,6 +26,8 @@ import androidx.annotation.VisibleForTesting import androidx.core.animation.Animator import androidx.core.animation.AnimatorListenerAdapter import androidx.core.animation.ValueAnimator import com.android.wm.shell.shared.bubbles.DragZone.Bounds.CircleZone import com.android.wm.shell.shared.bubbles.DragZone.Bounds.RectZone import com.android.wm.shell.shared.bubbles.DragZone.DropTargetRect import com.android.wm.shell.shared.bubbles.DraggedObject.Bubble import com.android.wm.shell.shared.bubbles.DraggedObject.BubbleBar Loading Loading @@ -110,6 +112,26 @@ class DropTargetManager( return newDragZone } /** * Called when the drop target views should be hidden. This method will not remove the drop * target views from the container. * * It is mandatory to call [onDragEnded] when the drag operation ends, ensuring that the drop * target views are removed from the container. */ fun hideDropTargets() { val dragZones = state?.dragZones if (dragZones.isNullOrEmpty()) return val lowestBottom = dragZones.map { it.bounds }.maxOf { dragZone -> when (dragZone) { is RectZone -> dragZone.rect.bottom // rect bottom is CircleZone -> dragZone.y - dragZone.radius // circle bottom } } // use coordinate that will not hit any drag zone, so drop targets will be hidden onDragUpdated(0, lowestBottom + 1) } /** Called when the drag ended. */ fun onDragEnded() { val dropState = state ?: return Loading Loading @@ -219,7 +241,7 @@ class DropTargetManager( /** Stores the current drag state. */ private inner class DragState( private val dragZones: List<DragZone>, val dragZones: List<DragZone>, val draggedObject: DraggedObject, ) { val initialDragZone = Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/DragToBubbleController.kt +5 −0 Original line number Diff line number Diff line Loading @@ -91,6 +91,11 @@ class DragToBubbleController( return lastDragZone != null } /** Called when drop targets should be hidden. */ fun hideDropTargets() { dropTargetManager.hideDropTargets() } /** Called when the item with the [ShortcutInfo] is dropped over the bubble bar drop target. */ fun onItemDropped(shortcutInfo: ShortcutInfo) { val dropLocation = lastDragZone?.getBubbleBarLocation() ?: return Loading
libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +2 −2 Original line number Diff line number Diff line Loading @@ -637,9 +637,9 @@ public class DragLayout extends LinearLayout } }); if (mIsOverBubblesDropZone) { // bubble bar is still showing drop target, notify bubbles of drag cancel // bubble bar is still showing drop target, notify bubbles to hide drop targets mIsOverBubblesDropZone = false; Objects.requireNonNull(mDragToBubbleController).onDragEnded(); Objects.requireNonNull(mDragToBubbleController).hideDropTargets(); } // Reset the state if we previously force-ignore the bottom margin mDropZoneView1.setForceIgnoreBottomMargin(false); Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt +143 −0 Original line number Diff line number Diff line Loading @@ -655,6 +655,149 @@ class DropTargetManagerTest { assertThat(container.childCount).isEqualTo(randomViewsCount) // All managers views removed } @Test fun hideDropTargets_whenInAZone_notifiesAndHidesDropTarget() { dropTargetManager.onDragStarted( DraggedObject.LauncherIcon(bubbleBarHasBubbles = true) {}, listOf(bubbleLeftDragZone, bubbleRightDragZone) ) // Initially, drag into the left zone InstrumentationRegistry.getInstrumentation().runOnMainSync { val dragZone = dropTargetManager.onDragUpdated( bubbleLeftDragZone.bounds.rect.centerX(), bubbleLeftDragZone.bounds.rect.centerY() ) assertThat(dragZone).isEqualTo(bubbleLeftDragZone) animatorTestRule.advanceTimeBy(250) } assertThat(dropTargetView.alpha).isEqualTo(1f) assertThat(dragZoneChangedListener.toDragZone).isEqualTo(bubbleLeftDragZone) // Call hideDropTargets InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.hideDropTargets() animatorTestRule.advanceTimeBy(250) } // Verify listener was notified of leaving the zone assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone) assertThat(dragZoneChangedListener.toDragZone).isNull() // Verify drop target is hidden and container still has the view assertThat(dropTargetView.alpha).isEqualTo(0f) assertThat(container.childCount).isEqualTo(DROP_VIEWS_COUNT) } @Test fun hideDropTargets_whenInAZoneWithSecondDropTarget_notifiesAndHidesBothDropTargets() { dropTargetManager.onDragStarted( DraggedObject.LauncherIcon(bubbleBarHasBubbles = false) {}, listOf(bubbleLeftDragZoneWithSecondDropTarget, bubbleRightDragZoneWithSecondDropTarget) ) // Initially, drag into the left zone InstrumentationRegistry.getInstrumentation().runOnMainSync { val dragZone = dropTargetManager.onDragUpdated( bubbleLeftDragZoneWithSecondDropTarget.bounds.rect.centerX(), bubbleLeftDragZoneWithSecondDropTarget.bounds.rect.centerY() ) assertThat(dragZone).isEqualTo(bubbleLeftDragZoneWithSecondDropTarget) animatorTestRule.advanceTimeBy(250) } assertThat(dropTargetView.alpha).isEqualTo(1f) assertThat(secondDropTargetView!!.alpha).isEqualTo(1f) assertThat(dragZoneChangedListener.toDragZone).isEqualTo( bubbleLeftDragZoneWithSecondDropTarget ) // Call hideDropTargets InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.hideDropTargets() animatorTestRule.advanceTimeBy(250) } // Verify listener was notified of leaving the zone assertThat(dragZoneChangedListener.fromDragZone).isEqualTo( bubbleLeftDragZoneWithSecondDropTarget ) assertThat(dragZoneChangedListener.toDragZone).isNull() // Verify drop targets are hidden and container still has the views assertThat(dropTargetView.alpha).isEqualTo(0f) assertThat(secondDropTargetView!!.alpha).isEqualTo(0f) assertThat(container.childCount).isEqualTo(DROP_VIEWS_COUNT_FOR_TWO_DROP_TARGETS) } @Test fun hideDropTargets_whenAlreadyOutsideZones_doesNothingAndDoesNotNotify() { dropTargetManager.onDragStarted( DraggedObject.Bubble(BubbleBarLocation.LEFT), listOf(bubbleLeftDragZone, bubbleRightDragZone) ) // Initially, drag outside all zones InstrumentationRegistry.getInstrumentation().runOnMainSync { val dragZone = dropTargetManager.onDragUpdated( 500, // outside any defined zone 500 ) assertThat(dragZone).isNull() animatorTestRule.advanceTimeBy(250) } assertThat(dropTargetView.alpha).isEqualTo(0f) assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone) assertThat(dragZoneChangedListener.toDragZone).isNull() // Reset listener state dragZoneChangedListener.fromDragZone = null dragZoneChangedListener.toDragZone = null // Call hideDropTargets InstrumentationRegistry.getInstrumentation().runOnMainSync { // No animation should occur as it's already hidden dropTargetManager.hideDropTargets() } // Verify listener was NOT notified again (already outside) assertThat(dragZoneChangedListener.fromDragZone).isNull() assertThat(dragZoneChangedListener.toDragZone).isNull() // Verify drop target remains hidden assertThat(dropTargetView.alpha).isEqualTo(0f) assertThat(container.childCount).isEqualTo(DROP_VIEWS_COUNT) } @Test fun hideDropTargets_dragNotStarted_doesNothing() { // Call hideDropTargets InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.hideDropTargets() } // Verify no listener calls assertThat(dragZoneChangedListener.initialDragZone).isNull() assertThat(dragZoneChangedListener.fromDragZone).isNull() assertThat(dragZoneChangedListener.toDragZone).isNull() assertThat(container.childCount).isEqualTo(0) } @Test fun hideDropTargets_afterDragEnded_doesNothing() { dropTargetManager.onDragStarted( DraggedObject.Bubble(BubbleBarLocation.LEFT), listOf(bubbleLeftDragZone, bubbleRightDragZone) ) InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.onDragEnded() animatorTestRule.advanceTimeBy(250) } assertThat(container.childCount).isEqualTo(0) // Views removed on drag end // Call hideDropTargets InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.hideDropTargets() } // Drop target alpha is irrelevant as views are removed. // Listener should not be affected further. assertThat(dragZoneChangedListener.endedDragZone).isEqualTo(bubbleLeftDragZone) // From onDragEnded } private fun verifyDropTargetPosition(rect: Rect) { verifyDropTargetPosition(dropTargetView, rect) } Loading