• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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.launcher3.statehandlers
17 
18 import android.content.Context
19 import android.os.Debug
20 import android.util.Log
21 import android.util.Slog
22 import android.util.SparseArray
23 import android.view.Display.DEFAULT_DISPLAY
24 import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
25 import androidx.core.util.forEach
26 import com.android.launcher3.LauncherState
27 import com.android.launcher3.dagger.ApplicationContext
28 import com.android.launcher3.dagger.LauncherAppComponent
29 import com.android.launcher3.dagger.LauncherAppSingleton
30 import com.android.launcher3.statemanager.BaseState
31 import com.android.launcher3.statemanager.StatefulActivity
32 import com.android.launcher3.uioverrides.QuickstepLauncher
33 import com.android.launcher3.util.DaggerSingletonObject
34 import com.android.launcher3.util.DaggerSingletonTracker
35 import com.android.launcher3.util.DisplayController
36 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
37 import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener
38 import com.android.quickstep.GestureState.GestureEndTarget
39 import com.android.quickstep.SystemUiProxy
40 import com.android.quickstep.fallback.RecentsState
41 import com.android.wm.shell.desktopmode.DisplayDeskState
42 import com.android.wm.shell.desktopmode.IDesktopTaskListener.Stub
43 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.enableMultipleDesktops
44 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useRoundedCorners
45 import java.io.PrintWriter
46 import java.lang.ref.WeakReference
47 import javax.inject.Inject
48 
49 /**
50  * Controls the visibility of the workspace and the resumed / paused state when desktop mode is
51  * enabled.
52  */
53 @LauncherAppSingleton
54 class DesktopVisibilityController
55 @Inject
56 constructor(
57     @ApplicationContext private val context: Context,
58     systemUiProxy: SystemUiProxy,
59     lifecycleTracker: DaggerSingletonTracker,
60 ) {
61     /**
62      * Tracks the desks configurations on each display.
63      *
64      * (Used only when multiple desks are enabled).
65      *
66      * @property displayId The ID of the display this object represents.
67      * @property activeDeskId The ID of the active desk on the associated display (if any). It has a
68      *   value of `INACTIVE_DESK_ID` (-1) if there are no active desks. Note that there can only be
69      *   at most one active desk on each display.
70      * @property deskIds a set containing the IDs of the desks on the associated display.
71      */
72     private data class DisplayDeskConfig(
73         val displayId: Int,
74         var activeDeskId: Int = INACTIVE_DESK_ID,
75         val deskIds: MutableSet<Int>,
76     )
77 
78     /** True if it is possible to create new desks on current setup. */
79     var canCreateDesks: Boolean = false
80         private set(value) {
81             if (field == value) return
82             field = value
83             desktopVisibilityListeners.forEach { it.onCanCreateDesksChanged(field) }
84         }
85 
86     /** Maps each display by its ID to its desks configuration. */
87     private val displaysDesksConfigsMap = SparseArray<DisplayDeskConfig>()
88 
89     private val desktopVisibilityListeners: MutableSet<DesktopVisibilityListener> = HashSet()
90     private val taskbarDesktopModeListeners: MutableSet<TaskbarDesktopModeListener> = HashSet()
91 
92     // This simply indicates that user is currently in desktop mode or not.
93     @Deprecated("Does not work with multi-desks") private var isInDesktopModeDeprecated = false
94 
95     // to track if any pending notification to be done.
96     var isNotifyingDesktopVisibilityPending = false
97 
98     // to let launcher hold off on notifying desktop visibility listeners.
99     var launcherAnimationRunning = false
100 
101     // TODO: b/394387739 - Deprecate this and replace it with something that tracks the count per
102     //  desk.
103     /**
104      * Number of visible desktop windows in desktop mode. This can be > 0 when user goes to overview
105      * from desktop window mode.
106      */
107     @Deprecated("Does not work with multi-desks")
108     var visibleDesktopTasksCountDeprecated: Int = 0
109         /**
110          * Sets the number of desktop windows that are visible and updates launcher visibility based
111          * on it.
112          */
113         set(visibleTasksCount) {
114             if (enableMultipleDesktops(context)) {
115                 return
116             }
117             if (DEBUG) {
118                 Log.d(
119                     TAG,
120                     ("setVisibleDesktopTasksCount: visibleTasksCount=" +
121                         visibleTasksCount +
122                         " currentValue=" +
123                         field),
124                 )
125             }
126 
127             if (visibleTasksCount != field) {
128                 if (visibleDesktopTasksCountDeprecated == 0 && visibleTasksCount == 1) {
129                     isInDesktopModeDeprecated = true
130                 }
131                 if (visibleDesktopTasksCountDeprecated == 1 && visibleTasksCount == 0) {
132                     isInDesktopModeDeprecated = false
133                 }
134                 val wasVisible = field > 0
135                 val isVisible = visibleTasksCount > 0
136                 val wereDesktopTasksVisibleBefore = areDesktopTasksVisibleAndNotInOverview()
137                 field = visibleTasksCount
138                 val areDesktopTasksVisibleNow = areDesktopTasksVisibleAndNotInOverview()
139 
140                 if (
141                     wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow ||
142                         wasVisible != isVisible
143                 ) {
144                     if (!launcherAnimationRunning) {
145                         notifyIsInDesktopModeChanged(DEFAULT_DISPLAY, areDesktopTasksVisibleNow)
146                     } else {
147                         isNotifyingDesktopVisibilityPending = true
148                     }
149                 }
150 
151                 if (
152                     !ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue && wasVisible != isVisible
153                 ) {
154                     // TODO: b/333533253 - Remove after flag rollout
155                     if (field > 0) {
156                         if (!inOverviewState) {
157                             // When desktop tasks are visible & we're not in overview, we want
158                             // launcher
159                             // to appear paused, this ensures that taskbar displays.
160                             markLauncherPaused()
161                         }
162                     } else {
163                         // If desktop tasks aren't visible, ensure that launcher appears resumed to
164                         // behave normally.
165                         markLauncherResumed()
166                     }
167                 }
168             }
169         }
170 
171     private var inOverviewState = false
172     private var backgroundStateEnabled = false
173     private var gestureInProgress = false
174 
175     private var desktopTaskListener: DesktopTaskListenerImpl?
176 
177     init {
178         desktopTaskListener = DesktopTaskListenerImpl(this, context, context.displayId)
179         systemUiProxy.setDesktopTaskListener(desktopTaskListener)
180 
181         lifecycleTracker.addCloseable {
182             desktopTaskListener = null
183             systemUiProxy.setDesktopTaskListener(null)
184         }
185     }
186 
187     /**
188      * Returns the ID of the active desk (if any) on the display whose ID is [displayId], or
189      * [INACTIVE_DESK_ID] if no desk is currently active or the multiple desks feature is disabled.
190      */
191     fun getActiveDeskId(displayId: Int): Int {
192         if (!enableMultipleDesktops(context)) {
193             // When the multiple desks feature is disabled, callers should not rely on the concept
194             // of a desk ID.
195             return INACTIVE_DESK_ID
196         }
197 
198         return getDisplayDeskConfig(displayId)?.activeDeskId ?: INACTIVE_DESK_ID
199     }
200 
201     /** Returns whether a desk is currently active on the display with the given [displayId]. */
202     fun isInDesktopMode(displayId: Int): Boolean {
203         if (!enableMultipleDesktops(context)) {
204             return isInDesktopModeDeprecated
205         }
206 
207         val activeDeskId = getDisplayDeskConfig(displayId)?.activeDeskId ?: INACTIVE_DESK_ID
208         val isInDesktopMode = activeDeskId != INACTIVE_DESK_ID
209         if (DEBUG) {
210             Log.d(TAG, "isInDesktopMode: $isInDesktopMode")
211         }
212         return isInDesktopMode
213     }
214 
215     /**
216      * Returns whether a desk is currently active on the display with the given [displayId] and
217      * Overview is not active.
218      */
219     fun isInDesktopModeAndNotInOverview(displayId: Int): Boolean {
220         if (!enableMultipleDesktops(context)) {
221             return areDesktopTasksVisibleAndNotInOverview()
222         }
223 
224         if (DEBUG) {
225             Log.d(TAG, "isInDesktopModeAndNotInOverview: overview=$inOverviewState")
226         }
227         return isInDesktopMode(displayId) && !inOverviewState
228     }
229 
230     /** Whether desktop tasks are visible in desktop mode. */
231     private fun areDesktopTasksVisibleAndNotInOverview(): Boolean {
232         val desktopTasksVisible: Boolean = visibleDesktopTasksCountDeprecated > 0
233         if (DEBUG) {
234             Log.d(
235                 TAG,
236                 ("areDesktopTasksVisible: desktopVisible=" +
237                     desktopTasksVisible +
238                     " overview=" +
239                     inOverviewState),
240             )
241         }
242         return desktopTasksVisible && !inOverviewState
243     }
244 
245     /** Registers a listener for Taskbar changes in Desktop Mode. */
246     fun registerTaskbarDesktopModeListener(listener: TaskbarDesktopModeListener) {
247         taskbarDesktopModeListeners.add(listener)
248     }
249 
250     /** Removes a previously registered listener for Taskbar changes in Desktop Mode. */
251     fun unregisterTaskbarDesktopModeListener(listener: TaskbarDesktopModeListener) {
252         taskbarDesktopModeListeners.remove(listener)
253     }
254 
255     fun onLauncherStateChanged(state: LauncherState) {
256         onLauncherStateChanged(
257             state,
258             state === LauncherState.BACKGROUND_APP,
259             state.isRecentsViewVisible,
260         )
261     }
262 
263     /**
264      * Launcher Driven Desktop Mode changes. For example, swipe to home and quick switch from
265      * Desktop Windowing Mode. if there is any pending notification please notify desktop visibility
266      * listeners.
267      */
268     fun onLauncherAnimationFromDesktopEnd() {
269         launcherAnimationRunning = false
270         if (isNotifyingDesktopVisibilityPending) {
271             isNotifyingDesktopVisibilityPending = false
272             notifyIsInDesktopModeChanged(
273                 DEFAULT_DISPLAY,
274                 isInDesktopModeAndNotInOverview(DEFAULT_DISPLAY),
275             )
276         }
277     }
278 
279     fun onLauncherStateChanged(state: RecentsState) {
280         onLauncherStateChanged(
281             state,
282             state === RecentsState.BACKGROUND_APP,
283             state.isRecentsViewVisible,
284         )
285     }
286 
287     /** Process launcher state change and update launcher view visibility based on desktop state */
288     fun onLauncherStateChanged(
289         state: BaseState<*>,
290         isBackgroundAppState: Boolean,
291         isRecentsViewVisible: Boolean,
292     ) {
293         if (DEBUG) {
294             Log.d(TAG, "onLauncherStateChanged: newState=$state")
295         }
296         setBackgroundStateEnabled(isBackgroundAppState)
297         // Desktop visibility tracks overview and background state separately
298         setOverviewStateEnabled(!isBackgroundAppState && isRecentsViewVisible)
299     }
300 
301     private fun setOverviewStateEnabled(overviewStateEnabled: Boolean) {
302         if (DEBUG) {
303             Log.d(
304                 TAG,
305                 ("setOverviewStateEnabled: enabled=" +
306                     overviewStateEnabled +
307                     " currentValue=" +
308                     inOverviewState),
309             )
310         }
311         if (overviewStateEnabled != inOverviewState) {
312             val wereDesktopTasksVisibleBefore = areDesktopTasksVisibleAndNotInOverview()
313             inOverviewState = overviewStateEnabled
314             val areDesktopTasksVisibleNow = areDesktopTasksVisibleAndNotInOverview()
315 
316             if (!enableMultipleDesktops(context)) {
317                 if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) {
318                     notifyIsInDesktopModeChanged(DEFAULT_DISPLAY, areDesktopTasksVisibleNow)
319                 }
320             } else {
321                 // When overview state changes, it changes together on all displays.
322                 displaysDesksConfigsMap.forEach { displayId, deskConfig ->
323                     // Overview affects the state of desks only if desktop mode is active on this
324                     // display.
325                     if (isInDesktopMode(displayId)) {
326                         notifyIsInDesktopModeChanged(
327                             displayId,
328                             isInDesktopModeAndNotInOverview(displayId),
329                         )
330                     }
331                 }
332             }
333 
334             if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue) {
335                 return
336             }
337 
338             // TODO: b/333533253 - Clean up after flag rollout
339             if (inOverviewState) {
340                 markLauncherResumed()
341             } else if (areDesktopTasksVisibleNow && !gestureInProgress) {
342                 // Switching out of overview state and gesture finished.
343                 // If desktop tasks are still visible, hide launcher again.
344                 markLauncherPaused()
345             }
346         }
347     }
348 
349     /** Registers a listener for Taskbar changes in Desktop Mode. */
350     fun registerDesktopVisibilityListener(listener: DesktopVisibilityListener) {
351         desktopVisibilityListeners.add(listener)
352     }
353 
354     /** Removes a previously registered listener for Taskbar changes in Desktop Mode. */
355     fun unregisterDesktopVisibilityListener(listener: DesktopVisibilityListener) {
356         desktopVisibilityListeners.remove(listener)
357     }
358 
359     private fun notifyIsInDesktopModeChanged(
360         displayId: Int,
361         isInDesktopModeAndNotInOverview: Boolean,
362     ) {
363         if (DEBUG) {
364             Log.d(
365                 TAG,
366                 "notifyIsInDesktopModeChanged: displayId=$displayId, isInDesktopModeAndNotInOverview=$isInDesktopModeAndNotInOverview",
367             )
368         }
369 
370         for (listener in desktopVisibilityListeners) {
371             listener.onIsInDesktopModeChanged(displayId, isInDesktopModeAndNotInOverview)
372         }
373     }
374 
375     private fun notifyTaskbarDesktopModeListeners(doesAnyTaskRequireTaskbarRounding: Boolean) {
376         if (DEBUG) {
377             Log.d(
378                 TAG,
379                 "notifyTaskbarDesktopModeListeners: doesAnyTaskRequireTaskbarRounding=" +
380                     doesAnyTaskRequireTaskbarRounding,
381             )
382         }
383         for (listener in taskbarDesktopModeListeners) {
384             listener.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding)
385         }
386     }
387 
388     private fun notifyTaskbarDesktopModeListenersForEntry(duration: Int) {
389         if (DEBUG) {
390             Log.d(TAG, "notifyTaskbarDesktopModeListenersForEntry: duration=" + duration)
391         }
392         for (listener in taskbarDesktopModeListeners) {
393             listener.onEnterDesktopMode(duration)
394         }
395         DisplayController.INSTANCE.get(context).notifyConfigChange()
396     }
397 
398     private fun notifyTaskbarDesktopModeListenersForExit(duration: Int) {
399         if (DEBUG) {
400             Log.d(TAG, "notifyTaskbarDesktopModeListenersForExit: duration=" + duration)
401         }
402         for (listener in taskbarDesktopModeListeners) {
403             listener.onExitDesktopMode(duration)
404         }
405         DisplayController.INSTANCE.get(context).notifyConfigChange()
406     }
407 
408     private fun notifyOnDeskAdded(displayId: Int, deskId: Int) {
409         if (DEBUG) {
410             Log.d(TAG, "notifyOnDeskAdded: displayId=$displayId, deskId=$deskId")
411         }
412 
413         for (listener in desktopVisibilityListeners) {
414             listener.onDeskAdded(displayId, deskId)
415         }
416     }
417 
418     private fun notifyOnDeskRemoved(displayId: Int, deskId: Int) {
419         if (DEBUG) {
420             Log.d(TAG, "notifyOnDeskRemoved: displayId=$displayId, deskId=$deskId")
421         }
422 
423         for (listener in desktopVisibilityListeners) {
424             listener.onDeskRemoved(displayId, deskId)
425         }
426     }
427 
428     private fun notifyOnActiveDeskChanged(displayId: Int, newActiveDesk: Int, oldActiveDesk: Int) {
429         if (DEBUG) {
430             Log.d(
431                 TAG,
432                 "notifyOnActiveDeskChanged: displayId=$displayId, newActiveDesk=$newActiveDesk, oldActiveDesk=$oldActiveDesk",
433             )
434         }
435 
436         for (listener in desktopVisibilityListeners) {
437             listener.onActiveDeskChanged(displayId, newActiveDesk, oldActiveDesk)
438         }
439     }
440 
441     /** TODO: b/333533253 - Remove after flag rollout */
442     private fun setBackgroundStateEnabled(backgroundStateEnabled: Boolean) {
443         if (DEBUG) {
444             Log.d(
445                 TAG,
446                 ("setBackgroundStateEnabled: enabled=" +
447                     backgroundStateEnabled +
448                     " currentValue=" +
449                     this.backgroundStateEnabled),
450             )
451         }
452         if (backgroundStateEnabled != this.backgroundStateEnabled) {
453             this.backgroundStateEnabled = backgroundStateEnabled
454             if (this.backgroundStateEnabled) {
455                 markLauncherResumed()
456             } else if (areDesktopTasksVisibleAndNotInOverview() && !gestureInProgress) {
457                 // Switching out of background state. If desktop tasks are visible, pause launcher.
458                 markLauncherPaused()
459             }
460         }
461     }
462 
463     var isRecentsGestureInProgress: Boolean
464         /**
465          * Whether recents gesture is currently in progress.
466          *
467          * TODO: b/333533253 - Remove after flag rollout
468          */
469         get() = gestureInProgress
470         /** TODO: b/333533253 - Remove after flag rollout */
471         private set(gestureInProgress) {
472             if (gestureInProgress != this.gestureInProgress) {
473                 this.gestureInProgress = gestureInProgress
474             }
475         }
476 
477     /**
478      * Notify controller that recents gesture has started.
479      *
480      * TODO: b/333533253 - Remove after flag rollout
481      */
482     fun setRecentsGestureStart() {
483         if (DEBUG) {
484             Log.d(TAG, "setRecentsGestureStart")
485         }
486         isRecentsGestureInProgress = true
487     }
488 
489     /**
490      * Notify controller that recents gesture finished with the given
491      * [com.android.quickstep.GestureState.GestureEndTarget]
492      *
493      * TODO: b/333533253 - Remove after flag rollout
494      */
495     fun setRecentsGestureEnd(endTarget: GestureEndTarget?) {
496         if (DEBUG) {
497             Log.d(TAG, "setRecentsGestureEnd: endTarget=$endTarget")
498         }
499         isRecentsGestureInProgress = false
500 
501         if (endTarget == null) {
502             // Gesture did not result in a new end target. Ensure launchers gets paused again.
503             markLauncherPaused()
504         }
505     }
506 
507     private fun onListenerConnected(
508         displayDeskStates: Array<DisplayDeskState>,
509         canCreateDesks: Boolean,
510     ) {
511         if (!enableMultipleDesktops(context)) {
512             return
513         }
514 
515         displaysDesksConfigsMap.clear()
516 
517         displayDeskStates.forEach { displayDeskState ->
518             displaysDesksConfigsMap[displayDeskState.displayId] =
519                 DisplayDeskConfig(
520                     displayId = displayDeskState.displayId,
521                     activeDeskId = displayDeskState.activeDeskId,
522                     deskIds = displayDeskState.deskIds.toMutableSet(),
523                 )
524         }
525 
526         this.canCreateDesks = canCreateDesks
527     }
528 
529     private fun getDisplayDeskConfig(displayId: Int) =
530         displaysDesksConfigsMap[displayId]
531             ?: null.also { Slog.e(TAG, "Expected non-null desk config for display: $displayId") }
532 
533     private fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
534         if (!enableMultipleDesktops(context)) {
535             return
536         }
537 
538         this.canCreateDesks = canCreateDesks
539     }
540 
541     private fun onDeskAdded(displayId: Int, deskId: Int) {
542         if (!enableMultipleDesktops(context)) {
543             return
544         }
545 
546         getDisplayDeskConfig(displayId)?.also {
547             check(it.deskIds.add(deskId)) {
548                 "Found a duplicate desk Id: $deskId on display: $displayId"
549             }
550         }
551 
552         notifyOnDeskAdded(displayId, deskId)
553     }
554 
555     private fun onDeskRemoved(displayId: Int, deskId: Int) {
556         if (!enableMultipleDesktops(context)) {
557             return
558         }
559 
560         getDisplayDeskConfig(displayId)?.also {
561             check(it.deskIds.remove(deskId)) {
562                 "Removing non-existing desk Id: $deskId on display: $displayId"
563             }
564             if (it.activeDeskId == deskId) {
565                 it.activeDeskId = INACTIVE_DESK_ID
566             }
567         }
568 
569         notifyOnDeskRemoved(displayId, deskId)
570     }
571 
572     private fun onActiveDeskChanged(displayId: Int, newActiveDesk: Int, oldActiveDesk: Int) {
573         if (!enableMultipleDesktops(context)) {
574             return
575         }
576 
577         val wasInDesktopMode = isInDesktopModeAndNotInOverview(displayId)
578 
579         getDisplayDeskConfig(displayId)?.also {
580             check(oldActiveDesk == it.activeDeskId) {
581                 "Mismatch between the Shell's oldActiveDesk: $oldActiveDesk, and Launcher's: ${it.activeDeskId}"
582             }
583             check(newActiveDesk == INACTIVE_DESK_ID || it.deskIds.contains(newActiveDesk)) {
584                 "newActiveDesk: $newActiveDesk was never added to display: $displayId"
585             }
586             it.activeDeskId = newActiveDesk
587         }
588 
589         if (newActiveDesk != oldActiveDesk) {
590             notifyOnActiveDeskChanged(displayId, newActiveDesk, oldActiveDesk)
591         }
592 
593         if (wasInDesktopMode != isInDesktopModeAndNotInOverview(displayId)) {
594             notifyIsInDesktopModeChanged(displayId, !wasInDesktopMode)
595         }
596     }
597 
598     /** TODO: b/333533253 - Remove after flag rollout */
599     private fun markLauncherPaused() {
600         if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue) {
601             return
602         }
603         if (DEBUG) {
604             Log.d(TAG, "markLauncherPaused " + Debug.getCaller())
605         }
606         val activity: StatefulActivity<LauncherState>? =
607             QuickstepLauncher.ACTIVITY_TRACKER.getCreatedContext()
608         activity?.setPaused()
609     }
610 
611     /** TODO: b/333533253 - Remove after flag rollout */
612     private fun markLauncherResumed() {
613         if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue) {
614             return
615         }
616         if (DEBUG) {
617             Log.d(TAG, "markLauncherResumed " + Debug.getCaller())
618         }
619         val activity: StatefulActivity<LauncherState>? =
620             QuickstepLauncher.ACTIVITY_TRACKER.getCreatedContext()
621         // Check activity state before calling setResumed(). Launcher may have been actually
622         // paused (eg fullscreen task moved to front).
623         // In this case we should not mark the activity as resumed.
624         if (activity != null && activity.isResumed) {
625             activity.setResumed()
626         }
627     }
628 
629     fun dumpLogs(prefix: String, pw: PrintWriter) {
630         pw.println(prefix + "DesktopVisibilityController:")
631 
632         pw.println("$prefix\tdesktopVisibilityListeners=$desktopVisibilityListeners")
633         pw.println("$prefix\tvisibleDesktopTasksCount=$visibleDesktopTasksCountDeprecated")
634         pw.println("$prefix\tinOverviewState=$inOverviewState")
635         pw.println("$prefix\tbackgroundStateEnabled=$backgroundStateEnabled")
636         pw.println("$prefix\tgestureInProgress=$gestureInProgress")
637         pw.println("$prefix\tdesktopTaskListener=$desktopTaskListener")
638         pw.println("$prefix\tcontext=$context")
639     }
640 
641     /**
642      * Wrapper for the IDesktopTaskListener stub to prevent lingering references to the launcher
643      * activity via the controller.
644      */
645     private class DesktopTaskListenerImpl(
646         controller: DesktopVisibilityController,
647         @ApplicationContext private val context: Context,
648         private val displayId: Int,
649     ) : Stub() {
650         private val controller = WeakReference(controller)
651 
652         override fun onListenerConnected(
653             displayDeskStates: Array<DisplayDeskState>,
654             canCreateDesks: Boolean,
655         ) {
656             MAIN_EXECUTOR.execute {
657                 controller.get()?.onListenerConnected(displayDeskStates, canCreateDesks)
658             }
659         }
660 
661         override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
662             if (displayId != this.displayId) return
663             MAIN_EXECUTOR.execute {
664                 controller.get()?.apply {
665                     if (DEBUG) {
666                         Log.d(TAG, "desktop visible tasks count changed=$visibleTasksCount")
667                     }
668                     visibleDesktopTasksCountDeprecated = visibleTasksCount
669                 }
670             }
671         }
672 
673         override fun onStashedChanged(displayId: Int, stashed: Boolean) {
674             Log.w(TAG, "DesktopTaskListenerImpl: onStashedChanged is deprecated")
675         }
676 
677         override fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {
678             if (!useRoundedCorners()) return
679             MAIN_EXECUTOR.execute {
680                 controller.get()?.apply {
681                     Log.d(
682                         TAG,
683                         "DesktopTaskListenerImpl: doesAnyTaskRequireTaskbarRounding= " +
684                             doesAnyTaskRequireTaskbarRounding,
685                     )
686                     notifyTaskbarDesktopModeListeners(doesAnyTaskRequireTaskbarRounding)
687                 }
688             }
689         }
690 
691         // TODO: b/402496827 - The multi-desks backend needs to be updated to call this API only
692         //  once, not between desk switches.
693         override fun onEnterDesktopModeTransitionStarted(transitionDuration: Int) {
694             val controller = controller.get() ?: return
695             MAIN_EXECUTOR.execute {
696                 Log.d(
697                     TAG,
698                     ("DesktopTaskListenerImpl: onEnterDesktopModeTransitionStarted with " +
699                         "duration= " +
700                         transitionDuration),
701                 )
702                 if (enableMultipleDesktops(context)) {
703                     controller.notifyTaskbarDesktopModeListenersForEntry(transitionDuration)
704                 } else if (!controller.isInDesktopModeDeprecated) {
705                     controller.isInDesktopModeDeprecated = true
706                     controller.notifyTaskbarDesktopModeListenersForEntry(transitionDuration)
707                 }
708             }
709         }
710 
711         // TODO: b/402496827 - The multi-desks backend needs to be updated to call this API only
712         //  once, not between desk switches.
713         override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {
714             val controller = controller.get() ?: return
715             MAIN_EXECUTOR.execute {
716                 Log.d(
717                     TAG,
718                     ("DesktopTaskListenerImpl: onExitDesktopModeTransitionStarted with " +
719                         "duration= " +
720                         transitionDuration),
721                 )
722                 if (enableMultipleDesktops(context)) {
723                     controller.notifyTaskbarDesktopModeListenersForExit(transitionDuration)
724                 } else if (controller.isInDesktopModeDeprecated) {
725                     controller.isInDesktopModeDeprecated = false
726                     controller.notifyTaskbarDesktopModeListenersForExit(transitionDuration)
727                 }
728             }
729         }
730 
731         override fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
732             MAIN_EXECUTOR.execute { controller.get()?.onCanCreateDesksChanged(canCreateDesks) }
733         }
734 
735         override fun onDeskAdded(displayId: Int, deskId: Int) {
736             MAIN_EXECUTOR.execute { controller.get()?.onDeskAdded(displayId, deskId) }
737         }
738 
739         override fun onDeskRemoved(displayId: Int, deskId: Int) {
740             MAIN_EXECUTOR.execute { controller.get()?.onDeskRemoved(displayId, deskId) }
741         }
742 
743         override fun onActiveDeskChanged(displayId: Int, newActiveDesk: Int, oldActiveDesk: Int) {
744             MAIN_EXECUTOR.execute {
745                 controller.get()?.onActiveDeskChanged(displayId, newActiveDesk, oldActiveDesk)
746             }
747         }
748     }
749 
750     /** A listener for Taskbar in Desktop Mode. */
751     interface TaskbarDesktopModeListener {
752         /**
753          * Callback for when task is resized in desktop mode.
754          *
755          * @param doesAnyTaskRequireTaskbarRounding whether task requires taskbar corner roundness.
756          */
757         fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {}
758 
759         /**
760          * Callback for when user is exiting desktop mode.
761          *
762          * @param duration for exit transition
763          */
764         fun onExitDesktopMode(duration: Int) {}
765 
766         /**
767          * Callback for when user is entering desktop mode.
768          *
769          * @param duration for enter transition
770          */
771         fun onEnterDesktopMode(duration: Int) {}
772     }
773 
774     companion object {
775         @JvmField
776         val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getDesktopVisibilityController)
777 
778         private const val TAG = "DesktopVisController"
779         private const val DEBUG = false
780 
781         const val INACTIVE_DESK_ID = -1
782     }
783 }
784