From 4205013b0a5412875042ae01845a5f562d197e7e Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Tue, 7 May 2024 17:03:35 +0100 Subject: [PATCH] Recycle thumbnailViews in DesktopTaskView - Changed mTaskContainers to a List - Added an XML for TaskThumbnailView to make it compatible with ViewPool - Removed mSnapshotViews variable, and streamlined DesktopTaskView binding logic to re-use thumbnailvIew from mTaskContainers or ViewPool Bug: 249371338 Fix: 251586230 Test: manual Flag: ACONFIG com.android.window.flags.enable_desktop_windowing_mode DEVELOPMENT Change-Id: Iab7575e9c36b8ebf3eb62f19f13d75ba671f9f4f --- quickstep/res/layout/task.xml | 5 +- quickstep/res/layout/task_desktop.xml | 5 +- quickstep/res/layout/task_grouped.xml | 11 +--- quickstep/res/layout/task_thumbnail.xml | 20 ++++++ .../quickstep/TaskShortcutFactory.java | 3 +- .../quickstep/util/AppPairsController.java | 6 +- .../quickstep/views/DesktopTaskView.java | 66 +++++++++---------- .../quickstep/views/GroupedTaskView.java | 21 +++--- .../android/quickstep/views/RecentsView.java | 8 +-- .../views/TaskThumbnailViewDeprecated.java | 8 ++- .../com/android/quickstep/views/TaskView.java | 39 ++++++----- .../util/SplitAnimationControllerTest.kt | 4 +- 12 files changed, 102 insertions(+), 94 deletions(-) create mode 100644 quickstep/res/layout/task_thumbnail.xml diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml index 9f648a7e69..cc3b30e9dc 100644 --- a/quickstep/res/layout/task.xml +++ b/quickstep/res/layout/task.xml @@ -28,10 +28,7 @@ launcher:focusBorderColor="?androidprv:attr/materialColorOutline" launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary"> - + diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml index 36d7f86f25..89e9b3d9fd 100644 --- a/quickstep/res/layout/task_desktop.xml +++ b/quickstep/res/layout/task_desktop.xml @@ -42,10 +42,7 @@ views that do not inherint from TaskView only or create a generic TaskView that have N number of tasks. --> - - + - + diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml new file mode 100644 index 0000000000..f1a3d62021 --- /dev/null +++ b/quickstep/res/layout/task_thumbnail.xml @@ -0,0 +1,20 @@ + + \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index cd9df264c1..f052a9d580 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -65,7 +65,6 @@ import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFutur import com.android.systemui.shared.recents.view.RecentsTransition; import com.android.systemui.shared.system.ActivityManagerWrapper; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Function; @@ -336,7 +335,7 @@ public interface TaskShortcutFactory { recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView)); boolean shouldShowActionsButtonInstead = isLargeTileFocusedTask && isInExpectedScrollPosition; - boolean hasUnpinnableApp = Arrays.stream(taskView.getTaskContainers()) + boolean hasUnpinnableApp = taskView.getTaskContainers().stream() .anyMatch(att -> att != null && att.getItemInfo() != null && ((att.getItemInfo().runtimeStatusFlags & ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0)); diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java index 5c5ee7ed57..770a4525cf 100644 --- a/quickstep/src/com/android/quickstep/util/AppPairsController.java +++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java @@ -116,9 +116,9 @@ public class AppPairsController { */ public void saveAppPair(GroupedTaskView gtv) { InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR); - TaskView.TaskContainer[] containers = gtv.getTaskContainers(); - WorkspaceItemInfo recentsInfo1 = containers[0].getItemInfo(); - WorkspaceItemInfo recentsInfo2 = containers[1].getItemInfo(); + List containers = gtv.getTaskContainers(); + WorkspaceItemInfo recentsInfo1 = containers.get(0).getItemInfo(); + WorkspaceItemInfo recentsInfo2 = containers.get(1).getItemInfo(); WorkspaceItemInfo app1 = lookupLaunchableItem(recentsInfo1.getComponentKey()); WorkspaceItemInfo app2 = lookupLaunchableItem(recentsInfo2.getComponentKey()); diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java index 5968901787..57bc2d78ba 100644 --- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java +++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java @@ -16,7 +16,7 @@ package com.android.quickstep.views; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; @@ -42,6 +42,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.desktop.DesktopRecentsTransitionController; import com.android.launcher3.util.CancellableTask; import com.android.launcher3.util.RunnableList; +import com.android.launcher3.util.ViewPool; import com.android.quickstep.BaseContainerInterface; import com.android.quickstep.RecentsModel; import com.android.quickstep.TaskThumbnailCache; @@ -69,8 +70,6 @@ public class DesktopTaskView extends TaskView { private static final boolean DEBUG = false; - private final ArrayList mSnapshotViews = new ArrayList<>(); - private final ArrayList> mPendingThumbnailRequests = new ArrayList<>(); private final TaskView.FullscreenDrawParams mSnapshotDrawParams; @@ -81,6 +80,8 @@ public class DesktopTaskView extends TaskView { private final PointF mTempPointF = new PointF(); + private final ViewPool mTaskthumbnailViewPool; + public DesktopTaskView(Context context) { this(context, null); } @@ -103,6 +104,10 @@ public class DesktopTaskView extends TaskView { return QuickStepContract.getWindowCornerRadius(context); } }; + // As DesktopTaskView is inflated in background, use initialSize=0 to avoid initPool. + mTaskthumbnailViewPool = new ViewPool<>(context, this, R.layout.task_thumbnail, + /* maxSize= */10, /* initialSize= */ 0); + mTaskContainers = new ArrayList<>(); } @Override @@ -163,45 +168,36 @@ public class DesktopTaskView extends TaskView { } cancelPendingLoadTasks(); - mTaskContainers = new TaskContainer[tasks.size()]; - - // Ensure there are equal number of snapshot views and tasks. - // More tasks than views, add views. More views than tasks, remove views. - // TODO(b/251586230): use a ViewPool for creating TaskThumbnailViews - if (mSnapshotViews.size() > tasks.size()) { - int diff = mSnapshotViews.size() - tasks.size(); - for (int i = 0; i < diff; i++) { - TaskThumbnailViewDeprecated snapshotView = mSnapshotViews.remove(0); - removeView(snapshotView); - } - } else if (mSnapshotViews.size() < tasks.size()) { - int diff = tasks.size() - mSnapshotViews.size(); - for (int i = 0; i < diff; i++) { - TaskThumbnailViewDeprecated snapshotView = - new TaskThumbnailViewDeprecated(getContext()); - mSnapshotViews.add(snapshotView); - // Add snapshots from to position after the initial child views. - addView(snapshotView, mChildCountAtInflation, - new LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - } - } - + ((ArrayList) mTaskContainers).ensureCapacity(tasks.size()); for (int i = 0; i < tasks.size(); i++) { Task task = tasks.get(i); - TaskThumbnailViewDeprecated thumbnailView = mSnapshotViews.get(i); + TaskThumbnailViewDeprecated thumbnailView; + if (i >= mTaskContainers.size()) { + thumbnailView = mTaskthumbnailViewPool.getView(); + // Add thumbnailView from to position after the initial child views. + addView(thumbnailView, mChildCountAtInflation, + new LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + } else { + thumbnailView = mTaskContainers.get(i).getThumbnailView(); + } thumbnailView.bind(task); - mTaskContainers[i] = createAttributeContainer(task, thumbnailView); + TaskContainer taskContainer = new TaskContainer(task, thumbnailView, mIconView, + STAGE_POSITION_UNDEFINED, /*digitalWellBeingToast=*/ null); + if (i >= mTaskContainers.size()) { + mTaskContainers.add(taskContainer); + } else { + mTaskContainers.set(i, taskContainer); + } + } + while (mTaskContainers.size() > tasks.size()) { + TaskContainer taskContainer = mTaskContainers.remove(mTaskContainers.size() - 1); + removeView(taskContainer.getThumbnailView()); + mTaskthumbnailViewPool.recycle(taskContainer.getThumbnailView()); } setOrientationState(orientedState); } - private TaskContainer createAttributeContainer(Task task, - TaskThumbnailViewDeprecated thumbnailView) { - return new TaskContainer(task, thumbnailView, mIconView, - STAGE_POSITION_UNDEFINED, /*digitalWellBeingToast=*/ null); - } - @Override public void onTaskListVisibilityChanged(boolean visible, int changes) { cancelPendingLoadTasks(); @@ -314,7 +310,7 @@ public class DesktopTaskView extends TaskView { int thumbnailTopMarginPx = mContainer.getDeviceProfile().overviewTaskThumbnailTopMarginPx; containerHeight -= thumbnailTopMarginPx; - if (mTaskContainers.length == 0) { + if (mTaskContainers.isEmpty()) { return; } diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java index c26fc0c342..1b4d22fc2e 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -98,7 +98,7 @@ public class GroupedTaskView extends TaskView { @Deprecated @Nullable private Task getSecondTask() { - return mTaskContainers.length > 1 ? mTaskContainers[1].getTask() : null; + return mTaskContainers.size() > 1 ? mTaskContainers.get(1).getTask() : null; } @Override @@ -150,11 +150,11 @@ public class GroupedTaskView extends TaskView { public void bind(Task primary, Task secondary, RecentsOrientedState orientedState, @Nullable SplitBounds splitBoundsConfig) { super.bind(primary, orientedState); - mTaskContainers = new TaskContainer[]{ - mTaskContainers[0], + mTaskContainers = Arrays.asList( + mTaskContainers.get(0), new TaskContainer(secondary, findViewById(R.id.bottomright_snapshot), - mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT, mDigitalWellBeingToast2)}; - mTaskContainers[0].setStagePosition( + mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT, mDigitalWellBeingToast2)); + mTaskContainers.get(0).setStagePosition( SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT); mSnapshotView2.bind(secondary); mSplitBoundsConfig = splitBoundsConfig; @@ -176,12 +176,12 @@ public class GroupedTaskView extends TaskView { public void setUpShowAllInstancesListener() { // sets up the listener for the left/top task super.setUpShowAllInstancesListener(); - if (mTaskContainers.length < 2) { + if (mTaskContainers.size() < 2) { return; } // right/bottom task's base package name - String taskPackageName = mTaskContainers[1].getTask().key.getPackageName(); + String taskPackageName = mTaskContainers.get(1).getTask().key.getPackageName(); // icon of the right/bottom task View showWindowsView = findViewById(R.id.show_windows_right); @@ -279,7 +279,7 @@ public class GroupedTaskView extends TaskView { @Nullable @Override public RunnableList launchTaskAnimated() { - if (mTaskContainers.length == 0) { + if (mTaskContainers.isEmpty()) { return null; } @@ -407,9 +407,8 @@ public class GroupedTaskView extends TaskView { } else { // Currently being split with this taskView, let the non-split selected thumbnail // take up full thumbnail area - Optional nonSplitContainer = Arrays.stream( - mTaskContainers).filter( - container -> container.getTask().key.id != initSplitTaskId).findAny(); + Optional nonSplitContainer = mTaskContainers.stream().filter( + container -> container.getTask().key.id != initSplitTaskId).findAny(); nonSplitContainer.ifPresent( taskIdAttributeContainer -> taskIdAttributeContainer.getThumbnailView().measure( widthMeasureSpec, MeasureSpec.makeMeasureSpec( diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 075f159d10..eceeaecbd6 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -2354,8 +2354,8 @@ public abstract class RecentsView containers = taskView.getTaskContainers(); + if (containers.isEmpty()) { continue; } int index = indexOfChild(taskView); @@ -2367,7 +2367,7 @@ public abstract class RecentsView tasksToUpdate = Arrays.stream(containers).filter(Objects::nonNull) + List tasksToUpdate = containers.stream() .map(TaskContainer::getTask) .collect(Collectors.toCollection(ArrayList::new)); if (mTmpRunningTasks != null) { @@ -4801,7 +4801,7 @@ public abstract class RecentsView TEMP_PARAMS = new MainThreadInitializedObject<>(FullscreenDrawParams::new); @@ -606,4 +607,9 @@ public class TaskThumbnailViewDeprecated extends View { } return mThumbnailData.isRealSnapshot && !mTask.isLocked; } + + @Override + public void onRecycle() { + // Do nothing + } } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 4046a89f5f..26f313a4d9 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -368,7 +368,7 @@ public class TaskView extends FrameLayout implements Reusable { private float mStableAlpha = 1; private int mTaskViewId = -1; - protected TaskContainer[] mTaskContainers = new TaskContainer[0]; + protected List mTaskContainers = Collections.emptyList(); private boolean mShowScreenshot; private boolean mBorderEnabled; @@ -496,7 +496,7 @@ public class TaskView extends FrameLayout implements Reusable { public void notifyIsRunningTaskUpdated() { // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView - if (mTaskContainers.length > 0) { + if (!mTaskContainers.isEmpty()) { bindTaskThumbnailView(); } } @@ -685,9 +685,9 @@ public class TaskView extends FrameLayout implements Reusable { */ public void bind(Task task, RecentsOrientedState orientedState) { cancelPendingLoadTasks(); - mTaskContainers = new TaskContainer[]{ + mTaskContainers = Collections.singletonList( new TaskContainer(task, mTaskThumbnailViewDeprecated, mIconView, - STAGE_POSITION_UNDEFINED, mDigitalWellBeingToast)}; + STAGE_POSITION_UNDEFINED, mDigitalWellBeingToast)); if (enableRefactorTaskThumbnail()) { bindTaskThumbnailView(); } else { @@ -706,10 +706,10 @@ public class TaskView extends FrameLayout implements Reusable { * Sets up an on-click listener and the visibility for show_windows icon on top of the task. */ public void setUpShowAllInstancesListener() { - if (mTaskContainers.length == 0) { + if (mTaskContainers.isEmpty()) { return; } - String taskPackageName = mTaskContainers[0].getTask().key.getPackageName(); + String taskPackageName = mTaskContainers.get(0).getTask().key.getPackageName(); // icon of the top/left task View showWindowsView = findViewById(R.id.show_windows); @@ -752,9 +752,9 @@ public class TaskView extends FrameLayout implements Reusable { } /** - * Returns an array of all TaskContainers in the TaskView. + * Returns a list of all TaskContainers in the TaskView. */ - public TaskContainer[] getTaskContainers() { + public List getTaskContainers() { return mTaskContainers; } @@ -766,7 +766,7 @@ public class TaskView extends FrameLayout implements Reusable { @Deprecated @Nullable public Task getFirstTask() { - return mTaskContainers.length > 0 ? mTaskContainers[0].getTask() : null; + return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getTask() : null; } /** @@ -780,12 +780,12 @@ public class TaskView extends FrameLayout implements Reusable { * Returns a copy of integer array containing taskIds of all tasks in the TaskView. */ public int[] getTaskIds() { - return Arrays.stream(mTaskContainers).mapToInt( + return mTaskContainers.stream().mapToInt( container -> container.getTask().key.id).toArray(); } public boolean containsMultipleTasks() { - return mTaskContainers.length > 1; + return mTaskContainers.size() > 1; } /** @@ -794,7 +794,7 @@ public class TaskView extends FrameLayout implements Reusable { */ @Nullable public TaskContainer getTaskContainerById(int taskId) { - return Arrays.stream(mTaskContainers).filter( + return mTaskContainers.stream().filter( container -> container.getTask().key.id == taskId).findFirst().orElse(null); } @@ -805,7 +805,7 @@ public class TaskView extends FrameLayout implements Reusable { */ @Deprecated public TaskThumbnailViewDeprecated getFirstThumbnailView() { - return mTaskContainers.length > 0 ? mTaskContainers[0].getThumbnailView() + return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getThumbnailView() : mTaskThumbnailViewDeprecated; } @@ -826,7 +826,7 @@ public class TaskView extends FrameLayout implements Reusable { } public TaskThumbnailViewDeprecated[] getThumbnailViews() { - return Arrays.stream(mTaskContainers).map( + return mTaskContainers.stream().map( TaskContainer::getThumbnailView).toArray( TaskThumbnailViewDeprecated[]::new); } @@ -839,7 +839,7 @@ public class TaskView extends FrameLayout implements Reusable { @Deprecated @Nullable public TaskViewIcon getFirstIconView() { - return mTaskContainers.length > 0 ? mTaskContainers[0].getIconView() : null; + return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getIconView() : null; } @Override @@ -892,10 +892,10 @@ public class TaskView extends FrameLayout implements Reusable { */ protected boolean confirmSecondSplitSelectApp() { int index = getLastSelectedChildTaskIndex(); - if (index >= mTaskContainers.length) { + if (index >= mTaskContainers.size()) { return false; } - TaskContainer container = mTaskContainers[index]; + TaskContainer container = mTaskContainers.get(index); if (container != null) { return getRecentsView().confirmSplitSelect(this, container.getTask(), container.getIconView().getDrawable(), container.getThumbnailView(), @@ -1217,9 +1217,8 @@ public class TaskView extends FrameLayout implements Reusable { } protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) { - Optional menuContainer = Arrays.stream( - mTaskContainers).filter( - container -> container.getIconView() == iconView).findAny(); + Optional menuContainer = mTaskContainers.stream().filter( + container -> container.getIconView() == iconView).findAny(); if (menuContainer.isEmpty()) { return false; } diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt index eb20de2810..f29df613cd 100644 --- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt +++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt @@ -91,7 +91,7 @@ class SplitAnimationControllerTest { whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap) whenever(mockTaskContainer.iconView).thenReturn(mockIconView) whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable) - whenever(mockTaskView.taskContainers).thenReturn(Array(1) { mockTaskContainer }) + whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer }) whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable) whenever(splitSelectSource.view).thenReturn(mockSplitSourceView) @@ -184,7 +184,7 @@ class SplitAnimationControllerTest { whenever(mockTask.getKey()).thenReturn(mockTaskKey) whenever(mockTaskKey.getId()).thenReturn(taskId) whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId) - whenever(mockGroupedTaskView.taskContainers).thenReturn(Array(1) { mockTaskContainer }) + whenever(mockGroupedTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer }) val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps = splitAnimationController.getFirstAnimInitViews( { mockGroupedTaskView },