Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt +12 −0 Original line number Diff line number Diff line Loading @@ -60,6 +60,18 @@ enum class BubbleBarLocation : Parcelable { override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size) } /** * Checks whether locations are on the different sides from each other. If any of the * locations is null returns false. */ fun isDifferentSides( first: BubbleBarLocation?, second: BubbleBarLocation?, isRtl: Boolean ): Boolean { return first != null && second != null && first.isOnLeft(isRtl) != second.isOnLeft(isRtl) } } /** Define set of constants that allow to determine why location changed. */ Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragToBubblesZoneChangeListener.kt 0 → 100644 +132 −0 Original line number Diff line number Diff line /* * 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.shared.bubbles import com.android.wm.shell.shared.bubbles.BubbleBarLocation.Companion.isDifferentSides import com.android.wm.shell.shared.bubbles.DropTargetManager.DragZoneChangedListener /** * Class that encapsulates common logic of reacting to the drag zone changes for dragging launcher * icons to the bubble bar. */ class DragToBubblesZoneChangeListener( private val isRtl: Boolean, private val callback: Callback, ) : DragZoneChangedListener { private var lastUpdateLocation: BubbleBarLocation? = null private val isLocationChangedFromOriginal: Boolean get() = lastUpdateLocation != null && isDifferentSides( lastUpdateLocation, callback.getStartingBubbleBarLocation(), isRtl ) override fun onInitialDragZoneSet(dragZone: DragZone?) {} override fun onDragZoneChanged( draggedObject: DraggedObject, from: DragZone?, to: DragZone?, ) { val updateLocation = to.toBubbleBarLocation() updateBubbleBarLocation(updateLocation) lastUpdateLocation = updateLocation } override fun onDragEnded(zone: DragZone?) { updateBubbleBarLocation(updateLocation = null) } fun updateBubbleBarLocation(updateLocation: BubbleBarLocation?) { val updatedBefore = lastUpdateLocation != null val originalLocation = callback.getStartingBubbleBarLocation() val isLocationUpdated = isDifferentSides(lastUpdateLocation, updateLocation, isRtl) if (shouldNotifyZoneChanged(updateLocation)) { callback.onDragEnteredLocation(updateLocation) } if (!callback.hasBubbles()) { // has no bubbles, so showing the pin view if (updateLocation == null || !updatedBefore || isLocationUpdated) { callback.bubbleBarPillowShownAtLocation(updateLocation) } return } if (updateLocation == null) { if (isLocationChangedFromOriginal) { callback.animateBubbleBarLocation(originalLocation) } return } if (updatedBefore && isLocationUpdated) { // updated before and location updated - update to new location callback.animateBubbleBarLocation(updateLocation) return } if (!updatedBefore && isDifferentSides(originalLocation, updateLocation, isRtl)) { // not updated before and location changed from original callback.animateBubbleBarLocation(updateLocation) } } private fun DragZone?.toBubbleBarLocation(): BubbleBarLocation? { return when (this) { is DragZone.Bubble.Left -> BubbleBarLocation.LEFT is DragZone.Bubble.Right -> BubbleBarLocation.RIGHT else -> null } } private fun shouldNotifyZoneChanged(updateLocation: BubbleBarLocation?): Boolean { // Notify if one is null and the other isn't (entering/exiting a general zone area) // OR if both are non-null and they represent different sides. return (lastUpdateLocation == null) != (updateLocation == null) || isDifferentSides(lastUpdateLocation, updateLocation, isRtl) } /** * Callback interface for {@link DragToBubblesZoneChangeListener} to communicate drag-related * events and request actions on the bubble bar. * The primary purpose of this callback is to decouple the generic drag zone detection logic * within {@code DragToBubblesZoneChangeListener} from the specific UI implementation details * of the bubble bar. */ interface Callback { /** The starting bubble bar location before the drag started. */ fun getStartingBubbleBarLocation(): BubbleBarLocation /** Check if the bubble bar has any bubbles. */ fun hasBubbles(): Boolean /** Called when need to animate the bubble bar location. */ fun animateBubbleBarLocation(bubbleBarLocation: BubbleBarLocation) /** Called when the bubble bar pillow view is shown at position. */ fun bubbleBarPillowShownAtLocation(bubbleBarLocation: BubbleBarLocation?) /** * Called when a drag operation enters or exits a bubble bar location. * * @param bubbleBarLocation The [BubbleBarLocation] that the drag operation has entered. * This will be non-null if the drag has entered a valid bubble bar * location. It will be `null` if the drag operation has exited * all bubble bar locations. */ fun onDragEnteredLocation(bubbleBarLocation: BubbleBarLocation?) {} } } libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/DragToBubbleController.kt +24 −65 Original line number Diff line number Diff line Loading @@ -28,11 +28,12 @@ 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.ContextUtils.isRtl import com.android.wm.shell.shared.bubbles.DragToBubblesZoneChangeListener 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 Loading @@ -52,12 +53,13 @@ class DragToBubbleController( ) } private val isRtl: Boolean get() = containerView.isLayoutRtl @VisibleForTesting val dropTargetManager: DropTargetManager = DropTargetManager(context, containerView, createDragZoneListener()) DropTargetManager( context, containerView, createDragZoneChangedListener() ) @VisibleForTesting val dragZoneFactory = createDragZoneFactory() Loading Loading @@ -116,72 +118,29 @@ class DragToBubbleController( ) } 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) { private fun DragZone.getBubbleBarLocation(): BubbleBarLocation? = when (this) { 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 createDragZoneChangedListener() = DragToBubblesZoneChangeListener( context.isRtl, object : DragToBubblesZoneChangeListener.Callback { override fun getStartingBubbleBarLocation(): BubbleBarLocation { return bubbleController.bubbleBarLocation ?: BubbleBarLocation.DEFAULT } override fun hasBubbles(): Boolean = bubbleController.hasBubbles() override fun animateBubbleBarLocation(bubbleBarLocation: BubbleBarLocation) { bubbleController.animateBubbleBarLocation(bubbleBarLocation) } private fun DragZone.getBubbleBarLocation(): BubbleBarLocation? = when (this) { is DragZone.Bubble.Left -> BubbleBarLocation.LEFT is DragZone.Bubble.Right -> BubbleBarLocation.RIGHT else -> null override fun bubbleBarPillowShownAtLocation(bubbleBarLocation: BubbleBarLocation?) { bubbleController.showBubbleBarPinAtLocation(bubbleBarLocation) } }) } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragToBubblesZoneChangeListenerTest.kt 0 → 100644 +366 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt +12 −0 Original line number Diff line number Diff line Loading @@ -60,6 +60,18 @@ enum class BubbleBarLocation : Parcelable { override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size) } /** * Checks whether locations are on the different sides from each other. If any of the * locations is null returns false. */ fun isDifferentSides( first: BubbleBarLocation?, second: BubbleBarLocation?, isRtl: Boolean ): Boolean { return first != null && second != null && first.isOnLeft(isRtl) != second.isOnLeft(isRtl) } } /** Define set of constants that allow to determine why location changed. */ Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragToBubblesZoneChangeListener.kt 0 → 100644 +132 −0 Original line number Diff line number Diff line /* * 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.shared.bubbles import com.android.wm.shell.shared.bubbles.BubbleBarLocation.Companion.isDifferentSides import com.android.wm.shell.shared.bubbles.DropTargetManager.DragZoneChangedListener /** * Class that encapsulates common logic of reacting to the drag zone changes for dragging launcher * icons to the bubble bar. */ class DragToBubblesZoneChangeListener( private val isRtl: Boolean, private val callback: Callback, ) : DragZoneChangedListener { private var lastUpdateLocation: BubbleBarLocation? = null private val isLocationChangedFromOriginal: Boolean get() = lastUpdateLocation != null && isDifferentSides( lastUpdateLocation, callback.getStartingBubbleBarLocation(), isRtl ) override fun onInitialDragZoneSet(dragZone: DragZone?) {} override fun onDragZoneChanged( draggedObject: DraggedObject, from: DragZone?, to: DragZone?, ) { val updateLocation = to.toBubbleBarLocation() updateBubbleBarLocation(updateLocation) lastUpdateLocation = updateLocation } override fun onDragEnded(zone: DragZone?) { updateBubbleBarLocation(updateLocation = null) } fun updateBubbleBarLocation(updateLocation: BubbleBarLocation?) { val updatedBefore = lastUpdateLocation != null val originalLocation = callback.getStartingBubbleBarLocation() val isLocationUpdated = isDifferentSides(lastUpdateLocation, updateLocation, isRtl) if (shouldNotifyZoneChanged(updateLocation)) { callback.onDragEnteredLocation(updateLocation) } if (!callback.hasBubbles()) { // has no bubbles, so showing the pin view if (updateLocation == null || !updatedBefore || isLocationUpdated) { callback.bubbleBarPillowShownAtLocation(updateLocation) } return } if (updateLocation == null) { if (isLocationChangedFromOriginal) { callback.animateBubbleBarLocation(originalLocation) } return } if (updatedBefore && isLocationUpdated) { // updated before and location updated - update to new location callback.animateBubbleBarLocation(updateLocation) return } if (!updatedBefore && isDifferentSides(originalLocation, updateLocation, isRtl)) { // not updated before and location changed from original callback.animateBubbleBarLocation(updateLocation) } } private fun DragZone?.toBubbleBarLocation(): BubbleBarLocation? { return when (this) { is DragZone.Bubble.Left -> BubbleBarLocation.LEFT is DragZone.Bubble.Right -> BubbleBarLocation.RIGHT else -> null } } private fun shouldNotifyZoneChanged(updateLocation: BubbleBarLocation?): Boolean { // Notify if one is null and the other isn't (entering/exiting a general zone area) // OR if both are non-null and they represent different sides. return (lastUpdateLocation == null) != (updateLocation == null) || isDifferentSides(lastUpdateLocation, updateLocation, isRtl) } /** * Callback interface for {@link DragToBubblesZoneChangeListener} to communicate drag-related * events and request actions on the bubble bar. * The primary purpose of this callback is to decouple the generic drag zone detection logic * within {@code DragToBubblesZoneChangeListener} from the specific UI implementation details * of the bubble bar. */ interface Callback { /** The starting bubble bar location before the drag started. */ fun getStartingBubbleBarLocation(): BubbleBarLocation /** Check if the bubble bar has any bubbles. */ fun hasBubbles(): Boolean /** Called when need to animate the bubble bar location. */ fun animateBubbleBarLocation(bubbleBarLocation: BubbleBarLocation) /** Called when the bubble bar pillow view is shown at position. */ fun bubbleBarPillowShownAtLocation(bubbleBarLocation: BubbleBarLocation?) /** * Called when a drag operation enters or exits a bubble bar location. * * @param bubbleBarLocation The [BubbleBarLocation] that the drag operation has entered. * This will be non-null if the drag has entered a valid bubble bar * location. It will be `null` if the drag operation has exited * all bubble bar locations. */ fun onDragEnteredLocation(bubbleBarLocation: BubbleBarLocation?) {} } }
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/DragToBubbleController.kt +24 −65 Original line number Diff line number Diff line Loading @@ -28,11 +28,12 @@ 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.ContextUtils.isRtl import com.android.wm.shell.shared.bubbles.DragToBubblesZoneChangeListener 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 Loading @@ -52,12 +53,13 @@ class DragToBubbleController( ) } private val isRtl: Boolean get() = containerView.isLayoutRtl @VisibleForTesting val dropTargetManager: DropTargetManager = DropTargetManager(context, containerView, createDragZoneListener()) DropTargetManager( context, containerView, createDragZoneChangedListener() ) @VisibleForTesting val dragZoneFactory = createDragZoneFactory() Loading Loading @@ -116,72 +118,29 @@ class DragToBubbleController( ) } 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) { private fun DragZone.getBubbleBarLocation(): BubbleBarLocation? = when (this) { 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 createDragZoneChangedListener() = DragToBubblesZoneChangeListener( context.isRtl, object : DragToBubblesZoneChangeListener.Callback { override fun getStartingBubbleBarLocation(): BubbleBarLocation { return bubbleController.bubbleBarLocation ?: BubbleBarLocation.DEFAULT } override fun hasBubbles(): Boolean = bubbleController.hasBubbles() override fun animateBubbleBarLocation(bubbleBarLocation: BubbleBarLocation) { bubbleController.animateBubbleBarLocation(bubbleBarLocation) } private fun DragZone.getBubbleBarLocation(): BubbleBarLocation? = when (this) { is DragZone.Bubble.Left -> BubbleBarLocation.LEFT is DragZone.Bubble.Right -> BubbleBarLocation.RIGHT else -> null override fun bubbleBarPillowShownAtLocation(bubbleBarLocation: BubbleBarLocation?) { bubbleController.showBubbleBarPinAtLocation(bubbleBarLocation) } }) }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragToBubblesZoneChangeListenerTest.kt 0 → 100644 +366 −0 File added.Preview size limit exceeded, changes collapsed. Show changes