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

Commit 5536d060 authored by Mykola Podolian's avatar Mykola Podolian Committed by Android (Google) Code Review
Browse files

Merge changes Ibffcd851,I6d3e496f into main

* changes:
  Added support for a second drop target view in DropTargetManager
  Added LauncherIcon DraggedObject
parents 89f894ae f3f47b74
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -91,6 +91,7 @@ class DragZoneFactoryScreenshotTest(private val param: Param) {
                is DraggedObject.Bubble -> "bubble"
                is DraggedObject.BubbleBar -> "bubbleBar"
                is DraggedObject.ExpandedView -> "expandedView"
                is DraggedObject.LauncherIcon -> "launcherIcon"
            }

        private val splitScreenModeName =
+14 −5
Original line number Diff line number Diff line
@@ -35,6 +35,9 @@ sealed interface DragZone {
    /** The bounds of the drop target associated with this drag zone. */
    val dropTarget: DropTargetRect?

    /** The bounds of the second drop target associated with this drag zone. */
    val secondDropTarget: DropTargetRect?

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

    sealed interface Bounds {
@@ -54,39 +57,45 @@ sealed interface DragZone {
    /** Represents the bubble drag area on the screen. */
    sealed class Bubble(
        override val bounds: Bounds.RectZone,
        override val dropTarget: DropTargetRect
        override val dropTarget: DropTargetRect,
    ) : DragZone {
        data class Left(
            override val bounds: Bounds.RectZone,
            override val dropTarget: DropTargetRect
            override val dropTarget: DropTargetRect,
            override val secondDropTarget: DropTargetRect? = null,
        ) : Bubble(bounds, dropTarget)

        data class Right(
            override val bounds: Bounds.RectZone,
            override val dropTarget: DropTargetRect
            override val dropTarget: DropTargetRect,
            override val secondDropTarget: DropTargetRect? = null,
        ) : Bubble(bounds, dropTarget)
    }

    /** Represents dragging to Desktop Window. */
    data class DesktopWindow(
        override val bounds: Bounds.RectZone,
        override val dropTarget: DropTargetRect
        override val dropTarget: DropTargetRect,
        override val secondDropTarget: DropTargetRect? = null,
    ) : DragZone

    /** Represents dragging to Full Screen. */
    data class FullScreen(
        override val bounds: Bounds.RectZone,
        override val dropTarget: DropTargetRect
        override val dropTarget: DropTargetRect,
        override val secondDropTarget: DropTargetRect? = null,
    ) : DragZone

    /** Represents dragging to dismiss. */
    data class Dismiss(override val bounds: Bounds.CircleZone) : DragZone {
        override val dropTarget: DropTargetRect? = null
        override val secondDropTarget: DropTargetRect? = null
    }

    /** Represents dragging to enter Split or replace a Split app. */
    sealed class Split(override val bounds: Bounds.RectZone) : DragZone {
        override val dropTarget: DropTargetRect? = null
        override val secondDropTarget: DropTargetRect? = null

        data class Left(override val bounds: Bounds.RectZone) : Split(bounds)

+7 −1
Original line number Diff line number Diff line
@@ -237,6 +237,10 @@ class DragZoneFactory(
                }
                dragZones.addAll(createBubbleHalfScreenDragZones(forBubbleBar = false))
            }
            is DraggedObject.LauncherIcon -> {
                val showSecondDropTarget = !draggedObject.bubbleBarHasBubbles
                dragZones.addAll(createBubbleCornerDragZones(showSecondDropTarget))
            }
        }
        return dragZones
    }
@@ -252,7 +256,7 @@ class DragZoneFactory(
        )
    }

    private fun createBubbleCornerDragZones(): List<DragZone> {
    private fun createBubbleCornerDragZones(showSecondDropTarget: Boolean = false): List<DragZone> {
        val dragZoneSize =
            if (deviceConfig.isSmallTablet) {
                bubbleDragZoneFoldableSize
@@ -271,6 +275,7 @@ class DragZoneFactory(
                        ),
                    ),
                dropTarget = expandedViewDropTargetLeft,
                secondDropTarget = if (showSecondDropTarget) bubbleBarDropTargetLeft else null
            ),
            DragZone.Bubble.Right(
                bounds =
@@ -283,6 +288,7 @@ class DragZoneFactory(
                        ),
                    ),
                dropTarget = expandedViewDropTargetRight,
                secondDropTarget = if (showSecondDropTarget) bubbleBarDropTargetRight else null
            )
        )
    }
