Initial overflow compatibility with layout transition.
Also implements RTL support for overflow.
Flag: com.android.window.flags.enable_taskbar_recents_layout_transition
Bug: 343521765
Test: go/testedequals
Change-Id: I34983821a2a1a80793dc6d811d42cfeafe274ef4
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 5292470..573bc1d 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.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 @@
private final int mNumStaticViews;
private Set<GroupTask> mPrevRecentTasks = Collections.emptySet();
+ private Set<GroupTask> mPrevOverflowTasks = Collections.emptySet();
public TaskbarView(@NonNull Context context) {
this(context, null);
@@ -431,7 +431,7 @@
mAddedDividerForRecents = true;
}
- updateRecents(recentTasks);
+ updateRecents(recentTasks, hotseatItemInfos.length);
addView(mAllAppsButtonContainer, mIsRtl ? hotseatItemInfos.length : 0);
@@ -460,7 +460,7 @@
// Update left section.
if (mIsRtl) {
- updateRecents(recentTasks.reversed());
+ updateRecents(recentTasks.reversed(), hotseatItemInfos.length);
} else {
updateHotseatItems(hotseatItemInfos);
}
@@ -475,7 +475,7 @@
if (mIsRtl) {
updateHotseatItems(hotseatItemInfos);
} else {
- updateRecents(recentTasks);
+ updateRecents(recentTasks, hotseatItemInfos.length);
}
// Recents divider takes priority.
@@ -606,47 +606,59 @@
}
}
- private void updateRecents(List<GroupTask> 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<GroupTask> 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<Task> 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<GroupTask> overflownRecentsSet;
+ if (hasOverflow && mTaskbarOverflowView != null) {
+ final int startIndex = mIsRtl ? recentTasks.size() - itemsToAddToOverflow : 0;
+ final int endIndex = mIsRtl ? recentTasks.size() : itemsToAddToOverflow;
+ final List<GroupTask> 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<GroupTask> 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 @@
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 @@
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 53b4d9c..2df4fab 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 @@
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 @@
@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 @@
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 @@
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)..<recentsSize).toList(),
+ )
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtl_moreThanMaxRecents_verifyShownRecentsReversed() {
+ val recentsSize = maxShownRecents + 2
+ runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(recentsSize)) }
+
+ val expectedNumRecents = getExpectedNumRecentsWithOverflow()
+ assertThat(taskbarView)
+ .hasRecentsOrder(
+ startIndex = 0,
+ expectedIds = ((recentsSize - expectedNumRecents)..<recentsSize).toList().reversed(),
+ )
+ }
+
+ /** Returns the number of expected recents outside of the overflow based on [hotseatSize]. */
+ private fun getExpectedNumRecentsWithOverflow(hotseatSize: Int = 0): Int {
+ return maxShownRecents - hotseatSize - 1 // Account for overflow.
+ }
}