1 /*
2  * Copyright (C) 2018 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 android.util.ArrayMap;
20 import android.util.ArraySet;
21 
22 import java.io.PrintWriter;
23 import java.util.ArrayList;
24 
25 /**
26  * Keeps track of all {@link AppWindowToken} that are animating and makes sure all animations are
27  * finished at the same time such that we don't run into issues with z-ordering: An activity A
28  * that has a shorter animation that is above another activity B with a longer animation in the same
29  * task, the animation layer would put the B on top of A, but from the hierarchy, A needs to be on
30  * top of B. Thus, we defer reparenting A to the original hierarchy such that it stays on top of B
31  * until B finishes animating.
32  */
33 class AnimatingAppWindowTokenRegistry {
34 
35     private ArraySet<AppWindowToken> mAnimatingTokens = new ArraySet<>();
36     private ArrayMap<AppWindowToken, Runnable> mFinishedTokens = new ArrayMap<>();
37 
38     private ArrayList<Runnable> mTmpRunnableList = new ArrayList<>();
39 
40     private boolean mEndingDeferredFinish;
41 
42     /**
43      * Notifies that an {@link AppWindowToken} has started animating.
44      */
notifyStarting(AppWindowToken token)45     void notifyStarting(AppWindowToken token) {
46         mAnimatingTokens.add(token);
47     }
48 
49     /**
50      * Notifies that an {@link AppWindowToken} has finished animating.
51      */
notifyFinished(AppWindowToken token)52     void notifyFinished(AppWindowToken token) {
53         mAnimatingTokens.remove(token);
54         mFinishedTokens.remove(token);
55 
56         // If we were the last token, make sure the end all deferred finishes.
57         if (mAnimatingTokens.isEmpty()) {
58             endDeferringFinished();
59         }
60     }
61 
62     /**
63      * Called when an {@link AppWindowToken} is about to finish animating.
64      *
65      * @param endDeferFinishCallback Callback to run when defer finish should be ended.
66      * @return {@code true} if finishing the animation should be deferred, {@code false} otherwise.
67      */
notifyAboutToFinish(AppWindowToken token, Runnable endDeferFinishCallback)68     boolean notifyAboutToFinish(AppWindowToken token, Runnable endDeferFinishCallback) {
69         final boolean removed = mAnimatingTokens.remove(token);
70         if (!removed) {
71             return false;
72         }
73 
74         if (mAnimatingTokens.isEmpty()) {
75 
76             // If no animations are animating anymore, finish all others.
77             endDeferringFinished();
78             return false;
79         } else {
80 
81             // Otherwise let's put it into the pending list of to be finished animations.
82             mFinishedTokens.put(token, endDeferFinishCallback);
83             return true;
84         }
85     }
86 
endDeferringFinished()87     private void endDeferringFinished() {
88 
89         // Don't start recursing. Running the finished listener invokes notifyFinished, which may
90         // invoked us again.
91         if (mEndingDeferredFinish) {
92             return;
93         }
94         try {
95             mEndingDeferredFinish = true;
96 
97             // Copy it into a separate temp list to avoid modifying the collection while iterating
98             // as calling the callback may call back into notifyFinished.
99             for (int i = mFinishedTokens.size() - 1; i >= 0; i--) {
100                 mTmpRunnableList.add(mFinishedTokens.valueAt(i));
101             }
102             mFinishedTokens.clear();
103             for (int i = mTmpRunnableList.size() - 1; i >= 0; i--) {
104                 mTmpRunnableList.get(i).run();
105             }
106             mTmpRunnableList.clear();
107         } finally {
108             mEndingDeferredFinish = false;
109         }
110     }
111 
dump(PrintWriter pw, String header, String prefix)112     void dump(PrintWriter pw, String header, String prefix) {
113         if (!mAnimatingTokens.isEmpty() || !mFinishedTokens.isEmpty()) {
114             pw.print(prefix); pw.println(header);
115             prefix = prefix + "  ";
116             pw.print(prefix); pw.print("mAnimatingTokens="); pw.println(mAnimatingTokens);
117             pw.print(prefix); pw.print("mFinishedTokens="); pw.println(mFinishedTokens);
118         }
119     }
120 }
121