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

Commit 7378f6a7 authored by Liran Binyamin's avatar Liran Binyamin Committed by Android (Google) Code Review
Browse files

Merge changes I15b4b97f,Ica7e3dac into main

* changes:
  Create DragZoneFactory for bubble drag zones
  Initial creation of drag zones for bubble anything
parents 073700f2 5a420f3d
Loading
Loading
Loading
Loading
+59 −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 android.graphics.Rect

/**
 * Represents an invisible area on the screen that determines what happens to a dragged object if it
 * is released in that area.
 *
 * [bounds] are the bounds of the drag zone. Drag zones have an associated drop target that serves
 * as visual feedback hinting what would happen if the object is released. When a dragged object is
 * dragged into a drag zone, the associated drop target will be displayed. Not all drag zones have
 * drop targets; only those that are made visible by Bubbles do.
 */
sealed interface DragZone {

    /** The bounds of this drag zone. */
    val bounds: Rect

    fun contains(x: Int, y: Int) = bounds.contains(x, y)

    /** Represents the bubble drag area on the screen. */
    sealed class Bubble(override val bounds: Rect) : DragZone {
        data class Left(override val bounds: Rect, val dropTarget: Rect) : Bubble(bounds)
        data class Right(override val bounds: Rect, val dropTarget: Rect) : Bubble(bounds)
    }

    /** Represents dragging to Desktop Window. */
    data class DesktopWindow(override val bounds: Rect, val dropTarget: Rect) : DragZone

    /** Represents dragging to Full Screen. */
    data class FullScreen(override val bounds: Rect, val dropTarget: Rect) : DragZone

    /** Represents dragging to dismiss. */
    data class Dismiss(override val bounds: Rect) : DragZone

    /** Represents dragging to enter Split or replace a Split app. */
    sealed class Split(override val bounds: Rect) : DragZone {
        data class Left(override val bounds: Rect) : Split(bounds)
        data class Right(override val bounds: Rect) : Split(bounds)
        data class Top(override val bounds: Rect) : Split(bounds)
        data class Bottom(override val bounds: Rect) : Split(bounds)
    }
}
+450 −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 android.graphics.Rect
import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode

