Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3c947979 authored by mpodolian's avatar mpodolian
Browse files

Moved DragZoneChangeListener logic to the shared package.

Extracted logic that could be reused in the shell and Launcher to
DragToBubbleController

Bug: 411505605
Test: DragToBubbleControllerTest
Test: DragToBubblesZoneChangeListenerTest
Flag: com.android.wm.shell.enable_create_any_bubble
Change-Id: Ib9a34fc6326904a0ecaef5acfc7c17bb5f1b1076
parent 87fa64c1
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -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. */
+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?) {}
    }
}
+24 −65
Original line number Diff line number Diff line
@@ -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

@@ -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()
@@ -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)
            }
        })
}
+366 −0

File added.

Preview size limit exceeded, changes collapsed.