• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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