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