::new));
+
callback.accept(result);
}
});
diff --git a/quickstep/src/com/android/quickstep/RecentsFilterState.java b/quickstep/src/com/android/quickstep/RecentsFilterState.java
new file mode 100644
index 0000000000..ff6951d33f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsFilterState.java
@@ -0,0 +1,176 @@
+/*
+ * 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 androidx.annotation.Nullable;
+
+import com.android.quickstep.util.GroupTask;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+/**
+ * Keeps track of the state of {@code RecentsView}.
+ *
+ * More specifically, used for keeping track of the state of filters applied on tasks
+ * in {@code RecentsView} for multi-instance management.
+ */
+public class RecentsFilterState {
+ // the minimum number of tasks per package present to allow filtering
+ public static final int MIN_FILTERING_TASK_COUNT = 2;
+
+ // default filter that returns true for any input
+ public static final Predicate DEFAULT_FILTER = (groupTask -> true);
+
+ // the package name to filter recent tasks by
+ @Nullable
+ private String mPackageNameToFilter = null;
+
+ // the callback that gets executed upon filter change
+ @Nullable
+ private Runnable mOnFilterUpdatedListener = null;
+
+ // map maintaining the count for each unique base activity package name currently in the recents
+ @Nullable
+ private Map mInstanceCountMap;
+
+ /**
+ * Returns {@code true} if {@code RecentsView} filters tasks by some package name.
+ */
+ public boolean isFiltered() {
+ return mPackageNameToFilter != null;
+ }
+
+ /**
+ * Returns the package name that tasks are filtered by.
+ */
+ @Nullable
+ public String getPackageNameToFilter() {
+ return mPackageNameToFilter;
+ }
+
+
+ /**
+ * Sets a listener on any changes to the filter.
+ *
+ * @param callback listener to be executed upon filter updates
+ */
+ public void setOnFilterUpdatedListener(@Nullable Runnable callback) {
+ mOnFilterUpdatedListener = callback;
+ }
+
+ /**
+ * Updates the filter such that tasks are filtered by a certain package name.
+ *
+ * @param packageName package name of the base activity to filter tasks by;
+ * if null, filter is turned off
+ */
+ public void setFilterBy(@Nullable String packageName) {
+ if (Objects.equals(packageName, mPackageNameToFilter)) {
+ return;
+ }
+
+ mPackageNameToFilter = packageName;
+
+ if (mOnFilterUpdatedListener != null) {
+ mOnFilterUpdatedListener.run();
+ }
+ }
+
+ /**
+ * Updates the map of package names to their count in the most recent list of tasks.
+ *
+ * @param groupTaskList the list of tasks that map update is be based on
+ */
+ public void updateInstanceCountMap(List groupTaskList) {
+ mInstanceCountMap = getInstanceCountMap(groupTaskList);
+ }
+
+ /**
+ * Returns the map of package names to their count in the most recent list of tasks.
+ */
+ @Nullable
+ public Map getInstanceCountMap() {
+ return mInstanceCountMap;
+ }
+
+ /**
+ * Returns a predicate for filtering out GroupTasks by package name.
+ *
+ * @param packageName package name to filter GroupTasks by
+ * if null, Predicate always returns true.
+ */
+ public static Predicate getFilter(@Nullable String packageName) {
+ if (packageName == null) {
+ return DEFAULT_FILTER;
+ }
+
+ return (groupTask) -> (groupTask.task2 != null
+ && groupTask.task2.key.getPackageName().equals(packageName))
+ || groupTask.task1.key.getPackageName().equals(packageName);
+ }
+
+ /**
+ * Returns a map of package names to their frequencies in a list of GroupTasks.
+ *
+ * @param groupTasks the list to go through to create the map
+ */
+ public static Map getInstanceCountMap(List groupTasks) {
+ Map instanceCountMap = new HashMap<>();
+
+ for (GroupTask groupTask : groupTasks) {
+ final String firstTaskPkgName = groupTask.task1.key.getPackageName();
+ final String secondTaskPkgName =
+ groupTask.task2 == null ? null : groupTask.task2.key.getPackageName();
+
+ // increment the instance count for the first task's base activity package name
+ incrementOrAddIfNotExists(instanceCountMap, firstTaskPkgName);
+
+ // check if second task is non existent
+ if (secondTaskPkgName != null) {
+ // increment the instance count for the second task's base activity package name
+ incrementOrAddIfNotExists(instanceCountMap, secondTaskPkgName);
+ }
+ }
+
+ return instanceCountMap;
+ }
+
+ /**
+ * Returns true if tasks of provided package name should show filter UI.
+ *
+ * @param taskPackageName package name of the task in question
+ */
+ public boolean shouldShowFilterUI(String taskPackageName) {
+ // number of occurrences in recents overview with the package name of this task
+ int instanceCount = getInstanceCountMap().get(taskPackageName);
+
+ // if the number of occurrences isn't enough make sure tasks can't be filtered by
+ // the package name of this task
+ return !(isFiltered() || instanceCount < MIN_FILTERING_TASK_COUNT);
+ }
+
+ private static void incrementOrAddIfNotExists(Map map, String pkgName) {
+ if (!map.containsKey(pkgName)) {
+ map.put(pkgName, 0);
+ }
+ map.put(pkgName, map.get(pkgName) + 1);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 3d6da8e726..913f08f41d 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -50,6 +50,7 @@ import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
* Singleton class to load and manage recents model.
@@ -104,7 +105,22 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener
* @return the request id associated with this call.
*/
public int getTasks(Consumer> callback) {
- return mTaskList.getTasks(false /* loadKeysOnly */, callback);
+ return mTaskList.getTasks(false /* loadKeysOnly */, callback,
+ RecentsFilterState.DEFAULT_FILTER);
+ }
+
+
+ /**
+ * Fetches the list of recent tasks, based on a filter
+ *
+ * @param callback The callback to receive the task plan once its complete or null. This is
+ * always called on the UI thread.
+ * @param filter Returns true if a GroupTask should be included into the list passed into
+ * callback.
+ * @return the request id associated with this call.
+ */
+ public int getTasks(Consumer> callback, Predicate filter) {
+ return mTaskList.getTasks(false /* loadKeysOnly */, callback, filter);
}
/**
@@ -126,8 +142,9 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener
* Checks if a task has been removed or not.
*
* @param callback Receives true if task is removed, false otherwise
+ * @param filter Returns true if GroupTask should be in the list of considerations
*/
- public void isTaskRemoved(int taskId, Consumer callback) {
+ public void isTaskRemoved(int taskId, Consumer callback, Predicate filter) {
mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> {
for (GroupTask group : taskGroups) {
if (group.containsTask(taskId)) {
@@ -136,7 +153,7 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener
}
}
callback.accept(true);
- });
+ }, filter);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 2cada0afcc..3f7d677970 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -2,7 +2,6 @@ package com.android.quickstep.views;
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import android.content.Context;
import android.graphics.PointF;
@@ -102,6 +101,22 @@ public class GroupedTaskView extends TaskView {
PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT);
}
+ /**
+ * Sets up an on-click listener and the visibility for show_windows icon on top of each task.
+ */
+ @Override
+ public void setUpShowAllInstancesListener() {
+ // sets up the listener for the left/top task
+ super.setUpShowAllInstancesListener();
+
+ // right/bottom task's base package name
+ String taskPackageName = mTaskIdAttributeContainer[1].getTask().key.getPackageName();
+
+ // icon of the right/bottom task
+ View showWindowsView = findViewById(R.id.show_windows_right);
+ updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName));
+ }
+
@Override
public void onTaskListVisibilityChanged(boolean visible, int changes) {
super.onTaskListVisibilityChanged(visible, changes);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 63aea452d0..68628746a5 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -165,6 +165,7 @@ import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.RecentsAnimationController;
import com.android.quickstep.RecentsAnimationTargets;
+import com.android.quickstep.RecentsFilterState;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.RemoteTargetGluer;
@@ -581,7 +582,7 @@ public abstract class RecentsView {
+ this.setAndApplyFilter(null);
+ });
+ } else {
+ mClearAllButton.setText(R.string.recents_clear_all);
+ mClearAllButton.setOnClickListener(this::dismissAllTasks);
+ }
+ }
+
+ /**
+ * Invalidates the list of tasks so that an update occurs to the list of tasks if requested.
+ */
+ private void invalidateTaskList() {
+ mTaskListChangeId = -1;
}
public OverScroller getScroller() {
@@ -1547,6 +1600,9 @@ public abstract class RecentsView= 0; i--) {
@@ -1581,6 +1637,7 @@ public abstract class RecentsView {
+ // update and apply a new filter
+ getRecentsView().setAndApplyFilter(taskPackageName);
+ };
+
+ if (!getRecentsView().getFilterState().shouldShowFilterUI(taskPackageName)) {
+ cb = null;
+ }
+ return cb;
+ }
+
+ /**
+ * Sets the correct visibility and callback on the provided filterView based on whether
+ * the callback is null or not
+ */
+ protected void updateFilterCallback(@NonNull View filterView,
+ @Nullable View.OnClickListener callback) {
+ if (callback == null) {
+ filterView.setVisibility(GONE);
+ } else {
+ filterView.setVisibility(VISIBLE);
+ }
+
+ filterView.setOnClickListener(callback);
+ }
+
public TaskIdAttributeContainer[] getTaskIdAttributeContainers() {
return mTaskIdAttributeContainer;
}
diff --git a/res/drawable/ic_select_windows.xml b/res/drawable/ic_select_windows.xml
new file mode 100644
index 0000000000..cba0fde614
--- /dev/null
+++ b/res/drawable/ic_select_windows.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index f6f55f1ec3..2a70c46d00 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -380,6 +380,10 @@ public final class FeatureFlags {
"ENABLE_TASKBAR_EDU_TOOLTIP", false,
"Enable the tooltip version of the Taskbar education flow.");
+ public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(
+ "ENABLE_MULTI_INSTANCE", false,
+ "Enables creation and filtering of multiple task instances in overview");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {