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

Commit 21dfeaa8 authored by Qijing Yao's avatar Qijing Yao Committed by Android (Google) Code Review
Browse files

Merge "Switch to use fluid indicator surface" into main

parents 191c65bb a60d25dd
Loading
Loading
Loading
Loading
+5 −6
Original line number Diff line number Diff line
@@ -51,11 +51,14 @@ class MultiDisplayDragMoveIndicatorController(
        boundsDp: RectF,
        currentDisplayId: Int,
        startDisplayId: Int,
        taskLeash: SurfaceControl,
        taskInfo: RunningTaskInfo,
        displayIds: Set<Int>,
        transactionSupplier: () -> SurfaceControl.Transaction,
    ) {
        desktopExecutor.execute {
            val startDisplayDpi =
                displayController.getDisplayLayout(startDisplayId)?.densityDpi() ?: return@execute
            for (displayId in displayIds) {
                if (
                    displayId == startDisplayId ||
@@ -102,12 +105,7 @@ class MultiDisplayDragMoveIndicatorController(
                    transaction.apply()
                }
                    ?: run {
                        val newIndicator =
                            indicatorSurfaceFactory.create(
                                taskInfo,
                                displayController.getDisplay(displayId),
                                displayContext,
                            )
                        val newIndicator = indicatorSurfaceFactory.create(displayContext, taskLeash)
                        newIndicator.show(
                            transactionSupplier(),
                            taskInfo,
@@ -115,6 +113,7 @@ class MultiDisplayDragMoveIndicatorController(
                            displayId,
                            boundsPx,
                            visibility,
                            displayLayout.densityDpi().toFloat() / startDisplayDpi.toFloat(),
                        )
                        dragIndicatorsForTask[displayId] = newIndicator
                    }
+27 −201
Original line number Diff line number Diff line
@@ -17,59 +17,25 @@ package com.android.wm.shell.common

import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.PointF
import android.graphics.Rect
import android.os.Trace
import android.view.Display
import android.view.LayoutInflater
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL
import android.view.WindowlessWindowManager
import android.widget.ImageView
import android.window.TaskConstants
import androidx.compose.ui.graphics.toArgb
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.R as sharedR
import com.android.wm.shell.shared.annotations.ShellBackgroundThread
import com.android.wm.shell.shared.R
import com.android.wm.shell.shared.annotations.ShellDesktopThread
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.windowdecor.WindowDecoration
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * Represents the indicator surface that visualizes the current position of a dragged window during
 * a multi-display drag operation.
 *
 * This class manages the creation, display, and manipulation of the [SurfaceControl]s that act as a
 * This class manages the creation, display, and manipulation of the [SurfaceControl] that act as a
 * visual indicator, providing feedback to the user about the dragged window's location.
 */
@ShellDesktopThread
class MultiDisplayDragMoveIndicatorSurface(
    context: Context,
    taskInfo: RunningTaskInfo,
    display: Display,
    surfaceControlBuilderFactory: Factory.SurfaceControlBuilderFactory,
    taskResourceLoader: WindowDecorTaskResourceLoader,
    @ShellDesktopThread desktopDispatcher: CoroutineDispatcher,
    @ShellBackgroundThread bgScope: CoroutineScope,
    surfaceControlViewHostFactory: WindowDecoration.SurfaceControlViewHostFactory =
        object : WindowDecoration.SurfaceControlViewHostFactory {},
) {
class MultiDisplayDragMoveIndicatorSurface(context: Context, taskSurface: SurfaceControl) {
    public enum class Visibility {
        INVISIBLE,
        TRANSLUCENT,
@@ -78,109 +44,31 @@ class MultiDisplayDragMoveIndicatorSurface(

    private var visibility = Visibility.INVISIBLE

    // A container surface to host the veil background
    private var veilSurface: SurfaceControl? = null
    // A color surface for the veil background.
    private var backgroundSurface: SurfaceControl? = null
    // A surface that hosts a windowless window with the app icon.
    private var iconSurface: SurfaceControl? = null

    private var viewHost: SurfaceControlViewHost? = null
    private var loadAppInfoJob: Job? = null

    @VisibleForTesting var iconView: ImageView
    private var iconSize = 0
    private var surface: SurfaceControl? = null

    private val decorThemeUtil = DecorThemeUtil(context)
    private val cornerRadius =
        context.resources
            .getDimensionPixelSize(sharedR.dimen.desktop_windowing_freeform_rounded_corner_radius)
            .getDimensionPixelSize(R.dimen.desktop_windowing_freeform_rounded_corner_radius)
            .toFloat()

    init {
        Trace.beginSection("DragIndicatorSurface#init")

        val displayId = display.displayId
        veilSurface =
            surfaceControlBuilderFactory
                .create("Drag indicator veil of Task=${taskInfo.taskId} Display=$displayId")
                .setColorLayer()
                .setCallsite("DragIndicatorSurface#init")
                .setHidden(true)
                .build()
        backgroundSurface =
            surfaceControlBuilderFactory
                .create("Drag indicator background of Task=${taskInfo.taskId} Display=$displayId")
                .setColorLayer()
                .setParent(veilSurface)
                .setCallsite("DragIndicatorSurface#init")
                .setHidden(true)
                .build()
        iconSurface =
            surfaceControlBuilderFactory
                .create("Drag indicator icon of Task=${taskInfo.taskId} Display=$displayId")
                .setContainerLayer()
                .setParent(veilSurface)
                .setCallsite("DragIndicatorSurface#init")
                .setHidden(true)
                .build()
        iconSize =
            context.resources.getDimensionPixelSize(R.dimen.desktop_mode_resize_veil_icon_size)
        val root =
            LayoutInflater.from(context)
                .inflate(R.layout.desktop_mode_resize_veil, /* root= */ null)
        iconView = root.requireViewById(R.id.veil_application_icon)
        val lp =
            WindowManager.LayoutParams(
                iconSize,
                iconSize,
                WindowManager.LayoutParams.TYPE_APPLICATION,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSPARENT,
            )
        lp.title = "Drag indicator veil icon window of Task=${taskInfo.taskId} Display=$displayId"
        lp.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL
        lp.setTrustedOverlay()
        val wwm =
            WindowlessWindowManager(
                taskInfo.configuration,
                iconSurface,
                /* hostInputTransferToken = */ null,
            )
        viewHost =
            surfaceControlViewHostFactory.create(context, display, wwm, "DragIndicatorSurface")
        viewHost?.setView(root, lp)
        loadAppInfoJob =
            bgScope.launch {
                if (!isActive) return@launch
                val icon = taskResourceLoader.getVeilIcon(taskInfo)
                withContext(desktopDispatcher) {
                    if (!isActive) return@withContext
                    iconView.setImageBitmap(icon)
                }
            }
        surface = SurfaceControl.mirrorSurface(taskSurface)

        Trace.endSection()
    }

    /** Disposes the viewHost and indicator surfaces using the provided [transaction]. */
    /** Disposes the indicator surface using the provided [transaction]. */
    fun dispose(transaction: SurfaceControl.Transaction) {
        loadAppInfoJob?.cancel()
        viewHost?.release()
        viewHost = null

        backgroundSurface?.let { background -> transaction.remove(background) }
        backgroundSurface = null
        iconSurface?.let { icon -> transaction.remove(icon) }
        iconSurface = null
        veilSurface?.let { veil -> transaction.remove(veil) }
        veilSurface = null
        surface?.let { sc -> transaction.remove(sc) }
        surface = null
    }

    /**
     * Shows the indicator surface at [bounds] on the specified display ([displayId]), visualizing
     * the drag of the [taskInfo]. The indicator surface is shown using [transaction], and the
     * [rootTaskDisplayAreaOrganizer] is used to reparent the surfaces.
     * Shows the indicator surface at [bounds] on the specified display ([displayId]), with the
     * [scale], visualizing the drag of the [taskInfo]. The indicator surface is shown using
     * [transaction], and the [rootTaskDisplayAreaOrganizer] is used to reparent the surfaces.
     */
    fun show(
        transaction: SurfaceControl.Transaction,
@@ -189,39 +77,23 @@ class MultiDisplayDragMoveIndicatorSurface(
        displayId: Int,
        bounds: Rect,
        visibility: Visibility,
        scale: Float,
    ) {
        val background = backgroundSurface
        val icon = iconSurface
        val veil = veilSurface
        if (veil == null || icon == null || background == null) {
            val nullSurfacesString = buildString {
                if (veil == null) append(" veilSurface")
                if (icon == null) append(" iconSurface")
                if (background == null) append(" backgroundSurface")
            }
        val sc = surface
        if (sc == null) {
            ProtoLog.w(
                WM_SHELL_DESKTOP_MODE,
                "Cannot show drag indicator for Task %d on Display %d because " +
                    "required surface(s) are null: %s",
                    "indicator surface is null.",
                taskInfo.taskId,
                displayId,
                nullSurfacesString,
            )
            return
        }

        val backgroundColor = decorThemeUtil.getColorScheme(taskInfo).surfaceContainer

        rootTaskDisplayAreaOrganizer.reparentToDisplayArea(displayId, veil, transaction)
        rootTaskDisplayAreaOrganizer.reparentToDisplayArea(displayId, sc, transaction)
        relayout(bounds, transaction, visibility)
        transaction
            .show(veil)
            .show(background)
            .show(icon)
            .setColor(background, Color.valueOf(backgroundColor.toArgb()).components)
            .setLayer(veil, MOVE_INDICATOR_LAYER)
            .setLayer(icon, MOVE_INDICATOR_ICON_LAYER)
            .setLayer(background, MOVE_INDICATOR_BACKGROUND_LAYER)
        transaction.show(sc).setLayer(sc, MOVE_INDICATOR_LAYER).setScale(sc, scale, scale)
        transaction.apply()
    }

@@ -236,22 +108,15 @@ class MultiDisplayDragMoveIndicatorSurface(
        }

        visibility = newVisibility
        val veil = veilSurface ?: return
        val icon = iconSurface ?: return
        val iconPosition = calculateAppIconPosition(bounds)
        val sc = surface ?: return
        transaction
            .setCrop(veil, bounds)
            .setCornerRadius(veil, cornerRadius)
            .setPosition(icon, iconPosition.x, iconPosition.y)
            .setCornerRadius(sc, cornerRadius)
            .setPosition(sc, bounds.left.toFloat(), bounds.top.toFloat())
        when (visibility) {
            Visibility.VISIBLE ->
                transaction
                    .setAlpha(veil, ALPHA_FOR_MOVE_INDICATOR_ON_DISPLAY_WITH_CURSOR)
                    .setAlpha(icon, ALPHA_FOR_MOVE_INDICATOR_ON_DISPLAY_WITH_CURSOR)
                transaction.setAlpha(sc, ALPHA_FOR_MOVE_INDICATOR_ON_DISPLAY_WITH_CURSOR)
            Visibility.TRANSLUCENT ->
                transaction
                    .setAlpha(veil, ALPHA_FOR_MOVE_INDICATOR_ON_NON_CURSOR_DISPLAY)
                    .setAlpha(icon, ALPHA_FOR_MOVE_INDICATOR_ON_NON_CURSOR_DISPLAY)
                transaction.setAlpha(sc, ALPHA_FOR_MOVE_INDICATOR_ON_NON_CURSOR_DISPLAY)
            Visibility.INVISIBLE -> {
                // Do nothing intentionally. Falling into this means the bounds is outside
                // of the display, so no need to hide the surface explicitly.
@@ -259,47 +124,14 @@ class MultiDisplayDragMoveIndicatorSurface(
        }
    }

    private fun calculateAppIconPosition(surfaceBounds: Rect): PointF {
        return PointF(
            surfaceBounds.left + surfaceBounds.width().toFloat() / 2 - iconSize.toFloat() / 2,
            surfaceBounds.top + surfaceBounds.height().toFloat() / 2 - iconSize.toFloat() / 2,
        )
    }

    /** Factory for creating [MultiDisplayDragMoveIndicatorSurface] instances with the [context]. */
    class Factory(
        private val taskResourceLoader: WindowDecorTaskResourceLoader,
        @ShellMainThread private val desktopDispatcher: CoroutineDispatcher,
        @ShellBackgroundThread private val bgScope: CoroutineScope,
    ) {
        private val surfaceControlBuilderFactory: SurfaceControlBuilderFactory =
            object : SurfaceControlBuilderFactory {}

    /** Factory for creating [MultiDisplayDragMoveIndicatorSurface] instances. */
    class Factory() {
        /**
         * Creates a new [MultiDisplayDragMoveIndicatorSurface] instance to visualize the drag
         * operation of the [taskInfo] on the given [display].
         */
        fun create(taskInfo: RunningTaskInfo, display: Display, displayContext: Context) =
            MultiDisplayDragMoveIndicatorSurface(
                displayContext,
                taskInfo,
                display,
                surfaceControlBuilderFactory,
                taskResourceLoader,
                desktopDispatcher,
                bgScope,
            )

        /**
         * Interface for creating [SurfaceControl.Builder] instances.
         *
         * This provides an abstraction over [SurfaceControl.Builder] creation for testing purposes.
         */
        interface SurfaceControlBuilderFactory {
            fun create(name: String): SurfaceControl.Builder {
                return SurfaceControl.Builder().setName(name)
            }
        }
        fun create(displayContext: Context, taskSurface: SurfaceControl) =
            MultiDisplayDragMoveIndicatorSurface(displayContext, taskSurface)
    }

    companion object {
@@ -307,12 +139,6 @@ class MultiDisplayDragMoveIndicatorSurface(

        private const val MOVE_INDICATOR_LAYER = TaskConstants.TASK_CHILD_LAYER_RESIZE_VEIL

        // Layers for child surfaces within veilSurface. Higher values are drawn on top.
        /** Background layer (drawn first/bottom). */
        private const val MOVE_INDICATOR_BACKGROUND_LAYER = 0
        /** Icon layer (drawn second/top, above the background). */
        private const val MOVE_INDICATOR_ICON_LAYER = 1

        private const val ALPHA_FOR_MOVE_INDICATOR_ON_DISPLAY_WITH_CURSOR = 1.0f
        private const val ALPHA_FOR_MOVE_INDICATOR_ON_NON_CURSOR_DISPLAY = 0.7f
    }
+2 −7
Original line number Diff line number Diff line
@@ -1196,13 +1196,8 @@ public abstract class WMShellModule {
    @WMSingleton
    @Provides
    static MultiDisplayDragMoveIndicatorSurface.Factory
            providesMultiDisplayDragMoveIndicatorSurfaceFactory(
                    WindowDecorTaskResourceLoader windowDecorTaskResourceLoader,
                    @ShellDesktopThread MainCoroutineDispatcher desktopDispatcher,
                    @ShellBackgroundThread CoroutineScope bgScope) {
        return new MultiDisplayDragMoveIndicatorSurface.Factory(
                windowDecorTaskResourceLoader, desktopDispatcher, bgScope
        );
            providesMultiDisplayDragMoveIndicatorSurfaceFactory() {
        return new MultiDisplayDragMoveIndicatorSurface.Factory();
    }

    @WMSingleton
+1 −0
Original line number Diff line number Diff line
@@ -205,6 +205,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
                    boundsDp,
                    displayId,
                    startDisplayId,
                    desktopWindowDecoration.leash,
                    desktopWindowDecoration.mTaskInfo,
                    displayIds,
                    transactionSupplier,
+26 −22
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import android.content.res.Configuration
import android.graphics.Rect
import android.graphics.RectF
import android.testing.TestableResources
import android.view.Display
import android.view.SurfaceControl
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -54,13 +53,13 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
    private val indicatorSurfaceFactory = mock<MultiDisplayDragMoveIndicatorSurface.Factory>()
    private val desktopState = FakeDesktopState()

    private val indicatorSurface0 = mock<MultiDisplayDragMoveIndicatorSurface>()
    private val indicatorSurface1 = mock<MultiDisplayDragMoveIndicatorSurface>()
    private val indicatorSurface = mock<MultiDisplayDragMoveIndicatorSurface>()
    private val transaction = mock<SurfaceControl.Transaction>()
    private val transactionSupplier = mock<Supplier<SurfaceControl.Transaction>>()
    private val taskInfo = mock<RunningTaskInfo>()
    private val display0 = mock<Display>()
    private val display1 = mock<Display>()
    private val taskLeash = mock<SurfaceControl>()
    private lateinit var spyDisplayLayout0: DisplayLayout
    private lateinit var spyDisplayLayout1: DisplayLayout

    private lateinit var resources: TestableResources
    private val executor = TestShellExecutor()
@@ -83,19 +82,16 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
                desktopState,
            )

        val spyDisplayLayout0 = TestDisplay.DISPLAY_0.getSpyDisplayLayout(resources.resources)
        val spyDisplayLayout1 = TestDisplay.DISPLAY_1.getSpyDisplayLayout(resources.resources)
        TestDisplay.DISPLAY_0.getSpyDisplayLayout(resources.resources)
        spyDisplayLayout0 = TestDisplay.DISPLAY_0.getSpyDisplayLayout(resources.resources)
        spyDisplayLayout1 = TestDisplay.DISPLAY_1.getSpyDisplayLayout(resources.resources)

        taskInfo.taskId = TASK_ID
        whenever(displayController.getDisplayLayout(0)).thenReturn(spyDisplayLayout0)
        whenever(displayController.getDisplayLayout(1)).thenReturn(spyDisplayLayout1)
        whenever(displayController.getDisplayContext(any())).thenReturn(mContext)
        whenever(displayController.getDisplay(0)).thenReturn(display0)
        whenever(displayController.getDisplay(1)).thenReturn(display1)
        whenever(indicatorSurfaceFactory.create(eq(taskInfo), eq(display0), any()))
            .thenReturn(indicatorSurface0)
        whenever(indicatorSurfaceFactory.create(eq(taskInfo), eq(display1), any()))
            .thenReturn(indicatorSurface1)
        whenever(displayController.getDisplayContext(1)).thenReturn(mContext)
        whenever(indicatorSurfaceFactory.create(eq(mContext), eq(taskLeash)))
            .thenReturn(indicatorSurface)
        whenever(transactionSupplier.get()).thenReturn(transaction)
        desktopState.canEnterDesktopMode = true
    }
@@ -106,6 +102,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
            RectF(2000f, 2000f, 2100f, 2200f), // not intersect with any display
            currentDisplayId = 0,
            startDisplayId = 0,
            taskLeash,
            taskInfo,
            displayIds = setOf(0, 1),
        ) {
@@ -113,7 +110,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
        }
        executor.flushAll()

        verify(indicatorSurfaceFactory, never()).create(any(), any(), any())
        verify(indicatorSurfaceFactory, never()).create(any(), any())
    }

    @Test
@@ -122,6 +119,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
            RectF(100f, 100f, 200f, 200f), // intersect with display 0
            currentDisplayId = 0,
            startDisplayId = 0,
            taskLeash,
            taskInfo,
            displayIds = setOf(0, 1),
        ) {
@@ -129,7 +127,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
        }
        executor.flushAll()

        verify(indicatorSurfaceFactory, never()).create(any(), any(), any())
        verify(indicatorSurfaceFactory, never()).create(any(), any())
    }

    @Test
@@ -140,12 +138,15 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
            RectF(100f, -100f, 200f, 200f), // intersect with display 0 and 1
            currentDisplayId = 1,
            startDisplayId = 0,
            taskLeash,
            taskInfo,
            displayIds = setOf(0, 1),
        ) { transaction }
        ) {
            transaction
        }
        executor.flushAll()

        verify(indicatorSurfaceFactory, never()).create(any(), any(), any())
        verify(indicatorSurfaceFactory, never()).create(any(), any())
    }

    @Test
@@ -154,6 +155,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
            RectF(100f, -100f, 200f, 200f), // intersect with display 0 and 1
            currentDisplayId = 1,
            startDisplayId = 0,
            taskLeash,
            taskInfo,
            displayIds = setOf(0, 1),
        ) {
@@ -161,8 +163,8 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
        }
        executor.flushAll()

        verify(indicatorSurfaceFactory, times(1)).create(eq(taskInfo), eq(display1), any())
        verify(indicatorSurface1, times(1))
        verify(indicatorSurfaceFactory, times(1)).create(eq(mContext), eq(taskLeash))
        verify(indicatorSurface, times(1))
            .show(
                transaction,
                taskInfo,
@@ -170,12 +172,14 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
                1,
                Rect(0, 1800, 200, 2400),
                MultiDisplayDragMoveIndicatorSurface.Visibility.VISIBLE,
                spyDisplayLayout1.densityDpi().toFloat() / spyDisplayLayout0.densityDpi().toFloat(),
            )

        controller.onDragMove(
            RectF(2000f, 2000f, 2100f, 2200f), // not intersect with display 1
            currentDisplayId = 0,
            startDisplayId = 0,
            taskLeash,
            taskInfo,
            displayIds = setOf(0, 1),
        ) {
@@ -185,7 +189,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
            executor.flushAll()
        }

        verify(indicatorSurface1, times(1))
        verify(indicatorSurface, times(1))
            .relayout(
                any(),
                eq(transaction),
@@ -197,7 +201,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
            executor.flushAll()
        }

        verify(indicatorSurface1, times(1)).dispose(transaction)
        verify(indicatorSurface, times(1)).dispose(transaction)
    }

    companion object {
Loading