• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.wm.shell;
18 
19 import static android.view.WindowManager.TRANSIT_OPEN;
20 import static android.view.WindowManager.TRANSIT_TO_BACK;
21 import static android.view.WindowManager.TRANSIT_TO_FRONT;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.ActivityManager;
26 import android.os.IBinder;
27 import android.util.Slog;
28 import android.view.SurfaceControl;
29 import android.view.WindowManager;
30 import android.window.TransitionInfo;
31 import android.window.TransitionRequestInfo;
32 import android.window.WindowContainerTransaction;
33 
34 import com.android.wm.shell.transition.Transitions;
35 
36 import java.util.ArrayList;
37 
38 /**
39  * Handles Shell Transitions that involve TaskView tasks.
40  */
41 public class TaskViewTransitions implements Transitions.TransitionHandler {
42     private static final String TAG = "TaskViewTransitions";
43 
44     private final ArrayList<TaskView> mTaskViews = new ArrayList<>();
45     private final ArrayList<PendingTransition> mPending = new ArrayList<>();
46     private final Transitions mTransitions;
47     private final boolean[] mRegistered = new boolean[]{ false };
48 
49     /**
50      * TaskView makes heavy use of startTransition. Only one shell-initiated transition can be
51      * in-flight (collecting) at a time (because otherwise, the operations could get merged into
52      * a single transition). So, keep a queue here until we add a queue in server-side.
53      */
54     private static class PendingTransition {
55         final @WindowManager.TransitionType int mType;
56         final WindowContainerTransaction mWct;
57         final @NonNull TaskView mTaskView;
58         IBinder mClaimed;
59 
PendingTransition(@indowManager.TransitionType int type, @Nullable WindowContainerTransaction wct, @NonNull TaskView taskView)60         PendingTransition(@WindowManager.TransitionType int type,
61                 @Nullable WindowContainerTransaction wct, @NonNull TaskView taskView) {
62             mType = type;
63             mWct = wct;
64             mTaskView = taskView;
65         }
66     }
67 
TaskViewTransitions(Transitions transitions)68     public TaskViewTransitions(Transitions transitions) {
69         mTransitions = transitions;
70         // Defer registration until the first TaskView because we want this to be the "first" in
71         // priority when handling requests.
72         // TODO(210041388): register here once we have an explicit ordering mechanism.
73     }
74 
addTaskView(TaskView tv)75     void addTaskView(TaskView tv) {
76         synchronized (mRegistered) {
77             if (!mRegistered[0]) {
78                 mRegistered[0] = true;
79                 mTransitions.addHandler(this);
80             }
81         }
82         mTaskViews.add(tv);
83     }
84 
removeTaskView(TaskView tv)85     void removeTaskView(TaskView tv) {
86         mTaskViews.remove(tv);
87         // Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
88     }
89 
isEnabled()90     boolean isEnabled() {
91         return mTransitions.isRegistered();
92     }
93 
94     /**
95      * Looks through the pending transitions for one matching `taskView`.
96      * @param taskView the pending transition should be for this.
97      * @param closing When true, this only returns a pending transition of the close/hide type.
98      *                Otherwise it selects open/show.
99      * @param latest When true, this will only check the most-recent pending transition for the
100      *               specified taskView. If it doesn't match `closing`, this will return null even
101      *               if there is a match earlier. The idea behind this is to check the state of
102      *               the taskviews "as if all transitions already happened".
103      */
findPending(TaskView taskView, boolean closing, boolean latest)104     private PendingTransition findPending(TaskView taskView, boolean closing, boolean latest) {
105         for (int i = mPending.size() - 1; i >= 0; --i) {
106             if (mPending.get(i).mTaskView != taskView) continue;
107             if (Transitions.isClosingType(mPending.get(i).mType) == closing) {
108                 return mPending.get(i);
109             }
110             if (latest) {
111                 return null;
112             }
113         }
114         return null;
115     }
116 
findPending(IBinder claimed)117     private PendingTransition findPending(IBinder claimed) {
118         for (int i = 0; i < mPending.size(); ++i) {
119             if (mPending.get(i).mClaimed != claimed) continue;
120             return mPending.get(i);
121         }
122         return null;
123     }
124 
125     /** @return whether there are pending transitions on TaskViews. */
hasPending()126     public boolean hasPending() {
127         return !mPending.isEmpty();
128     }
129 
130     @Override
handleRequest(@onNull IBinder transition, @Nullable TransitionRequestInfo request)131     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
132             @Nullable TransitionRequestInfo request) {
133         final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
134         if (triggerTask == null) {
135             return null;
136         }
137         final TaskView taskView = findTaskView(triggerTask);
138         if (taskView == null) return null;
139         // Opening types should all be initiated by shell
140         if (!Transitions.isClosingType(request.getType())) return null;
141         PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */);
142         if (pending == null) {
143             pending = new PendingTransition(request.getType(), null, taskView);
144         }
145         if (pending.mClaimed != null) {
146             throw new IllegalStateException("Task is closing in 2 collecting transitions?"
147                     + " This state doesn't make sense");
148         }
149         pending.mClaimed = transition;
150         return new WindowContainerTransaction();
151     }
152 
findTaskView(ActivityManager.RunningTaskInfo taskInfo)153     private TaskView findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
154         for (int i = 0; i < mTaskViews.size(); ++i) {
155             if (mTaskViews.get(i).getTaskInfo() == null) continue;
156             if (taskInfo.token.equals(mTaskViews.get(i).getTaskInfo().token)) {
157                 return mTaskViews.get(i);
158             }
159         }
160         return null;
161     }
162 
startTaskView(WindowContainerTransaction wct, TaskView taskView)163     void startTaskView(WindowContainerTransaction wct, TaskView taskView) {
164         mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView));
165         startNextTransition();
166     }
167 
setTaskViewVisible(TaskView taskView, boolean visible)168     void setTaskViewVisible(TaskView taskView, boolean visible) {
169         PendingTransition pending = findPending(taskView, !visible, true /* latest */);
170         if (pending != null) {
171             // Already opening or creating a task, so no need to do anything here.
172             return;
173         }
174         if (taskView.getTaskInfo() == null) {
175             // Nothing to update, task is not yet available
176             return;
177         }
178         final WindowContainerTransaction wct = new WindowContainerTransaction();
179         wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */);
180         pending = new PendingTransition(
181                 visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView);
182         mPending.add(pending);
183         startNextTransition();
184         // visibility is reported in transition.
185     }
186 
startNextTransition()187     private void startNextTransition() {
188         if (mPending.isEmpty()) return;
189         final PendingTransition pending = mPending.get(0);
190         if (pending.mClaimed != null) {
191             // Wait for this to start animating.
192             return;
193         }
194         pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this);
195     }
196 
197     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)198     public boolean startAnimation(@NonNull IBinder transition,
199             @NonNull TransitionInfo info,
200             @NonNull SurfaceControl.Transaction startTransaction,
201             @NonNull SurfaceControl.Transaction finishTransaction,
202             @NonNull Transitions.TransitionFinishCallback finishCallback) {
203         PendingTransition pending = findPending(transition);
204         if (pending == null) return false;
205         mPending.remove(pending);
206         TaskView taskView = pending.mTaskView;
207         final ArrayList<TransitionInfo.Change> tasks = new ArrayList<>();
208         for (int i = 0; i < info.getChanges().size(); ++i) {
209             final TransitionInfo.Change chg = info.getChanges().get(i);
210             if (chg.getTaskInfo() == null) continue;
211             tasks.add(chg);
212         }
213         if (tasks.isEmpty()) {
214             Slog.e(TAG, "Got a TaskView transition with no task.");
215             return false;
216         }
217         WindowContainerTransaction wct = null;
218         for (int i = 0; i < tasks.size(); ++i) {
219             TransitionInfo.Change chg = tasks.get(i);
220             if (Transitions.isClosingType(chg.getMode())) {
221                 final boolean isHide = chg.getMode() == TRANSIT_TO_BACK;
222                 TaskView tv = findTaskView(chg.getTaskInfo());
223                 if (tv == null) {
224                     throw new IllegalStateException("TaskView transition is closing a "
225                             + "non-taskview task ");
226                 }
227                 if (isHide) {
228                     tv.prepareHideAnimation(finishTransaction);
229                 } else {
230                     tv.prepareCloseAnimation();
231                 }
232             } else if (Transitions.isOpeningType(chg.getMode())) {
233                 final boolean taskIsNew = chg.getMode() == TRANSIT_OPEN;
234                 if (wct == null) wct = new WindowContainerTransaction();
235                 TaskView tv = taskView;
236                 if (!taskIsNew) {
237                     tv = findTaskView(chg.getTaskInfo());
238                     if (tv == null) {
239                         throw new IllegalStateException("TaskView transition is showing a "
240                             + "non-taskview task ");
241                     }
242                 }
243                 tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction,
244                         chg.getTaskInfo(), chg.getLeash(), wct);
245             } else {
246                 throw new IllegalStateException("Claimed transition isn't an opening or closing"
247                         + " type: " + chg.getMode());
248             }
249         }
250         // No animation, just show it immediately.
251         startTransaction.apply();
252         finishTransaction.apply();
253         finishCallback.onTransitionFinished(wct, null /* wctCB */);
254         startNextTransition();
255         return true;
256     }
257 }
258