1 /* <lambda>null2 * 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 package com.android.systemui.car.wm.displayarea 17 18 import android.annotation.AnyThread 19 import android.app.ActivityManager.RunningTaskInfo 20 import android.app.ActivityOptions 21 import android.app.PendingIntent 22 import android.content.ComponentName 23 import android.content.Context 24 import android.content.Intent 25 import android.content.Intent.FLAG_ACTIVITY_NEW_TASK 26 import android.graphics.Rect 27 import android.os.IBinder 28 import android.util.Slog 29 import android.view.SurfaceControl 30 import android.view.SurfaceControl.Transaction 31 import android.view.WindowManager 32 import android.view.WindowManager.TRANSIT_CHANGE 33 import android.view.WindowManager.TRANSIT_OPEN 34 import android.window.TransitionInfo 35 import android.window.TransitionRequestInfo 36 import android.window.WindowContainerToken 37 import android.window.WindowContainerTransaction 38 import com.android.systemui.car.Flags.daviewBasedWindowing 39 import com.android.wm.shell.ShellTaskOrganizer 40 import com.android.wm.shell.common.ShellExecutor 41 import com.android.wm.shell.dagger.WMSingleton 42 import com.android.wm.shell.shared.TransitionUtil 43 import com.android.wm.shell.shared.annotations.ShellMainThread 44 import com.android.wm.shell.taskview.TaskViewTransitions 45 import com.android.wm.shell.transition.Transitions 46 import com.android.wm.shell.transition.Transitions.TransitionFinishCallback 47 import com.google.common.annotations.VisibleForTesting 48 import javax.inject.Inject 49 50 /** 51 * This class handles the extra transitions work pertaining to shell transitions when using 52 * [DaView]. This class only works when shell transitions are enabled. 53 */ 54 @WMSingleton 55 class DaViewTransitions @Inject constructor( 56 private val shellTaskOrganizer: ShellTaskOrganizer, 57 private val transitions: Transitions, 58 @ShellMainThread private val shellMainThread: ShellExecutor, 59 private val taskViewTransitions: TaskViewTransitions, 60 private val context: Context, 61 ) : Transitions.TransitionHandler { 62 63 // TODO(b/370075926): think about synchronization here as this state might be getting changed 64 // in the shell main thread 65 val daCurrState = mutableMapOf<DaView, DaState>() 66 67 private val pendingTransitions = ArrayList<PendingTransition>() 68 private var animationHandler: AnimationHandler? = null 69 70 data class DaState( 71 /** 72 * Signifies if the tasks in the Da should be invisible. Please note that hiding/showing 73 * the surface of corresponding [DaView] is still taken care by the animation handler. 74 */ 75 var visible: Boolean = false, 76 var bounds: Rect = Rect(), 77 ) 78 79 // Represents a DaView related operation 80 data class DaTransaction( 81 /** 82 * Represents the state of the [DaView]s that are part of this transaction. It maps the 83 * [DaView.id] to its state. 84 */ 85 var daStates: Map<Long, DaState> = mutableMapOf(), 86 87 /** 88 * The [DaView.id] of the daview that needs to be focused as part of this transaction. This 89 * is useful to ensure that focus ends up at a reasonable place after a transition 90 * involving multiple DaViews is completed. 91 */ 92 var focusedDaId: Long? = null, 93 ) 94 95 /** 96 * This interface should be used by the window which hosts the DaViews to hook into transitions 97 * happening on the core side. 98 * It can be used to animate multiple DaViews when an activity is coming up inside a 99 * {@link DaView} 100 */ 101 interface AnimationHandler { 102 /** 103 * This method is called whenever a task gets started (adb shell, user, an app launch etc) 104 * on a DA. This is an opportunity to add more work to this transition and then animate 105 * later as part of playAnimation(). 106 * 107 * The returned [DaTransaction] is merged with the change happening in WM. 108 * If the [DaTransaction] doesn't have any participant, this transition will be handled by 109 * the default handler and [AnimationHandler.playAnimation] won't be called for that. 110 * 111 * Note: The returned participants must contain the passed DaView with visibility:true, 112 * otherwise it can lead to unexpected state and compliance issues. 113 */ 114 @ShellMainThread 115 fun handleOpenTransitionOnDa( 116 daView: DaView, 117 triggerTaskInfo: RunningTaskInfo, 118 wct: WindowContainerTransaction 119 ): DaTransaction 120 121 /** 122 * Similar to [AnimationHandler.handleOpenTransitionOnDa] but gets called when a 123 * display changes its dimensions. 124 */ 125 @ShellMainThread 126 fun handleDisplayChangeTransition( 127 displayId: Int, 128 newSize: Rect 129 ): DaTransaction 130 131 /** 132 * The penultimate method to play the animation. By this time, the required visibility and 133 * bounds change has already been applied to WM. Before this method is called, 134 * DaViewTransitions will ensure that the transition surfaces are reparented correctly to 135 * the participating DAViews. 136 * The handler can animate the DAView participants (using view animations) as per the state 137 * passed and trigger the finish callback which notifies the WM that the transition is 138 * done. 139 */ 140 @ShellMainThread 141 fun playAnimation( 142 resolvedDaTransaction: DaTransaction, 143 finishCallback: TransitionFinishCallback 144 ) 145 } 146 147 sealed class ChangeType { 148 data object None : ChangeType() 149 data object Hide : ChangeType() 150 data object Show : ChangeType() 151 data object Bounds : ChangeType() 152 153 fun logChange(daView: DaView) { 154 when (this) { 155 Hide -> Slog.d(TAG, "Hiding DA: $daView") 156 Show -> Slog.d(TAG, "Showing DA: $daView") 157 Bounds -> Slog.d(TAG, "Changing DA: $daView") 158 None -> {} // No logging for NONE 159 } 160 } 161 } 162 163 private class DaViewChange( 164 var type: ChangeType = ChangeType.None, 165 var snapshot: SurfaceControl? = null 166 ) 167 168 init { 169 if (!daviewBasedWindowing()) { 170 throw IllegalAccessException("DaView feature not available") 171 } 172 transitions.addHandler(this) 173 sInstance = this 174 } 175 176 /** 177 * Instantly apply this transaction using the {@link ShellTaskOrganizer}. Should only be 178 * used for updating insets. 179 */ 180 fun instantApplyViaTaskOrganizer(wct: WindowContainerTransaction) { 181 shellTaskOrganizer.applyTransaction(wct) 182 } 183 184 /** 185 * Instantly apply this transaction without any custom animation. 186 */ 187 fun instantApplyViaShellTransit(wct: WindowContainerTransaction) { 188 transitions.startTransition(TRANSIT_CHANGE, wct, null) 189 } 190 191 private fun findPending(claimed: IBinder): PendingTransition? { 192 for (pending in pendingTransitions) { 193 if (pending.isClaimed !== claimed) continue 194 return pending 195 } 196 return null 197 } 198 199 fun setAnimationHandler(handler: AnimationHandler?) { 200 animationHandler = handler 201 } 202 203 @AnyThread 204 fun add(daView: DaView) { 205 shellMainThread.execute { 206 daViews[daView.id] = daView 207 daCurrState[daView] = DaState() 208 } 209 } 210 211 @AnyThread 212 fun remove(daView: DaView) { 213 shellMainThread.execute { 214 daViews.remove(daView.id) 215 daCurrState.remove(daView) 216 } 217 } 218 219 /** 220 * Requests to animate the given DaViews to the specified visibility and bounds. It should be 221 * noted that this will send the request to WM but the real playing of the animation should 222 * be done as part of {@link AnimationHandler#playAnimation()}. 223 * 224 * Clients can also set the focus to the desired DaView as part of this transition. 225 */ 226 @AnyThread 227 fun startTransaction(daTransaction: DaTransaction) { 228 shellMainThread.execute { 229 val requestedDaStates = daTransaction.daStates 230 .filter { (key, _) -> 231 when { 232 daViews[key] != null -> true 233 else -> { 234 Slog.w(TAG, "$key is not known to DaViewTransitions") 235 false 236 } 237 } 238 } 239 .mapKeys { (key, _) -> daViews[key]!! } 240 241 val wct = WindowContainerTransaction() 242 val diffedRequestedDaViewStates = calculateWctForAnimationDiff( 243 requestedDaStates, 244 wct 245 ) 246 if (DBG) { 247 Slog.d(TAG, "requested da view states = $diffedRequestedDaViewStates") 248 } 249 if (daTransaction.focusedDaId != null) { 250 if (daViews[daTransaction.focusedDaId] != null) { 251 val toBeFocusedDa = daViews[daTransaction.focusedDaId]!! 252 wct.reorder(toBeFocusedDa.daInfo.token, true, true) 253 } else { 254 Slog.w(TAG, "DaView not found for ${daTransaction.focusedDaId}") 255 } 256 } 257 258 pendingTransitions.add( 259 PendingTransition( 260 TRANSIT_OPEN, // to signify opening of the DaHideActivity 261 wct, 262 diffedRequestedDaViewStates, 263 ) 264 ) 265 startNextTransition() 266 } 267 } 268 269 // The visibility and all will be calculated as part of this 270 // Use the same for hide/show/change bounds 271 fun calculateWctForAnimationDiff( 272 requestedDaStates: Map<DaView, DaState>, 273 wct: WindowContainerTransaction 274 ): Map<DaView, DaState> { 275 val newStates = mutableMapOf<DaView, DaState>() 276 requestedDaStates 277 .filter { (daView, newReqState) -> 278 when { 279 daCurrState[daView] != null -> true 280 else -> { 281 Slog.w(TAG, "$daView is not known to DaViewTransitions") 282 false 283 } 284 } 285 } 286 .forEach { (daView, newReqState) -> 287 when { 288 daCurrState[daView]!!.visible && !newReqState.visible -> { 289 // Being hidden 290 prepareHideDaWct(wct, daView, newReqState) 291 newStates[daView] = newReqState 292 } 293 294 !daCurrState[daView]!!.visible && newReqState.visible -> { 295 // Being shown 296 wct.setBounds(daView.daInfo.token, newReqState.bounds) 297 findAndRemoveDaHideActivity(daView, wct) 298 newStates[daView] = newReqState 299 } 300 301 daCurrState[daView]!!.bounds != newReqState.bounds -> { 302 // Changing bounds 303 prepareChangeBoundsWct(wct, daView, daCurrState[daView]!!, newReqState) 304 newStates[daView] = newReqState 305 } 306 // no changes; doesn't need to be animated 307 } 308 } 309 return newStates 310 } 311 312 private fun prepareHideDaWct( 313 wct: WindowContainerTransaction, 314 daView: DaView, 315 newState: DaState 316 ) { 317 var options = ActivityOptions.makeBasic() 318 .setPendingIntentBackgroundActivityStartMode( 319 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS 320 ) 321 .apply { 322 this.launchTaskDisplayAreaFeatureId = daView.launchTaskDisplayAreaFeatureId 323 } 324 325 var intent = Intent(context, DaHideActivity::class.java) 326 intent.setFlags(FLAG_ACTIVITY_NEW_TASK) 327 var pendingIntent = PendingIntent.getActivity( 328 context, 329 /* requestCode= */ 330 0, 331 intent, 332 PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT 333 ) 334 wct.setBounds(daView.daInfo.token, newState.bounds) 335 wct.sendPendingIntent(pendingIntent, intent, options.toBundle()) 336 } 337 338 private fun prepareChangeBoundsWct( 339 wct: WindowContainerTransaction, 340 daView: DaView, 341 prevState: DaState, 342 newReqState: DaState 343 ) { 344 wct.setBounds(daView.daInfo.token, newReqState.bounds) 345 346 if (DBG) { 347 val sizingUp = 348 (prevState.bounds.width() == newReqState.bounds.width() && 349 prevState.bounds.height() < newReqState.bounds.height()) || 350 ( 351 prevState.bounds.width() < newReqState.bounds.width() && 352 prevState.bounds.height() == newReqState.bounds.height() 353 ) || 354 ( 355 prevState.bounds.width() < newReqState.bounds.width() && 356 prevState.bounds.height() < newReqState.bounds.height() 357 ) 358 Slog.d(TAG, if (sizingUp) "Sizing up $daView" else "Sizing down $daView") 359 } 360 } 361 362 fun startNextTransition() { 363 if (pendingTransitions.isEmpty()) return 364 val pending: PendingTransition = pendingTransitions[0] 365 if (pending.isClaimed != null) { 366 // Wait for this to start animating. 367 return 368 } 369 pending.isClaimed = transitions.startTransition(pending.mType, pending.wct, this) 370 } 371 372 private fun getDaViewFromDisplayAreaToken(token: WindowContainerToken?): DaView? { 373 val displayArea = daViews.values.stream().filter { 374 it.daInfo.token == token 375 }.findAny() 376 if (displayArea.isEmpty) { 377 return null 378 } 379 return displayArea.get() 380 } 381 382 private fun getDaView(taskInfo: RunningTaskInfo): DaView? = daViews.values.find { 383 it.launchTaskDisplayAreaFeatureId == taskInfo.displayAreaFeatureId && 384 it.display.displayId == taskInfo.displayId 385 } 386 387 private fun isHideActivity(taskInfo: RunningTaskInfo): Boolean { 388 return taskInfo.topActivity == HIDE_ACTIVITY_NAME 389 } 390 391 override fun handleRequest( 392 transition: IBinder, 393 request: TransitionRequestInfo 394 ): WindowContainerTransaction? { 395 if (DBG) { 396 Slog.d(TAG, "handle request, type=${request.type}") 397 } 398 if (request.displayChange != null && request.displayChange!!.endAbsBounds != null) { 399 return handleRequestForDisplayChange(transition, request) 400 } 401 402 var triggerTask = request.triggerTask ?: run { return@handleRequest null } 403 404 if (DBG) { 405 Slog.d( 406 TAG, 407 "trigger task feature id = ${triggerTask.displayAreaFeatureId}, " + 408 "type=${request.type}" 409 ) 410 } 411 412 // Note: A DaHideActivity is always started as part of a transition from the handler, 413 // so it will never be caught here 414 if (TransitionUtil.isOpeningType(request.type)) { 415 val daView = getDaView(triggerTask) 416 if (daView == null) { 417 if (DBG) { 418 Slog.d(TAG, "DA not found") 419 } 420 return null 421 } 422 if (!daCurrState.containsKey(daView)) { 423 if (DBG) { 424 Slog.d(TAG, "DA state not found") 425 } 426 return null 427 } 428 return handleRequestForOpenTransitionOnDa(daView, transition, request) 429 } else { 430 if (DBG) { 431 Slog.d( 432 TAG, 433 "current event either not opening event or an event from blank activity" 434 ) 435 } 436 } 437 return null 438 } 439 440 private fun handleRequestForDisplayChange( 441 transition: IBinder, 442 request: TransitionRequestInfo 443 ): WindowContainerTransaction? { 444 var wct: WindowContainerTransaction? = null 445 val participants = 446 animationHandler?.handleDisplayChangeTransition( 447 request.displayChange!!.displayId, 448 request.displayChange!!.endAbsBounds!! 449 )?.daStates?.mapKeys { (key, _) -> daViews[key]!! } ?: mapOf() 450 451 if (participants.isEmpty()) { 452 Slog.e( 453 TAG, 454 "No participants in the DA transition, can lead to " + 455 "inconsistent state" 456 ) 457 return null 458 } 459 460 wct = wct ?: WindowContainerTransaction() 461 var participantsAsPerStateDiff = calculateWctForAnimationDiff(participants, wct) 462 val pending = PendingTransition( 463 request.type, 464 wct, 465 participantsAsPerStateDiff, 466 ) 467 pending.isClaimed = transition 468 pendingTransitions.add(pending) 469 return wct 470 } 471 472 private fun handleRequestForOpenTransitionOnDa( 473 daView: DaView, 474 transition: IBinder, 475 request: TransitionRequestInfo 476 ): WindowContainerTransaction? { 477 var wct: WindowContainerTransaction? = null 478 var participantsAsPerStateDiff = mapOf<DaView, DaState>() 479 480 // Even though daHideActivity is nohistory, it still needs to be manually removed here 481 // because the newly opened activity might be translucent which would make the 482 // DaHideActivity be visible in paused state otherwise; which is not desired. 483 wct = findAndRemoveDaHideActivity(daView, wct) 484 if (!pendingTransitions.isEmpty() && 485 pendingTransitions.get(0).requestedStates.containsKey(daView) && 486 pendingTransitions.get(0).requestedStates.get(daView)!!.visible == true 487 ) { 488 // This means it will become visible eventually and hence skip visibility 489 if (DBG) { 490 Slog.d(TAG, "DA is already requested to be visible and pending animation") 491 } 492 } else { 493 if (DBG) Slog.d(TAG, "try to show the da ${daView.id}") 494 wct = wct ?: WindowContainerTransaction() 495 val participants = 496 animationHandler!!.handleOpenTransitionOnDa( 497 daView, 498 request.triggerTask!!, 499 wct 500 ).daStates.mapKeys { (key, _) -> daViews[key]!! } 501 if (participants.isEmpty()) { 502 Slog.e( 503 TAG, 504 "No participants in the DA transition, can lead to " + 505 "inconsistent state" 506 ) 507 // set wct back to null as this should be handled by the default handler in 508 // shell 509 wct = null 510 } else { 511 participantsAsPerStateDiff = calculateWctForAnimationDiff(participants, wct) 512 if (participantsAsPerStateDiff.isEmpty()) { 513 wct = null 514 } 515 } 516 } 517 if (wct == null) { 518 // Should be handled by default handler in shell 519 return null 520 } 521 val pending = PendingTransition( 522 request.type, 523 wct 524 .reorder(request.triggerTask!!.token, true, true), 525 participantsAsPerStateDiff, 526 ) 527 pending.isClaimed = transition 528 pendingTransitions.add(pending) 529 return wct 530 } 531 532 private fun findAndRemoveDaHideActivity( 533 daView: DaView, 534 inputWct: WindowContainerTransaction? 535 ): WindowContainerTransaction? { 536 var tasks = shellTaskOrganizer.getRunningTasks() 537 if (daView.display == null) { 538 if (DBG) { 539 Slog.d( 540 TAG, 541 "daView.display is null, cannot find and remove the hide " + 542 "activity" 543 ) 544 } 545 } 546 val daHideTasks = 547 tasks.filter { 548 it.displayAreaFeatureId == daView.launchTaskDisplayAreaFeatureId && 549 it.displayId == daView.display.displayId && 550 it.topActivity == HIDE_ACTIVITY_NAME 551 // TODO: Think about handling the home task 552 } 553 if (daHideTasks.isEmpty()) { 554 return inputWct 555 } 556 val wct = inputWct ?: WindowContainerTransaction() 557 for (daHideTask in daHideTasks) { 558 wct.removeTask(daHideTask.token) 559 } 560 return wct 561 } 562 563 private fun reSyncDaLeashesToView() { 564 // consider this an opportunity to restore the DA surfaces because even if this is a 565 // not known transition, it could still involve known DAs which reparent their surfaces. 566 val tr = Transaction() 567 for (daView in daViews.values) { 568 if (daView.surfaceControl == null) { 569 continue 570 } 571 daView.resyncLeashToView(tr) 572 } 573 tr.apply() 574 } 575 576 private fun logChanges(daViewChanges: Map<DaView, DaViewChange>) { 577 for ((daView, daViewChange) in daViewChanges) { 578 daViewChange.type.logChange(daView) 579 } 580 } 581 582 override fun startAnimation( 583 transition: IBinder, 584 info: TransitionInfo, 585 startTransaction: Transaction, 586 finishTransaction: Transaction, 587 finishCallback: TransitionFinishCallback 588 ): Boolean { 589 if (DBG) Slog.d(TAG, " changes = " + info.changes) 590 val pending: PendingTransition? = findPending(transition) 591 if (pending != null) { 592 pendingTransitions.remove(pending) 593 } 594 if (pending == null) { 595 // TODO: ideally, based on the info.changes, a new transaction should be created and also 596 // routed via client which should eventually result into a new transition. 597 // This should be done so that client gets a chance to act on these missed changes. 598 Slog.e(TAG, "Found a non-DA related transition") 599 reSyncDaLeashesToView() 600 return false 601 } 602 603 if (pending.isInstant) { 604 if (DBG) Slog.d(TAG, "Playing a special instant transition") 605 startTransaction.apply() 606 finishCallback.onTransitionFinished(null) 607 startNextTransition() 608 return true 609 } 610 611 val daViewChanges = calculateDaViewChangesFromTransition( 612 info, 613 pending, 614 startTransaction, 615 finishTransaction 616 ) 617 if (DBG) logChanges(daViewChanges) 618 619 configureTaskLeashesAsPerDaChange( 620 info, 621 pending, 622 startTransaction, 623 daViewChanges 624 ) 625 if (pending.requestedStates.isEmpty() || animationHandler == null) { 626 startNextTransition() 627 return false 628 } 629 startTransaction.apply() 630 animationHandler?.playAnimation( 631 DaTransaction(daStates = pending.requestedStates.mapKeys { (key, _) -> key.id }), 632 { 633 shellMainThread.execute { 634 daCurrState.putAll(pending.requestedStates) 635 finishCallback.onTransitionFinished(null) 636 startNextTransition() 637 } 638 } 639 ) 640 return true 641 } 642 643 private fun calculateDaViewChangesFromTransition( 644 info: TransitionInfo, 645 pending: PendingTransition, 646 startTransaction: Transaction, 647 finishTransaction: Transaction 648 ): Map<DaView, DaViewChange> { 649 val viewChanges = mutableMapOf<DaView, DaViewChange>() 650 for (chg in info.changes) { 651 var daView = getDaViewFromDisplayAreaToken(chg.container) 652 if (daView != null) { 653 // It means that the change being processed is a display area level change 654 // which will have the snapshot. 655 if (chg.snapshot != null) { 656 viewChanges.getOrPut(daView) { DaViewChange() }.snapshot = chg.snapshot!! 657 } 658 continue 659 } 660 661 if (chg.taskInfo == null) { 662 continue 663 } 664 Slog.d(TAG, "------- ${chg.mode} change ${chg.taskInfo!!.topActivity} ") 665 // The change being processed is a task level change 666 667 daView = getDaView(chg.taskInfo!!) 668 if (daView == null) { 669 Slog.e(TAG, "The da being changed isn't known to DaViewTransitions") 670 continue 671 } 672 673 // Regardless of being in the requested state or not, resync the leashes to view to be 674 // on the safe side 675 daView.resyncLeashToView(startTransaction) 676 daView.resyncLeashToView(finishTransaction) 677 678 if (!pending.requestedStates.contains(daView)) { 679 Slog.e(TAG, "The da being changed isn't part of pending.mDas") 680 startTransaction.reparent(chg.leash, daView.surfaceControl) 681 .setPosition(chg.leash, 0f, 0f) 682 .setAlpha(chg.leash, 1f) 683 continue 684 } 685 686 var changeType = viewChanges.getOrDefault(daView, DaViewChange()).type 687 if (TransitionUtil.isOpeningType(chg.mode) && 688 HIDE_ACTIVITY_NAME == chg.taskInfo?.topActivity) { 689 if (daCurrState.containsKey(daView) && daCurrState[daView]!!.visible == false) { 690 Slog.e(TAG, "The da being hidden, is already hidden") 691 continue 692 } 693 changeType = ChangeType.Hide 694 } else if ( 695 (TransitionUtil.isClosingType(chg.mode) && 696 HIDE_ACTIVITY_NAME == chg.taskInfo?.topActivity) || 697 (TransitionUtil.isOpeningType(chg.mode) && 698 HIDE_ACTIVITY_NAME != chg.taskInfo?.topActivity) 699 ) { 700 if (daCurrState.containsKey(daView) && daCurrState[daView]!!.visible == true) { 701 Slog.e(TAG, "The da being shown, is already shown") 702 continue 703 } 704 changeType = ChangeType.Show 705 } else { 706 if (daCurrState.containsKey(daView) && 707 daCurrState[daView]!!.bounds == pending.requestedStates[daView]!!.bounds) { 708 Slog.e(TAG, "The da being changed, already has the same bounds") 709 continue 710 } 711 if (changeType != ChangeType.Show && changeType != ChangeType.Hide) { 712 // A task inside a display area which is being shown or hidden can have a bounds 713 // change as well. Prefer treating the DisplayArea change as SHOW or HIDE 714 // respectively instead of a more generic CHANGE. 715 changeType = ChangeType.Bounds 716 } 717 } 718 719 viewChanges.getOrPut(daView) { DaViewChange() }.type = changeType 720 } 721 722 return viewChanges 723 } 724 725 private fun configureTaskLeashesAsPerDaChange( 726 info: TransitionInfo, 727 pending: PendingTransition, 728 startTransaction: Transaction, 729 viewChanges: Map<DaView, DaViewChange> 730 ) { 731 // Attach the snapshots for hiding or changing DaViews 732 for ((daView, chg) in viewChanges) { 733 if (chg.type == ChangeType.Hide || chg.type == ChangeType.Bounds) { 734 if (chg.snapshot != null) { 735 startTransaction.reparent(chg.snapshot!!, daView.surfaceControl) 736 } 737 } 738 } 739 740 // Determine leash visibility and placement for each task level change 741 for (chg in info.changes) { 742 if (chg.taskInfo == null) continue 743 744 val daView = getDaView(chg.taskInfo!!) 745 if (daView == null) { 746 Slog.e(TAG, "The da being changed isn't known to DaViewTransitions") 747 continue 748 } 749 val daViewChg = viewChanges[daView] 750 if (daViewChg == null) { 751 Slog.e(TAG, "The da being change isn't known. $daView") 752 continue 753 } 754 755 if (!pending.requestedStates.containsKey(daView)) { 756 Slog.e(TAG, "The da being changed isn't part of pending.mDas") 757 continue 758 } 759 760 if (isHideActivity(chg.taskInfo!!)) { 761 Slog.e(TAG, "Disregard the change from blank activity as its leash not needed") 762 continue 763 } 764 765 // TODO(b/357635714), revisit this once the window's surface is stable during da 766 // transition. 767 val shouldTaskLeashBeVisible = when (daViewChg.type) { 768 ChangeType.Show -> TransitionUtil.isOpeningType(chg.mode) 769 ChangeType.Hide -> TransitionUtil.isClosingType(chg.mode) && 770 daViewChg.snapshot == null 771 ChangeType.Bounds -> daViewChg.snapshot == null 772 else -> false 773 } 774 775 startTransaction.reparent(chg.leash, daView.surfaceControl) 776 .apply { 777 if (taskViewTransitions.isTaskViewTask(chg.taskInfo) && 778 shouldTaskLeashBeVisible) { 779 val daBounds = daCurrState[daView]!!.bounds 780 val taskBounds = chg.taskInfo!!.configuration.windowConfiguration!!.bounds 781 taskBounds.offset(daBounds.left, daBounds.right) 782 setPosition( 783 chg.leash, 784 taskBounds.left.toFloat(), 785 taskBounds.bottom.toFloat() 786 ) 787 } else { 788 setPosition(chg.leash, 0f, 0f) 789 } 790 } 791 .setAlpha(chg.leash, if (shouldTaskLeashBeVisible) 1f else 0f) 792 } 793 } 794 795 override fun onTransitionConsumed( 796 transition: IBinder, 797 aborted: Boolean, 798 finishTransaction: Transaction? 799 ) { 800 Slog.d(TAG, "onTransitionConsumed, aborted=$aborted") 801 if (!aborted) return 802 val pending = findPending(transition) ?: return 803 pendingTransitions.remove(pending) 804 // Probably means that the UI should adjust as per the last (daCurrState) state. 805 // Something should be done but needs more thought. 806 // For now just update the local state with what was requested. 807 daCurrState.putAll(pending.requestedStates) 808 startNextTransition() 809 } 810 811 companion object { 812 private val TAG: String = DaViewTransitions::class.java.simpleName 813 private val DBG = true 814 private val HIDE_ACTIVITY_NAME = 815 ComponentName("com.android.systemui", DaHideActivity::class.java.name) 816 private val daViews = mutableMapOf<Long, DaView>() 817 818 var sInstance: DaViewTransitions? = null 819 } 820 821 @VisibleForTesting 822 internal class PendingTransition( 823 @field:WindowManager.TransitionType @param:WindowManager.TransitionType val mType: Int, 824 val wct: WindowContainerTransaction, 825 val requestedStates: Map<DaView, DaState> = mutableMapOf<DaView, DaState>(), 826 val isInstant: Boolean = false, 827 ) { 828 var isClaimed: IBinder? = null 829 } 830 } 831