/** A class for creating drag zones for dragging bubble objects or dragging into bubbles. */
class DragZoneFactory(
    private val deviceConfig: DeviceConfig,
    private val splitScreenModeChecker: SplitScreenModeChecker,
    private val desktopWindowModeChecker: DesktopWindowModeChecker,
) {

    private val windowBounds: Rect
        get() = deviceConfig.windowBounds

    // TODO b/393172431: move these to xml
    private val dismissDragZoneSize = if (deviceConfig.isSmallTablet) 140 else 200
    private val bubbleDragZoneTabletSize = 200
    private val bubbleDragZoneFoldableSize = 140
    private val fullScreenDragZoneWidth = 512
    private val fullScreenDragZoneHeight = 44
    private val desktopWindowDragZoneWidth = 880
    private val desktopWindowDragZoneHeight = 300
    private val desktopWindowFromExpandedViewDragZoneWidth = 200
    private val desktopWindowFromExpandedViewDragZoneHeight = 350
    private val splitFromBubbleDragZoneHeight = 100
    private val splitFromBubbleDragZoneWidth = 60
    private val hSplitFromExpandedViewDragZoneWidth = 60
    private val vSplitFromExpandedViewDragZoneWidth = 200
    private val vSplitFromExpandedViewDragZoneHeightTablet = 285
    private val vSplitFromExpandedViewDragZoneHeightFold = 150
    private val vUnevenSplitFromExpandedViewDragZoneHeight = 96

    /**
     * Creates the list of drag zones for the dragged object.
     *
     * Drag zones may have overlap, but the list is sorted by priority where the first drag zone has
     * the highest priority so it should be checked first.
     */
    fun createSortedDragZones(draggedObject: DraggedObject): List<DragZone> {
        val dragZones = mutableListOf<DragZone>()
        when (draggedObject) {
            is DraggedObject.BubbleBar -> {
                dragZones.add(createDismissDragZone())
                dragZones.addAll(createBubbleDragZones())
            }
            is DraggedObject.Bubble -> {
                dragZones.add(createDismissDragZone())
                dragZones.addAll(createBubbleDragZones())
                dragZones.add(createFullScreenDragZone())
                if (shouldShowDesktopWindowDragZones()) {
                    dragZones.add(createDesktopWindowDragZoneForBubble())
                }
                dragZones.addAll(createSplitScreenDragZonesForBubble())
            }
            is DraggedObject.ExpandedView -> {
                dragZones.add(createDismissDragZone())
                dragZones.add(createFullScreenDragZone())
                if (shouldShowDesktopWindowDragZones()) {
                    dragZones.add(createDesktopWindowDragZoneForExpandedView())
                }
                if (deviceConfig.isSmallTablet) {
                    dragZones.addAll(createSplitScreenDragZonesForExpandedViewOnFoldable())
                } else {
                    dragZones.addAll(createSplitScreenDragZonesForExpandedViewOnTablet())
                }
                createBubbleDragZonesForExpandedView()
            }
        }
        return dragZones
    }

    private fun createDismissDragZone(): DragZone {
        return DragZone.Dismiss(
            bounds =
                Rect(
                    windowBounds.right / 2 - dismissDragZoneSize / 2,
                    windowBounds.bottom - dismissDragZoneSize,
                    windowBounds.right / 2 + dismissDragZoneSize / 2,
                    windowBounds.bottom
                )
        )
    }

    private fun createBubbleDragZones(): List<DragZone> {
        val dragZoneSize =
            if (deviceConfig.isSmallTablet) {
                bubbleDragZoneFoldableSize
            } else {
                bubbleDragZoneTabletSize
            }
        return listOf(
            DragZone.Bubble.Left(
                bounds =
                    Rect(0, windowBounds.bottom - dragZoneSize, dragZoneSize, windowBounds.bottom),
                dropTarget = Rect(0, 0, 0, 0),
            ),
            DragZone.Bubble.Right(
                bounds =
                    Rect(
                        windowBounds.right - dragZoneSize,
                        windowBounds.bottom - dragZoneSize,
                        windowBounds.right,
                        windowBounds.bottom,
                    ),
                dropTarget = Rect(0, 0, 0, 0),
            )
        )
    }

    private fun createBubbleDragZonesForExpandedView(): List<DragZone> {
        return listOf(
            DragZone.Bubble.Left(
                bounds = Rect(0, 0, windowBounds.right / 2, windowBounds.bottom),
                dropTarget = Rect(0, 0, 0, 0),
            ),
            DragZone.Bubble.Right(
                bounds =
                    Rect(
                        windowBounds.right / 2,
                        0,
                        windowBounds.right,
                        windowBounds.bottom,
                    ),
                dropTarget = Rect(0, 0, 0, 0),
            )
        )
    }

    private fun createFullScreenDragZone(): DragZone {
        return DragZone.FullScreen(
            bounds =
                Rect(
                    windowBounds.right / 2 - fullScreenDragZoneWidth / 2,
                    0,
                    windowBounds.right / 2 + fullScreenDragZoneWidth / 2,
                    fullScreenDragZoneHeight
                ),
            dropTarget = Rect(0, 0, 0, 0)
        )
    }

    private fun shouldShowDesktopWindowDragZones() =
        !deviceConfig.isSmallTablet && desktopWindowModeChecker.isSupported()

    private fun createDesktopWindowDragZoneForBubble(): DragZone {
        return DragZone.DesktopWindow(
            bounds =
                if (deviceConfig.isLandscape) {
                    Rect(
                        windowBounds.right / 2 - desktopWindowDragZoneWidth / 2,
                        windowBounds.bottom / 2 - desktopWindowDragZoneHeight / 2,
                        windowBounds.right / 2 + desktopWindowDragZoneWidth / 2,
                        windowBounds.bottom / 2 + desktopWindowDragZoneHeight / 2
                    )
                } else {
                    Rect(
                        0,
                        windowBounds.bottom / 2 - desktopWindowDragZoneHeight / 2,
                        windowBounds.right,
                        windowBounds.bottom / 2 + desktopWindowDragZoneHeight / 2
                    )
                },
            dropTarget = Rect(0, 0, 0, 0)
        )
    }

    private fun createDesktopWindowDragZoneForExpandedView(): DragZone {
        return DragZone.DesktopWindow(
            bounds =
                Rect(
                    windowBounds.right / 2 - desktopWindowFromExpandedViewDragZoneWidth / 2,
                    windowBounds.bottom / 2 - desktopWindowFromExpandedViewDragZoneHeight / 2,
                    windowBounds.right / 2 + desktopWindowFromExpandedViewDragZoneWidth / 2,
                    windowBounds.bottom / 2 + desktopWindowFromExpandedViewDragZoneHeight / 2
                ),
            dropTarget = Rect(0, 0, 0, 0)
        )
    }

    private fun createSplitScreenDragZonesForBubble(): List<DragZone> {
        // for foldables in landscape mode or tables in portrait modes we have vertical split drag
        // zones. otherwise we have horizontal split drag zones.
        val isVerticalSplit = deviceConfig.isSmallTablet == deviceConfig.isLandscape
        return if (isVerticalSplit) {
            when (splitScreenModeChecker.getSplitScreenMode()) {
                SplitScreenMode.SPLIT_50_50,
                SplitScreenMode.NONE ->
                    listOf(
                        DragZone.Split.Top(
                            bounds = Rect(0, 0, windowBounds.right, windowBounds.bottom / 2),
                        ),
                        DragZone.Split.Bottom(
                            bounds =
                                Rect(
                                    0,
                                    windowBounds.bottom / 2,
                                    windowBounds.right,
                                    windowBounds.bottom
                                ),
                        )
                    )
                SplitScreenMode.SPLIT_90_10 -> {
                    listOf(
                        DragZone.Split.Top(
                            bounds =
                                Rect(
                                    0,
                                    0,
                                    windowBounds.right,
                                    windowBounds.bottom - splitFromBubbleDragZoneHeight
                                ),
                        ),
                        DragZone.Split.Bottom(
                            bounds =
                                Rect(
                                    0,
                                    windowBounds.bottom - splitFromBubbleDragZoneHeight,
                                    windowBounds.right,
                                    windowBounds.bottom
                                ),
                        )
                    )
                }
                SplitScreenMode.SPLIT_10_90 -> {
                    listOf(
                        DragZone.Split.Top(
                            bounds = Rect(0, 0, windowBounds.right, splitFromBubbleDragZoneHeight),
                        ),
                        DragZone.Split.Bottom(
                            bounds =
                                Rect(
                                    0,
                                    splitFromBubbleDragZoneHeight,
                                    windowBounds.right,
                                    windowBounds.bottom
                                ),
                        )
                    )
                }
            }
        } else {
            when (splitScreenModeChecker.getSplitScreenMode()) {
                SplitScreenMode.SPLIT_50_50,
                SplitScreenMode.NONE ->
                    listOf(
                        DragZone.Split.Left(
                            bounds = Rect(0, 0, windowBounds.right / 2, windowBounds.bottom),
                        ),
                        DragZone.Split.Right(
                            bounds =
                                Rect(
                                    windowBounds.right / 2,
                                    0,
                                    windowBounds.right,
                                    windowBounds.bottom
                                ),
                        )
                    )
                SplitScreenMode.SPLIT_90_10 ->
                    listOf(
                        DragZone.Split.Left(
                            bounds =
                                Rect(
                                    0,
                                    0,
                                    windowBounds.right - splitFromBubbleDragZoneWidth,
                                    windowBounds.bottom
                                ),
                        ),
                        DragZone.Split.Right(
                            bounds =
                                Rect(
                                    windowBounds.right - splitFromBubbleDragZoneWidth,
                                    0,
                                    windowBounds.right,
                                    windowBounds.bottom
                                ),
                        )
                    )
                SplitScreenMode.SPLIT_10_90 ->
                    listOf(
                        DragZone.Split.Left(
                            bounds = Rect(0, 0, splitFromBubbleDragZoneWidth, windowBounds.bottom),
                        ),
                        DragZone.Split.Right(
                            bounds =
                                Rect(
                                    splitFromBubbleDragZoneWidth,
                                    0,
                                    windowBounds.right,
                                    windowBounds.bottom
                                ),
                        )
                    )
            }
        }
    }

    private fun createSplitScreenDragZonesForExpandedViewOnTablet(): List<DragZone> {
        return if (deviceConfig.isLandscape) {
            createHorizontalSplitDragZonesForExpandedView()
        } else {
            // for tablets in portrait mode, split drag zones appear below the full screen drag zone
            // for the top split zone, and above the dismiss zone. Both are horizontally centered.
            val splitZoneLeft = windowBounds.right / 2 - vSplitFromExpandedViewDragZoneWidth / 2
            val splitZoneRight = splitZoneLeft + vSplitFromExpandedViewDragZoneWidth
            val bottomSplitZoneBottom = windowBounds.bottom - dismissDragZoneSize
            listOf(
                DragZone.Split.Top(
                    bounds =
                        Rect(
                            splitZoneLeft,
                            fullScreenDragZoneHeight,
                            splitZoneRight,
                            fullScreenDragZoneHeight + vSplitFromExpandedViewDragZoneHeightTablet
                        ),
                ),
                DragZone.Split.Bottom(
                    bounds =
                        Rect(
                            splitZoneLeft,
                            bottomSplitZoneBottom - vSplitFromExpandedViewDragZoneHeightTablet,
                            splitZoneRight,
                            bottomSplitZoneBottom
                        ),
                )
            )
        }
    }

    private fun createSplitScreenDragZonesForExpandedViewOnFoldable(): List<DragZone> {
        return if (deviceConfig.isLandscape) {
            // vertical split drag zones are aligned with the full screen drag zone width
            val splitZoneLeft = windowBounds.right / 2 - fullScreenDragZoneWidth / 2
            when (splitScreenModeChecker.getSplitScreenMode()) {
                SplitScreenMode.SPLIT_50_50,
                SplitScreenMode.NONE ->
                    listOf(
                        DragZone.Split.Top(
                            bounds =
                                Rect(
                                    splitZoneLeft,
                                    fullScreenDragZoneHeight,
                                    splitZoneLeft + fullScreenDragZoneWidth,
                                    fullScreenDragZoneHeight +
                                        vSplitFromExpandedViewDragZoneHeightFold
                                ),
                        ),
                        DragZone.Split.Bottom(
                            bounds =
                                Rect(
                                    splitZoneLeft,
                                    windowBounds.bottom / 2,
                                    splitZoneLeft + fullScreenDragZoneWidth,
                                    windowBounds.bottom / 2 +
                                        vSplitFromExpandedViewDragZoneHeightFold
                                ),
                        )
                    )
                // TODO b/393172431: add this zone when it's defined
                SplitScreenMode.SPLIT_10_90 -> listOf()
                SplitScreenMode.SPLIT_90_10 ->
                    listOf(
                        DragZone.Split.Top(
                            bounds =
                                Rect(
                                    splitZoneLeft,
                                    fullScreenDragZoneHeight,
                                    splitZoneLeft + fullScreenDragZoneWidth,
                                    fullScreenDragZoneHeight +
                                        vUnevenSplitFromExpandedViewDragZoneHeight
                                ),
                        ),
                        DragZone.Split.Bottom(
                            bounds =
                                Rect(
                                    0,
                                    windowBounds.bottom -
                                        vUnevenSplitFromExpandedViewDragZoneHeight,
                                    windowBounds.right,
                                    windowBounds.bottom
                                ),
                        )
                    )
            }
        } else {
            // horizontal split drag zones
            createHorizontalSplitDragZonesForExpandedView()
        }
    }

    private fun createHorizontalSplitDragZonesForExpandedView(): List<DragZone> {
        // horizontal split drag zones for expanded view appear on the edges of the screen from the
        // top down until the dismiss drag zone height
        return listOf(
            DragZone.Split.Left(
                bounds =
                    Rect(
                        0,
                        0,
                        hSplitFromExpandedViewDragZoneWidth,
                        windowBounds.bottom - dismissDragZoneSize
                    ),
            ),
            DragZone.Split.Right(
                bounds =
                    Rect(
                        windowBounds.right - hSplitFromExpandedViewDragZoneWidth,
                        0,
                        windowBounds.right,
                        windowBounds.bottom - dismissDragZoneSize
                    ),
            )
        )
    }

    /** Checks the current split screen mode. */
    fun interface SplitScreenModeChecker {
        enum class SplitScreenMode {
            NONE,
            SPLIT_50_50,
            SPLIT_10_90,
            SPLIT_90_10
        }

        fun getSplitScreenMode(): SplitScreenMode
    }

    /** Checks if desktop window mode is supported. */
    fun interface DesktopWindowModeChecker {
        fun isSupported(): Boolean
    }
}
+27 −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

/** A Bubble object being dragged. */
sealed interface DraggedObject {
    /** The initial location of the object at the start of the drag gesture. */
    val initialLocation: BubbleBarLocation

    data class Bubble(override val initialLocation: BubbleBarLocation) : DraggedObject
    data class BubbleBar(override val initialLocation: BubbleBarLocation) : DraggedObject
    data class ExpandedView(override val initialLocation: BubbleBarLocation) : DraggedObject
}
+249 −0

File added.

Preview size limit exceeded, changes collapsed.