blob: 83335ac24799833bfca616c9290bb499261c5868 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.os.IBinder;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
/**
* Handles Shell Transitions that involve TaskView tasks.
*/
public class TaskViewTransitions implements Transitions.TransitionHandler {
private static final String TAG = "TaskViewTransitions";
private final ArrayList<TaskView> mTaskViews = new ArrayList<>();
private final ArrayList<PendingTransition> mPending = new ArrayList<>();
private final Transitions mTransitions;
private final boolean[] mRegistered = new boolean[]{ false };
/**
* TaskView makes heavy use of startTransition. Only one shell-initiated transition can be
* in-flight (collecting) at a time (because otherwise, the operations could get merged into
* a single transition). So, keep a queue here until we add a queue in server-side.
*/
private static class PendingTransition {
final @WindowManager.TransitionType int mType;
final WindowContainerTransaction mWct;
final @NonNull TaskView mTaskView;
IBinder mClaimed;
PendingTransition(@WindowManager.TransitionType int type,
@Nullable WindowContainerTransaction wct, @NonNull TaskView taskView) {
mType = type;
mWct = wct;
mTaskView = taskView;
}
}
public TaskViewTransitions(Transitions transitions) {
mTransitions = transitions;
// Defer registration until the first TaskView because we want this to be the "first" in
// priority when handling requests.
// TODO(210041388): register here once we have an explicit ordering mechanism.
}
void addTaskView(TaskView tv) {
synchronized (mRegistered) {
if (!mRegistered[0]) {
mRegistered[0] = true;
mTransitions.addHandler(this);
}
}
mTaskViews.add(tv);
}
void removeTaskView(TaskView tv) {
mTaskViews.remove(tv);
// Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
}
/**
* Looks through the pending transitions for one matching `taskView`.
* @param taskView the pending transition should be for this.
* @param closing When true, this only returns a pending transition of the close/hide type.
* Otherwise it selects open/show.
* @param latest When true, this will only check the most-recent pending transition for the
* specified taskView. If it doesn't match `closing`, this will return null even
* if there is a match earlier. The idea behind this is to check the state of
* the taskviews "as if all transitions already happened".
*/
private PendingTransition findPending(TaskView taskView, boolean closing, boolean latest) {
for (int i = mPending.size() - 1; i >= 0; --i) {
if (mPending.get(i).mTaskView != taskView) continue;
if (Transitions.isClosingType(mPending.get(i).mType) == closing) {
return mPending.get(i);
}
if (latest) {
return null;
}
}
return null;
}
private PendingTransition findPending(IBinder claimed) {
for (int i = 0; i < mPending.size(); ++i) {
if (mPending.get(i).mClaimed != claimed) continue;
return mPending.get(i);
}
return null;
}
/** @return whether there are pending transitions on TaskViews. */
public boolean hasPending() {
return !mPending.isEmpty();
}
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@Nullable TransitionRequestInfo request) {
final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
if (triggerTask == null) {
return null;
}
final TaskView taskView = findTaskView(triggerTask);
if (taskView == null) return null;
// Opening types should all be initiated by shell
if (!Transitions.isClosingType(request.getType())) return null;
PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */);
if (pending == null) {
pending = new PendingTransition(request.getType(), null, taskView);
}
if (pending.mClaimed != null) {
throw new IllegalStateException("Task is closing in 2 collecting transitions?"
+ " This state doesn't make sense");
}
pending.mClaimed = transition;
return new WindowContainerTransaction();
}
private TaskView findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
for (int i = 0; i < mTaskViews.size(); ++i) {
if (mTaskViews.get(i).getTaskInfo() == null) continue;
if (taskInfo.token.equals(mTaskViews.get(i).getTaskInfo().token)) {
return mTaskViews.get(i);
}
}
return null;
}
void startTaskView(WindowContainerTransaction wct, TaskView taskView) {
mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView));
startNextTransition();
}
void setTaskViewVisible(TaskView taskView, boolean visible) {
PendingTransition pending = findPending(taskView, !visible, true /* latest */);
if (pending != null) {
// Already opening or creating a task, so no need to do anything here.
return;
}
if (taskView.getTaskInfo() == null) {
// Nothing to update, task is not yet available
return;
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */);
pending = new PendingTransition(
visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView);
mPending.add(pending);
startNextTransition();
// visibility is reported in transition.
}
private void startNextTransition() {
if (mPending.isEmpty()) return;
final PendingTransition pending = mPending.get(0);
if (pending.mClaimed != null) {
// Wait for this to start animating.
return;
}
pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this);
}
@Override
public boolean startAnimation(@NonNull IBinder transition,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
PendingTransition pending = findPending(transition);
if (pending == null) return false;
mPending.remove(pending);
TaskView taskView = pending.mTaskView;
final ArrayList<TransitionInfo.Change> tasks = new ArrayList<>();
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change chg = info.getChanges().get(i);
if (chg.getTaskInfo() == null) continue;
tasks.add(chg);
}
if (tasks.isEmpty()) {
Slog.e(TAG, "Got a TaskView transition with no task.");
return false;
}
WindowContainerTransaction wct = null;
for (int i = 0; i < tasks.size(); ++i) {
TransitionInfo.Change chg = tasks.get(i);
if (Transitions.isClosingType(chg.getMode())) {
final boolean isHide = chg.getMode() == TRANSIT_TO_BACK;
TaskView tv = findTaskView(chg.getTaskInfo());
if (tv == null) {
throw new IllegalStateException("TaskView transition is closing a "
+ "non-taskview task ");
}
if (isHide) {
tv.prepareHideAnimation(finishTransaction);
} else {
tv.prepareCloseAnimation();
}
} else if (Transitions.isOpeningType(chg.getMode())) {
final boolean taskIsNew = chg.getMode() == TRANSIT_OPEN;
if (wct == null) wct = new WindowContainerTransaction();
TaskView tv = taskView;
if (!taskIsNew) {
tv = findTaskView(chg.getTaskInfo());
if (tv == null) {
throw new IllegalStateException("TaskView transition is showing a "
+ "non-taskview task ");
}
}
tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction,
chg.getTaskInfo(), chg.getLeash(), wct);
} else {
throw new IllegalStateException("Claimed transition isn't an opening or closing"
+ " type: " + chg.getMode());
}
}
// No animation, just show it immediately.
startTransaction.apply();
finishTransaction.apply();
finishCallback.onTransitionFinished(wct, null /* wctCB */);
startNextTransition();
return true;
}
}