Merge changes from topic "taskbar-layout-transition-prepare" into main
* changes: Prepare for LayoutTransition with RTL support. Split up hotseat and recents into two methods. Filter out unsupported items immediately when updating Taskbar.
This commit is contained in:
committed by
Android (Google) Code Review
commit
852dccdff2
@@ -208,7 +208,7 @@ public class TaskbarModelCallbacks implements
|
||||
|
||||
private void commitHotseatItemUpdates(
|
||||
ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
|
||||
mContainer.updateHotseatItems(hotseatItemInfos, recentTasks);
|
||||
mContainer.updateItems(hotseatItemInfos, recentTasks);
|
||||
mControllers.taskbarViewController.updateIconViewsRunningStates();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,14 @@ import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_
|
||||
import static com.android.launcher3.BubbleTextView.DISPLAY_TASKBAR;
|
||||
import static com.android.launcher3.Flags.enableCursorHoverStates;
|
||||
import static com.android.launcher3.Flags.enableRecentsInTaskbar;
|
||||
import static com.android.launcher3.Flags.taskbarRecentsLayoutTransition;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
|
||||
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
|
||||
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
|
||||
|
||||
import static java.util.function.Predicate.not;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
@@ -70,7 +73,9 @@ 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.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
@@ -108,6 +113,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
// Only non-null when device supports having a Taskbar Overflow button.
|
||||
@Nullable private TaskbarOverflowView mTaskbarOverflowView;
|
||||
|
||||
private int mNextViewIndex;
|
||||
|
||||
/**
|
||||
* Whether the divider is between Hotseat icons and Recents,
|
||||
* instead of between All Apps button and Hotseat.
|
||||
@@ -125,6 +132,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
|
||||
private final int mAllAppsButtonTranslationOffset;
|
||||
|
||||
private final int mNumStaticViews;
|
||||
|
||||
public TaskbarView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -189,6 +198,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
|
||||
// TODO: Disable touch events on QSB otherwise it can crash.
|
||||
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
|
||||
|
||||
mNumStaticViews = taskbarRecentsLayoutTransition() ? addStaticViews() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,6 +260,24 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
&& (mIdealNumIcons > oldMaxNumIcons || mIdealNumIcons > mMaxNumIcons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-adds views that are always children of this view for LayoutTransition support.
|
||||
* <p>
|
||||
* Normally these views are removed and re-added when updating hotseat and recents. This
|
||||
* approach does not behave well with LayoutTransition, so we instead need to add them
|
||||
* initially and avoid removing them during updates.
|
||||
*/
|
||||
private int addStaticViews() {
|
||||
int numStaticViews = 1;
|
||||
addView(mAllAppsButtonContainer);
|
||||
if (mActivityContext.getDeviceProfile().isQsbInline) {
|
||||
addView(mQsb, mIsRtl ? 1 : 0);
|
||||
mQsb.setVisibility(View.INVISIBLE);
|
||||
numStaticViews++;
|
||||
}
|
||||
return numStaticViews;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibility(int visibility) {
|
||||
boolean changed = getVisibility() != visibility;
|
||||
@@ -362,12 +391,26 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
view.setTag(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
|
||||
*/
|
||||
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
|
||||
int nextViewIndex = 0;
|
||||
int numViewsAnimated = 0;
|
||||
/** Inflates/binds the hotseat items and recent tasks to the view. */
|
||||
protected void updateItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
|
||||
// Filter out unsupported items.
|
||||
hotseatItemInfos = Arrays.stream(hotseatItemInfos)
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(ItemInfo[]::new);
|
||||
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
|
||||
recentTasks = recentTasks.stream().filter(not(GroupTask::supportsMultipleTasks)).toList();
|
||||
|
||||
if (taskbarRecentsLayoutTransition()) {
|
||||
updateItemsWithLayoutTransition(hotseatItemInfos, recentTasks);
|
||||
} else {
|
||||
updateItemsWithoutLayoutTransition(hotseatItemInfos, recentTasks);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateItemsWithoutLayoutTransition(
|
||||
ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
|
||||
|
||||
mNextViewIndex = 0;
|
||||
mAddedDividerForRecents = false;
|
||||
|
||||
removeView(mAllAppsButtonContainer);
|
||||
@@ -380,12 +423,101 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
removeView(mQsb);
|
||||
|
||||
// Add Hotseat icons.
|
||||
for (ItemInfo hotseatItemInfo : hotseatItemInfos) {
|
||||
if (hotseatItemInfo == null) {
|
||||
continue;
|
||||
}
|
||||
updateHotseatItems(hotseatItemInfos);
|
||||
|
||||
if (mTaskbarDividerContainer != null && !recentTasks.isEmpty()) {
|
||||
addView(mTaskbarDividerContainer, mNextViewIndex++);
|
||||
mAddedDividerForRecents = true;
|
||||
}
|
||||
|
||||
updateRecents(recentTasks);
|
||||
|
||||
addView(mAllAppsButtonContainer, mIsRtl ? hotseatItemInfos.length : 0);
|
||||
|
||||
// If there are no recent tasks, add divider after All Apps (unless it's the only view).
|
||||
if (!mAddedDividerForRecents
|
||||
&& mTaskbarDividerContainer != null
|
||||
&& getChildCount() > 1) {
|
||||
addView(mTaskbarDividerContainer, mIsRtl ? (getChildCount() - 1) : 1);
|
||||
}
|
||||
|
||||
if (mActivityContext.getDeviceProfile().isQsbInline) {
|
||||
addView(mQsb, mIsRtl ? getChildCount() : 0);
|
||||
// Always set QSB to invisible after re-adding.
|
||||
mQsb.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateItemsWithLayoutTransition(
|
||||
ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
|
||||
|
||||
// Skip static views and potential All Apps divider, if they are on the left.
|
||||
mNextViewIndex = mIsRtl ? 0 : mNumStaticViews;
|
||||
if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer) {
|
||||
mNextViewIndex++;
|
||||
}
|
||||
|
||||
// Update left section.
|
||||
if (mIsRtl) {
|
||||
updateRecents(recentTasks.reversed());
|
||||
} else {
|
||||
updateHotseatItems(hotseatItemInfos);
|
||||
}
|
||||
|
||||
// Now at theoretical position for recent apps divider.
|
||||
updateRecentsDivider(!recentTasks.isEmpty());
|
||||
if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer) {
|
||||
mNextViewIndex++;
|
||||
}
|
||||
|
||||
// Update right section.
|
||||
if (mIsRtl) {
|
||||
updateHotseatItems(hotseatItemInfos);
|
||||
} else {
|
||||
updateRecents(recentTasks);
|
||||
}
|
||||
|
||||
// Recents divider takes priority.
|
||||
if (!mAddedDividerForRecents) {
|
||||
updateAllAppsDivider();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRecentsDivider(boolean hasRecents) {
|
||||
if (hasRecents && !mAddedDividerForRecents) {
|
||||
mAddedDividerForRecents = true;
|
||||
|
||||
// Remove possible All Apps divider.
|
||||
if (getChildAt(mNumStaticViews) == mTaskbarDividerContainer) {
|
||||
mNextViewIndex--; // All Apps divider on the left. Need to account for removing it.
|
||||
}
|
||||
removeView(mTaskbarDividerContainer);
|
||||
|
||||
addView(mTaskbarDividerContainer, mNextViewIndex);
|
||||
} else if (!hasRecents && mAddedDividerForRecents) {
|
||||
mAddedDividerForRecents = false;
|
||||
removeViewAt(mNextViewIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAllAppsDivider() {
|
||||
final int allAppsDividerIndex =
|
||||
mIsRtl ? getChildCount() - mNumStaticViews : mNumStaticViews;
|
||||
if (getChildAt(allAppsDividerIndex) == mTaskbarDividerContainer
|
||||
&& getChildCount() == mNumStaticViews + 1) {
|
||||
// Only static views with divider so remove divider.
|
||||
removeView(mTaskbarDividerContainer);
|
||||
} else if (getChildAt(allAppsDividerIndex) != mTaskbarDividerContainer
|
||||
&& getChildCount() >= mNumStaticViews + 1) {
|
||||
// Static views with at least one app icon so add divider.
|
||||
addView(mTaskbarDividerContainer, allAppsDividerIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
|
||||
int numViewsAnimated = 0;
|
||||
|
||||
for (ItemInfo hotseatItemInfo : hotseatItemInfos) {
|
||||
// Replace any Hotseat views with the appropriate type if it's not already that type.
|
||||
final int expectedLayoutResId;
|
||||
boolean isCollection = false;
|
||||
@@ -401,8 +533,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
|
||||
View hotseatView = null;
|
||||
while (nextViewIndex < getChildCount()) {
|
||||
hotseatView = getChildAt(nextViewIndex);
|
||||
while (isNextViewInSection(ItemInfo.class)) {
|
||||
hotseatView = getChildAt(mNextViewIndex);
|
||||
|
||||
// see if the view can be reused
|
||||
if ((hotseatView.getSourceLayoutResId() != expectedLayoutResId)
|
||||
@@ -443,7 +575,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
|
||||
hotseatView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
|
||||
addView(hotseatView, nextViewIndex, lp);
|
||||
addView(hotseatView, mNextViewIndex, lp);
|
||||
}
|
||||
|
||||
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
|
||||
@@ -459,34 +591,27 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
if (enableCursorHoverStates()) {
|
||||
setHoverListenerForIcon(hotseatView);
|
||||
}
|
||||
nextViewIndex++;
|
||||
mNextViewIndex++;
|
||||
}
|
||||
|
||||
if (mTaskbarDividerContainer != null && !recentTasks.isEmpty()) {
|
||||
addView(mTaskbarDividerContainer, nextViewIndex++);
|
||||
mAddedDividerForRecents = true;
|
||||
while (isNextViewInSection(ItemInfo.class)) {
|
||||
removeAndRecycle(getChildAt(mNextViewIndex));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
boolean supportsOverflow = Flags.taskbarOverflow();
|
||||
int overflowSize = 0;
|
||||
int numberOfSupportedRecents = 0;
|
||||
if (supportsOverflow) {
|
||||
for (GroupTask task : recentTasks) {
|
||||
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
|
||||
if (!task.supportsMultipleTasks()) {
|
||||
++numberOfSupportedRecents;
|
||||
}
|
||||
}
|
||||
|
||||
mIdealNumIcons = nextViewIndex + numberOfSupportedRecents + nonTaskIconsToBeAdded;
|
||||
mIdealNumIcons = mNextViewIndex + recentTasks.size() + nonTaskIconsToBeAdded;
|
||||
overflowSize = mIdealNumIcons - mMaxNumIcons;
|
||||
|
||||
if (overflowSize > 0 && mTaskbarOverflowView != null) {
|
||||
addView(mTaskbarOverflowView, nextViewIndex++);
|
||||
addView(mTaskbarOverflowView, mNextViewIndex++);
|
||||
} else if (mTaskbarOverflowView != null) {
|
||||
mTaskbarOverflowView.clearItems();
|
||||
}
|
||||
@@ -496,9 +621,9 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
// 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, numberOfSupportedRecents) : 0;
|
||||
(overflowSize > 0) ? Math.min(overflowSize + 1, recentTasks.size()) : 0;
|
||||
if (overflowSize > 0) {
|
||||
overflownTasks = new ArrayList<Task>(itemsToAddToOverflow);
|
||||
overflownTasks = new ArrayList<>(itemsToAddToOverflow);
|
||||
}
|
||||
|
||||
// Add Recent/Running icons.
|
||||
@@ -506,10 +631,6 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
if (mTaskbarOverflowView != null && overflownTasks != null
|
||||
&& overflownTasks.size() < itemsToAddToOverflow) {
|
||||
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
|
||||
if (task.supportsMultipleTasks()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
overflownTasks.add(task.task1);
|
||||
if (overflownTasks.size() == itemsToAddToOverflow) {
|
||||
mTaskbarOverflowView.setItems(overflownTasks);
|
||||
@@ -534,8 +655,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
|
||||
View recentIcon = null;
|
||||
while (nextViewIndex < getChildCount()) {
|
||||
recentIcon = getChildAt(nextViewIndex);
|
||||
while (isNextViewInSection(GroupTask.class)) {
|
||||
recentIcon = getChildAt(mNextViewIndex);
|
||||
|
||||
// see if the view can be reused
|
||||
if ((recentIcon.getSourceLayoutResId() != expectedLayoutResId)
|
||||
@@ -549,15 +670,11 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
|
||||
if (recentIcon == null) {
|
||||
if (isCollection) {
|
||||
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
|
||||
recentIcon = inflate(expectedLayoutResId);
|
||||
LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
|
||||
recentIcon.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
|
||||
addView(recentIcon, nextViewIndex, lp);
|
||||
addView(recentIcon, mNextViewIndex, lp);
|
||||
}
|
||||
|
||||
if (recentIcon instanceof BubbleTextView btv) {
|
||||
@@ -567,29 +684,17 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
if (enableCursorHoverStates()) {
|
||||
setHoverListenerForIcon(recentIcon);
|
||||
}
|
||||
nextViewIndex++;
|
||||
mNextViewIndex++;
|
||||
}
|
||||
|
||||
// Remove remaining views
|
||||
while (nextViewIndex < getChildCount()) {
|
||||
removeAndRecycle(getChildAt(nextViewIndex));
|
||||
while (isNextViewInSection(GroupTask.class)) {
|
||||
removeAndRecycle(getChildAt(mNextViewIndex));
|
||||
}
|
||||
}
|
||||
|
||||
addView(mAllAppsButtonContainer, mIsRtl ? hotseatItemInfos.length : 0);
|
||||
|
||||
// If there are no recent tasks, add divider after All Apps (unless it's the only view).
|
||||
if (!mAddedDividerForRecents
|
||||
&& mTaskbarDividerContainer != null
|
||||
&& getChildCount() > 1) {
|
||||
addView(mTaskbarDividerContainer, mIsRtl ? (getChildCount() - 1) : 1);
|
||||
}
|
||||
|
||||
|
||||
if (mActivityContext.getDeviceProfile().isQsbInline) {
|
||||
addView(mQsb, mIsRtl ? getChildCount() : 0);
|
||||
// Always set QSB to invisible after re-adding.
|
||||
mQsb.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
private boolean isNextViewInSection(Class<?> tagClass) {
|
||||
return mNextViewIndex < getChildCount()
|
||||
&& tagClass.isInstance(getChildAt(mNextViewIndex).getTag());
|
||||
}
|
||||
|
||||
/** Binds the GroupTask to the BubbleTextView to be ready to present to the user. */
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.launcher3.taskbar
|
||||
|
||||
import android.platform.test.flag.junit.FlagsParameterization
|
||||
import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
|
||||
import android.platform.test.flag.junit.SetFlagsRule
|
||||
import com.android.launcher3.Flags.FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION
|
||||
import com.android.launcher3.R
|
||||
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.RECENT
|
||||
import com.android.launcher3.taskbar.TaskbarViewTestUtil.assertThat
|
||||
import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
|
||||
import com.android.launcher3.taskbar.TaskbarViewTestUtil.createRecents
|
||||
import com.android.launcher3.taskbar.rules.TaskbarDeviceEmulationRule
|
||||
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
|
||||
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.ForceRtl
|
||||
import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
|
||||
import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
|
||||
import platform.test.runner.parameterized.Parameters
|
||||
|
||||
@RunWith(ParameterizedAndroidJunit4::class)
|
||||
class TaskbarViewTest(deviceName: String, flags: FlagsParameterization) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameters(name = "{0},{1}")
|
||||
fun getParams(): List<Array<Any>> {
|
||||
val devices =
|
||||
if (isRunningInRobolectric) {
|
||||
listOf("pixelFoldable2023", "pixelTablet2023")
|
||||
} else {
|
||||
listOf("onDevice") // Unused.
|
||||
}
|
||||
val flags = allCombinationsOf(FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION)
|
||||
return devices.flatMap { d -> flags.map { f -> arrayOf(d, f) } } // Cartesian product.
|
||||
}
|
||||
}
|
||||
|
||||
@get:Rule(order = 0) val setFlagsRule = SetFlagsRule(flags)
|
||||
@get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create()
|
||||
@get:Rule(order = 2) val deviceEmulationRule = TaskbarDeviceEmulationRule(context, deviceName)
|
||||
@get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
|
||||
|
||||
private lateinit var taskbarView: TaskbarView
|
||||
|
||||
@Before
|
||||
fun obtainView() {
|
||||
taskbarView = taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateItems_noItems_hasOnlyAllApps() {
|
||||
runOnMainSync { taskbarView.updateItems(emptyArray(), emptyList()) }
|
||||
assertThat(taskbarView).hasIconTypes(ALL_APPS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateItems_hotseatItems_hasDividerBetweenAllAppsAndHotseat() {
|
||||
runOnMainSync { taskbarView.updateItems(createHotseatItems(2), emptyList()) }
|
||||
assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, HOTSEAT, HOTSEAT)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForceRtl
|
||||
fun testUpdateItems_rtlWithHotseatItems_hasDividerBetweenHotseatAndAllApps() {
|
||||
runOnMainSync { taskbarView.updateItems(createHotseatItems(2), emptyList()) }
|
||||
assertThat(taskbarView).hasIconTypes(HOTSEAT, HOTSEAT, DIVIDER, ALL_APPS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateItems_withNullHotseatItem_filtersNullItem() {
|
||||
runOnMainSync {
|
||||
taskbarView.updateItems(arrayOf(*createHotseatItems(2), null), emptyList())
|
||||
}
|
||||
assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, HOTSEAT, HOTSEAT)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForceRtl
|
||||
fun testUpdateItems_rtlWithNullHotseatItem_filtersNullItem() {
|
||||
runOnMainSync {
|
||||
taskbarView.updateItems(arrayOf(*createHotseatItems(2), null), emptyList())
|
||||
}
|
||||
assertThat(taskbarView).hasIconTypes(HOTSEAT, HOTSEAT, DIVIDER, ALL_APPS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateItems_recentsItems_hasDividerBetweenAllAppsAndRecents() {
|
||||
runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(4)) }
|
||||
assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, *RECENT * 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateItems_hotseatItemsAndRecents_hasDividerBetweenHotseatAndRecents() {
|
||||
runOnMainSync { taskbarView.updateItems(createHotseatItems(3), createRecents(2)) }
|
||||
assertThat(taskbarView).hasIconTypes(ALL_APPS, *HOTSEAT * 3, DIVIDER, *RECENT * 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateItems_addHotseatItem_updatesHotseat() {
|
||||
runOnMainSync {
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(1))
|
||||
taskbarView.updateItems(createHotseatItems(2), createRecents(1))
|
||||
}
|
||||
assertThat(taskbarView).hasIconTypes(ALL_APPS, *HOTSEAT * 2, DIVIDER, RECENT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateItems_removeHotseatItem_updatesHotseat() {
|
||||
runOnMainSync {
|
||||
taskbarView.updateItems(createHotseatItems(2), createRecents(1))
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(1))
|
||||
}
|
||||
assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateItems_addRecentsItem_updatesRecents() {
|
||||
runOnMainSync {
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(1))
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(2))
|
||||
}
|
||||
assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, *RECENT * 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateItems_removeRecentsItem_updatesRecents() {
|
||||
runOnMainSync {
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(2))
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(1))
|
||||
}
|
||||
assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT)
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.launcher3.taskbar
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.os.Process
|
||||
import com.android.launcher3.model.data.AppInfo
|
||||
import com.android.launcher3.model.data.ItemInfo
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo
|
||||
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.quickstep.util.GroupTask
|
||||
import com.android.systemui.shared.recents.model.Task
|
||||
import com.android.systemui.shared.recents.model.Task.TaskKey
|
||||
import com.google.common.truth.FailureMetadata
|
||||
import com.google.common.truth.Subject
|
||||
import com.google.common.truth.Truth.assertAbout
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
||||
/** Common utilities for testing [TaskbarView]. */
|
||||
object TaskbarViewTestUtil {
|
||||
|
||||
/** Begins an assertion about a [TaskbarView]. */
|
||||
fun assertThat(view: TaskbarView): TaskbarViewSubject {
|
||||
return assertAbout(::TaskbarViewSubject).that(view)
|
||||
}
|
||||
|
||||
/** Creates an array of fake hotseat items. */
|
||||
fun createHotseatItems(size: Int): Array<ItemInfo> {
|
||||
return Array(size) {
|
||||
WorkspaceItemInfo(
|
||||
AppInfo(TEST_COMPONENT, "Test App $it", Process.myUserHandle(), Intent())
|
||||
)
|
||||
.apply { id = it }
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a list of fake recent tasks. */
|
||||
fun createRecents(size: Int): List<GroupTask> {
|
||||
return List(size) {
|
||||
GroupTask(
|
||||
Task().apply {
|
||||
key =
|
||||
TaskKey(
|
||||
it,
|
||||
5,
|
||||
TEST_INTENT,
|
||||
TEST_COMPONENT,
|
||||
Process.myUserHandle().identifier,
|
||||
System.currentTimeMillis(),
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A `Truth` [Subject] with extensions for verifying [TaskbarView]. */
|
||||
class TaskbarViewSubject(failureMetadata: FailureMetadata, private val view: TaskbarView) :
|
||||
Subject(failureMetadata, view) {
|
||||
|
||||
/** Verifies that the types of icons match [expectedTypes] in order. */
|
||||
fun hasIconTypes(vararg expectedTypes: TaskbarIconType) {
|
||||
val actualTypes =
|
||||
view.iconViews.map {
|
||||
when (it) {
|
||||
view.allAppsButtonContainer -> ALL_APPS
|
||||
view.taskbarDividerViewContainer -> DIVIDER
|
||||
view.taskbarOverflowView -> OVERFLOW
|
||||
else ->
|
||||
when (it.tag) {
|
||||
is ItemInfo -> HOTSEAT
|
||||
is GroupTask -> RECENT
|
||||
else -> throw IllegalStateException("Unknown type for $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
assertThat(actualTypes).containsExactly(*expectedTypes).inOrder()
|
||||
}
|
||||
|
||||
/** Verifies that recents from [startIndex] have IDs that match [expectedIds] in order. */
|
||||
fun hasRecentsOrder(startIndex: Int, expectedIds: List<Int>) {
|
||||
val actualIds =
|
||||
view.iconViews.slice(startIndex..<expectedIds.size).map {
|
||||
assertThat(it.tag).isInstanceOf(GroupTask::class.java)
|
||||
(it.tag as? GroupTask)?.task1?.key?.id
|
||||
}
|
||||
assertThat(actualIds).containsExactlyElementsIn(expectedIds).inOrder()
|
||||
}
|
||||
}
|
||||
|
||||
/** Types of icons in the [TaskbarView]. */
|
||||
enum class TaskbarIconType {
|
||||
ALL_APPS,
|
||||
DIVIDER,
|
||||
HOTSEAT,
|
||||
RECENT,
|
||||
OVERFLOW;
|
||||
|
||||
operator fun times(size: Int) = Array(size) { this }
|
||||
}
|
||||
|
||||
private const val TEST_PACKAGE = "com.android.launcher3.taskbar"
|
||||
private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "Activity")
|
||||
private val TEST_INTENT = Intent().apply { `package` = TEST_PACKAGE }
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.launcher3.taskbar
|
||||
|
||||
import android.platform.test.annotations.EnableFlags
|
||||
import android.platform.test.flag.junit.SetFlagsRule
|
||||
import com.android.launcher3.Flags.FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION
|
||||
import com.android.launcher3.R
|
||||
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.RECENT
|
||||
import com.android.launcher3.taskbar.TaskbarViewTestUtil.assertThat
|
||||
import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
|
||||
import com.android.launcher3.taskbar.TaskbarViewTestUtil.createRecents
|
||||
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
|
||||
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.ForceRtl
|
||||
import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
|
||||
import com.android.launcher3.util.LauncherMultivalentJUnit
|
||||
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(LauncherMultivalentJUnit::class)
|
||||
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
|
||||
@EnableFlags(FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION)
|
||||
class TaskbarViewWithLayoutTransitionTest {
|
||||
|
||||
@get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
|
||||
@get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create()
|
||||
@get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
|
||||
|
||||
private lateinit var taskbarView: TaskbarView
|
||||
|
||||
@Before
|
||||
fun obtainView() {
|
||||
taskbarView = taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForceRtl
|
||||
fun testUpdateItems_rtl_hotseatItems_hasDividerBetweenHotseatAndAllApps() {
|
||||
runOnMainSync { taskbarView.updateItems(createHotseatItems(2), emptyList()) }
|
||||
assertThat(taskbarView).hasIconTypes(*HOTSEAT * 2, DIVIDER, ALL_APPS)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForceRtl
|
||||
fun testUpdateItems_rtl_recentsItems_hasDividerBetweenRecentsAndAllApps() {
|
||||
runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(4)) }
|
||||
assertThat(taskbarView).hasIconTypes(*RECENT * 4, DIVIDER, ALL_APPS)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForceRtl
|
||||
fun testUpdateItems_rtl_recentsItems_recentsAreReversed() {
|
||||
runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(4)) }
|
||||
assertThat(taskbarView).hasRecentsOrder(startIndex = 0, expectedIds = listOf(3, 2, 1, 0))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForceRtl
|
||||
fun testUpdateItems_rtl_hotseatItemsAndRecents_hasDividerBetweenRecentsAndHotseat() {
|
||||
runOnMainSync { taskbarView.updateItems(createHotseatItems(3), createRecents(2)) }
|
||||
assertThat(taskbarView).hasIconTypes(*RECENT * 2, DIVIDER, *HOTSEAT * 3, ALL_APPS)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForceRtl
|
||||
fun testUpdateItems_rtl_addHotseatItem_updatesHotseat() {
|
||||
runOnMainSync {
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(1))
|
||||
taskbarView.updateItems(createHotseatItems(2), createRecents(1))
|
||||
}
|
||||
assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, *HOTSEAT * 2, ALL_APPS)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForceRtl
|
||||
fun testUpdateItems_rtl_removeHotseatItem_updatesHotseat() {
|
||||
runOnMainSync {
|
||||
taskbarView.updateItems(createHotseatItems(2), createRecents(1))
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(1))
|
||||
}
|
||||
assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, HOTSEAT, ALL_APPS)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForceRtl
|
||||
fun testUpdateItems_rtl_addRecentsItem_updatesRecents() {
|
||||
runOnMainSync {
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(1))
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(2))
|
||||
}
|
||||
assertThat(taskbarView).hasIconTypes(*RECENT * 2, DIVIDER, HOTSEAT, ALL_APPS)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForceRtl
|
||||
fun testUpdateItems_rtl_removeRecentsItem_updatesRecents() {
|
||||
runOnMainSync {
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(2))
|
||||
taskbarView.updateItems(createHotseatItems(1), createRecents(1))
|
||||
}
|
||||
assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, HOTSEAT, ALL_APPS)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user