Merge "Filter recents view instances by package name" into tm-qpr-dev am: 231e061ae1 am: de88fbb252

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/20342914

Change-Id: I6e847c620e20be97194184ee9cfe94c07752c322
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Ikram Gabiyev
2022-12-29 02:29:08 +00:00
committed by Automerger Merge Worker
13 changed files with 410 additions and 9 deletions
@@ -45,6 +45,8 @@ import java.io.PrintWriter;
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 @@ public class RecentTasksList {
* @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 @@ public class RecentTasksList {
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);
}
});
@@ -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);
}
}
@@ -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<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 @@ 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<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 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener
}
}
callback.accept(true);
});
}, filter);
}
@Override
@@ -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);
@@ -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<ACTIVITY_TYPE extends StatefulActivity<STATE_T
if (taskRemoved) {
dismissTask(taskId);
}
});
}, RecentsFilterState.getFilter(mFilterState.getPackageNameToFilter()));
}
}));
}
@@ -722,6 +723,9 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
@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);
@@ -785,6 +789,55 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
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() {
@@ -1547,6 +1600,9 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
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--) {
@@ -1581,6 +1637,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
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) {
@@ -1589,6 +1646,11 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
} 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()) {
@@ -2258,7 +2320,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
*/
public void reloadIfNeeded() {
if (!mModel.isTaskListValid(mTaskListChangeId)) {
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan, RecentsFilterState
.getFilter(mFilterState.getPackageNameToFilter()));
}
}
@@ -526,6 +526,50 @@ public class TaskView extends FrameLayout implements Reusable {
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;
}