diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index 529247083e..573bc1df38 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -70,7 +70,6 @@ import com.android.quickstep.views.TaskViewType; import com.android.systemui.shared.recents.model.Task; import com.android.wm.shell.shared.bubbles.BubbleBarLocation; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -135,6 +134,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar private final int mNumStaticViews; private Set mPrevRecentTasks = Collections.emptySet(); + private Set mPrevOverflowTasks = Collections.emptySet(); public TaskbarView(@NonNull Context context) { this(context, null); @@ -431,7 +431,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar mAddedDividerForRecents = true; } - updateRecents(recentTasks); + updateRecents(recentTasks, hotseatItemInfos.length); addView(mAllAppsButtonContainer, mIsRtl ? hotseatItemInfos.length : 0); @@ -460,7 +460,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar // Update left section. if (mIsRtl) { - updateRecents(recentTasks.reversed()); + updateRecents(recentTasks.reversed(), hotseatItemInfos.length); } else { updateHotseatItems(hotseatItemInfos); } @@ -475,7 +475,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar if (mIsRtl) { updateHotseatItems(hotseatItemInfos); } else { - updateRecents(recentTasks); + updateRecents(recentTasks, hotseatItemInfos.length); } // Recents divider takes priority. @@ -606,47 +606,59 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar } } - private void updateRecents(List recentTasks) { - // At this point, the all apps button has not been added as a child view, but needs to be - // accounted for when comparing current icon count to max number of icons. - int nonTaskIconsToBeAdded = 1; - + private void updateRecents(List recentTasks, int hotseatSize) { boolean supportsOverflow = Flags.taskbarOverflow() && recentTasks.size() > 1; int overflowSize = 0; - if (supportsOverflow) { - mIdealNumIcons = mNextViewIndex + recentTasks.size() + nonTaskIconsToBeAdded; + boolean hasOverflow = false; + if (supportsOverflow && mTaskbarOverflowView != null) { + // Need to account for All Apps and the divider. If we need to have an overflow, we will + // have a divider for recents. + final int nonTaskIconsToBeAdded = 2; + mIdealNumIcons = hotseatSize + recentTasks.size() + nonTaskIconsToBeAdded; overflowSize = mIdealNumIcons - mMaxNumIcons; + hasOverflow = overflowSize > 0; - if (overflowSize > 0 && mTaskbarOverflowView != null) { + if (!ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue() && hasOverflow) { addView(mTaskbarOverflowView, mNextViewIndex++); - } else if (mTaskbarOverflowView != null) { + } else if (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()) { + // RTL case is handled after we add the recent icons, because the button needs to + // then be to the right of them. + if (hasOverflow && !mIsRtl) { + if (mPrevOverflowTasks.isEmpty()) addView(mTaskbarOverflowView, mNextViewIndex); + // NOTE: If overflow already existed, assume the overflow view is already + // at the correct position. + mNextViewIndex++; + } else if (!hasOverflow && !mPrevOverflowTasks.isEmpty()) { + removeView(mTaskbarOverflowView); + mTaskbarOverflowView.clearItems(); + } + } else { mTaskbarOverflowView.clearItems(); } } - List overflownTasks = null; // An extra item needs to be added to overflow button to account for the space taken up by // the overflow button. final int itemsToAddToOverflow = - (overflowSize > 0) ? Math.min(overflowSize + 1, recentTasks.size()) : 0; - if (overflowSize > 0) { - overflownTasks = new ArrayList<>(itemsToAddToOverflow); + hasOverflow ? Math.min(overflowSize + 1, recentTasks.size()) : 0; + final Set overflownRecentsSet; + if (hasOverflow && mTaskbarOverflowView != null) { + final int startIndex = mIsRtl ? recentTasks.size() - itemsToAddToOverflow : 0; + final int endIndex = mIsRtl ? recentTasks.size() : itemsToAddToOverflow; + final List overflownRecents = recentTasks.subList(startIndex, endIndex); + mTaskbarOverflowView.setItems( + overflownRecents.stream().map(t -> ((SingleTask) t).getTask()).toList()); + overflownRecentsSet = new ArraySet<>(overflownRecents); + } else { + overflownRecentsSet = Collections.emptySet(); } // Add Recent/Running icons. final Set recentTasksSet = new ArraySet<>(recentTasks); - for (GroupTask task : recentTasks) { - if (mTaskbarOverflowView != null && overflownTasks != null - && overflownTasks.size() < itemsToAddToOverflow - && task instanceof SingleTask singleTask) { - // TODO(b/343289567 and b/316004172): support app pairs and desktop mode. - overflownTasks.add(singleTask.getTask()); - if (overflownTasks.size() == itemsToAddToOverflow) { - mTaskbarOverflowView.setItems(overflownTasks); - } - continue; - } - + final int startIndex = mIsRtl ? 0 : itemsToAddToOverflow; + final int endIndex = + mIsRtl ? recentTasks.size() - itemsToAddToOverflow : recentTasks.size(); + for (GroupTask task : recentTasks.subList(startIndex, endIndex)) { // Replace any Recent views with the appropriate type if it's not already that type. final int expectedLayoutResId; boolean isCollection = false; @@ -666,16 +678,18 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar View recentIcon = null; // If a task is new, we should not reuse a view so that it animates in when it is added. final boolean canReuseView = !ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue() - || mPrevRecentTasks.contains(task); + || (mPrevRecentTasks.contains(task) && !mPrevOverflowTasks.contains(task)); while (canReuseView && isNextViewInSection(GroupTask.class)) { recentIcon = getChildAt(mNextViewIndex); + GroupTask tag = (GroupTask) recentIcon.getTag(); // see if the view can be reused if ((recentIcon.getSourceLayoutResId() != expectedLayoutResId) - || (isCollection && (recentIcon.getTag() != task)) + || (isCollection && tag != task) // Remove view corresponding to removed task so that it animates out. || (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue() - && !recentTasksSet.contains(recentIcon.getTag()))) { + && (!recentTasksSet.contains(tag) + || overflownRecentsSet.contains(tag)))) { removeAndRecycle(recentIcon); recentIcon = null; } else { @@ -706,7 +720,15 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar removeAndRecycle(getChildAt(mNextViewIndex)); } + if (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue() && mIsRtl && hasOverflow) { + if (mPrevOverflowTasks.isEmpty()) { + addView(mTaskbarOverflowView, mNextViewIndex); + } + mNextViewIndex++; + } + mPrevRecentTasks = recentTasksSet; + mPrevOverflowTasks = overflownRecentsSet; } private boolean isNextViewInSection(Class tagClass) { diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt index 53b4d9c40a..2df4fab457 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt @@ -19,12 +19,14 @@ package com.android.launcher3.taskbar import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.view.View +import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW import com.android.launcher3.R import com.android.launcher3.statehandlers.DesktopVisibilityController import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync import com.android.launcher3.taskbar.TaskbarIconType.ALL_APPS import com.android.launcher3.taskbar.TaskbarIconType.DIVIDER import com.android.launcher3.taskbar.TaskbarIconType.HOTSEAT +import com.android.launcher3.taskbar.TaskbarIconType.OVERFLOW import com.android.launcher3.taskbar.TaskbarIconType.RECENT import com.android.launcher3.taskbar.TaskbarViewTestUtil.assertThat import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems @@ -44,7 +46,7 @@ import org.mockito.kotlin.whenever @RunWith(LauncherMultivalentJUnit::class) @EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) -@EnableFlags(FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION) +@EnableFlags(FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION, FLAG_TASKBAR_OVERFLOW) class TaskbarViewWithLayoutTransitionTest { @get:Rule(order = 0) val setFlagsRule = SetFlagsRule() @@ -59,6 +61,9 @@ class TaskbarViewWithLayoutTransitionTest { private val desktopVisibilityController: DesktopVisibilityController get() = DesktopVisibilityController.INSTANCE[context] + private val maxShownRecents: Int + get() = taskbarView.maxNumIconViews - 2 // Account for All Apps and Divider. + @Before fun obtainView() { taskbarView = taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view) @@ -260,4 +265,88 @@ class TaskbarViewWithLayoutTransitionTest { runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(1)) } assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, ALL_APPS) } + + @Test + fun testUpdateItems_maxRecents_noOverflow() { + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(maxShownRecents)) } + assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, *RECENT * maxShownRecents) + } + + @Test + fun testUpdateItems_moreThanMaxRecents_overflowShownBeforeRecents() { + val recentsSize = maxShownRecents + 2 + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(recentsSize)) } + + val expectedNumRecents = RECENT * getExpectedNumRecentsWithOverflow() + assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, OVERFLOW, *expectedNumRecents) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_moreThanMaxRecents_overflowShownAfterRecents() { + val recentsSize = maxShownRecents + 2 + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(recentsSize)) } + + val expectedRecents = RECENT * getExpectedNumRecentsWithOverflow() + assertThat(taskbarView).hasIconTypes(*expectedRecents, OVERFLOW, DIVIDER, ALL_APPS) + } + + @Test + fun testUpdateItems_moreThanMaxRecentsWithHotseat_fewerRecentsShown() { + val hotseatSize = 4 + val recentsSize = maxShownRecents + 2 + runOnMainSync { + taskbarView.updateItems(createHotseatItems(hotseatSize), createRecents(recentsSize)) + } + + val expectedRecents = RECENT * getExpectedNumRecentsWithOverflow(hotseatSize) + assertThat(taskbarView) + .hasIconTypes(ALL_APPS, *HOTSEAT * hotseatSize, DIVIDER, OVERFLOW, *expectedRecents) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_moreThanMaxRecentsWithHotseat_fewerRecentsShown() { + val hotseatSize = 4 + val recentsSize = maxShownRecents + 2 + runOnMainSync { + taskbarView.updateItems(createHotseatItems(hotseatSize), createRecents(recentsSize)) + } + + val expectedRecents = RECENT * getExpectedNumRecentsWithOverflow(hotseatSize) + assertThat(taskbarView) + .hasIconTypes(*expectedRecents, OVERFLOW, DIVIDER, *HOTSEAT * hotseatSize, ALL_APPS) + } + + @Test + fun testUpdateItems_moreThanMaxRecents_verifyShownRecentsOrder() { + val recentsSize = maxShownRecents + 2 + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(recentsSize)) } + + val expectedNumRecents = getExpectedNumRecentsWithOverflow() + assertThat(taskbarView) + .hasRecentsOrder( + startIndex = iconViews.size - expectedNumRecents, + expectedIds = ((recentsSize - expectedNumRecents)..