Filter recents view instances by package name
Filter instances of GroupTasks based on package name
as a part of support for multi-instance
Add a feature flag to toggle multi-instance features
See the video below for how to use the demo.
Note: some extra UI elements were added since video
was recorded, but the filtering process is the same.
http://recall/-/da585DRwKRZK3S2xxcQrSm/gW9HZnbCvGyH1DQiVizOW2
See go/multi-instance for more info about the feature
Bug: 253520408
Test: manually tested the instance filtering
Change-Id: I19c947ca353699096388b9fbbdca6d75cb0041a7
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 7e5b85c..02a439d 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -28,6 +28,17 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
+ <ImageView
+ android:id="@+id/show_windows"
+ android:layout_height="@dimen/recents_filter_icon_size"
+ android:layout_width="@dimen/recents_filter_icon_size"
+ android:layout_gravity="end"
+ android:visibility="gone"
+ android:tint="@color/recents_filter_icon"
+ android:contentDescription="@string/recents_filter_icon_desc"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_select_windows" />
+
<com.android.quickstep.views.IconView
android:id="@+id/icon"
android:layout_width="@dimen/task_thumbnail_icon_size"
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index cd5bcbd..7792801 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -38,6 +38,28 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
+ <ImageView
+ android:id="@+id/show_windows"
+ android:layout_height="@dimen/recents_filter_icon_size"
+ android:layout_width="@dimen/recents_filter_icon_size"
+ android:layout_gravity="start"
+ android:visibility="gone"
+ android:tint="@color/recents_filter_icon"
+ android:contentDescription="@string/recents_filter_icon_desc"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_select_windows" />
+
+ <ImageView
+ android:id="@+id/show_windows_right"
+ android:layout_height="@dimen/recents_filter_icon_size"
+ android:layout_width="@dimen/recents_filter_icon_size"
+ android:layout_gravity="end"
+ android:visibility="gone"
+ android:tint="@color/recents_filter_icon"
+ android:contentDescription="@string/recents_filter_icon_desc"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_select_windows" />
+
<com.android.quickstep.views.IconView
android:id="@+id/icon"
android:layout_width="@dimen/task_thumbnail_icon_size"
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 185c815..f63997b 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -76,4 +76,7 @@
<color name="all_set_page_background">#FFFFFFFF</color>
+ <!-- Recents overview -->
+ <color name="recents_filter_icon">#333333</color>
+
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index ad77768..403d6bd 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -306,6 +306,9 @@
<dimen name="taskbar_button_margin_6_5">75dp</dimen>
<dimen name="taskbar_button_margin_default">48dp</dimen>
+ <!-- Recents overview -->
+ <dimen name="recents_filter_icon_size">30dp</dimen>
+
<!-- Launcher splash screen -->
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
<!-- starting_surface_exit_animation_window_shift_length -->
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 801ba26..e691522 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -36,6 +36,13 @@
<!-- Recents: Title of a button that clears the task list, i.e. closes all tasks. [CHAR LIMIT=30] -->
<string name="recents_clear_all">Clear all</string>
+ <!-- Recents: Title of a button that goes back from displaying tasks filtered by package name to displaying all tasks [CHAR LIMIT=30] -->
+ <string name="recents_back" translatable="false">Back</string>
+
+ <!-- TODO: b/260610444. Content description of filtering icons needs to be updated -->
+ <!-- Recents: Content description for the icon on top of taskviews to initiate filtering -->
+ <string name="recents_filter_icon_desc" translatable="false">Click to show only this app\'s tasks</string>
+
<!-- Accessibility title for the list of recent apps [CHAR_LIMIT=none] -->
<string name="accessibility_recent_apps">Recent apps</string>
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index d46565b..b33ceca 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -45,6 +45,8 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* Manages the recent task list from the system, caching it as necessary.
@@ -129,14 +131,18 @@
* @return The change id of the current task list
*/
public synchronized int getTasks(boolean loadKeysOnly,
- Consumer<ArrayList<GroupTask>> callback) {
+ Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) {
final int requestLoadId = mChangeId;
if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
// The list is up to date, send the callback on the next frame,
// so that requestID can be returned first.
if (callback != null) {
// Copy synchronously as the changeId might change by next frame
- ArrayList<GroupTask> result = copyOf(mResultsUi);
+ // and filter GroupTasks
+ ArrayList<GroupTask> result = mResultsUi.stream().filter(filter)
+ .map(GroupTask::copy)
+ .collect(Collectors.toCollection(ArrayList<GroupTask>::new));
+
mMainThreadExecutor.post(() -> {
callback.accept(result);
});
@@ -156,7 +162,11 @@
mLoadingTasksInBackground = false;
mResultsUi = loadResult;
if (callback != null) {
- ArrayList<GroupTask> result = copyOf(mResultsUi);
+ // filter the tasks if needed before passing them into the callback
+ ArrayList<GroupTask> result = mResultsUi.stream().filter(filter)
+ .map(GroupTask::copy)
+ .collect(Collectors.toCollection(ArrayList<GroupTask>::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 0000000..ff6951d
--- /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}.
+ *
+ * <p> 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<GroupTask> 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<String, Integer> 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<GroupTask> groupTaskList) {
+ mInstanceCountMap = getInstanceCountMap(groupTaskList);
+ }
+
+ /**
+ * Returns the map of package names to their count in the most recent list of tasks.
+ */
+ @Nullable
+ public Map<String, Integer> 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<GroupTask> 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<String, Integer> getInstanceCountMap(List<GroupTask> groupTasks) {
+ Map<String, Integer> 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<String, Integer> 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 3d6da8e..913f08f 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -50,6 +50,7 @@
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 @@
* @return the request id associated with this call.
*/
public int getTasks(Consumer<ArrayList<GroupTask>> 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<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) {
+ return mTaskList.getTasks(false /* loadKeysOnly */, callback, filter);
}
/**
@@ -126,8 +142,9 @@
* 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<Boolean> callback) {
+ public void isTaskRemoved(int taskId, Consumer<Boolean> callback, Predicate<GroupTask> filter) {
mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> {
for (GroupTask group : taskGroups) {
if (group.containsTask(taskId)) {
@@ -136,7 +153,7 @@
}
}
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 2cada0a..3f7d677 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -2,7 +2,6 @@
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 @@
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 3a2841e..971eda5 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -164,6 +164,7 @@
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;
@@ -580,7 +581,7 @@
if (taskRemoved) {
dismissTask(taskId);
}
- });
+ }, RecentsFilterState.getFilter(mFilterState.getPackageNameToFilter()));
}
}));
}
@@ -721,6 +722,9 @@
@Nullable
private TaskLaunchListener mTaskLaunchListener;
+ // keeps track of the state of the filter for tasks in recents view
+ private final RecentsFilterState mFilterState = new RecentsFilterState();
+
public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
BaseActivityInterface sizeStrategy) {
super(context, attrs, defStyleAttr);
@@ -784,6 +788,55 @@
mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
mTintingColor = getForegroundScrimDimColor(context);
+
+ // if multi-instance feature is enabled
+ if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) {
+ // invalidate the current list of tasks if filter changes
+ mFilterState.setOnFilterUpdatedListener(this::invalidateTaskList);
+ }
+ // make sure filter is turned off by default
+ mFilterState.setFilterBy(null);
+ }
+
+ /** Get the state of the filter */
+ public RecentsFilterState getFilterState() {
+ return mFilterState;
+ }
+
+ /**
+ * Toggles the filter and reloads the recents view if needed.
+ *
+ * @param packageName package name to filter by if the filter is being turned on;
+ * should be null if filter is being turned off
+ */
+ public void setAndApplyFilter(@Nullable String packageName) {
+ mFilterState.setFilterBy(packageName);
+ updateClearAllFunction();
+ reloadIfNeeded();
+ }
+
+ /**
+ * Updates the "Clear All" button and its function depending on the recents view state.
+ *
+ * TODO: add a different button for going back to overview. Present solution is for demo only.
+ */
+ public void updateClearAllFunction() {
+ if (mFilterState.isFiltered()) {
+ mClearAllButton.setText(R.string.recents_back);
+ mClearAllButton.setOnClickListener((view) -> {
+ 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() {
@@ -1546,6 +1599,9 @@
Task stagedTaskToBeRemovedFromGrid =
mSplitSelectSource != null ? mSplitSelectSource.alreadyRunningTask : null;
+ // update the map of instance counts
+ mFilterState.updateInstanceCountMap(taskGroups);
+
// Add views as children based on whether it's grouped or single task. Looping through
// taskGroups backwards populates the thumbnail grid from least recent to most recent.
for (int i = taskGroups.size() - 1; i >= 0; i--) {
@@ -1580,6 +1636,7 @@
groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
+
((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
groupTask.mSplitBounds);
} else if (taskView instanceof DesktopTaskView) {
@@ -1588,6 +1645,11 @@
} else {
taskView.bind(groupTask.task1, mOrientationState);
}
+
+ // enables instance filtering if the feature flag for it is on
+ if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) {
+ taskView.setUpShowAllInstancesListener();
+ }
}
if (!taskGroups.isEmpty()) {
@@ -2257,7 +2319,8 @@
*/
public void reloadIfNeeded() {
if (!mModel.isTaskListValid(mTaskListChangeId)) {
- mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
+ mTaskListChangeId = mModel.getTasks(this::applyLoadPlan, RecentsFilterState
+ .getFilter(mFilterState.getPackageNameToFilter()));
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index bda30a5..1d9e05f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -526,6 +526,50 @@
setOrientationState(orientedState);
}
+ /**
+ * Sets up an on-click listener and the visibility for show_windows icon on top of the task.
+ */
+ public void setUpShowAllInstancesListener() {
+ String taskPackageName = mTaskIdAttributeContainer[0].mTask.key.getPackageName();
+
+ // icon of the top/left task
+ View showWindowsView = findViewById(R.id.show_windows);
+ updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName));
+ }
+
+ /**
+ * Returns a callback that updates the state of the filter and the recents overview
+ *
+ * @param taskPackageName package name of the task to filter by
+ */
+ @Nullable
+ protected View.OnClickListener getFilterUpdateCallback(String taskPackageName) {
+ View.OnClickListener cb = (view) -> {
+ // 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 0000000..cba0fde
--- /dev/null
+++ b/res/drawable/ic_select_windows.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M7,44Q5.8,44 4.9,43.1Q4,42.2 4,41V21.65Q4,20.45 4.9,19.55Q5.8,18.65 7,18.65H12.75V7Q12.75,5.8 13.65,4.9Q14.55,4 15.75,4H41Q42.2,4 43.1,4.9Q44,5.8 44,7V26.35Q44,27.55 43.1,28.45Q42.2,29.35 41,29.35H35.3V41Q35.3,42.2 34.4,43.1Q33.5,44 32.3,44ZM7,41H32.3Q32.3,41 32.3,41Q32.3,41 32.3,41V24.65H7V41Q7,41 7,41Q7,41 7,41ZM35.3,26.35H41Q41,26.35 41,26.35Q41,26.35 41,26.35V10H15.75V18.65H31.6Q33.2,18.65 34.25,19.7Q35.3,20.75 35.3,22.35Z"/>
+</vector>
\ 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 b46e43f..082f6a1 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -377,6 +377,10 @@
"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) {