Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt +5 −6 Original line number Diff line number Diff line Loading @@ -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 || Loading Loading @@ -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, Loading @@ -115,6 +113,7 @@ class MultiDisplayDragMoveIndicatorController( displayId, boundsPx, visibility, displayLayout.densityDpi().toFloat() / startDisplayDpi.toFloat(), ) dragIndicatorsForTask[displayId] = newIndicator } Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt +27 −201 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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, Loading @@ -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() } Loading @@ -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. Loading @@ -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 { Loading @@ -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 } Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +2 −7 Original line number Diff line number Diff line Loading @@ -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 Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt +1 −0 Original line number Diff line number Diff line Loading @@ -205,6 +205,7 @@ class MultiDisplayVeiledResizeTaskPositioner( boundsDp, displayId, startDisplayId, desktopWindowDecoration.leash, desktopWindowDecoration.mTaskInfo, displayIds, transactionSupplier, Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt +26 −22 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() Loading @@ -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 } Loading @@ -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), ) { Loading @@ -113,7 +110,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { } executor.flushAll() verify(indicatorSurfaceFactory, never()).create(any(), any(), any()) verify(indicatorSurfaceFactory, never()).create(any(), any()) } @Test Loading @@ -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), ) { Loading @@ -129,7 +127,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { } executor.flushAll() verify(indicatorSurfaceFactory, never()).create(any(), any(), any()) verify(indicatorSurfaceFactory, never()).create(any(), any()) } @Test Loading @@ -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 Loading @@ -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), ) { Loading @@ -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, Loading @@ -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), ) { Loading @@ -185,7 +189,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { executor.flushAll() } verify(indicatorSurface1, times(1)) verify(indicatorSurface, times(1)) .relayout( any(), eq(transaction), Loading @@ -197,7 +201,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { executor.flushAll() } verify(indicatorSurface1, times(1)).dispose(transaction) verify(indicatorSurface, times(1)).dispose(transaction) } companion object { Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt +5 −6 Original line number Diff line number Diff line Loading @@ -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 || Loading Loading @@ -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, Loading @@ -115,6 +113,7 @@ class MultiDisplayDragMoveIndicatorController( displayId, boundsPx, visibility, displayLayout.densityDpi().toFloat() / startDisplayDpi.toFloat(), ) dragIndicatorsForTask[displayId] = newIndicator } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt +27 −201 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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, Loading @@ -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() } Loading @@ -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. Loading @@ -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 { Loading @@ -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 } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +2 −7 Original line number Diff line number Diff line Loading @@ -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 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt +1 −0 Original line number Diff line number Diff line Loading @@ -205,6 +205,7 @@ class MultiDisplayVeiledResizeTaskPositioner( boundsDp, displayId, startDisplayId, desktopWindowDecoration.leash, desktopWindowDecoration.mTaskInfo, displayIds, transactionSupplier, Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt +26 −22 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() Loading @@ -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 } Loading @@ -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), ) { Loading @@ -113,7 +110,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { } executor.flushAll() verify(indicatorSurfaceFactory, never()).create(any(), any(), any()) verify(indicatorSurfaceFactory, never()).create(any(), any()) } @Test Loading @@ -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), ) { Loading @@ -129,7 +127,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { } executor.flushAll() verify(indicatorSurfaceFactory, never()).create(any(), any(), any()) verify(indicatorSurfaceFactory, never()).create(any(), any()) } @Test Loading @@ -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 Loading @@ -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), ) { Loading @@ -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, Loading @@ -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), ) { Loading @@ -185,7 +189,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { executor.flushAll() } verify(indicatorSurface1, times(1)) verify(indicatorSurface, times(1)) .relayout( any(), eq(transaction), Loading @@ -197,7 +201,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { executor.flushAll() } verify(indicatorSurface1, times(1)).dispose(transaction) verify(indicatorSurface, times(1)).dispose(transaction) } companion object { Loading