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