• 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.desktopmode.multidesks
17 
18 import android.annotation.SuppressLint
19 import android.app.ActivityManager.RunningTaskInfo
20 import android.app.ActivityTaskManager.INVALID_TASK_ID
21 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
22 import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
23 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
24 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
25 import android.util.SparseArray
26 import android.view.SurfaceControl
27 import android.view.WindowManager.TRANSIT_TO_FRONT
28 import android.window.DesktopExperienceFlags
29 import android.window.TransitionInfo
30 import android.window.WindowContainerToken
31 import android.window.WindowContainerTransaction
32 import androidx.core.util.forEach
33 import androidx.core.util.valueIterator
34 import com.android.internal.annotations.VisibleForTesting
35 import com.android.internal.protolog.ProtoLog
36 import com.android.wm.shell.ShellTaskOrganizer
37 import com.android.wm.shell.common.LaunchAdjacentController
38 import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback
39 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
40 import com.android.wm.shell.sysui.ShellCommandHandler
41 import com.android.wm.shell.sysui.ShellInit
42 import java.io.PrintWriter
43 
44 /**
45  * A [DesksOrganizer] that uses root tasks as the container of each desk.
46  *
47  * Note that root tasks are reusable between multiple users at the same time, and may also be
48  * pre-created to have one ready for the first entry to the default desk, so root-task existence
49  * does not imply a formal desk exists to the user.
50  */
51 class RootTaskDesksOrganizer(
52     shellInit: ShellInit,
53     shellCommandHandler: ShellCommandHandler,
54     private val shellTaskOrganizer: ShellTaskOrganizer,
55     private val launchAdjacentController: LaunchAdjacentController,
56 ) : DesksOrganizer, ShellTaskOrganizer.TaskListener {
57 
58     private val createDeskRootRequests = mutableListOf<CreateDeskRequest>()
59     @VisibleForTesting val deskRootsByDeskId = SparseArray<DeskRoot>()
60     private val createDeskMinimizationRootRequests =
61         mutableListOf<CreateDeskMinimizationRootRequest>()
62     @VisibleForTesting
63     val deskMinimizationRootsByDeskId: MutableMap<Int, DeskMinimizationRoot> = mutableMapOf()
64     private var onTaskInfoChangedListener: ((RunningTaskInfo) -> Unit)? = null
65 
66     init {
67         if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
68             shellInit.addInitCallback(
69                 { shellCommandHandler.addDumpCallback(this::dump, this) },
70                 this,
71             )
72         }
73     }
74 
75     override fun createDesk(displayId: Int, userId: Int, callback: OnCreateCallback) {
76         logV("createDesk in displayId=%d userId=%s", displayId, userId)
77         // Find an existing desk that is not yet used by this user.
78         val unassignedDesk =
79             deskRootsByDeskId
80                 .valueIterator()
81                 .asSequence()
82                 .filterNot { desk -> userId in desk.users }
83                 .firstOrNull()
84         if (unassignedDesk != null) {
85             unassignedDesk.users.add(userId)
86             callback.onCreated(unassignedDesk.deskId)
87             return
88         }
89         createDeskRoot(displayId, userId, callback)
90     }
91 
92     private fun createDeskRoot(displayId: Int, userId: Int, callback: OnCreateCallback) {
93         logV("createDeskRoot in display: %d for user: %d", displayId, userId)
94         createDeskRootRequests += CreateDeskRequest(displayId, userId, callback)
95         shellTaskOrganizer.createRootTask(
96             displayId,
97             WINDOWING_MODE_FREEFORM,
98             /* listener = */ this,
99             /* removeWithTaskOrganizer = */ true,
100         )
101     }
102 
103     override fun removeDesk(wct: WindowContainerTransaction, deskId: Int, userId: Int) {
104         logV("removeDesk %d for userId=%d", deskId, userId)
105         val deskRoot = deskRootsByDeskId[deskId]
106         if (deskRoot == null) {
107             logW("removeDesk attempted to remove non-existent desk=%d", deskId)
108             return
109         }
110         updateLaunchRoot(wct, deskId, enabled = false)
111         deskRoot.users.remove(userId)
112         if (deskRoot.users.isEmpty()) {
113             // No longer in use by any users, remove it completely.
114             logD("removeDesk %d is no longer used by any users, removing it completely", deskId)
115             wct.removeRootTask(deskRoot.token)
116             deskMinimizationRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) }
117         }
118     }
119 
120     override fun activateDesk(wct: WindowContainerTransaction, deskId: Int) {
121         logV("activateDesk %d", deskId)
122         val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" }
123         wct.reorder(root.token, /* onTop= */ true)
124         updateLaunchRoot(wct, deskId, enabled = true)
125     }
126 
127     override fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int) {
128         logV("deactivateDesk %d", deskId)
129         updateLaunchRoot(wct, deskId, enabled = false)
130     }
131 
132     private fun updateLaunchRoot(wct: WindowContainerTransaction, deskId: Int, enabled: Boolean) {
133         val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" }
134         root.isLaunchRootRequested = enabled
135         logD("updateLaunchRoot deskId=%d enabled=%b", deskId, enabled)
136         if (enabled) {
137             wct.setLaunchRoot(
138                 /* container= */ root.taskInfo.token,
139                 /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED),
140                 /* activityTypes= */ intArrayOf(ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD),
141             )
142         } else {
143             wct.setLaunchRoot(
144                 /* container= */ root.taskInfo.token,
145                 /* windowingModes= */ null,
146                 /* activityTypes= */ null,
147             )
148         }
149     }
150 
151     override fun moveTaskToDesk(
152         wct: WindowContainerTransaction,
153         deskId: Int,
154         task: RunningTaskInfo,
155     ) {
156         val root = deskRootsByDeskId[deskId] ?: error("Root not found for desk: $deskId")
157         wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
158         wct.reparent(task.token, root.taskInfo.token, /* onTop= */ true)
159     }
160 
161     override fun reorderTaskToFront(
162         wct: WindowContainerTransaction,
163         deskId: Int,
164         task: RunningTaskInfo,
165     ) {
166         logV("reorderTaskToFront task=${task.taskId} desk=$deskId")
167         val root = deskRootsByDeskId[deskId] ?: error("Root not found for desk: $deskId")
168         if (task.taskId in root.children) {
169             wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true)
170             return
171         }
172         val minimizationRoot =
173             checkNotNull(deskMinimizationRootsByDeskId[deskId]) {
174                 "Minimization root not found for desk: $deskId"
175             }
176         if (task.taskId in minimizationRoot.children) {
177             unminimizeTask(wct, deskId, task)
178             wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true)
179             return
180         }
181         logE("Attempted to reorder task=${task.taskId} in desk=$deskId but it was not a child")
182     }
183 
184     override fun minimizeTask(wct: WindowContainerTransaction, deskId: Int, task: RunningTaskInfo) {
185         logV("minimizeTask task=${task.taskId} desk=$deskId")
186         val deskRoot =
187             checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" }
188         val minimizationRoot =
189             checkNotNull(deskMinimizationRootsByDeskId[deskId]) {
190                 "Minimization root not found for desk: $deskId"
191             }
192         val taskId = task.taskId
193         if (taskId in minimizationRoot.children) {
194             logV("Task #$taskId is already minimized in desk #$deskId")
195             return
196         }
197         if (taskId !in deskRoot.children) {
198             logE("Attempted to minimize task=${task.taskId} in desk=$deskId but it was not a child")
199             return
200         }
201         wct.reparent(task.token, minimizationRoot.token, /* onTop= */ true)
202     }
203 
204     override fun unminimizeTask(
205         wct: WindowContainerTransaction,
206         deskId: Int,
207         task: RunningTaskInfo,
208     ) {
209         val taskId = task.taskId
210         logV("unminimizeTask task=$taskId desk=$deskId")
211         val deskRoot =
212             checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" }
213         val minimizationRoot =
214             checkNotNull(deskMinimizationRootsByDeskId[deskId]) {
215                 "Minimization root not found for desk: $deskId"
216             }
217         if (taskId in deskRoot.children) {
218             logV("Task #$taskId is already unminimized in desk=$deskId")
219             return
220         }
221         if (taskId !in minimizationRoot.children) {
222             logE("Attempted to unminimize task=$taskId in desk=$deskId but it was not a child")
223             return
224         }
225         wct.reparent(task.token, deskRoot.token, /* onTop= */ true)
226     }
227 
228     override fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean =
229         (isDeskRootChange(change) && change.taskId == deskId) ||
230             (getDeskMinimizationRootInChange(change)?.deskId == deskId)
231 
232     override fun isDeskChange(change: TransitionInfo.Change): Boolean =
233         isDeskRootChange(change) || getDeskMinimizationRootInChange(change) != null
234 
235     private fun isDeskRootChange(change: TransitionInfo.Change): Boolean =
236         change.taskId in deskRootsByDeskId
237 
238     private fun getDeskMinimizationRootInChange(
239         change: TransitionInfo.Change
240     ): DeskMinimizationRoot? =
241         deskMinimizationRootsByDeskId.values.find { it.rootId == change.taskId }
242 
243     private val TransitionInfo.Change.taskId: Int
244         get() = taskInfo?.taskId ?: INVALID_TASK_ID
245 
246     override fun getDeskAtEnd(change: TransitionInfo.Change): Int? {
247         val parentTaskId = change.taskInfo?.parentTaskId ?: return null
248         if (parentTaskId in deskRootsByDeskId) {
249             return parentTaskId
250         }
251         val deskMinimizationRoot =
252             deskMinimizationRootsByDeskId.values.find { root -> root.rootId == parentTaskId }
253                 ?: return null
254         return deskMinimizationRoot.deskId
255     }
256 
257     override fun isDeskActiveAtEnd(change: TransitionInfo.Change, deskId: Int): Boolean =
258         change.taskInfo?.taskId == deskId &&
259             change.taskInfo?.isVisibleRequested == true &&
260             change.mode == TRANSIT_TO_FRONT
261 
262     override fun setOnDesktopTaskInfoChangedListener(listener: (RunningTaskInfo) -> Unit) {
263         onTaskInfoChangedListener = listener
264     }
265 
266     override fun onTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) {
267         handleTaskAppeared(taskInfo, leash)
268         updateLaunchAdjacentController()
269     }
270 
271     override fun onTaskInfoChanged(taskInfo: RunningTaskInfo) {
272         handleTaskInfoChanged(taskInfo)
273         if (
274             taskInfo.taskId !in deskRootsByDeskId &&
275                 deskMinimizationRootsByDeskId.values.none { it.rootId == taskInfo.taskId }
276         ) {
277             onTaskInfoChangedListener?.invoke(taskInfo)
278         }
279         updateLaunchAdjacentController()
280     }
281 
282     override fun onTaskVanished(taskInfo: RunningTaskInfo) {
283         handleTaskVanished(taskInfo)
284         updateLaunchAdjacentController()
285     }
286 
287     private fun handleTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) {
288         // Check whether this task is appearing inside a desk.
289         if (taskInfo.parentTaskId in deskRootsByDeskId) {
290             val deskId = taskInfo.parentTaskId
291             val taskId = taskInfo.taskId
292             logV("Task #$taskId appeared in desk #$deskId")
293             addChildToDesk(taskId = taskId, deskId = deskId)
294             return
295         }
296         // Check whether this task is appearing in a minimization root.
297         val minimizationRoot =
298             deskMinimizationRootsByDeskId.values.singleOrNull { it.rootId == taskInfo.parentTaskId }
299         if (minimizationRoot != null) {
300             val deskId = minimizationRoot.deskId
301             val taskId = taskInfo.taskId
302             logV("Task #$taskId was minimized in desk #$deskId ")
303             addChildToMinimizationRoot(taskId = taskId, deskId = deskId)
304             return
305         }
306         // The appearing task is a root (either a desk or a minimization root), it should not exist
307         // already.
308         check(taskInfo.taskId !in deskRootsByDeskId) {
309             "A root already exists for desk: ${taskInfo.taskId}"
310         }
311         check(deskMinimizationRootsByDeskId.values.none { it.rootId == taskInfo.taskId }) {
312             "A minimization root already exists with rootId: ${taskInfo.taskId}"
313         }
314 
315         val appearingInDisplayId = taskInfo.displayId
316         // Check if there's any pending desk creation requests under this display.
317         val deskRequest =
318             createDeskRootRequests.firstOrNull { it.displayId == appearingInDisplayId }
319         if (deskRequest != null) {
320             // Appearing root matches desk request.
321             val deskId = taskInfo.taskId
322             logV("Desk #$deskId appeared")
323             deskRootsByDeskId[deskId] =
324                 DeskRoot(
325                     deskId = deskId,
326                     taskInfo = taskInfo,
327                     leash = leash,
328                     users = mutableSetOf(deskRequest.userId),
329                 )
330             createDeskRootRequests.remove(deskRequest)
331             deskRequest.onCreateCallback.onCreated(deskId)
332             createDeskMinimizationRoot(displayId = appearingInDisplayId, deskId = deskId)
333             return
334         }
335         // Check if there's any pending minimization container creation requests under this display.
336         val deskMinimizationRootRequest =
337             createDeskMinimizationRootRequests.first { it.displayId == appearingInDisplayId }
338         val deskId = deskMinimizationRootRequest.deskId
339         logV("Minimization container for desk #$deskId appeared with id=${taskInfo.taskId}")
340         val deskMinimizationRoot = DeskMinimizationRoot(deskId, taskInfo, leash)
341         deskMinimizationRootsByDeskId[deskId] = deskMinimizationRoot
342         createDeskMinimizationRootRequests.remove(deskMinimizationRootRequest)
343         hideMinimizationRoot(deskMinimizationRoot)
344     }
345 
346     private fun handleTaskInfoChanged(taskInfo: RunningTaskInfo) {
347         if (deskRootsByDeskId.contains(taskInfo.taskId)) {
348             val deskId = taskInfo.taskId
349             deskRootsByDeskId[deskId] = deskRootsByDeskId[deskId].copy(taskInfo = taskInfo)
350             logV("Desk #$deskId's task info changed")
351             return
352         }
353         val minimizationRoot =
354             deskMinimizationRootsByDeskId.values.find { root -> root.rootId == taskInfo.taskId }
355         if (minimizationRoot != null) {
356             deskMinimizationRootsByDeskId.remove(minimizationRoot.deskId)
357             deskMinimizationRootsByDeskId[minimizationRoot.deskId] =
358                 minimizationRoot.copy(taskInfo = taskInfo)
359             logV("Minimization root for desk#${minimizationRoot.deskId} task info changed")
360             return
361         }
362 
363         val parentTaskId = taskInfo.parentTaskId
364         if (parentTaskId in deskRootsByDeskId) {
365             val deskId = taskInfo.parentTaskId
366             val taskId = taskInfo.taskId
367             logV("onTaskInfoChanged: Task #$taskId appeared in desk #$deskId")
368             addChildToDesk(taskId = taskId, deskId = deskId)
369             return
370         }
371         // Check whether this task is appearing in a minimization root.
372         val parentMinimizationRoot =
373             deskMinimizationRootsByDeskId.values.singleOrNull { it.rootId == parentTaskId }
374         if (parentMinimizationRoot != null) {
375             val deskId = parentMinimizationRoot.deskId
376             val taskId = taskInfo.taskId
377             logV("onTaskInfoChanged: Task #$taskId was minimized in desk #$deskId ")
378             addChildToMinimizationRoot(taskId = taskId, deskId = deskId)
379             return
380         }
381         logE("onTaskInfoChanged: unknown task: ${taskInfo.taskId}")
382     }
383 
384     private fun handleTaskVanished(taskInfo: RunningTaskInfo) {
385         if (deskRootsByDeskId.contains(taskInfo.taskId)) {
386             val deskId = taskInfo.taskId
387             val deskRoot = deskRootsByDeskId[deskId]
388             // Use the last saved taskInfo to obtain the displayId. Using the local one here will
389             // return -1 since the task is not unassociated with a display.
390             val displayId = deskRoot.taskInfo.displayId
391             logV("Desk #$deskId vanished from display #$displayId")
392             deskRootsByDeskId.remove(deskId)
393             return
394         }
395         val deskMinimizationRoot =
396             deskMinimizationRootsByDeskId.values.singleOrNull { it.rootId == taskInfo.taskId }
397         if (deskMinimizationRoot != null) {
398             logV("Minimization root for desk ${deskMinimizationRoot.deskId} vanished")
399             deskMinimizationRootsByDeskId.remove(deskMinimizationRoot.deskId)
400             return
401         }
402 
403         // Check whether the vanishing task was a child of any desk.
404         // At this point, [parentTaskId] may be unset even if this is a task vanishing from a desk,
405         // so search through each root to remove this if it's a child.
406         deskRootsByDeskId.forEach { deskId, deskRoot ->
407             if (deskRoot.children.remove(taskInfo.taskId)) {
408                 logV("Task #${taskInfo.taskId} vanished from desk #$deskId")
409                 return
410             }
411         }
412         // Check whether the vanishing task was a child of the minimized root and remove it.
413         deskMinimizationRootsByDeskId.values.forEach { root ->
414             val taskId = taskInfo.taskId
415             if (root.children.remove(taskId)) {
416                 logV("Task #$taskId vanished from minimization root of desk #${root.deskId}")
417                 return
418             }
419         }
420     }
421 
422     private fun createDeskMinimizationRoot(displayId: Int, deskId: Int) {
423         createDeskMinimizationRootRequests +=
424             CreateDeskMinimizationRootRequest(displayId = displayId, deskId = deskId)
425         shellTaskOrganizer.createRootTask(
426             displayId,
427             WINDOWING_MODE_FREEFORM,
428             /* listener = */ this,
429             /* removeWithTaskOrganizer = */ true,
430         )
431     }
432 
433     @SuppressLint("MissingPermission")
434     private fun hideMinimizationRoot(root: DeskMinimizationRoot) {
435         shellTaskOrganizer.applyTransaction(
436             WindowContainerTransaction().apply { setHidden(root.token, /* hidden= */ true) }
437         )
438     }
439 
440     private fun addChildToDesk(taskId: Int, deskId: Int) {
441         deskRootsByDeskId.forEach { _, deskRoot ->
442             if (deskRoot.deskId == deskId) {
443                 deskRoot.children.add(taskId)
444             } else {
445                 deskRoot.children.remove(taskId)
446             }
447         }
448         // A task cannot be in both a desk root and a minimization root at the same time, so make
449         // sure to remove them if needed.
450         deskMinimizationRootsByDeskId.values.forEach { root -> root.children.remove(taskId) }
451     }
452 
453     private fun addChildToMinimizationRoot(taskId: Int, deskId: Int) {
454         deskMinimizationRootsByDeskId.forEach { _, minimizationRoot ->
455             if (minimizationRoot.deskId == deskId) {
456                 minimizationRoot.children += taskId
457             } else {
458                 minimizationRoot.children -= taskId
459             }
460         }
461         // A task cannot be in both a desk root and a minimization root at the same time, so make
462         // sure to remove them if needed.
463         deskRootsByDeskId.forEach { _, deskRoot -> deskRoot.children -= taskId }
464     }
465 
466     private fun updateLaunchAdjacentController() {
467         deskRootsByDeskId.forEach { deskId, root ->
468             if (root.taskInfo.isVisible) {
469                 // Disable launch adjacent handling if any desk is active, otherwise the split
470                 // launch root and the desk root will both be eligible to take launching tasks.
471                 launchAdjacentController.launchAdjacentEnabled = false
472                 return
473             }
474         }
475         launchAdjacentController.launchAdjacentEnabled = true
476     }
477 
478     @VisibleForTesting
479     data class DeskRoot(
480         val deskId: Int,
481         val taskInfo: RunningTaskInfo,
482         val leash: SurfaceControl,
483         val children: MutableSet<Int> = mutableSetOf(),
484         val users: MutableSet<Int> = mutableSetOf(),
485         var isLaunchRootRequested: Boolean = false,
486     ) {
487         val token: WindowContainerToken = taskInfo.token
488     }
489 
490     @VisibleForTesting
491     data class DeskMinimizationRoot(
492         val deskId: Int,
493         val taskInfo: RunningTaskInfo,
494         val leash: SurfaceControl,
495         val children: MutableSet<Int> = mutableSetOf(),
496     ) {
497         val rootId: Int
498             get() = taskInfo.taskId
499 
500         val token: WindowContainerToken = taskInfo.token
501     }
502 
503     private data class CreateDeskRequest(
504         val displayId: Int,
505         val userId: Int,
506         val onCreateCallback: OnCreateCallback,
507     )
508 
509     private data class CreateDeskMinimizationRootRequest(val displayId: Int, val deskId: Int)
510 
511     private fun logD(msg: String, vararg arguments: Any?) {
512         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
513     }
514 
515     private fun logV(msg: String, vararg arguments: Any?) {
516         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
517     }
518 
519     private fun logW(msg: String, vararg arguments: Any?) {
520         ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
521     }
522 
523     private fun logE(msg: String, vararg arguments: Any?) {
524         ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
525     }
526 
527     override fun dump(pw: PrintWriter, prefix: String) {
528         val innerPrefix = "$prefix  "
529         pw.println("$prefix$TAG")
530         pw.println(
531             "${innerPrefix}launchAdjacentEnabled=" + launchAdjacentController.launchAdjacentEnabled
532         )
533         pw.println("${innerPrefix}Desk Roots:")
534         deskRootsByDeskId.forEach { deskId, root ->
535             val minimizationRoot = deskMinimizationRootsByDeskId[deskId]
536             pw.println("$innerPrefix  #$deskId visible=${root.taskInfo.isVisible}")
537             pw.println("$innerPrefix    displayId=${root.taskInfo.displayId}")
538             pw.println("$innerPrefix    isLaunchRootRequested=${root.isLaunchRootRequested}")
539             pw.println("$innerPrefix    children=${root.children}")
540             pw.println("$innerPrefix    users=${root.users}")
541             pw.println("$innerPrefix    minimization root:")
542             pw.println("$innerPrefix      rootId=${minimizationRoot?.rootId}")
543             if (minimizationRoot != null) {
544                 pw.println("$innerPrefix      children=${minimizationRoot.children}")
545             }
546         }
547     }
548 
549     companion object {
550         private const val TAG = "RootTaskDesksOrganizer"
551     }
552 }
553