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