blob: 23eec96a5d8f478730dfba08d9c79607f5259960 [file] [log] [blame]
/*
* Copyright (C) 2020 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.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SurfaceUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import java.io.PrintWriter;
import java.util.function.Predicate;
/**
* Base class that handle common task org. related for split-screen stages.
* Note that this class and its sub-class do not directly perform hierarchy operations.
* They only serve to hold a collection of tasks and provide APIs like
* {@link #addTask(ActivityManager.RunningTaskInfo, WindowContainerTransaction)} for the centralized
* {@link StageCoordinator} to perform hierarchy operations in-sync with other containers.
*
* @see StageCoordinator
*/
class StageTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = StageTaskListener.class.getSimpleName();
protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
protected static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
/** Callback interface for listening to changes in a split-screen stage. */
public interface StageListenerCallbacks {
void onRootTaskAppeared();
void onStatusChanged(boolean visible, boolean hasChildren);
void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
void onChildTaskEnterPip(int taskId);
void onRootTaskVanished();
void onNoLongerSupportMultiWindow();
}
private final Context mContext;
private final StageListenerCallbacks mCallbacks;
private final SurfaceSession mSurfaceSession;
private final SyncTransactionQueue mSyncQueue;
private final IconProvider mIconProvider;
protected ActivityManager.RunningTaskInfo mRootTaskInfo;
protected SurfaceControl mRootLeash;
protected SurfaceControl mDimLayer;
protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
// TODO(b/204308910): Extracts SplitDecorManager related code to common package.
private SplitDecorManager mSplitDecorManager;
StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession, IconProvider iconProvider) {
mContext = context;
mCallbacks = callbacks;
mSyncQueue = syncQueue;
mSurfaceSession = surfaceSession;
mIconProvider = iconProvider;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
}
int getChildCount() {
return mChildrenTaskInfo.size();
}
boolean containsTask(int taskId) {
return mChildrenTaskInfo.contains(taskId);
}
boolean containsToken(WindowContainerToken token) {
return contains(t -> t.token.equals(token));
}
boolean containsContainer(IBinder binder) {
return contains(t -> t.token.asBinder() == binder);
}
/**
* Returns the top visible child task's id.
*/
int getTopVisibleChildTaskId() {
final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.isVisible);
return taskInfo != null ? taskInfo.taskId : INVALID_TASK_ID;
}
/**
* Returns the top activity uid for the top child task.
*/
int getTopChildTaskUid() {
final ActivityManager.RunningTaskInfo taskInfo =
getChildTaskInfo(t -> t.topActivityInfo != null);
return taskInfo != null ? taskInfo.topActivityInfo.applicationInfo.uid : 0;
}
/** @return {@code true} if this listener contains the currently focused task. */
boolean isFocused() {
return contains(t -> t.isFocused);
}
private boolean contains(Predicate<ActivityManager.RunningTaskInfo> predicate) {
if (mRootTaskInfo != null && predicate.test(mRootTaskInfo)) {
return true;
}
return getChildTaskInfo(predicate) != null;
}
@Nullable
private ActivityManager.RunningTaskInfo getChildTaskInfo(
Predicate<ActivityManager.RunningTaskInfo> predicate) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
if (predicate.test(taskInfo)) {
return taskInfo;
}
}
return null;
}
@Override
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
if (mRootTaskInfo == null) {
mRootLeash = leash;
mRootTaskInfo = taskInfo;
mSplitDecorManager = new SplitDecorManager(
mRootTaskInfo.configuration,
mIconProvider,
mSurfaceSession);
mCallbacks.onRootTaskAppeared();
sendStatusChanged();
mSyncQueue.runInSync(t -> mDimLayer =
SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession));
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
final int taskId = taskInfo.taskId;
mChildrenLeashes.put(taskId, leash);
mChildrenTaskInfo.put(taskId, taskInfo);
updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
if (ENABLE_SHELL_TRANSITIONS) {
// Status is managed/synchronized by the transition lifecycle.
return;
}
sendStatusChanged();
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
}
@Override
@CallSuper
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
if (!taskInfo.supportsMultiWindow) {
// Leave split screen if the task no longer supports multi window.
mCallbacks.onNoLongerSupportMultiWindow();
return;
}
if (mRootTaskInfo.taskId == taskInfo.taskId) {
// Inflates split decor view only when the root task is visible.
if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
if (taskInfo.isVisible) {
mSplitDecorManager.inflate(mContext, mRootLeash,
taskInfo.configuration.windowConfiguration.getBounds());
} else {
mSyncQueue.runInSync(t -> mSplitDecorManager.release(t));
}
}
mRootTaskInfo = taskInfo;
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
taskInfo.isVisible);
if (!ENABLE_SHELL_TRANSITIONS) {
updateChildTaskSurface(
taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
}
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
if (ENABLE_SHELL_TRANSITIONS) {
// Status is managed/synchronized by the transition lifecycle.
return;
}
sendStatusChanged();
}
@Override
@CallSuper
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
final int taskId = taskInfo.taskId;
if (mRootTaskInfo.taskId == taskId) {
mCallbacks.onRootTaskVanished();
mRootTaskInfo = null;
mSyncQueue.runInSync(t -> {
t.remove(mDimLayer);
mSplitDecorManager.release(t);
});
} else if (mChildrenTaskInfo.contains(taskId)) {
mChildrenTaskInfo.remove(taskId);
mChildrenLeashes.remove(taskId);
mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
if (ENABLE_SHELL_TRANSITIONS) {
// Status is managed/synchronized by the transition lifecycle.
return;
}
if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
mCallbacks.onChildTaskEnterPip(taskId);
}
sendStatusChanged();
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
}
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
b.setParent(findTaskSurface(taskId));
}
@Override
public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
SurfaceControl.Transaction t) {
t.reparent(sc, findTaskSurface(taskId));
}
private SurfaceControl findTaskSurface(int taskId) {
if (mRootTaskInfo.taskId == taskId) {
return mRootLeash;
} else if (mChildrenLeashes.contains(taskId)) {
return mChildrenLeashes.get(taskId);
} else {
throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
}
}
void onResizing(Rect newBounds, SurfaceControl.Transaction t) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, t);
}
}
void onResized(SurfaceControl.Transaction t) {
if (mSplitDecorManager != null) {
mSplitDecorManager.onResized(t);
}
}
void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
// Clear overridden bounds and windowing mode to make sure the child task can inherit
// windowing mode and bounds from split root.
wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
.setBounds(task.token, null);
wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/);
}
void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
if (!containsTask(taskId)) {
return;
}
wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
}
/** Collects all the current child tasks and prepares transaction to evict them to display. */
void evictAllChildren(WindowContainerTransaction wct) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
}
}
void evictInvisibleChildren(WindowContainerTransaction wct) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
if (!taskInfo.isVisible) {
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
}
}
}
void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
@StageType int stage) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
int taskId = mChildrenTaskInfo.keyAt(i);
listener.onTaskStageChanged(taskId, stage,
mChildrenTaskInfo.get(taskId).isVisible);
}
}
private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl leash, boolean firstAppeared) {
final Point taskPositionInParent = taskInfo.positionInParent;
mSyncQueue.runInSync(t -> {
t.setWindowCrop(leash, null);
t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
t.setAlpha(leash, 1f);
t.setMatrix(leash, 1, 0, 0, 1);
t.show(leash);
}
});
}
private void sendStatusChanged() {
mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
}
@Override
@CallSuper
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
}
}