blob: e6d088e6537de10474780a9cc5353574d72e63ef [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.apppairs;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import android.app.ActivityManager;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.SurfaceUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitLayout;
import java.io.PrintWriter;
/**
* An app-pairs consisting of {@link #mRootTaskInfo} that acts as the hierarchy parent of
* {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair.
* Also includes all UI for managing the pair like the divider.
*/
class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayoutHandler {
private static final String TAG = AppPair.class.getSimpleName();
private ActivityManager.RunningTaskInfo mRootTaskInfo;
private SurfaceControl mRootTaskLeash;
private ActivityManager.RunningTaskInfo mTaskInfo1;
private SurfaceControl mTaskLeash1;
private ActivityManager.RunningTaskInfo mTaskInfo2;
private SurfaceControl mTaskLeash2;
private SurfaceControl mDimLayer1;
private SurfaceControl mDimLayer2;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final AppPairsController mController;
private final SyncTransactionQueue mSyncQueue;
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private SplitLayout mSplitLayout;
AppPair(AppPairsController controller) {
mController = controller;
mSyncQueue = controller.getSyncTransactionQueue();
mDisplayController = controller.getDisplayController();
mDisplayImeController = controller.getDisplayImeController();
}
int getRootTaskId() {
return mRootTaskInfo != null ? mRootTaskInfo.taskId : INVALID_TASK_ID;
}
private int getTaskId1() {
return mTaskInfo1 != null ? mTaskInfo1.taskId : INVALID_TASK_ID;
}
private int getTaskId2() {
return mTaskInfo2 != null ? mTaskInfo2.taskId : INVALID_TASK_ID;
}
boolean contains(int taskId) {
return taskId == getRootTaskId() || taskId == getTaskId1() || taskId == getTaskId2();
}
boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
ProtoLog.v(WM_SHELL_TASK_ORG, "pair task1=%d task2=%d in AppPair=%s",
task1.taskId, task2.taskId, this);
if (!task1.supportsMultiWindow || !task2.supportsMultiWindow) {
ProtoLog.e(WM_SHELL_TASK_ORG,
"Can't pair tasks that doesn't support multi window, "
+ "task1.supportsMultiWindow=%b, task2.supportsMultiWindow=%b",
task1.supportsMultiWindow, task2.supportsMultiWindow);
return false;
}
mTaskInfo1 = task1;
mTaskInfo2 = task2;
mSplitLayout = new SplitLayout(TAG + "SplitDivider",
mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
mRootTaskInfo.configuration, this /* layoutChangeListener */,
b -> b.setParent(mRootTaskLeash), mDisplayImeController,
mController.getTaskOrganizer());
final WindowContainerToken token1 = task1.token;
final WindowContainerToken token2 = task2.token;
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setHidden(mRootTaskInfo.token, false)
.reparent(token1, mRootTaskInfo.token, true /* onTop */)
.reparent(token2, mRootTaskInfo.token, true /* onTop */)
.setWindowingMode(token1, WINDOWING_MODE_MULTI_WINDOW)
.setWindowingMode(token2, WINDOWING_MODE_MULTI_WINDOW)
.setBounds(token1, mSplitLayout.getBounds1())
.setBounds(token2, mSplitLayout.getBounds2())
// Moving the root task to top after the child tasks were repareted , or the root
// task cannot be visible and focused.
.reorder(mRootTaskInfo.token, true);
mController.getTaskOrganizer().applyTransaction(wct);
return true;
}
void unpair() {
unpair(null /* toTopToken */);
}
private void unpair(@Nullable WindowContainerToken toTopToken) {
final WindowContainerToken token1 = mTaskInfo1.token;
final WindowContainerToken token2 = mTaskInfo2.token;
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Reparent out of this container and reset windowing mode.
wct.setHidden(mRootTaskInfo.token, true)
.reorder(mRootTaskInfo.token, false)
.reparent(token1, null, token1 == toTopToken /* onTop */)
.reparent(token2, null, token2 == toTopToken /* onTop */)
.setWindowingMode(token1, WINDOWING_MODE_UNDEFINED)
.setWindowingMode(token2, WINDOWING_MODE_UNDEFINED);
mController.getTaskOrganizer().applyTransaction(wct);
mTaskInfo1 = null;
mTaskInfo2 = null;
mSplitLayout.release();
mSplitLayout = null;
}
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
if (mRootTaskInfo == null || taskInfo.taskId == mRootTaskInfo.taskId) {
mRootTaskInfo = taskInfo;
mRootTaskLeash = leash;
} else if (taskInfo.taskId == getTaskId1()) {
mTaskInfo1 = taskInfo;
mTaskLeash1 = leash;
mSyncQueue.runInSync(t -> mDimLayer1 =
SurfaceUtils.makeDimLayer(t, mTaskLeash1, "Dim layer", mSurfaceSession));
} else if (taskInfo.taskId == getTaskId2()) {
mTaskInfo2 = taskInfo;
mTaskLeash2 = leash;
mSyncQueue.runInSync(t -> mDimLayer2 =
SurfaceUtils.makeDimLayer(t, mTaskLeash2, "Dim layer", mSurfaceSession));
} else {
throw new IllegalStateException("Unknown task=" + taskInfo.taskId);
}
if (mTaskLeash1 == null || mTaskLeash2 == null) return;
mSplitLayout.init();
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
final Rect dividerBounds = mSplitLayout.getDividerBounds();
// TODO: Is there more we need to do here?
mSyncQueue.runInSync(t -> {
t.setLayer(dividerLeash, Integer.MAX_VALUE)
.setPosition(mTaskLeash1, mTaskInfo1.positionInParent.x,
mTaskInfo1.positionInParent.y)
.setPosition(mTaskLeash2, mTaskInfo2.positionInParent.x,
mTaskInfo2.positionInParent.y)
.setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
.show(mRootTaskLeash)
.show(mTaskLeash1)
.show(mTaskLeash2);
});
}
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
if (!taskInfo.supportsMultiWindow) {
// Dismiss AppPair if the task no longer supports multi window.
mController.unpair(mRootTaskInfo.taskId);
return;
}
if (taskInfo.taskId == getRootTaskId()) {
if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
mSyncQueue.runInSync(t -> {
if (taskInfo.isVisible) {
t.show(mRootTaskLeash);
} else {
t.hide(mRootTaskLeash);
}
});
}
mRootTaskInfo = taskInfo;
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
onBoundsChanged(mSplitLayout);
}
} else if (taskInfo.taskId == getTaskId1()) {
mTaskInfo1 = taskInfo;
} else if (taskInfo.taskId == getTaskId2()) {
mTaskInfo2 = taskInfo;
} else {
throw new IllegalStateException("Unknown task=" + taskInfo.taskId);
}
}
@Override
public int getSplitItemPosition(WindowContainerToken token) {
if (token == null) {
return SPLIT_POSITION_UNDEFINED;
}
if (token.equals(mTaskInfo1.getToken())) {
return SPLIT_POSITION_TOP_OR_LEFT;
} else if (token.equals(mTaskInfo2.getToken())) {
return SPLIT_POSITION_BOTTOM_OR_RIGHT;
}
return SPLIT_POSITION_UNDEFINED;
}
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
if (taskInfo.taskId == getRootTaskId()) {
// We don't want to release this object back to the pool since the root task went away.
mController.unpair(mRootTaskInfo.taskId, false /* releaseToPool */);
} else if (taskInfo.taskId == getTaskId1()) {
mController.unpair(mRootTaskInfo.taskId);
mSyncQueue.runInSync(t -> t.remove(mDimLayer1));
} else if (taskInfo.taskId == getTaskId2()) {
mController.unpair(mRootTaskInfo.taskId);
mSyncQueue.runInSync(t -> t.remove(mDimLayer2));
}
}
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
if (getRootTaskId() == taskId) {
b.setParent(mRootTaskLeash);
} else if (getTaskId1() == taskId) {
b.setParent(mTaskLeash1);
} else if (getTaskId2() == taskId) {
b.setParent(mTaskLeash2);
} else {
throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
}
}
@Override
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
pw.println(innerPrefix + "Root taskId=" + getRootTaskId()
+ " winMode=" + mRootTaskInfo.getWindowingMode());
if (mTaskInfo1 != null) {
pw.println(innerPrefix + "1 taskId=" + mTaskInfo1.taskId
+ " winMode=" + mTaskInfo1.getWindowingMode());
}
if (mTaskInfo2 != null) {
pw.println(innerPrefix + "2 taskId=" + mTaskInfo2.taskId
+ " winMode=" + mTaskInfo2.getWindowingMode());
}
}
@Override
public String toString() {
return TAG + "#" + getRootTaskId();
}
@Override
public void onSnappedToDismiss(boolean snappedToEnd) {
unpair(snappedToEnd ? mTaskInfo1.token : mTaskInfo2.token /* toTopToken */);
}
@Override
public void onBoundsChanging(SplitLayout layout) {
mSyncQueue.runInSync(t ->
layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2));
}
@Override
public void onBoundsChanged(SplitLayout layout) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
layout.applyTaskChanges(wct, mTaskInfo1, mTaskInfo2);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t ->
layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2));
}
}