* Use "select" when in the split screen invocation process * Drop usage of "staged" except when defining constants Fixes: 233006032 Change-Id: I809f0e1e9742aa6b6b09ad8731896ad3f0c9deb0
273 lines
10 KiB
Java
273 lines
10 KiB
Java
/*
|
|
* Copyright (C) 2022 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.quickstep;
|
|
|
|
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
|
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
|
|
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
|
|
import static android.content.Intent.ACTION_CHOOSER;
|
|
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
|
|
|
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
|
|
|
|
import android.app.ActivityManager.RunningTaskInfo;
|
|
import android.content.Context;
|
|
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.UiThread;
|
|
|
|
import com.android.launcher3.util.MainThreadInitializedObject;
|
|
import com.android.launcher3.util.SplitConfigurationOptions;
|
|
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
|
|
import com.android.launcher3.util.SplitConfigurationOptions.StageType;
|
|
import com.android.launcher3.util.SplitConfigurationOptions.SplitStageInfo;
|
|
import com.android.launcher3.util.TraceHelper;
|
|
import com.android.systemui.shared.recents.model.Task;
|
|
import com.android.systemui.shared.recents.model.Task.TaskKey;
|
|
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
|
import com.android.systemui.shared.system.TaskStackChangeListener;
|
|
import com.android.systemui.shared.system.TaskStackChangeListeners;
|
|
import com.android.wm.shell.splitscreen.ISplitScreenListener;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* This class tracked the top-most task and some 'approximate' task history to allow faster
|
|
* system state estimation during touch interaction
|
|
*/
|
|
public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskStackChangeListener {
|
|
|
|
public static MainThreadInitializedObject<TopTaskTracker> INSTANCE =
|
|
new MainThreadInitializedObject<>(TopTaskTracker::new);
|
|
|
|
private static final int HISTORY_SIZE = 5;
|
|
|
|
// Ordered list with first item being the most recent task.
|
|
private final LinkedList<RunningTaskInfo> mOrderedTaskList = new LinkedList<>();
|
|
|
|
|
|
private final SplitStageInfo mMainStagePosition = new SplitStageInfo();
|
|
private final SplitStageInfo mSideStagePosition = new SplitStageInfo();
|
|
private int mPinnedTaskId = INVALID_TASK_ID;
|
|
|
|
private TopTaskTracker(Context context) {
|
|
mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
|
|
mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
|
|
|
|
TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
|
|
SystemUiProxy.INSTANCE.get(context).registerSplitScreenListener(this);
|
|
}
|
|
|
|
@Override
|
|
public void onTaskRemoved(int taskId) {
|
|
mOrderedTaskList.removeIf(rto -> rto.taskId == taskId);
|
|
}
|
|
|
|
@Override
|
|
public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
|
|
mOrderedTaskList.removeIf(rto -> rto.taskId == taskInfo.taskId);
|
|
mOrderedTaskList.addFirst(taskInfo);
|
|
if (mOrderedTaskList.size() >= HISTORY_SIZE) {
|
|
// If we grow in size, remove the last taskInfo which is not part of the split task.
|
|
Iterator<RunningTaskInfo> itr = mOrderedTaskList.descendingIterator();
|
|
while (itr.hasNext()) {
|
|
RunningTaskInfo info = itr.next();
|
|
if (info.taskId != taskInfo.taskId
|
|
&& info.taskId != mMainStagePosition.taskId
|
|
&& info.taskId != mSideStagePosition.taskId) {
|
|
itr.remove();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
|
|
if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
|
|
mMainStagePosition.stagePosition = position;
|
|
} else {
|
|
mSideStagePosition.stagePosition = position;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
|
|
// If task is not visible but we are tracking it, stop tracking it
|
|
if (!visible) {
|
|
if (mMainStagePosition.taskId == taskId) {
|
|
resetTaskId(mMainStagePosition);
|
|
} else if (mSideStagePosition.taskId == taskId) {
|
|
resetTaskId(mSideStagePosition);
|
|
} // else it's an un-tracked child
|
|
return;
|
|
}
|
|
|
|
// If stage has moved to undefined, stop tracking the task
|
|
if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
|
|
resetTaskId(taskId == mMainStagePosition.taskId
|
|
? mMainStagePosition : mSideStagePosition);
|
|
return;
|
|
}
|
|
|
|
if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
|
|
mMainStagePosition.taskId = taskId;
|
|
} else {
|
|
mSideStagePosition.taskId = taskId;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
|
|
mPinnedTaskId = taskId;
|
|
}
|
|
|
|
@Override
|
|
public void onActivityUnpinned() {
|
|
mPinnedTaskId = INVALID_TASK_ID;
|
|
}
|
|
|
|
private void resetTaskId(SplitStageInfo taskPosition) {
|
|
taskPosition.taskId = -1;
|
|
}
|
|
|
|
/**
|
|
* @return index 0 will be task in left/top position, index 1 in right/bottom position.
|
|
* Will return empty array if device is not in staged split
|
|
*/
|
|
public int[] getRunningSplitTaskIds() {
|
|
if (mMainStagePosition.taskId == INVALID_TASK_ID
|
|
|| mSideStagePosition.taskId == INVALID_TASK_ID) {
|
|
return new int[]{};
|
|
}
|
|
int[] out = new int[2];
|
|
if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
|
|
out[0] = mMainStagePosition.taskId;
|
|
out[1] = mSideStagePosition.taskId;
|
|
} else {
|
|
out[1] = mMainStagePosition.taskId;
|
|
out[0] = mSideStagePosition.taskId;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the CachedTaskInfo for the top most task
|
|
*/
|
|
@UiThread
|
|
public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
|
|
if (filterOnlyVisibleRecents) {
|
|
// Since we only know about the top most task, any filtering may not be applied on the
|
|
// cache. The second to top task may change while the top task is still the same.
|
|
RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.true", () ->
|
|
ActivityManagerWrapper.getInstance().getRunningTasks(true));
|
|
return new CachedTaskInfo(Arrays.asList(tasks));
|
|
}
|
|
|
|
if (mOrderedTaskList.isEmpty()) {
|
|
RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.false", () ->
|
|
ActivityManagerWrapper.getInstance().getRunningTasks(
|
|
false /* filterOnlyVisibleRecents */));
|
|
Collections.addAll(mOrderedTaskList, tasks);
|
|
}
|
|
|
|
// Strip the pinned task
|
|
ArrayList<RunningTaskInfo> tasks = new ArrayList<>(mOrderedTaskList);
|
|
tasks.removeIf(t -> t.taskId == mPinnedTaskId);
|
|
return new CachedTaskInfo(tasks);
|
|
}
|
|
|
|
/**
|
|
* Class to provide information about a task which can be safely cached and do not change
|
|
* during the lifecycle of the task.
|
|
*/
|
|
public static class CachedTaskInfo {
|
|
|
|
@Nullable
|
|
private final RunningTaskInfo mTopTask;
|
|
private final List<RunningTaskInfo> mAllCachedTasks;
|
|
|
|
CachedTaskInfo(List<RunningTaskInfo> allCachedTasks) {
|
|
mAllCachedTasks = allCachedTasks;
|
|
mTopTask = allCachedTasks.isEmpty() ? null : allCachedTasks.get(0);
|
|
}
|
|
|
|
public int getTaskId() {
|
|
return mTopTask == null ? INVALID_TASK_ID : mTopTask.taskId;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the root of the task chooser activity
|
|
*/
|
|
public boolean isRootChooseActivity() {
|
|
return mTopTask != null && ACTION_CHOOSER.equals(mTopTask.baseIntent.getAction());
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given task holds an Assistant activity that is excluded from recents
|
|
*/
|
|
public boolean isExcludedAssistant() {
|
|
return mTopTask != null && mTopTask.configuration.windowConfiguration
|
|
.getActivityType() == ACTIVITY_TYPE_ASSISTANT
|
|
&& (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
|
|
}
|
|
|
|
/**
|
|
* Returns true if this represents the HOME task
|
|
*/
|
|
public boolean isHomeTask() {
|
|
return mTopTask != null && mTopTask.configuration.windowConfiguration
|
|
.getActivityType() == ACTIVITY_TYPE_HOME;
|
|
}
|
|
|
|
/**
|
|
* Returns {@link Task} array which can be used as a placeholder until the true object
|
|
* is loaded by the model
|
|
*/
|
|
public Task[] getPlaceholderTasks() {
|
|
return mTopTask == null ? new Task[0]
|
|
: new Task[] {Task.from(new TaskKey(mTopTask), mTopTask, false)};
|
|
}
|
|
|
|
/**
|
|
* Returns {@link Task} array corresponding to the provided task ids which can be used as a
|
|
* placeholder until the true object is loaded by the model
|
|
*/
|
|
public Task[] getPlaceholderTasks(int[] taskIds) {
|
|
if (mTopTask == null) {
|
|
return new Task[0];
|
|
}
|
|
Task[] result = new Task[taskIds.length];
|
|
for (int i = 0; i < taskIds.length; i++) {
|
|
final int index = i;
|
|
int taskId = taskIds[i];
|
|
mAllCachedTasks.forEach(rti -> {
|
|
if (rti.taskId == taskId) {
|
|
result[index] = Task.from(new TaskKey(rti), rti, false);
|
|
}
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|