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