• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 package com.android.server.wm;
18 
19 
20 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
21 import static android.view.Display.DEFAULT_DISPLAY;
22 import static android.view.WindowManager.TRANSIT_CHANGE;
23 import static android.view.WindowManager.TRANSIT_CLOSE;
24 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
25 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
26 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
27 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
28 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
29 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
30 import static android.view.WindowManager.TRANSIT_NONE;
31 import static android.view.WindowManager.TRANSIT_OPEN;
32 import static android.view.WindowManager.TRANSIT_TO_BACK;
33 import static android.view.WindowManager.TRANSIT_TO_FRONT;
34 import static android.view.WindowManager.transitTypeToString;
35 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
36 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
37 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
38 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
39 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
40 
41 import android.annotation.IntDef;
42 import android.annotation.NonNull;
43 import android.app.ActivityManager;
44 import android.graphics.Point;
45 import android.graphics.Rect;
46 import android.os.Binder;
47 import android.os.IBinder;
48 import android.os.RemoteException;
49 import android.os.SystemClock;
50 import android.util.ArrayMap;
51 import android.util.ArraySet;
52 import android.util.Slog;
53 import android.view.SurfaceControl;
54 import android.view.WindowManager;
55 import android.view.animation.Animation;
56 import android.window.IRemoteTransition;
57 import android.window.TransitionInfo;
58 
59 import com.android.internal.annotations.VisibleForTesting;
60 import com.android.internal.protolog.ProtoLogGroup;
61 import com.android.internal.protolog.common.ProtoLog;
62 
63 import java.lang.annotation.Retention;
64 import java.lang.annotation.RetentionPolicy;
65 import java.util.ArrayList;
66 
67 /**
68  * Represents a logical transition.
69  * @see TransitionController
70  */
71 class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
72     private static final String TAG = "Transition";
73 
74     /** The transition has been created and is collecting, but hasn't formally started. */
75     private static final int STATE_COLLECTING = 0;
76 
77     /**
78      * The transition has formally started. It is still collecting but will stop once all
79      * participants are ready to animate (finished drawing).
80      */
81     private static final int STATE_STARTED = 1;
82 
83     /**
84      * This transition is currently playing its animation and can no longer collect or be changed.
85      */
86     private static final int STATE_PLAYING = 2;
87 
88     /**
89      * This transition is aborting or has aborted. No animation will play nor will anything get
90      * sent to the player.
91      */
92     private static final int STATE_ABORT = 3;
93 
94     @IntDef(prefix = { "STATE_" }, value = {
95             STATE_COLLECTING,
96             STATE_STARTED,
97             STATE_PLAYING,
98             STATE_ABORT
99     })
100     @Retention(RetentionPolicy.SOURCE)
101     @interface TransitionState {}
102 
103     final @WindowManager.TransitionType int mType;
104     private int mSyncId;
105     private @WindowManager.TransitionFlags int mFlags;
106     private final TransitionController mController;
107     private final BLASTSyncEngine mSyncEngine;
108     private IRemoteTransition mRemoteTransition = null;
109 
110     /** Only use for clean-up after binder death! */
111     private SurfaceControl.Transaction mStartTransaction = null;
112     private SurfaceControl.Transaction mFinishTransaction = null;
113 
114     /**
115      * Contains change infos for both participants and all ancestors. We have to track ancestors
116      * because they are all promotion candidates and thus we need their start-states
117      * to be captured.
118      */
119     final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>();
120 
121     /** The collected participants in the transition. */
122     final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
123 
124     /** The final animation targets derived from participants after promotion. */
125     private ArraySet<WindowContainer> mTargets = null;
126 
127     private @TransitionState int mState = STATE_COLLECTING;
128     private boolean mReadyCalled = false;
129 
Transition(@indowManager.TransitionType int type, @WindowManager.TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine)130     Transition(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags,
131             TransitionController controller, BLASTSyncEngine syncEngine) {
132         mType = type;
133         mFlags = flags;
134         mController = controller;
135         mSyncEngine = syncEngine;
136         mSyncId = mSyncEngine.startSyncSet(this);
137     }
138 
139     @VisibleForTesting
getSyncId()140     int getSyncId() {
141         return mSyncId;
142     }
143 
144     /**
145      * Formally starts the transition. Participants can be collected before this is started,
146      * but this won't consider itself ready until started -- even if all the participants have
147      * drawn.
148      */
start()149     void start() {
150         if (mState >= STATE_STARTED) {
151             Slog.w(TAG, "Transition already started: " + mSyncId);
152         }
153         mState = STATE_STARTED;
154         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
155                 mSyncId);
156         if (mReadyCalled) {
157             setReady();
158         }
159     }
160 
161     /**
162      * Adds wc to set of WindowContainers participating in this transition.
163      */
collect(@onNull WindowContainer wc)164     void collect(@NonNull WindowContainer wc) {
165         if (mSyncId < 0) return;
166         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
167                 mSyncId, wc);
168         // "snapshot" all parents (as potential promotion targets). Do this before checking
169         // if this is already a participant in case it has since been re-parented.
170         for (WindowContainer curr = wc.getParent(); curr != null && !mChanges.containsKey(curr);
171                 curr = curr.getParent()) {
172             mChanges.put(curr, new ChangeInfo(curr));
173         }
174         if (mParticipants.contains(wc)) return;
175         mSyncEngine.addToSyncSet(mSyncId, wc);
176         ChangeInfo info = mChanges.get(wc);
177         if (info == null) {
178             info = new ChangeInfo(wc);
179             mChanges.put(wc, info);
180         }
181         mParticipants.add(wc);
182         if (info.mShowWallpaper) {
183             // Collect the wallpaper so it is part of the sync set.
184             final WindowContainer wallpaper =
185                     wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper();
186             if (wallpaper != null) {
187                 collect(wallpaper);
188             }
189         }
190     }
191 
192     /**
193      * Records wc as changing its state of existence during this transition. For example, a new
194      * task is considered an existence change while moving a task to front is not. wc is added
195      * to the collection set. Note: Existence is NOT a promotable characteristic.
196      *
197      * This must be explicitly recorded because there are o number of situations where the actual
198      * hierarchy operations don't align with the intent (eg. re-using a task with a new activity
199      * or waiting until after the animation to close).
200      */
collectExistenceChange(@onNull WindowContainer wc)201     void collectExistenceChange(@NonNull WindowContainer wc) {
202         if (mSyncId < 0) return;
203         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
204                 + " %s", mSyncId, wc);
205         collect(wc);
206         mChanges.get(wc).mExistenceChanged = true;
207     }
208 
209     /**
210      * Call this when all known changes related to this transition have been applied. Until
211      * all participants have finished drawing, the transition can still collect participants.
212      *
213      * If this is called before the transition is started, it will be deferred until start.
214      */
setReady(boolean ready)215     void setReady(boolean ready) {
216         if (mSyncId < 0) return;
217         if (mState < STATE_STARTED) {
218             mReadyCalled = ready;
219             return;
220         }
221         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
222                 "Set transition ready=%b %d", ready, mSyncId);
223         mSyncEngine.setReady(mSyncId, ready);
224     }
225 
226     /** @see #setReady . This calls with parameter true. */
setReady()227     void setReady() {
228         setReady(true);
229     }
230 
231     /**
232      * Build a transaction that "resets" all the re-parenting and layer changes. This is
233      * intended to be applied at the end of the transition but before the finish callback. This
234      * needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
235      * Additionally, this gives shell the ability to better deal with merged transitions.
236      */
buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash)237     private void buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash) {
238         final Point tmpPos = new Point();
239         // usually only size 1
240         final ArraySet<DisplayContent> displays = new ArraySet<>();
241         for (int i = mTargets.size() - 1; i >= 0; --i) {
242             final WindowContainer target = mTargets.valueAt(i);
243             if (target.getParent() != null) {
244                 final SurfaceControl targetLeash = getLeashSurface(target);
245                 final SurfaceControl origParent = getOrigParentSurface(target);
246                 // Ensure surfaceControls are re-parented back into the hierarchy.
247                 t.reparent(targetLeash, origParent);
248                 t.setLayer(targetLeash, target.getLastLayer());
249                 target.getRelativePosition(tmpPos);
250                 t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
251                 t.setCornerRadius(targetLeash, 0);
252                 t.setShadowRadius(targetLeash, 0);
253                 displays.add(target.getDisplayContent());
254             }
255         }
256         // Need to update layers on involved displays since they were all paused while
257         // the animation played. This puts the layers back into the correct order.
258         for (int i = displays.size() - 1; i >= 0; --i) {
259             if (displays.valueAt(i) == null) continue;
260             displays.valueAt(i).assignChildLayers(t);
261         }
262         if (rootLeash.isValid()) {
263             t.reparent(rootLeash, null);
264         }
265     }
266 
267     /**
268      * The transition has finished animating and is ready to finalize WM state. This should not
269      * be called directly; use {@link TransitionController#finishTransition} instead.
270      */
finishTransition()271     void finishTransition() {
272         mStartTransaction = mFinishTransaction = null;
273         if (mState < STATE_PLAYING) {
274             throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
275         }
276 
277         // Commit all going-invisible containers
278         for (int i = 0; i < mParticipants.size(); ++i) {
279             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
280             if (ar != null && !ar.isVisibleRequested()) {
281                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
282                         "  Commit activity becoming invisible: %s", ar);
283                 ar.commitVisibility(false /* visible */, false /* performLayout */);
284             }
285             final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
286             if (wt != null && !wt.isVisibleRequested()) {
287                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
288                         "  Commit wallpaper becoming invisible: %s", ar);
289                 wt.commitVisibility(false /* visible */);
290             }
291         }
292     }
293 
abort()294     void abort() {
295         // This calls back into itself via controller.abort, so just early return here.
296         if (mState == STATE_ABORT) return;
297         if (mState != STATE_COLLECTING) {
298             throw new IllegalStateException("Too late to abort.");
299         }
300         mState = STATE_ABORT;
301         // Syncengine abort will call through to onTransactionReady()
302         mSyncEngine.abort(mSyncId);
303     }
304 
setRemoteTransition(IRemoteTransition remoteTransition)305     void setRemoteTransition(IRemoteTransition remoteTransition) {
306         mRemoteTransition = remoteTransition;
307     }
308 
getRemoteTransition()309     IRemoteTransition getRemoteTransition() {
310         return mRemoteTransition;
311     }
312 
313     @Override
onTransactionReady(int syncId, SurfaceControl.Transaction transaction)314     public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
315         if (syncId != mSyncId) {
316             Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
317             return;
318         }
319         int displayId = DEFAULT_DISPLAY;
320         for (WindowContainer container : mParticipants) {
321             if (container.mDisplayContent == null) continue;
322             displayId = container.mDisplayContent.getDisplayId();
323         }
324 
325         if (mState == STATE_ABORT) {
326             mController.abort(this);
327             mController.mAtm.mRootWindowContainer.getDisplayContent(displayId)
328                     .getPendingTransaction().merge(transaction);
329             mSyncId = -1;
330             return;
331         }
332 
333         mState = STATE_PLAYING;
334         mController.moveToPlaying(this);
335 
336         if (mController.mAtm.mTaskSupervisor.getKeyguardController().isKeyguardLocked()) {
337             mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
338         }
339 
340         // Resolve the animating targets from the participants
341         mTargets = calculateTargets(mParticipants, mChanges);
342         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges);
343 
344         handleNonAppWindowsInTransition(displayId, mType, mFlags);
345 
346         // Manually show any activities that are visibleRequested. This is needed to properly
347         // support simultaneous animation queueing/merging. Specifically, if transition A makes
348         // an activity invisible, it's finishTransaction (which is applied *after* the animation)
349         // will hide the activity surface. If transition B then makes the activity visible again,
350         // the normal surfaceplacement logic won't add a show to this start transaction because
351         // the activity visibility hasn't been committed yet. To deal with this, we have to manually
352         // show here in the same way that we manually hide in finishTransaction.
353         for (int i = mParticipants.size() - 1; i >= 0; --i) {
354             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
355             if (ar == null || !ar.mVisibleRequested) continue;
356             transaction.show(ar.getSurfaceControl());
357         }
358 
359         mStartTransaction = transaction;
360         mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
361         buildFinishTransaction(mFinishTransaction, info.getRootLeash());
362         if (mController.getTransitionPlayer() != null) {
363             try {
364                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
365                         "Calling onTransitionReady: %s", info);
366                 mController.getTransitionPlayer().onTransitionReady(
367                         this, info, transaction, mFinishTransaction);
368             } catch (RemoteException e) {
369                 // If there's an exception when trying to send the mergedTransaction to the
370                 // client, we should finish and apply it here so the transactions aren't lost.
371                 cleanUpOnFailure();
372             }
373         } else {
374             // No player registered, so just finish/apply immediately
375             cleanUpOnFailure();
376         }
377         mSyncId = -1;
378     }
379 
380     /**
381      * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call
382      * this directly, it's designed to by called by {@link TransitionController} only.
383      */
cleanUpOnFailure()384     void cleanUpOnFailure() {
385         // No need to clean-up if this isn't playing yet.
386         if (mState < STATE_PLAYING) return;
387 
388         if (mStartTransaction != null) {
389             mStartTransaction.apply();
390         }
391         if (mFinishTransaction != null) {
392             mFinishTransaction.apply();
393         }
394         finishTransition();
395     }
396 
handleNonAppWindowsInTransition(int displayId, @WindowManager.TransitionType int transit, int flags)397     private void handleNonAppWindowsInTransition(int displayId,
398             @WindowManager.TransitionType int transit, int flags) {
399         final DisplayContent dc =
400                 mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
401         if (dc == null) {
402             return;
403         }
404         if (transit == TRANSIT_KEYGUARD_GOING_AWAY
405                 && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
406             if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
407                     && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
408                     && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
409                 Animation anim = mController.mAtm.mWindowManager.mPolicy
410                         .createKeyguardWallpaperExit(
411                                 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
412                 if (anim != null) {
413                     anim.scaleCurrentDuration(
414                             mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked());
415                     dc.mWallpaperController.startWallpaperAnimation(anim);
416                 }
417             }
418             dc.startKeyguardExitOnNonAppWindows(
419                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0,
420                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
421                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
422             mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(
423                     SystemClock.uptimeMillis(), 0 /* duration */);
424         }
425         if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
426             mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange();
427         }
428     }
429 
430     @Override
toString()431     public String toString() {
432         StringBuilder sb = new StringBuilder(64);
433         sb.append("TransitionRecord{");
434         sb.append(Integer.toHexString(System.identityHashCode(this)));
435         sb.append(" id=" + mSyncId);
436         sb.append(" type=" + transitTypeToString(mType));
437         sb.append(" flags=" + mFlags);
438         sb.append('}');
439         return sb.toString();
440     }
441 
reportIfNotTop(WindowContainer wc)442     private static boolean reportIfNotTop(WindowContainer wc) {
443         // Organized tasks need to be reported anyways because Core won't show() their surfaces
444         // and we can't rely on onTaskAppeared because it isn't in sync.
445         // Also report wallpaper so it can be handled properly during display change/rotation.
446         // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
447         return wc.isOrganized() || isWallpaper(wc);
448     }
449 
450     /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */
getChildDepth(WindowContainer child, WindowContainer ancestor)451     private static int getChildDepth(WindowContainer child, WindowContainer ancestor) {
452         WindowContainer parent = child;
453         int depth = 0;
454         while (parent != null) {
455             if (parent == ancestor) {
456                 return depth;
457             }
458             parent = parent.getParent();
459             ++depth;
460         }
461         return -1;
462     }
463 
isWallpaper(WindowContainer wc)464     private static boolean isWallpaper(WindowContainer wc) {
465         return wc.asWallpaperToken() != null;
466     }
467 
468     /**
469      * Under some conditions (eg. all visible targets within a parent container are transitioning
470      * the same way) the transition can be "promoted" to the parent container. This means an
471      * animation can play just on the parent rather than all the individual children.
472      *
473      * @return {@code true} if transition in target can be promoted to its parent.
474      */
canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets, ArrayMap<WindowContainer, ChangeInfo> changes)475     private static boolean canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets,
476             ArrayMap<WindowContainer, ChangeInfo> changes) {
477         final WindowContainer parent = target.getParent();
478         final ChangeInfo parentChanges = parent != null ? changes.get(parent) : null;
479         if (parent == null || !parent.canCreateRemoteAnimationTarget()
480                 || parentChanges == null || !parentChanges.hasChanged(parent)) {
481             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: %s",
482                     parent == null ? "no parent" : ("parent can't be target " + parent));
483             return false;
484         }
485         if (isWallpaper(target)) {
486             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: is wallpaper");
487             return false;
488         }
489         @TransitionInfo.TransitionMode int mode = TRANSIT_NONE;
490         // Go through all siblings of this target to see if any of them would prevent
491         // the target from promoting.
492         siblingLoop:
493         for (int i = parent.getChildCount() - 1; i >= 0; --i) {
494             final WindowContainer sibling = parent.getChildAt(i);
495             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      check sibling %s",
496                     sibling);
497             // Check if any topTargets are the sibling or within it
498             for (int j = topTargets.size() - 1; j >= 0; --j) {
499                 final int depth = getChildDepth(topTargets.valueAt(j), sibling);
500                 if (depth < 0) continue;
501                 if (depth == 0) {
502                     final int siblingMode = changes.get(sibling).getTransitMode(sibling);
503                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
504                             "        sibling is a top target with mode %s",
505                             TransitionInfo.modeToString(siblingMode));
506                     if (mode == TRANSIT_NONE) {
507                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
508                                 "          no common mode yet, so set it");
509                         mode = siblingMode;
510                     } else if (mode != siblingMode) {
511                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
512                                 "          SKIP: common mode mismatch. was %s",
513                                 TransitionInfo.modeToString(mode));
514                         return false;
515                     }
516                     continue siblingLoop;
517                 } else {
518                     // Sibling subtree may not be promotable.
519                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
520                             "        SKIP: sibling contains top target %s",
521                             topTargets.valueAt(j));
522                     return false;
523                 }
524             }
525             // No other animations are playing in this sibling
526             if (sibling.isVisibleRequested()) {
527                 // Sibling is visible but not animating, so no promote.
528                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
529                         "        SKIP: sibling is visible but not part of transition");
530                 return false;
531             }
532         }
533         return true;
534     }
535 
536     /**
537      * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
538      *
539      * @param topTargets set of just the top-most targets in the hierarchy of participants.
540      * @param targets all targets that will be sent to the player.
541      * @return {@code true} if something was promoted.
542      */
tryPromote(ArraySet<WindowContainer> topTargets, ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes)543     private static boolean tryPromote(ArraySet<WindowContainer> topTargets,
544             ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
545         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  --- Start combine pass ---");
546         // Go through each target until we find one that can be promoted.
547         for (WindowContainer targ : topTargets) {
548             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", targ);
549             if (!canPromote(targ, topTargets, changes)) {
550                 continue;
551             }
552             // No obstructions found to promotion, so promote
553             final WindowContainer parent = targ.getParent();
554             final ChangeInfo parentInfo = changes.get(parent);
555             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
556                     "      CAN PROMOTE: promoting to parent %s", parent);
557             targets.add(parent);
558 
559             // Go through all children of newly-promoted container and remove them from the
560             // top-targets.
561             for (int i = parent.getChildCount() - 1; i >= 0; --i) {
562                 final WindowContainer child = parent.getChildAt(i);
563                 int idx = targets.indexOf(child);
564                 if (idx >= 0) {
565                     final ChangeInfo childInfo = changes.get(child);
566                     if (reportIfNotTop(child)) {
567                         childInfo.mParent = parent;
568                         parentInfo.addChild(child);
569                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
570                                 "        keep as target %s", child);
571                     } else {
572                         if (childInfo.mChildren != null) {
573                             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
574                                     "        merging children in from %s: %s", child,
575                                     childInfo.mChildren);
576                             parentInfo.addChildren(childInfo.mChildren);
577                         }
578                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
579                                 "        remove from targets %s", child);
580                         targets.removeAt(idx);
581                     }
582                 }
583                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
584                         "        remove from topTargets %s", child);
585                 topTargets.remove(child);
586             }
587             topTargets.add(parent);
588             return true;
589         }
590         return false;
591     }
592 
593     /**
594      * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
595      * animation targets to higher level in the window hierarchy if possible.
596      */
597     @VisibleForTesting
598     @NonNull
calculateTargets(ArraySet<WindowContainer> participants, ArrayMap<WindowContainer, ChangeInfo> changes)599     static ArraySet<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants,
600             ArrayMap<WindowContainer, ChangeInfo> changes) {
601         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
602                 "Start calculating TransitionInfo based on participants: %s", participants);
603 
604         final ArraySet<WindowContainer> topTargets = new ArraySet<>();
605         // The final animation targets which cannot promote to higher level anymore.
606         final ArraySet<WindowContainer> targets = new ArraySet<>();
607 
608         final ArrayList<WindowContainer> tmpList = new ArrayList<>();
609 
610         // Build initial set of top-level participants by removing any participants that are no-ops
611         // or children of other participants or are otherwise invalid; however, keep around a list
612         // of participants that should always be reported even if they aren't top.
613         for (WindowContainer wc : participants) {
614             // Don't include detached windows.
615             if (!wc.isAttached()) continue;
616 
617             final ChangeInfo changeInfo = changes.get(wc);
618 
619             // Reject no-ops
620             if (!changeInfo.hasChanged(wc)) {
621                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
622                         "  Rejecting as no-op: %s", wc);
623                 continue;
624             }
625 
626             // Search through ancestors to find the top-most participant (if one exists)
627             WindowContainer topParent = null;
628             tmpList.clear();
629             if (reportIfNotTop(wc)) {
630                 tmpList.add(wc);
631             }
632             for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) {
633                 if (!p.isAttached() || !changes.get(p).hasChanged(p)) {
634                     // Again, we're skipping no-ops
635                     break;
636                 }
637                 if (participants.contains(p)) {
638                     topParent = p;
639                     break;
640                 } else if (reportIfNotTop(p)) {
641                     tmpList.add(p);
642                 }
643             }
644             if (topParent != null) {
645                 // There was an ancestor participant, so don't add wc to targets unless always-
646                 // report. Similarly, add any always-report parents along the way.
647                 for (int i = 0; i < tmpList.size(); ++i) {
648                     targets.add(tmpList.get(i));
649                     final ChangeInfo info = changes.get(tmpList.get(i));
650                     info.mParent = i < tmpList.size() - 1 ? tmpList.get(i + 1) : topParent;
651                 }
652                 continue;
653             }
654             // No ancestors in participant-list, so wc is a top target.
655             targets.add(wc);
656             topTargets.add(wc);
657         }
658 
659         // Populate children lists
660         for (int i = targets.size() - 1; i >= 0; --i) {
661             if (changes.get(targets.valueAt(i)).mParent != null) {
662                 changes.get(changes.get(targets.valueAt(i)).mParent).addChild(targets.valueAt(i));
663             }
664         }
665 
666         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s", targets);
667         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Top targets: %s", topTargets);
668 
669         // Combine targets by repeatedly going through the topTargets to see if they can be
670         // promoted until there aren't any promotions possible.
671         while (tryPromote(topTargets, targets, changes)) {
672             // Empty on purpose
673         }
674         return targets;
675     }
676 
677     /** Add any of `members` within `root` to `out` in top-to-bottom z-order. */
addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members, ArrayList<WindowContainer> out)678     private static void addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members,
679             ArrayList<WindowContainer> out) {
680         for (int i = root.getChildCount() - 1; i >= 0; --i) {
681             final WindowContainer child = root.getChildAt(i);
682             addMembersInOrder(child, members, out);
683             if (members.contains(child)) {
684                 out.add(child);
685             }
686         }
687     }
688 
689     /** Gets the leash surface for a window container */
getLeashSurface(WindowContainer wc)690     private static SurfaceControl getLeashSurface(WindowContainer wc) {
691         final DisplayContent asDC = wc.asDisplayContent();
692         if (asDC != null) {
693             // DisplayContent is the "root", so we use the windowing layer instead to avoid
694             // hardware-screen-level surfaces.
695             return asDC.getWindowingLayer();
696         }
697         return wc.getSurfaceControl();
698     }
699 
getOrigParentSurface(WindowContainer wc)700     private static SurfaceControl getOrigParentSurface(WindowContainer wc) {
701         if (wc.asDisplayContent() != null) {
702             // DisplayContent is the "root", so we reinterpret it's wc as the window layer
703             // making the parent surface the displaycontent's surface.
704             return wc.getSurfaceControl();
705         }
706         return wc.getParent().getSurfaceControl();
707     }
708 
709     /**
710      * Construct a TransitionInfo object from a set of targets and changes. Also populates the
711      * root surface.
712      */
713     @VisibleForTesting
714     @NonNull
calculateTransitionInfo(int type, int flags, ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes)715     static TransitionInfo calculateTransitionInfo(int type, int flags,
716             ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
717         final TransitionInfo out = new TransitionInfo(type, flags);
718 
719         final ArraySet<WindowContainer> appTargets = new ArraySet<>();
720         final ArraySet<WindowContainer> wallpapers = new ArraySet<>();
721         for (int i = targets.size() - 1; i >= 0; --i) {
722             (isWallpaper(targets.valueAt(i)) ? wallpapers : appTargets).add(targets.valueAt(i));
723         }
724 
725         // Find the top-most shared ancestor of app targets
726         WindowContainer ancestor = null;
727         for (int i = appTargets.size() - 1; i >= 0; --i) {
728             final WindowContainer wc = appTargets.valueAt(i);
729             ancestor = wc;
730             break;
731         }
732         if (ancestor == null) {
733             out.setRootLeash(new SurfaceControl(), 0, 0);
734             return out;
735         }
736         ancestor = ancestor.getParent();
737 
738         // Go up ancestor parent chain until all targets are descendants.
739         ancestorLoop:
740         while (ancestor != null) {
741             for (int i = appTargets.size() - 1; i >= 0; --i) {
742                 final WindowContainer wc = appTargets.valueAt(i);
743                 if (!wc.isDescendantOf(ancestor)) {
744                     ancestor = ancestor.getParent();
745                     continue ancestorLoop;
746                 }
747             }
748             break;
749         }
750 
751         // Sort targets top-to-bottom in Z. Check ALL targets here in case the display area itself
752         // is animating: then we want to include wallpapers at the right position.
753         ArrayList<WindowContainer> sortedTargets = new ArrayList<>();
754         addMembersInOrder(ancestor, targets, sortedTargets);
755 
756         // make leash based on highest (z-order) direct child of ancestor with a participant.
757         WindowContainer leashReference = sortedTargets.get(0);
758         while (leashReference.getParent() != ancestor) {
759             leashReference = leashReference.getParent();
760         }
761         final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
762                 "Transition Root: " + leashReference.getName()).build();
763         SurfaceControl.Transaction t = ancestor.mWmService.mTransactionFactory.get();
764         t.setLayer(rootLeash, leashReference.getLastLayer());
765         t.apply();
766         t.close();
767         out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top);
768 
769         // add the wallpapers at the bottom
770         for (int i = wallpapers.size() - 1; i >= 0; --i) {
771             final WindowContainer wc = wallpapers.valueAt(i);
772             // If the displayarea itself is animating, then the wallpaper was already added.
773             if (wc.isDescendantOf(ancestor)) break;
774             sortedTargets.add(wc);
775         }
776 
777         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
778         final int count = sortedTargets.size();
779         for (int i = 0; i < count; ++i) {
780             final WindowContainer target = sortedTargets.get(i);
781             final ChangeInfo info = changes.get(target);
782             final TransitionInfo.Change change = new TransitionInfo.Change(
783                     target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
784                             : null, getLeashSurface(target));
785             // TODO(shell-transitions): Use leash for non-organized windows.
786             if (info.mParent != null) {
787                 change.setParent(info.mParent.mRemoteToken.toWindowContainerToken());
788             }
789             change.setMode(info.getTransitMode(target));
790             change.setStartAbsBounds(info.mAbsoluteBounds);
791             change.setEndAbsBounds(target.getBounds());
792             change.setEndRelOffset(target.getBounds().left - target.getParent().getBounds().left,
793                     target.getBounds().top - target.getParent().getBounds().top);
794             change.setFlags(info.getChangeFlags(target));
795             change.setRotation(info.mRotation, target.getWindowConfiguration().getRotation());
796             final Task task = target.asTask();
797             if (task != null) {
798                 final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
799                 task.fillTaskInfo(tinfo);
800                 change.setTaskInfo(tinfo);
801             }
802             out.addChange(change);
803         }
804 
805         return out;
806     }
807 
fromBinder(IBinder binder)808     static Transition fromBinder(IBinder binder) {
809         return (Transition) binder;
810     }
811 
812     @VisibleForTesting
813     static class ChangeInfo {
814         // Usually "post" change state.
815         WindowContainer mParent;
816         ArraySet<WindowContainer> mChildren;
817 
818         // State tracking
819         boolean mExistenceChanged = false;
820         // before change state
821         boolean mVisible;
822         int mWindowingMode;
823         final Rect mAbsoluteBounds = new Rect();
824         boolean mShowWallpaper;
825         int mRotation = ROTATION_UNDEFINED;
826 
ChangeInfo(@onNull WindowContainer origState)827         ChangeInfo(@NonNull WindowContainer origState) {
828             mVisible = origState.isVisibleRequested();
829             mWindowingMode = origState.getWindowingMode();
830             mAbsoluteBounds.set(origState.getBounds());
831             mShowWallpaper = origState.showWallpaper();
832             mRotation = origState.getWindowConfiguration().getRotation();
833         }
834 
835         @VisibleForTesting
ChangeInfo(boolean visible, boolean existChange)836         ChangeInfo(boolean visible, boolean existChange) {
837             mVisible = visible;
838             mExistenceChanged = existChange;
839             mShowWallpaper = false;
840         }
841 
hasChanged(@onNull WindowContainer newState)842         boolean hasChanged(@NonNull WindowContainer newState) {
843             // If it's invisible and hasn't changed visibility, always return false since even if
844             // something changed, it wouldn't be a visible change.
845             final boolean currVisible = newState.isVisibleRequested();
846             if (currVisible == mVisible && !mVisible) return false;
847             return currVisible != mVisible
848                     // if mWindowingMode is 0, this container wasn't attached at collect time, so
849                     // assume no change in windowing-mode.
850                     || (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode)
851                     || !newState.getBounds().equals(mAbsoluteBounds)
852                     || mRotation != newState.getWindowConfiguration().getRotation();
853         }
854 
855         @TransitionInfo.TransitionMode
getTransitMode(@onNull WindowContainer wc)856         int getTransitMode(@NonNull WindowContainer wc) {
857             final boolean nowVisible = wc.isVisibleRequested();
858             if (nowVisible == mVisible) {
859                 return TRANSIT_CHANGE;
860             }
861             if (mExistenceChanged) {
862                 return nowVisible ? TRANSIT_OPEN : TRANSIT_CLOSE;
863             } else {
864                 return nowVisible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK;
865             }
866         }
867 
868         @TransitionInfo.ChangeFlags
getChangeFlags(@onNull WindowContainer wc)869         int getChangeFlags(@NonNull WindowContainer wc) {
870             int flags = 0;
871             if (mShowWallpaper || wc.showWallpaper()) {
872                 flags |= FLAG_SHOW_WALLPAPER;
873             }
874             if (!wc.fillsParent()) {
875                 // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
876                 //                    it is effected by child visibility but needs to work even
877                 //                    before visibility is committed. This means refactoring some
878                 //                    checks to use requested visibility.
879                 flags |= FLAG_TRANSLUCENT;
880             }
881             final Task task = wc.asTask();
882             if (task != null && task.voiceSession != null) {
883                 flags |= FLAG_IS_VOICE_INTERACTION;
884             }
885             final ActivityRecord record = wc.asActivityRecord();
886             if (record != null) {
887                 if (record.mUseTransferredAnimation) {
888                     flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
889                 }
890                 if (record.mVoiceInteraction) {
891                     flags |= FLAG_IS_VOICE_INTERACTION;
892                 }
893             }
894             if (isWallpaper(wc)) {
895                 flags |= FLAG_IS_WALLPAPER;
896             }
897             return flags;
898         }
899 
addChild(@onNull WindowContainer wc)900         void addChild(@NonNull WindowContainer wc) {
901             if (mChildren == null) {
902                 mChildren = new ArraySet<>();
903             }
904             mChildren.add(wc);
905         }
addChildren(@onNull ArraySet<WindowContainer> wcs)906         void addChildren(@NonNull ArraySet<WindowContainer> wcs) {
907             if (mChildren == null) {
908                 mChildren = new ArraySet<>();
909             }
910             mChildren.addAll(wcs);
911         }
912     }
913 }
914