• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.wm.shell.common
17 
18 import android.app.ActivityManager.RunningTaskInfo
19 import android.graphics.RectF
20 import android.view.SurfaceControl
21 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
22 import com.android.wm.shell.shared.annotations.ShellDesktopThread
23 
24 /**
25  * Controller to manage the indicators that show users the current position of the dragged window on
26  * the new display when performing drag move across displays.
27  */
28 class MultiDisplayDragMoveIndicatorController(
29     private val displayController: DisplayController,
30     private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
31     private val indicatorSurfaceFactory: MultiDisplayDragMoveIndicatorSurface.Factory,
32     @ShellDesktopThread private val desktopExecutor: ShellExecutor,
33 ) {
34     @ShellDesktopThread
35     private val dragIndicators =
36         mutableMapOf<Int, MutableMap<Int, MultiDisplayDragMoveIndicatorSurface>>()
37 
38     /**
39      * Called during drag move, which started at [startDisplayId]. Updates the position and
40      * visibility of the drag move indicators for the [taskInfo] based on [boundsDp] on the
41      * destination displays ([displayIds]) as the dragged window moves. [transactionSupplier]
42      * provides a [SurfaceControl.Transaction] for applying changes to the indicator surfaces.
43      *
44      * It is executed on the [desktopExecutor] to prevent blocking the main thread and avoid jank,
45      * as creating and manipulating surfaces can be expensive.
46      */
47     fun onDragMove(
48         boundsDp: RectF,
49         startDisplayId: Int,
50         taskInfo: RunningTaskInfo,
51         displayIds: Set<Int>,
52         transactionSupplier: () -> SurfaceControl.Transaction,
53     ) {
54         desktopExecutor.execute {
55             for (displayId in displayIds) {
56                 if (displayId == startDisplayId) {
57                     // No need to render indicators on the original display where the drag started.
58                     continue
59                 }
60                 val displayLayout = displayController.getDisplayLayout(displayId) ?: continue
61                 val shouldBeVisible =
62                     RectF.intersects(RectF(boundsDp), displayLayout.globalBoundsDp())
63                 if (
64                     dragIndicators[taskInfo.taskId]?.containsKey(displayId) != true &&
65                         !shouldBeVisible
66                 ) {
67                     // Skip this display if:
68                     // - It doesn't have an existing indicator that needs to be updated, AND
69                     // - The latest dragged window bounds don't intersect with this display.
70                     continue
71                 }
72 
73                 val boundsPx =
74                     MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
75                         boundsDp,
76                         displayLayout,
77                     )
78 
79                 // Get or create the inner map for the current task.
80                 val dragIndicatorsForTask =
81                     dragIndicators.getOrPut(taskInfo.taskId) { mutableMapOf() }
82                 dragIndicatorsForTask[displayId]?.also { existingIndicator ->
83                     val transaction = transactionSupplier()
84                     existingIndicator.relayout(boundsPx, transaction, shouldBeVisible)
85                     transaction.apply()
86                 } ?: run {
87                     val newIndicator =
88                         indicatorSurfaceFactory.create(
89                             taskInfo,
90                             displayController.getDisplay(displayId),
91                         )
92                     newIndicator.show(
93                         transactionSupplier(),
94                         taskInfo,
95                         rootTaskDisplayAreaOrganizer,
96                         displayId,
97                         boundsPx,
98                     )
99                     dragIndicatorsForTask[displayId] = newIndicator
100                 }
101             }
102         }
103     }
104 
105     /**
106      * Called when the drag ends. Disposes of the drag move indicator surfaces associated with the
107      * given [taskId]. [transactionSupplier] provides a [SurfaceControl.Transaction] for applying
108      * changes to the indicator surfaces.
109      *
110      * It is executed on the [desktopExecutor] to ensure that any pending `onDragMove` operations
111      * have completed before disposing of the surfaces.
112      */
113     fun onDragEnd(taskId: Int, transactionSupplier: () -> SurfaceControl.Transaction) {
114         desktopExecutor.execute {
115             dragIndicators.remove(taskId)?.values?.takeIf { it.isNotEmpty() }?.let { indicators ->
116                 val transaction = transactionSupplier()
117                 indicators.forEach { indicator ->
118                     indicator.disposeSurface(transaction)
119                 }
120                 transaction.apply()
121             }
122         }
123     }
124 }
125