+8 −5
Original line number Diff line number Diff line
@@ -18,10 +18,13 @@ 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
    data class Bubble(val initialLocation: BubbleBarLocation) : DraggedObject

    data class BubbleBar(val initialLocation: BubbleBarLocation) : DraggedObject

    data class ExpandedView(val initialLocation: BubbleBarLocation) : DraggedObject

    data class LauncherIcon(val bubbleBarHasBubbles: Boolean, val onDropAction: Runnable) :
        DraggedObject
}
+95 −36
Original line number Diff line number Diff line
@@ -19,12 +19,18 @@ package com.android.wm.shell.shared.bubbles
import android.content.Context
import android.graphics.RectF
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
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.DropTargetRect
import com.android.wm.shell.shared.bubbles.DraggedObject.Bubble
import com.android.wm.shell.shared.bubbles.DraggedObject.BubbleBar
import com.android.wm.shell.shared.bubbles.DraggedObject.ExpandedView
import com.android.wm.shell.shared.bubbles.DraggedObject.LauncherIcon

/**
 * Manages animating drop targets in response to dragging bubble icons or bubble expanded views
@@ -37,10 +43,12 @@ class DropTargetManager(
) {

    private var state: DragState? = null
    private val dropTargetView = DropTargetView(context)
    private var animator: ValueAnimator? = null

    @VisibleForTesting val dropTargetView = DropTargetView(context)
    @VisibleForTesting var secondDropTargetView: DropTargetView? = null
    private var morphRect: RectF = RectF(0f, 0f, 0f, 0f)
    private val isLayoutRtl = container.isLayoutRtl
    private val viewAnimatorsMap = mutableMapOf<View, ValueAnimator>()

    private companion object {
        const val MORPH_ANIM_DURATION = 250L
@@ -54,26 +62,38 @@ class DropTargetManager(
        val state = DragState(dragZones, draggedObject)
        dragZoneChangedListener.onInitialDragZoneSet(state.initialDragZone)
        this.state = state
        animator?.cancel()
        setupDropTarget()
        viewAnimatorsMap.values.forEach { it.cancel() }
        setupDropTarget(dropTargetView)
        if (dragZones.any { it.secondDropTarget != null }) {
            secondDropTargetView = secondDropTargetView ?: DropTargetView(context)
            setupDropTarget(secondDropTargetView)
        } else {
            secondDropTargetView?.let { container.removeView(it) }
            secondDropTargetView = null
        }
    }

    private fun setupDropTarget() {
        if (dropTargetView.parent != null) container.removeView(dropTargetView)
        container.addView(dropTargetView, 0)
        dropTargetView.alpha = 0f
    private fun setupDropTarget(view: View?) {
        if (view == null) return
        if (view.parent != null) container.removeView(view)
        container.addView(view, 0)
        view.alpha = 0f

        dropTargetView.elevation = TypedValue.applyDimension(
        view.elevation = TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            DROP_TARGET_ELEVATION_DP, context.resources.displayMetrics
        )
        // Match parent and the target is drawn within the view
        dropTargetView.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
        view.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
    }

    /** Called when the user drags to a new location. */
    fun onDragUpdated(x: Int, y: Int) {
        val state = state ?: return
    /**
     * Called when the user drags to a new location.
     *
     * @return DragZone that matches provided x and y coordinates.
     */
    fun onDragUpdated(x: Int, y: Int): DragZone? {
        val state = state ?: return null
        val oldDragZone = state.currentDragZone
        val newDragZone = state.getMatchingDragZone(x = x, y = y)
        state.currentDragZone = newDragZone
@@ -81,50 +101,78 @@ class DropTargetManager(
            dragZoneChangedListener.onDragZoneChanged(
                draggedObject = state.draggedObject,
                from = oldDragZone,
                to = newDragZone
                to = newDragZone,
            )
            updateDropTarget()
        }
        return newDragZone
    }

    /** Called when the drag ended. */
    fun onDragEnded() {
        val dropState = state ?: return
        startFadeAnimation(from = dropTargetView.alpha, to = 0f) {
            container.removeView(dropTargetView)
        startFadeAnimation(dropTargetView, to = 0f) { container.removeView(dropTargetView) }
        startFadeAnimation(secondDropTargetView, to = 0f) {
            container.removeView(secondDropTargetView)
            secondDropTargetView = null
        }
        dragZoneChangedListener.onDragEnded(dropState.currentDragZone)
        state = null
    }

    private fun updateDropTarget() {
        val currentDragZone = state?.currentDragZone ?: return
        val dropState = state ?: return
        val currentDragZone = dropState.currentDragZone
        if (currentDragZone == null) {
            startFadeAnimation(dropTargetView, to = 0f)
            startFadeAnimation(secondDropTargetView, to = 0f) {
                container.removeView(secondDropTargetView)
            }
            return
        }
        val dropTargetRect = currentDragZone.dropTarget
        when {
            dropTargetRect == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f)
            dropTargetRect == null -> startFadeAnimation(dropTargetView, to = 0f)

            dropTargetView.alpha == 0f -> {
                dropTargetView.update(RectF(dropTargetRect.rect), dropTargetRect.cornerRadius)
                startFadeAnimation(from = 0f, to = 1f)
                startFadeAnimation(dropTargetView, to = 1f)
            }

            else -> startMorphAnimation(dropTargetRect)
        }

        val secondDropTargetRect = currentDragZone.secondDropTarget
        when {
            secondDropTargetRect == null -> startFadeAnimation(secondDropTargetView, to = 0f)
            else -> {
                val secondDropTargetView = secondDropTargetView ?: return
                secondDropTargetView.update(
                    RectF(secondDropTargetRect.rect),
                    secondDropTargetRect.cornerRadius,
                )
                startFadeAnimation(secondDropTargetView, to = 1f)
            }
        }
    }

    private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) {
        animator?.cancel()
    private fun startFadeAnimation(view: View?, to: Float, onEnd: (() -> Unit)? = null) {
        if (view == null) return
        val from = view.alpha
        viewAnimatorsMap[view]?.cancel()
        val duration =
            if (from < to) DROP_TARGET_ALPHA_IN_DURATION else DROP_TARGET_ALPHA_OUT_DURATION
        val animator = ValueAnimator.ofFloat(from, to).setDuration(duration)
        animator.addUpdateListener { _ -> dropTargetView.alpha = animator.animatedValue as Float }
        animator.addUpdateListener { _ -> view.alpha = animator.animatedValue as Float }
        if (onEnd != null) {
            animator.doOnEnd(onEnd)
        }
        this.animator = animator
        viewAnimatorsMap[view] = animator
        animator.start()
    }

    private fun startMorphAnimation(dropTargetRect: DropTargetRect) {
        animator?.cancel()
        viewAnimatorsMap[dropTargetView]?.cancel()
        val startAlpha = dropTargetView.alpha
        val startRect = dropTargetView.getRect()
        val endRect = dropTargetRect.rect
@@ -139,38 +187,49 @@ class DropTargetManager(
            morphRect.bottom = (startRect.bottom + (endRect.bottom - startRect.bottom) * fraction)
            dropTargetView.update(morphRect, dropTargetRect.cornerRadius)
        }
        this.animator = animator
        viewAnimatorsMap[dropTargetView] = animator
        animator.start()
    }

    /** Stores the current drag state. */
    private inner class DragState(
        private val dragZones: List<DragZone>,
        val draggedObject: DraggedObject
        val draggedObject: DraggedObject,
    ) {
        val initialDragZone =
            if (draggedObject.initialLocation.isOnLeft(isLayoutRtl)) {
            draggedObject.initialLocation?.let {
                if (it.isOnLeft(isLayoutRtl)) {
                    dragZones.filterIsInstance<DragZone.Bubble.Left>().first()
                } else {
                    dragZones.filterIsInstance<DragZone.Bubble.Right>().first()
                }
        var currentDragZone: DragZone = initialDragZone
            }
        var currentDragZone: DragZone? = initialDragZone

        fun getMatchingDragZone(x: Int, y: Int): DragZone {
            return dragZones.firstOrNull { it.contains(x, y) } ?: currentDragZone
        fun getMatchingDragZone(x: Int, y: Int): DragZone? {
            return dragZones.firstOrNull { it.contains(x, y) }
        }
    }

    private val DraggedObject.initialLocation: BubbleBarLocation?
        get() =
            when (this) {
                is Bubble -> initialLocation
                is BubbleBar -> initialLocation
                is ExpandedView -> initialLocation
                is LauncherIcon -> null
            }

    /** An interface to be notified when drag zones change. */
    interface DragZoneChangedListener {
        /** An initial drag zone was set. Called when a drag starts. */
        fun onInitialDragZoneSet(dragZone: DragZone)
        fun onInitialDragZoneSet(dragZone: DragZone?)

        /** Called when the object was dragged to a different drag zone. */
        fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone, to: DragZone)
        fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone?, to: DragZone?)

        /** Called when the drag has ended with the zone it ended in. */
        fun onDragEnded(zone: DragZone)
        fun onDragEnded(zone: DragZone?)
    }

    private fun Animator.doOnEnd(onEnd: () -> Unit) {
Loading