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

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

Merge "Add icon for MultiDisplayDragMoveIndicatorSurface" into main

parents bda2f8ea 2309fd87
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -117,7 +117,7 @@ class MultiDisplayDragMoveIndicatorController(
            dragIndicators.remove(taskId)?.values?.takeIf { it.isNotEmpty() }?.let { indicators ->
                val transaction = transactionSupplier()
                indicators.forEach { indicator ->
                    indicator.disposeSurface(transaction)
                    indicator.dispose(transaction)
                }
                transaction.apply()
            }
+170 −29
Original line number Diff line number Diff line
@@ -18,15 +18,38 @@ 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.shared.R
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.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
@@ -35,20 +58,38 @@ import com.android.wm.shell.windowdecor.common.DecorThemeUtil
 * This class manages the creation, display, and manipulation of the [SurfaceControl]s 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 {},
) {
    private var isVisible = false

    // 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 val decorThemeUtil = DecorThemeUtil(context)
    private val cornerRadius = context.resources
        .getDimensionPixelSize(R.dimen.desktop_windowing_freeform_rounded_corner_radius).toFloat()
    private val cornerRadius =
        context.resources
            .getDimensionPixelSize(sharedR.dimen.desktop_windowing_freeform_rounded_corner_radius)
            .toFloat()

    init {
        Trace.beginSection("DragIndicatorSurface#init")
@@ -61,24 +102,79 @@ class MultiDisplayDragMoveIndicatorSurface(
                .setCallsite("DragIndicatorSurface#init")
                .setHidden(true)
                .build()

        // TODO: b/383069173 - Add icon for the surface.
        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)
                }
            }

        Trace.endSection()
    }

    /**
     * Disposes the indicator surface using the provided [transaction].
     */
    fun disposeSurface(transaction: SurfaceControl.Transaction) {
    /** Disposes the viewHost and indicator surfaces 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
    }

    /**
     * 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]), 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,
@@ -87,16 +183,39 @@ class MultiDisplayDragMoveIndicatorSurface(
        displayId: Int,
        bounds: Rect,
    ) {
        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")
            }
            ProtoLog.w(
                WM_SHELL_DESKTOP_MODE,
                "Cannot show drag indicator for Task %d on Display %d because " +
                    "required surface(s) are null: %s",
                taskInfo.taskId,
                displayId,
                nullSurfacesString,
            )
            return
        }

        val backgroundColor = decorThemeUtil.getColorScheme(taskInfo).surfaceContainer
        val veil = veilSurface ?: return
        isVisible = true

        rootTaskDisplayAreaOrganizer.reparentToDisplayArea(displayId, veil, transaction)
        relayout(bounds, transaction, shouldBeVisible = true)
        transaction
            .show(veil)
            .setColor(veil, Color.valueOf(backgroundColor.toArgb()).components)
            .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.apply()
    }

@@ -111,13 +230,27 @@ class MultiDisplayDragMoveIndicatorSurface(
        }
        isVisible = shouldBeVisible
        val veil = veilSurface ?: return
        transaction.setCrop(veil, bounds).setCornerRadius(veil, cornerRadius)
        val icon = iconSurface ?: return
        val iconPosition = calculateAppIconPosition(bounds)
        transaction
            .setCrop(veil, bounds)
            .setCornerRadius(veil, cornerRadius)
            .setPosition(icon, iconPosition.x, iconPosition.y)
    }

    /**
     * Factory for creating [MultiDisplayDragMoveIndicatorSurface] instances with the [context].
     */
    class Factory() {
    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 {}

@@ -125,15 +258,15 @@ class MultiDisplayDragMoveIndicatorSurface(
         * 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(
        fun create(taskInfo: RunningTaskInfo, display: Display, displayContext: Context) =
            MultiDisplayDragMoveIndicatorSurface(
                displayContext,
                taskInfo,
                display,
                surfaceControlBuilderFactory,
                taskResourceLoader,
                desktopDispatcher,
                bgScope,
            )

        /**
@@ -149,6 +282,14 @@ class MultiDisplayDragMoveIndicatorSurface(
    }

    companion object {
        private const val TAG = "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
    }
}
+7 −2
Original line number Diff line number Diff line
@@ -1153,8 +1153,13 @@ public abstract class WMShellModule {
    @WMSingleton
    @Provides
    static MultiDisplayDragMoveIndicatorSurface.Factory
            providesMultiDisplayDragMoveIndicatorSurfaceFactory() {
        return new MultiDisplayDragMoveIndicatorSurface.Factory();
            providesMultiDisplayDragMoveIndicatorSurfaceFactory(
                    WindowDecorTaskResourceLoader windowDecorTaskResourceLoader,
                    @ShellDesktopThread MainCoroutineDispatcher desktopDispatcher,
                    @ShellBackgroundThread CoroutineScope bgScope) {
        return new MultiDisplayDragMoveIndicatorSurface.Factory(
                windowDecorTaskResourceLoader, desktopDispatcher, bgScope
        );
    }

    @WMSingleton
+1 −1
Original line number Diff line number Diff line
@@ -151,7 +151,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
            executor.flushAll()
        }

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

    companion object {
+123 −34

File changed.

Preview size limit exceeded, changes collapsed.