• 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 import static android.view.WindowManager.TRANSIT_CHANGE;
20 import static android.view.WindowManager.TRANSIT_CLOSE;
21 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
22 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
23 import static android.view.WindowManager.TRANSIT_NONE;
24 import static android.view.WindowManager.TRANSIT_OPEN;
25 
26 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
27 
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.app.ActivityManager;
31 import android.app.IApplicationThread;
32 import android.app.WindowConfiguration;
33 import android.os.IBinder;
34 import android.os.IRemoteCallback;
35 import android.os.RemoteException;
36 import android.os.SystemClock;
37 import android.os.SystemProperties;
38 import android.util.ArrayMap;
39 import android.util.Slog;
40 import android.util.proto.ProtoOutputStream;
41 import android.view.SurfaceControl;
42 import android.view.WindowManager;
43 import android.window.ITransitionMetricsReporter;
44 import android.window.ITransitionPlayer;
45 import android.window.RemoteTransition;
46 import android.window.TransitionInfo;
47 import android.window.TransitionRequestInfo;
48 
49 import com.android.internal.annotations.GuardedBy;
50 import com.android.internal.annotations.VisibleForTesting;
51 import com.android.internal.protolog.ProtoLogGroup;
52 import com.android.internal.protolog.common.ProtoLog;
53 import com.android.server.LocalServices;
54 import com.android.server.statusbar.StatusBarManagerInternal;
55 
56 import java.util.ArrayList;
57 import java.util.function.LongConsumer;
58 
59 /**
60  * Handles all the aspects of recording and synchronizing transitions.
61  */
62 class TransitionController {
63     private static final String TAG = "TransitionController";
64 
65     /** Whether to use shell-transitions rotation instead of fixed-rotation. */
66     private static final boolean SHELL_TRANSITIONS_ROTATION =
67             SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
68 
69     /** Which sync method to use for transition syncs. */
70     static final int SYNC_METHOD =
71             android.os.SystemProperties.getBoolean("persist.wm.debug.shell_transit_blast", false)
72                     ? BLASTSyncEngine.METHOD_BLAST : BLASTSyncEngine.METHOD_NONE;
73 
74     /** The same as legacy APP_TRANSITION_TIMEOUT_MS. */
75     private static final int DEFAULT_TIMEOUT_MS = 5000;
76     /** Less duration for CHANGE type because it does not involve app startup. */
77     private static final int CHANGE_TIMEOUT_MS = 2000;
78 
79     // State constants to line-up with legacy app-transition proto expectations.
80     private static final int LEGACY_STATE_IDLE = 0;
81     private static final int LEGACY_STATE_READY = 1;
82     private static final int LEGACY_STATE_RUNNING = 2;
83 
84     private ITransitionPlayer mTransitionPlayer;
85     final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter();
86     final TransitionTracer mTransitionTracer;
87 
88     private WindowProcessController mTransitionPlayerProc;
89     final ActivityTaskManagerService mAtm;
90     final TaskSnapshotController mTaskSnapshotController;
91     final RemotePlayer mRemotePlayer;
92 
93     private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners =
94             new ArrayList<>();
95 
96     /**
97      * Currently playing transitions (in the order they were started). When finished, records are
98      * removed from this list.
99      */
100     private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>();
101 
102     final Lock mRunningLock = new Lock();
103 
104     private final IBinder.DeathRecipient mTransitionPlayerDeath;
105 
106     /** The transition currently being constructed (collecting participants). */
107     private Transition mCollectingTransition = null;
108 
109     // TODO(b/188595497): remove when not needed.
110     final StatusBarManagerInternal mStatusBar;
111 
112     /**
113      * `true` when building surface layer order for the finish transaction. We want to prevent
114      * wm from touching z-order of surfaces during transitions, but we still need to be able to
115      * calculate the layers for the finishTransaction. So, when assigning layers into the finish
116      * transaction, set this to true so that the {@link canAssignLayers} will allow it.
117      */
118     boolean mBuildingFinishLayers = false;
119 
120     private final SurfaceControl.Transaction mWakeT = new SurfaceControl.Transaction();
121 
TransitionController(ActivityTaskManagerService atm, TaskSnapshotController taskSnapshotController, TransitionTracer transitionTracer)122     TransitionController(ActivityTaskManagerService atm,
123             TaskSnapshotController taskSnapshotController,
124             TransitionTracer transitionTracer) {
125         mAtm = atm;
126         mRemotePlayer = new RemotePlayer(atm);
127         mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
128         mTaskSnapshotController = taskSnapshotController;
129         mTransitionTracer = transitionTracer;
130         mTransitionPlayerDeath = () -> {
131             synchronized (mAtm.mGlobalLock) {
132                 detachPlayer();
133             }
134         };
135     }
136 
detachPlayer()137     private void detachPlayer() {
138         if (mTransitionPlayer == null) return;
139         // Clean-up/finish any playing transitions.
140         for (int i = 0; i < mPlayingTransitions.size(); ++i) {
141             mPlayingTransitions.get(i).cleanUpOnFailure();
142         }
143         mPlayingTransitions.clear();
144         if (mCollectingTransition != null) {
145             mCollectingTransition.abort();
146         }
147         mTransitionPlayer = null;
148         mTransitionPlayerProc = null;
149         mRemotePlayer.clear();
150         mRunningLock.doNotifyLocked();
151     }
152 
153     /** @see #createTransition(int, int) */
154     @NonNull
createTransition(int type)155     Transition createTransition(int type) {
156         return createTransition(type, 0 /* flags */);
157     }
158 
159     /**
160      * Creates a transition. It can immediately collect participants.
161      */
162     @NonNull
createTransition(@indowManager.TransitionType int type, @WindowManager.TransitionFlags int flags)163     private Transition createTransition(@WindowManager.TransitionType int type,
164             @WindowManager.TransitionFlags int flags) {
165         if (mTransitionPlayer == null) {
166             throw new IllegalStateException("Shell Transitions not enabled");
167         }
168         if (mCollectingTransition != null) {
169             throw new IllegalStateException("Simultaneous transition collection not supported"
170                     + " yet. Use {@link #createPendingTransition} for explicit queueing.");
171         }
172         Transition transit = new Transition(type, flags, this, mAtm.mWindowManager.mSyncEngine);
173         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", transit);
174         moveToCollecting(transit);
175         return transit;
176     }
177 
178     /** Starts Collecting */
moveToCollecting(@onNull Transition transition)179     void moveToCollecting(@NonNull Transition transition) {
180         moveToCollecting(transition, SYNC_METHOD);
181     }
182 
183     /** Starts Collecting */
184     @VisibleForTesting
moveToCollecting(@onNull Transition transition, int method)185     void moveToCollecting(@NonNull Transition transition, int method) {
186         if (mCollectingTransition != null) {
187             throw new IllegalStateException("Simultaneous transition collection not supported.");
188         }
189         mCollectingTransition = transition;
190         // Distinguish change type because the response time is usually expected to be not too long.
191         final long timeoutMs =
192                 transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
193         mCollectingTransition.startCollecting(timeoutMs, method);
194         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
195                 mCollectingTransition);
196         dispatchLegacyAppTransitionPending();
197     }
198 
registerTransitionPlayer(@ullable ITransitionPlayer player, @Nullable WindowProcessController playerProc)199     void registerTransitionPlayer(@Nullable ITransitionPlayer player,
200             @Nullable WindowProcessController playerProc) {
201         try {
202             // Note: asBinder() can be null if player is same process (likely in a test).
203             if (mTransitionPlayer != null) {
204                 if (mTransitionPlayer.asBinder() != null) {
205                     mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0);
206                 }
207                 detachPlayer();
208             }
209             if (player.asBinder() != null) {
210                 player.asBinder().linkToDeath(mTransitionPlayerDeath, 0);
211             }
212             mTransitionPlayer = player;
213             mTransitionPlayerProc = playerProc;
214         } catch (RemoteException e) {
215             throw new RuntimeException("Unable to set transition player");
216         }
217     }
218 
getTransitionPlayer()219     @Nullable ITransitionPlayer getTransitionPlayer() {
220         return mTransitionPlayer;
221     }
222 
isShellTransitionsEnabled()223     boolean isShellTransitionsEnabled() {
224         return mTransitionPlayer != null;
225     }
226 
227     /** @return {@code true} if using shell-transitions rotation instead of fixed-rotation. */
useShellTransitionsRotation()228     boolean useShellTransitionsRotation() {
229         return isShellTransitionsEnabled() && SHELL_TRANSITIONS_ROTATION;
230     }
231 
232     /**
233      * @return {@code true} if transition is actively collecting changes. This is {@code false}
234      * once a transition is playing
235      */
isCollecting()236     boolean isCollecting() {
237         return mCollectingTransition != null;
238     }
239 
240     /**
241      * @return the collecting transition. {@code null} if there is no collecting transition.
242      */
243     @Nullable
getCollectingTransition()244     Transition getCollectingTransition() {
245         return mCollectingTransition;
246     }
247 
248     /**
249      * @return the collecting transition sync Id. This should only be called when there is a
250      * collecting transition.
251      */
getCollectingTransitionId()252     int getCollectingTransitionId() {
253         if (mCollectingTransition == null) {
254             throw new IllegalStateException("There is no collecting transition");
255         }
256         return mCollectingTransition.getSyncId();
257     }
258 
259     /**
260      * @return {@code true} if transition is actively collecting changes and `wc` is one of them.
261      *                      This is {@code false} once a transition is playing.
262      */
isCollecting(@onNull WindowContainer wc)263     boolean isCollecting(@NonNull WindowContainer wc) {
264         return mCollectingTransition != null && mCollectingTransition.mParticipants.contains(wc);
265     }
266 
267     /**
268      * @return {@code true} if transition is actively collecting changes and `wc` is one of them
269      *                      or a descendant of one of them. {@code false} once playing.
270      */
inCollectingTransition(@onNull WindowContainer wc)271     boolean inCollectingTransition(@NonNull WindowContainer wc) {
272         if (!isCollecting()) return false;
273         return mCollectingTransition.isInTransition(wc);
274     }
275 
276     /**
277      * @return {@code true} if transition is actively playing. This is not necessarily {@code true}
278      * during collection.
279      */
isPlaying()280     boolean isPlaying() {
281         return !mPlayingTransitions.isEmpty();
282     }
283 
284     /**
285      * @return {@code true} if one of the playing transitions contains `wc`.
286      */
inPlayingTransition(@onNull WindowContainer wc)287     boolean inPlayingTransition(@NonNull WindowContainer wc) {
288         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
289             if (mPlayingTransitions.get(i).isInTransition(wc)) return true;
290         }
291         return false;
292     }
293 
294     /** @return {@code true} if a transition is running */
inTransition()295     boolean inTransition() {
296         // TODO(shell-transitions): eventually properly support multiple
297         return isCollecting() || isPlaying();
298     }
299 
300     /** @return {@code true} if a transition is running in a participant subtree of wc */
inTransition(@onNull WindowContainer wc)301     boolean inTransition(@NonNull WindowContainer wc) {
302         return inCollectingTransition(wc) || inPlayingTransition(wc);
303     }
304 
inRecentsTransition(@onNull WindowContainer wc)305     boolean inRecentsTransition(@NonNull WindowContainer wc) {
306         for (WindowContainer p = wc; p != null; p = p.getParent()) {
307             // TODO(b/221417431): replace this with deterministic snapshots
308             if (mCollectingTransition == null) break;
309             if ((mCollectingTransition.getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0
310                     && mCollectingTransition.mParticipants.contains(wc)) {
311                 return true;
312             }
313         }
314 
315         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
316             for (WindowContainer p = wc; p != null; p = p.getParent()) {
317                 // TODO(b/221417431): replace this with deterministic snapshots
318                 if ((mPlayingTransitions.get(i).getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0
319                         && mPlayingTransitions.get(i).mParticipants.contains(p)) {
320                     return true;
321                 }
322             }
323         }
324         return false;
325     }
326 
327     /** @return {@code true} if wc is in a participant subtree */
isTransitionOnDisplay(@onNull DisplayContent dc)328     boolean isTransitionOnDisplay(@NonNull DisplayContent dc) {
329         if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
330             return true;
331         }
332         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
333             if (mPlayingTransitions.get(i).isOnDisplay(dc)) return true;
334         }
335         return false;
336     }
337 
isTransientHide(@onNull Task task)338     boolean isTransientHide(@NonNull Task task) {
339         if (mCollectingTransition != null && mCollectingTransition.isTransientHide(task)) {
340             return true;
341         }
342         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
343             if (mPlayingTransitions.get(i).isTransientHide(task)) return true;
344         }
345         return false;
346     }
347 
348     /**
349      * @return {@code true} if {@param ar} is part of a transient-launch activity in an active
350      * transition.
351      */
isTransientLaunch(@onNull ActivityRecord ar)352     boolean isTransientLaunch(@NonNull ActivityRecord ar) {
353         if (mCollectingTransition != null && mCollectingTransition.isTransientLaunch(ar)) {
354             return true;
355         }
356         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
357             if (mPlayingTransitions.get(i).isTransientLaunch(ar)) return true;
358         }
359         return false;
360     }
361 
362     /**
363      * Whether WM can assign layers to window surfaces at this time. This is usually false while
364      * playing, but can be "opened-up" for certain transition operations like calculating layers
365      * for finishTransaction.
366      */
canAssignLayers()367     boolean canAssignLayers() {
368         return mBuildingFinishLayers || !isPlaying();
369     }
370 
371     @WindowConfiguration.WindowingMode
getWindowingModeAtStart(@onNull WindowContainer wc)372     int getWindowingModeAtStart(@NonNull WindowContainer wc) {
373         if (mCollectingTransition == null) return wc.getWindowingMode();
374         final Transition.ChangeInfo ci = mCollectingTransition.mChanges.get(wc);
375         if (ci == null) {
376             // not part of transition, so use current state.
377             return wc.getWindowingMode();
378         }
379         return ci.mWindowingMode;
380     }
381 
382     @WindowManager.TransitionType
getCollectingTransitionType()383     int getCollectingTransitionType() {
384         return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE;
385     }
386 
387     /**
388      * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition)
389      */
390     @Nullable
requestTransitionIfNeeded(@indowManager.TransitionType int type, @NonNull WindowContainer trigger)391     Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
392             @NonNull WindowContainer trigger) {
393         return requestTransitionIfNeeded(type, 0 /* flags */, trigger, trigger /* readyGroupRef */);
394     }
395 
396     /**
397      * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition)
398      */
399     @Nullable
requestTransitionIfNeeded(@indowManager.TransitionType int type, @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger, @NonNull WindowContainer readyGroupRef)400     Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
401             @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
402             @NonNull WindowContainer readyGroupRef) {
403         return requestTransitionIfNeeded(type, flags, trigger, readyGroupRef,
404                 null /* remoteTransition */, null /* displayChange */);
405     }
406 
isExistenceType(@indowManager.TransitionType int type)407     private static boolean isExistenceType(@WindowManager.TransitionType int type) {
408         return type == TRANSIT_OPEN || type == TRANSIT_CLOSE;
409     }
410 
411     /**
412      * If a transition isn't requested yet, creates one and asks the TransitionPlayer (Shell) to
413      * start it. Collection can start immediately.
414      * @param trigger if non-null, this is the first container that will be collected
415      * @param readyGroupRef Used to identify which ready-group this request is for.
416      * @return the created transition if created or null otherwise.
417      */
418     @Nullable
requestTransitionIfNeeded(@indowManager.TransitionType int type, @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger, @NonNull WindowContainer readyGroupRef, @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange)419     Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
420             @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
421             @NonNull WindowContainer readyGroupRef, @Nullable RemoteTransition remoteTransition,
422             @Nullable TransitionRequestInfo.DisplayChange displayChange) {
423         if (mTransitionPlayer == null) {
424             return null;
425         }
426         Transition newTransition = null;
427         if (isCollecting()) {
428             if (displayChange != null) {
429                 throw new IllegalArgumentException("Provided displayChange for a non-new request");
430             }
431             // Make the collecting transition wait until this request is ready.
432             mCollectingTransition.setReady(readyGroupRef, false);
433             if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
434                 // Add keyguard flag to dismiss keyguard
435                 mCollectingTransition.addFlag(flags);
436             }
437         } else {
438             newTransition = requestStartTransition(createTransition(type, flags),
439                     trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
440         }
441         if (trigger != null) {
442             if (isExistenceType(type)) {
443                 collectExistenceChange(trigger);
444             } else {
445                 collect(trigger);
446             }
447         }
448         return newTransition;
449     }
450 
451     /** Asks the transition player (shell) to start a created but not yet started transition. */
452     @NonNull
requestStartTransition(@onNull Transition transition, @Nullable Task startTask, @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange)453     Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask,
454             @Nullable RemoteTransition remoteTransition,
455             @Nullable TransitionRequestInfo.DisplayChange displayChange) {
456         try {
457             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
458                     "Requesting StartTransition: %s", transition);
459             ActivityManager.RunningTaskInfo info = null;
460             if (startTask != null) {
461                 info = new ActivityManager.RunningTaskInfo();
462                 startTask.fillTaskInfo(info);
463             }
464             mTransitionPlayer.requestStartTransition(transition.getToken(),
465                     new TransitionRequestInfo(transition.mType, info, remoteTransition,
466                             displayChange));
467             transition.setRemoteTransition(remoteTransition);
468         } catch (RemoteException e) {
469             Slog.e(TAG, "Error requesting transition", e);
470             transition.start();
471         }
472         return transition;
473     }
474 
475     /** Requests transition for a window container which will be removed or invisible. */
requestCloseTransitionIfNeeded(@onNull WindowContainer<?> wc)476     void requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) {
477         if (mTransitionPlayer == null) return;
478         if (wc.isVisibleRequested()) {
479             if (!isCollecting()) {
480                 requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */),
481                         wc.asTask(), null /* remoteTransition */, null /* displayChange */);
482             }
483             collectExistenceChange(wc);
484         } else {
485             // Removing a non-visible window doesn't require a transition, but if there is one
486             // collecting, this should be a member just in case.
487             collect(wc);
488         }
489     }
490 
491     /** @see Transition#collect */
collect(@onNull WindowContainer wc)492     void collect(@NonNull WindowContainer wc) {
493         if (mCollectingTransition == null) return;
494         mCollectingTransition.collect(wc);
495     }
496 
497     /** @see Transition#collectExistenceChange  */
collectExistenceChange(@onNull WindowContainer wc)498     void collectExistenceChange(@NonNull WindowContainer wc) {
499         if (mCollectingTransition == null) return;
500         mCollectingTransition.collectExistenceChange(wc);
501     }
502 
503     /**
504      * Collects the window containers which need to be synced with the changing display area into
505      * the current collecting transition.
506      */
collectForDisplayAreaChange(@onNull DisplayArea<?> wc)507     void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc) {
508         final Transition transition = mCollectingTransition;
509         if (transition == null || !transition.mParticipants.contains(wc)) return;
510         transition.collectVisibleChange(wc);
511         // Collect all visible tasks.
512         wc.forAllLeafTasks(task -> {
513             if (task.isVisible()) {
514                 transition.collect(task);
515             }
516         }, true /* traverseTopToBottom */);
517         // Collect all visible non-app windows which need to be drawn before the animation starts.
518         final DisplayContent dc = wc.asDisplayContent();
519         if (dc != null) {
520             final boolean noAsyncRotation = dc.getAsyncRotationController() == null;
521             wc.forAllWindows(w -> {
522                 if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
523                         && (noAsyncRotation || !AsyncRotationController.canBeAsync(w.mToken))) {
524                     transition.collect(w.mToken);
525                 }
526             }, true /* traverseTopToBottom */);
527         }
528     }
529 
530     /**
531      * Records that a particular container is changing visibly (ie. something about it is changing
532      * while it remains visible). This only effects windows that are already in the collecting
533      * transition.
534      */
collectVisibleChange(WindowContainer wc)535     void collectVisibleChange(WindowContainer wc) {
536         if (!isCollecting()) return;
537         mCollectingTransition.collectVisibleChange(wc);
538     }
539 
540     /**
541      * Records that a particular container has been reparented. This only effects windows that have
542      * already been collected in the transition. This should be called before reparenting because
543      * the old parent may be removed during reparenting, for example:
544      * {@link Task#shouldRemoveSelfOnLastChildRemoval}
545      */
collectReparentChange(@onNull WindowContainer wc, @NonNull WindowContainer newParent)546     void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) {
547         if (!isCollecting()) return;
548         mCollectingTransition.collectReparentChange(wc, newParent);
549     }
550 
551     /** @see Transition#mStatusBarTransitionDelay */
setStatusBarTransitionDelay(long delay)552     void setStatusBarTransitionDelay(long delay) {
553         if (mCollectingTransition == null) return;
554         mCollectingTransition.mStatusBarTransitionDelay = delay;
555     }
556 
557     /** @see Transition#setOverrideAnimation */
setOverrideAnimation(TransitionInfo.AnimationOptions options, @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback)558     void setOverrideAnimation(TransitionInfo.AnimationOptions options,
559             @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
560         if (mCollectingTransition == null) return;
561         mCollectingTransition.setOverrideAnimation(options, startCallback, finishCallback);
562     }
563 
564     /** @see Transition#setReady */
setReady(WindowContainer wc, boolean ready)565     void setReady(WindowContainer wc, boolean ready) {
566         if (mCollectingTransition == null) return;
567         mCollectingTransition.setReady(wc, ready);
568     }
569 
570     /** @see Transition#setReady */
setReady(WindowContainer wc)571     void setReady(WindowContainer wc) {
572         setReady(wc, true);
573     }
574 
575     /** @see Transition#deferTransitionReady */
deferTransitionReady()576     void deferTransitionReady() {
577         if (!isShellTransitionsEnabled()) return;
578         if (mCollectingTransition == null) {
579             throw new IllegalStateException("No collecting transition to defer readiness for.");
580         }
581         mCollectingTransition.deferTransitionReady();
582     }
583 
584     /** @see Transition#continueTransitionReady */
continueTransitionReady()585     void continueTransitionReady() {
586         if (!isShellTransitionsEnabled()) return;
587         if (mCollectingTransition == null) {
588             throw new IllegalStateException("No collecting transition to defer readiness for.");
589         }
590         mCollectingTransition.continueTransitionReady();
591     }
592 
593     /** @see Transition#finishTransition */
finishTransition(@onNull IBinder token)594     void finishTransition(@NonNull IBinder token) {
595         // It is usually a no-op but make sure that the metric consumer is removed.
596         mTransitionMetricsReporter.reportAnimationStart(token, 0 /* startTime */);
597         // It is a no-op if the transition did not change the display.
598         mAtm.endLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
599         final Transition record = Transition.fromBinder(token);
600         if (record == null || !mPlayingTransitions.contains(record)) {
601             Slog.e(TAG, "Trying to finish a non-playing transition " + token);
602             return;
603         }
604         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
605         mPlayingTransitions.remove(record);
606         updateRunningRemoteAnimation(record, false /* isPlaying */);
607         record.finishTransition();
608         mRunningLock.doNotifyLocked();
609     }
610 
moveToPlaying(Transition transition)611     void moveToPlaying(Transition transition) {
612         if (transition != mCollectingTransition) {
613             throw new IllegalStateException("Trying to move non-collecting transition to playing");
614         }
615         mCollectingTransition = null;
616         mPlayingTransitions.add(transition);
617         updateRunningRemoteAnimation(transition, true /* isPlaying */);
618         mTransitionTracer.logState(transition);
619     }
620 
621     /** Updates the process state of animation player. */
updateRunningRemoteAnimation(Transition transition, boolean isPlaying)622     private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) {
623         if (mTransitionPlayerProc == null) return;
624         if (isPlaying) {
625             mWakeT.setEarlyWakeupStart();
626             mWakeT.apply();
627             // Usually transitions put quite a load onto the system already (with all the things
628             // happening in app), so pause task snapshot persisting to not increase the load.
629             mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(true);
630             mTransitionPlayerProc.setRunningRemoteAnimation(true);
631         } else if (mPlayingTransitions.isEmpty()) {
632             mWakeT.setEarlyWakeupEnd();
633             mWakeT.apply();
634             mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(false);
635             mTransitionPlayerProc.setRunningRemoteAnimation(false);
636             mRemotePlayer.clear();
637             return;
638         }
639         final RemoteTransition remote = transition.getRemoteTransition();
640         if (remote == null) return;
641         final IApplicationThread appThread = remote.getAppThread();
642         final WindowProcessController delegate = mAtm.getProcessController(appThread);
643         if (delegate == null) return;
644         mRemotePlayer.update(delegate, isPlaying, true /* predict */);
645     }
646 
abort(Transition transition)647     void abort(Transition transition) {
648         if (transition != mCollectingTransition) {
649             throw new IllegalStateException("Too late to abort.");
650         }
651         transition.abort();
652         mCollectingTransition = null;
653         mTransitionTracer.logState(transition);
654     }
655 
656     /**
657      * Record that the launch of {@param activity} is transient (meaning its lifecycle is currently
658      * tied to the transition).
659      * @param restoreBelowTask If non-null, the activity's task will be ordered right below this
660      *                         task if requested.
661      */
setTransientLaunch(@onNull ActivityRecord activity, @Nullable Task restoreBelowTask)662     void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelowTask) {
663         if (mCollectingTransition == null) return;
664         mCollectingTransition.setTransientLaunch(activity, restoreBelowTask);
665 
666         // TODO(b/188669821): Remove once legacy recents behavior is moved to shell.
667         // Also interpret HOME transient launch as recents
668         if (activity.isActivityTypeHomeOrRecents()) {
669             mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS);
670             // When starting recents animation, we assume the recents activity is behind the app
671             // task and should not affect system bar appearance,
672             // until WMS#setRecentsAppBehindSystemBars be called from launcher when passing
673             // the gesture threshold.
674             activity.getTask().setCanAffectSystemUiFlags(false);
675         }
676     }
677 
678     /** @see Transition#setCanPipOnFinish */
setCanPipOnFinish(boolean canPipOnFinish)679     void setCanPipOnFinish(boolean canPipOnFinish) {
680         if (mCollectingTransition == null) return;
681         mCollectingTransition.setCanPipOnFinish(canPipOnFinish);
682     }
683 
legacyDetachNavigationBarFromApp(@onNull IBinder token)684     void legacyDetachNavigationBarFromApp(@NonNull IBinder token) {
685         final Transition transition = Transition.fromBinder(token);
686         if (transition == null || !mPlayingTransitions.contains(transition)) {
687             Slog.e(TAG, "Transition isn't playing: " + token);
688             return;
689         }
690         transition.legacyRestoreNavigationBarFromApp();
691     }
692 
registerLegacyListener(WindowManagerInternal.AppTransitionListener listener)693     void registerLegacyListener(WindowManagerInternal.AppTransitionListener listener) {
694         mLegacyListeners.add(listener);
695     }
696 
unregisterLegacyListener(WindowManagerInternal.AppTransitionListener listener)697     void unregisterLegacyListener(WindowManagerInternal.AppTransitionListener listener) {
698         mLegacyListeners.remove(listener);
699     }
700 
dispatchLegacyAppTransitionPending()701     void dispatchLegacyAppTransitionPending() {
702         for (int i = 0; i < mLegacyListeners.size(); ++i) {
703             mLegacyListeners.get(i).onAppTransitionPendingLocked();
704         }
705     }
706 
dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay)707     void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) {
708         final boolean keyguardGoingAway = info.isKeyguardGoingAway();
709         for (int i = 0; i < mLegacyListeners.size(); ++i) {
710             // TODO(shell-transitions): handle (un)occlude transition.
711             mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
712                     false /* keyguardOcclude */, 0 /* durationHint */,
713                     SystemClock.uptimeMillis() + statusBarTransitionDelay,
714                     AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
715         }
716     }
717 
dispatchLegacyAppTransitionFinished(ActivityRecord ar)718     void dispatchLegacyAppTransitionFinished(ActivityRecord ar) {
719         for (int i = 0; i < mLegacyListeners.size(); ++i) {
720             mLegacyListeners.get(i).onAppTransitionFinishedLocked(ar.token);
721         }
722     }
723 
dispatchLegacyAppTransitionCancelled()724     void dispatchLegacyAppTransitionCancelled() {
725         for (int i = 0; i < mLegacyListeners.size(); ++i) {
726             mLegacyListeners.get(i).onAppTransitionCancelledLocked(
727                     false /* keyguardGoingAway */);
728         }
729     }
730 
dumpDebugLegacy(ProtoOutputStream proto, long fieldId)731     void dumpDebugLegacy(ProtoOutputStream proto, long fieldId) {
732         final long token = proto.start(fieldId);
733         int state = LEGACY_STATE_IDLE;
734         if (!mPlayingTransitions.isEmpty()) {
735             state = LEGACY_STATE_RUNNING;
736         } else if ((mCollectingTransition != null && mCollectingTransition.getLegacyIsReady())
737                 || mAtm.mWindowManager.mSyncEngine.hasPendingSyncSets()) {
738             // The transition may not be "ready", but we have a sync-transaction waiting to start.
739             // Usually the pending transaction is for a transition, so assuming that is the case,
740             // we can't be IDLE for test purposes. Ideally, we should have a STATE_COLLECTING.
741             state = LEGACY_STATE_READY;
742         }
743         proto.write(AppTransitionProto.APP_TRANSITION_STATE, state);
744         proto.end(token);
745     }
746 
747     /**
748      * This manages the animating state of processes that are running remote animations for
749      * {@link #mTransitionPlayerProc}.
750      */
751     static class RemotePlayer {
752         private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 100;
753         @GuardedBy("itself")
754         private final ArrayMap<IBinder, DelegateProcess> mDelegateProcesses = new ArrayMap<>();
755         private final ActivityTaskManagerService mAtm;
756 
757         private class DelegateProcess implements Runnable {
758             final WindowProcessController mProc;
759             /** Requires {@link RemotePlayer#reportRunning} to confirm it is really running. */
760             boolean mNeedReport;
761 
DelegateProcess(WindowProcessController proc)762             DelegateProcess(WindowProcessController proc) {
763                 mProc = proc;
764             }
765 
766             /** This runs when the remote player doesn't report running in time. */
767             @Override
run()768             public void run() {
769                 synchronized (mAtm.mGlobalLockWithoutBoost) {
770                     update(mProc, false /* running */, false /* predict */);
771                 }
772             }
773         }
774 
RemotePlayer(ActivityTaskManagerService atm)775         RemotePlayer(ActivityTaskManagerService atm) {
776             mAtm = atm;
777         }
778 
update(@onNull WindowProcessController delegate, boolean running, boolean predict)779         void update(@NonNull WindowProcessController delegate, boolean running, boolean predict) {
780             if (!running) {
781                 synchronized (mDelegateProcesses) {
782                     boolean removed = false;
783                     for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) {
784                         if (mDelegateProcesses.valueAt(i).mProc == delegate) {
785                             mDelegateProcesses.removeAt(i);
786                             removed = true;
787                             break;
788                         }
789                     }
790                     if (!removed) return;
791                 }
792                 delegate.setRunningRemoteAnimation(false);
793                 return;
794             }
795             if (delegate.isRunningRemoteTransition() || !delegate.hasThread()) return;
796             delegate.setRunningRemoteAnimation(true);
797             final DelegateProcess delegateProc = new DelegateProcess(delegate);
798             // If "predict" is true, that means the remote animation is set from
799             // ActivityOptions#makeRemoteAnimation(). But it is still up to shell side to decide
800             // whether to use the remote animation, so there is a timeout to cancel the prediction
801             // if the remote animation doesn't happen.
802             if (predict) {
803                 delegateProc.mNeedReport = true;
804                 mAtm.mH.postDelayed(delegateProc, REPORT_RUNNING_GRACE_PERIOD_MS);
805             }
806             synchronized (mDelegateProcesses) {
807                 mDelegateProcesses.put(delegate.getThread().asBinder(), delegateProc);
808             }
809         }
810 
clear()811         void clear() {
812             synchronized (mDelegateProcesses) {
813                 for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) {
814                     mDelegateProcesses.valueAt(i).mProc.setRunningRemoteAnimation(false);
815                 }
816                 mDelegateProcesses.clear();
817             }
818         }
819 
820         /** Returns {@code true} if the app is known to be running remote transition. */
reportRunning(@onNull IApplicationThread appThread)821         boolean reportRunning(@NonNull IApplicationThread appThread) {
822             final DelegateProcess delegate;
823             synchronized (mDelegateProcesses) {
824                 delegate = mDelegateProcesses.get(appThread.asBinder());
825                 if (delegate != null && delegate.mNeedReport) {
826                     // It was predicted to run remote transition. Now it is really requesting so
827                     // remove the timeout of restoration.
828                     delegate.mNeedReport = false;
829                     mAtm.mH.removeCallbacks(delegate);
830                 }
831             }
832             return delegate != null;
833         }
834     }
835 
836     static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
837         private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
838 
associate(IBinder transitionToken, LongConsumer consumer)839         void associate(IBinder transitionToken, LongConsumer consumer) {
840             synchronized (mMetricConsumers) {
841                 mMetricConsumers.put(transitionToken, consumer);
842             }
843         }
844 
845         @Override
reportAnimationStart(IBinder transitionToken, long startTime)846         public void reportAnimationStart(IBinder transitionToken, long startTime) {
847             final LongConsumer c;
848             synchronized (mMetricConsumers) {
849                 if (mMetricConsumers.isEmpty()) return;
850                 c = mMetricConsumers.remove(transitionToken);
851             }
852             if (c != null) {
853                 c.accept(startTime);
854             }
855         }
856     }
857 
858     class Lock {
859         private int mTransitionWaiters = 0;
runWhenIdle(long timeout, Runnable r)860         void runWhenIdle(long timeout, Runnable r) {
861             synchronized (mAtm.mGlobalLock) {
862                 if (!inTransition()) {
863                     r.run();
864                     return;
865                 }
866                 mTransitionWaiters += 1;
867             }
868             final long startTime = SystemClock.uptimeMillis();
869             final long endTime = startTime + timeout;
870             while (true) {
871                 synchronized (mAtm.mGlobalLock) {
872                     if (!inTransition() || SystemClock.uptimeMillis() > endTime) {
873                         mTransitionWaiters -= 1;
874                         r.run();
875                         return;
876                     }
877                 }
878                 synchronized (this) {
879                     try {
880                         this.wait(timeout);
881                     } catch (InterruptedException e) {
882                         return;
883                     }
884                 }
885             }
886         }
887 
doNotifyLocked()888         void doNotifyLocked() {
889             synchronized (this) {
890                 if (mTransitionWaiters > 0) {
891                     this.notifyAll();
892                 }
893             }
894         }
895     }
896 }
897