1 /* 2 * Copyright (C) 2024 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 17 package com.android.wm.shell.windowdecor.tiling 18 19 import android.app.ActivityManager 20 import android.app.ActivityManager.RunningTaskInfo 21 import android.content.Context 22 import android.graphics.Rect 23 import android.util.SparseArray 24 import android.window.DisplayAreaInfo 25 import android.window.WindowContainerTransaction 26 import androidx.core.util.valueIterator 27 import com.android.internal.annotations.VisibleForTesting 28 import com.android.wm.shell.R 29 import com.android.wm.shell.RootTaskDisplayAreaOrganizer 30 import com.android.wm.shell.ShellTaskOrganizer 31 import com.android.wm.shell.common.DisplayChangeController 32 import com.android.wm.shell.common.DisplayController 33 import com.android.wm.shell.common.ShellExecutor 34 import com.android.wm.shell.common.SyncTransactionQueue 35 import com.android.wm.shell.desktopmode.DesktopModeEventLogger 36 import com.android.wm.shell.desktopmode.DesktopTasksController 37 import com.android.wm.shell.desktopmode.DesktopUserRepositories 38 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator 39 import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler 40 import com.android.wm.shell.shared.annotations.ShellBackgroundThread 41 import com.android.wm.shell.shared.annotations.ShellMainThread 42 import com.android.wm.shell.transition.FocusTransitionObserver 43 import com.android.wm.shell.transition.Transitions 44 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration 45 import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader 46 import kotlinx.coroutines.CoroutineScope 47 import kotlinx.coroutines.MainCoroutineDispatcher 48 49 /** Manages tiling for each displayId/userId independently. */ 50 class DesktopTilingDecorViewModel( 51 private val context: Context, 52 @ShellMainThread private val mainDispatcher: MainCoroutineDispatcher, 53 @ShellBackgroundThread private val bgScope: CoroutineScope, 54 private val displayController: DisplayController, 55 private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer, 56 private val syncQueue: SyncTransactionQueue, 57 private val transitions: Transitions, 58 private val shellTaskOrganizer: ShellTaskOrganizer, 59 private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, 60 private val returnToDragStartAnimator: ReturnToDragStartAnimator, 61 private val desktopUserRepositories: DesktopUserRepositories, 62 private val desktopModeEventLogger: DesktopModeEventLogger, 63 private val taskResourceLoader: WindowDecorTaskResourceLoader, 64 private val focusTransitionObserver: FocusTransitionObserver, 65 private val mainExecutor: ShellExecutor, 66 ) : DisplayChangeController.OnDisplayChangingListener { 67 @VisibleForTesting 68 var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>() 69 70 init { 71 // TODO(b/374309287): Move this interface implementation to 72 // [DesktopModeWindowDecorViewModel] when the migration is done. 73 displayController.addDisplayChangingController(this) 74 } 75 snapToHalfScreennull76 fun snapToHalfScreen( 77 taskInfo: ActivityManager.RunningTaskInfo, 78 desktopModeWindowDecoration: DesktopModeWindowDecoration, 79 position: DesktopTasksController.SnapPosition, 80 destinationBounds: Rect, 81 ): Boolean { 82 val displayId = taskInfo.displayId 83 val handler = 84 tilingTransitionHandlerByDisplayId.get(displayId) 85 ?: run { 86 val newHandler = 87 DesktopTilingWindowDecoration( 88 context, 89 mainDispatcher, 90 bgScope, 91 syncQueue, 92 displayController, 93 taskResourceLoader, 94 displayId, 95 rootTdaOrganizer, 96 transitions, 97 shellTaskOrganizer, 98 toggleResizeDesktopTaskTransitionHandler, 99 returnToDragStartAnimator, 100 desktopUserRepositories, 101 desktopModeEventLogger, 102 focusTransitionObserver, 103 mainExecutor, 104 ) 105 tilingTransitionHandlerByDisplayId.put(displayId, newHandler) 106 newHandler 107 } 108 transitions.registerObserver(handler) 109 return handler.onAppTiled( 110 taskInfo, 111 desktopModeWindowDecoration, 112 position, 113 destinationBounds, 114 ) 115 } 116 removeTaskIfTilednull117 fun removeTaskIfTiled(displayId: Int, taskId: Int) { 118 tilingTransitionHandlerByDisplayId.get(displayId)?.removeTaskIfTiled(taskId) 119 } 120 moveTaskToFrontIfTilednull121 fun moveTaskToFrontIfTiled(taskInfo: RunningTaskInfo): Boolean { 122 // Always pass focus=true because taskInfo.isFocused is not updated yet. 123 return tilingTransitionHandlerByDisplayId 124 .get(taskInfo.displayId) 125 ?.moveTiledPairToFront(taskInfo.taskId, isFocusedOnDisplay = true) ?: false 126 } 127 onOverviewAnimationStateChangenull128 fun onOverviewAnimationStateChange(isRunning: Boolean) { 129 for (tilingHandler in tilingTransitionHandlerByDisplayId.valueIterator()) { 130 tilingHandler.onOverviewAnimationStateChange(isRunning) 131 } 132 } 133 onUserChangenull134 fun onUserChange() { 135 for (tilingHandler in tilingTransitionHandlerByDisplayId.valueIterator()) { 136 tilingHandler.resetTilingSession() 137 } 138 } 139 onTaskInfoChangenull140 fun onTaskInfoChange(taskInfo: RunningTaskInfo) { 141 tilingTransitionHandlerByDisplayId.get(taskInfo.displayId)?.onTaskInfoChange(taskInfo) 142 } 143 onDisplayChangenull144 override fun onDisplayChange( 145 displayId: Int, 146 fromRotation: Int, 147 toRotation: Int, 148 newDisplayAreaInfo: DisplayAreaInfo?, 149 t: WindowContainerTransaction?, 150 ) { 151 // Exit if the rotation hasn't changed or is changed by 180 degrees. [fromRotation] and 152 // [toRotation] can be one of the [@Surface.Rotation] values. 153 if ((fromRotation % 2 == toRotation % 2)) return 154 tilingTransitionHandlerByDisplayId.get(displayId)?.resetTilingSession() 155 } 156 getRightSnapBoundsIfTilednull157 fun getRightSnapBoundsIfTiled(displayId: Int): Rect { 158 val tilingBounds = 159 tilingTransitionHandlerByDisplayId.get(displayId)?.getRightSnapBoundsIfTiled() 160 if (tilingBounds != null) { 161 return tilingBounds 162 } 163 val displayLayout = displayController.getDisplayLayout(displayId) 164 val stableBounds = Rect() 165 displayLayout?.getStableBounds(stableBounds) 166 val snapBounds = 167 Rect( 168 stableBounds.left + 169 stableBounds.width() / 2 + 170 context.resources.getDimensionPixelSize(R.dimen.split_divider_bar_width) / 2, 171 stableBounds.top, 172 stableBounds.right, 173 stableBounds.bottom, 174 ) 175 return snapBounds 176 } 177 getLeftSnapBoundsIfTilednull178 fun getLeftSnapBoundsIfTiled(displayId: Int): Rect { 179 val tilingBounds = 180 tilingTransitionHandlerByDisplayId.get(displayId)?.getLeftSnapBoundsIfTiled() 181 if (tilingBounds != null) { 182 return tilingBounds 183 } 184 val displayLayout = displayController.getDisplayLayout(displayId) 185 val stableBounds = Rect() 186 displayLayout?.getStableBounds(stableBounds) 187 val snapBounds = 188 Rect( 189 stableBounds.left, 190 stableBounds.top, 191 stableBounds.left + stableBounds.width() / 2 - 192 context.resources.getDimensionPixelSize(R.dimen.split_divider_bar_width) / 2, 193 stableBounds.bottom, 194 ) 195 return snapBounds 196 } 197 } 198