Merge "[CD Taskbar] Refactor to Listen to Display Ready/Removed Signals" into main
diff --git a/quickstep/res/layout/overview_add_desktop_button.xml b/quickstep/res/layout/overview_add_desktop_button.xml
index 2333dd1..e36cf72 100644
--- a/quickstep/res/layout/overview_add_desktop_button.xml
+++ b/quickstep/res/layout/overview_add_desktop_button.xml
@@ -18,7 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apgk/res-auto"
android:id="@+id/add_desktop_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="@dimen/add_desktop_button_size"
+ android:layout_height="@dimen/add_desktop_button_size"
android:src="@drawable/ic_desktop_add"
android:padding="10dp" />
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 493a5b8..b253343 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -106,6 +106,9 @@
<dimen name="recents_clear_all_outline_radius">24dp</dimen>
<dimen name="recents_clear_all_outline_padding">2dp</dimen>
+ <!-- Recents add desktop button -->
+ <dimen name="add_desktop_button_size">56dp</dimen>
+
<!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
loading full resolution screenshots. -->
<dimen name="recents_fast_fling_velocity">600dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 2745129..9fe9202 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -85,6 +85,7 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
@@ -230,6 +231,7 @@
private DeviceProfile mPersistentTaskbarDeviceProfile;
private final LauncherPrefs mLauncherPrefs;
+ private final SystemUiProxy mSysUiProxy;
private TaskbarFeatureEvaluator mTaskbarFeatureEvaluator;
@@ -239,10 +241,11 @@
@Nullable Context navigationBarPanelContext, DeviceProfile launcherDp,
TaskbarNavButtonController buttonController,
ScopedUnfoldTransitionProgressProvider unfoldTransitionProgressProvider,
- boolean isPrimaryDisplay) {
+ boolean isPrimaryDisplay, SystemUiProxy sysUiProxy) {
super(windowContext);
mIsPrimaryDisplay = isPrimaryDisplay;
mNavigationBarPanelContext = navigationBarPanelContext;
+ mSysUiProxy = sysUiProxy;
applyDeviceProfile(launcherDp);
final Resources resources = getResources();
mTaskbarFeatureEvaluator = TaskbarFeatureEvaluator.getInstance(this);
@@ -1342,7 +1345,7 @@
mControllers.uiController.onTaskbarIconLaunched(api);
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
}
- } else if (tag instanceof TaskItemInfo info) {
+ } else if (tag instanceof TaskItemInfo info && !Flags.enableMultiInstanceMenuTaskbar()) {
RemoteTransition remoteTransition = canUnminimizeDesktopTask(info.getTaskId())
? createUnminimizeRemoteTransition() : null;
@@ -1645,12 +1648,12 @@
intent.getComponent(), info.user, intent.getSourceBounds(), null);
return;
}
+ int displayId = getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId();
// TODO(b/216683257): Use startActivityForResult for search results that require it.
if (taskInRecents != null) {
// Re launch instance from recents
ActivityOptionsWrapper opts = getActivityLaunchOptions(null, info);
- opts.options.setLaunchDisplayId(
- getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
+ opts.options.setLaunchDisplayId(displayId);
if (ActivityManagerWrapper.getInstance()
.startActivityFromRecents(taskInRecents.key, opts.options)) {
mControllers.uiController.getRecentsView()
@@ -1658,12 +1661,13 @@
return;
}
}
- ActivityOptionsWrapper opts = null;
if (areDesktopTasksVisible()) {
- opts = getActivityLaunchDesktopOptions(info);
+ ActivityOptionsWrapper opts = getActivityLaunchDesktopOptions(info);
+ Bundle optionsBundle = opts == null ? null : opts.options.toBundle();
+ mSysUiProxy.startLaunchIntentTransition(intent, optionsBundle, displayId);
+ } else {
+ startActivity(intent, null);
}
- Bundle optionsBundle = opts == null ? null : opts.options.toBundle();
- startActivity(intent, optionsBundle);
} catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT)
.show();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index ce0dbb7..21c8255 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -807,7 +807,8 @@
private TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp, int displayId) {
TaskbarActivityContext newTaskbar = new TaskbarActivityContext(mWindowContext,
mNavigationBarPanelContext, dp, mDefaultNavButtonController,
- mUnfoldProgressProvider, isDefaultDisplay(displayId));
+ mUnfoldProgressProvider, isDefaultDisplay(displayId),
+ SystemUiProxy.INSTANCE.get(mWindowContext));
addTaskbarToMap(displayId, newTaskbar);
return newTaskbar;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index a059b22..e654fe8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -19,6 +19,7 @@
import android.window.DesktopModeFlags
import androidx.annotation.VisibleForTesting
import com.android.launcher3.BubbleTextView.RunningAppState
+import com.android.launcher3.Flags
import com.android.launcher3.Flags.enableRecentsInTaskbar
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.TaskItemInfo
@@ -276,27 +277,60 @@
return newOrder
}
+ /**
+ * Computes the list of running tasks to be shown in the recent apps section of the taskbar in
+ * desktop mode, taking into account deduplication against hotseat items and existing tasks.
+ */
private fun computeShownRunningTasks(): List<GroupTask> {
if (!canShowRunningApps) {
return emptyList()
}
- val desktopTaskAsList = getOrderedAndWrappedDesktopTasks()
- val desktopTaskIds = desktopTaskAsList.map { it.task1.key.id }
- val shownTaskIds = shownTasks.map { it.task1.key.id }
- // TODO(b/315344726 Multi-instance support): only show one icon per package once we support
- // taskbar multi-instance menus
- val shownHotseatItemTaskIds =
- shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
- // Remove any newly-missing Tasks, and actual group-tasks
+
+ val desktopTasks = getOrderedAndWrappedDesktopTasks()
+
val newShownTasks =
- shownTasks
- .filter { !it.supportsMultipleTasks() }
- .filter { it.task1.key.id in desktopTaskIds }
- .toMutableList()
- // Add any new Tasks, maintaining the order from previous shownTasks.
- newShownTasks.addAll(desktopTaskAsList.filter { it.task1.key.id !in shownTaskIds })
- // Remove any tasks already covered by Hotseat icons
- return newShownTasks.filter { it.task1.key.id !in shownHotseatItemTaskIds }
+ if (Flags.enableMultiInstanceMenuTaskbar()) {
+ val deduplicatedDesktopTasks =
+ desktopTasks.distinctBy { Pair(it.task1.key.packageName, it.task1.key.userId) }
+
+ shownTasks
+ .filter {
+ !it.supportsMultipleTasks() &&
+ it.task1.key.id in deduplicatedDesktopTasks.map { it.task1.key.id }
+ }
+ .toMutableList()
+ .apply {
+ addAll(
+ deduplicatedDesktopTasks.filter { currentTask ->
+ val currentTaskKey = currentTask.task1.key
+ currentTaskKey.id !in shownTasks.map { it.task1.key.id } &&
+ shownHotseatItems.none { hotseatItem ->
+ hotseatItem.targetPackage == currentTaskKey.packageName &&
+ hotseatItem.user.identifier == currentTaskKey.userId
+ }
+ }
+ )
+ }
+ } else {
+ val desktopTaskIds = desktopTasks.map { it.task1.key.id }
+ val shownHotseatItemTaskIds =
+ shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
+
+ shownTasks
+ .filter { !it.supportsMultipleTasks() && it.task1.key.id in desktopTaskIds }
+ .toMutableList()
+ .apply {
+ addAll(
+ desktopTasks.filter { desktopTask ->
+ desktopTask.task1.key.id !in
+ shownTasks.map { shownTask -> shownTask.task1.key.id }
+ }
+ )
+ removeAll { it.task1.key.id in shownHotseatItemTaskIds }
+ }
+ }
+
+ return newShownTasks
}
private fun computeShownRecentTasks(): List<GroupTask> {
@@ -305,7 +339,6 @@
}
// Remove the current task.
val allRecentTasks = allRecentTasks.subList(0, allRecentTasks.size - 1)
- // TODO(b/315344726 Multi-instance support): dedupe Tasks of the same package too
var shownTasks = dedupeHotseatTasks(allRecentTasks, shownHotseatItems)
if (shownTasks.size > MAX_RECENT_TASKS) {
// Remove any tasks older than MAX_RECENT_TASKS.
@@ -318,10 +351,22 @@
groupTasks: List<GroupTask>,
shownHotseatItems: List<ItemInfo>,
): List<GroupTask> {
- val hotseatPackages = shownHotseatItems.map { item -> item.targetPackage }
- return groupTasks.filter { groupTask ->
- groupTask.hasMultipleTasks() ||
- !hotseatPackages.contains(groupTask.task1.key.packageName)
+ return if (Flags.enableMultiInstanceMenuTaskbar()) {
+ groupTasks.filter { groupTask ->
+ val taskKey = groupTask.task1.key
+ // Keep tasks that are group tasks or unique package name/user combinations
+ groupTask.hasMultipleTasks() ||
+ shownHotseatItems.none {
+ it.targetPackage == taskKey.packageName &&
+ it.user.identifier == taskKey.userId
+ }
+ }
+ } else {
+ val hotseatPackages = shownHotseatItems.map { it.targetPackage }
+ groupTasks.filter { groupTask ->
+ groupTask.hasMultipleTasks() ||
+ !hotseatPackages.contains(groupTask.task1.key.packageName)
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 6b9f5a9..e4e97e5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -402,6 +402,16 @@
view.setTag(null);
}
+ /** Loop through all {@link FolderIcon} as child views and clear listeners to avoid leak. */
+ public void removeFolderIconListeners() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (getChildAt(i) instanceof FolderIcon fi) {
+ fi.removeListeners();
+ }
+ }
+ }
+
/** Inflates/binds the hotseat items and recent tasks to the view. */
protected void updateItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
// Filter out unsupported items.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index e0be39d..0f05887 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -365,6 +365,7 @@
if (enableTaskbarPinning()) {
mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
}
+ mTaskbarView.removeFolderIconListeners();
LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index f2cedba..474ede0 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -838,6 +838,15 @@
splitScreen?.startIntent(intent, userId, fillInIntent, position, options, instanceId)
}
+ /**
+ * Call the desktop mode interface to start a TRANSIT_OPEN transition when launching an intent
+ * from the taskbar so that it can be handled in desktop mode.
+ */
+ fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) =
+ executeWithErrorLog({ "Failed call startLaunchIntentTransition" }) {
+ desktopMode?.startLaunchIntentTransition(intent, options, displayId)
+ }
+
//
// One handed
//
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
index f973dd0..1dab18a 100644
--- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -17,8 +17,11 @@
package com.android.quickstep.views
import android.content.Context
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
import android.util.AttributeSet
import android.widget.ImageButton
+import com.android.launcher3.R
/**
* Button for supporting multiple desktop sessions. The button will be next to the first TaskView
@@ -26,5 +29,21 @@
*/
class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
ImageButton(context, attrs) {
- // TODO(b/382057498): add this button the overview.
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+
+ background =
+ ShapeDrawable().apply {
+ shape =
+ RoundRectShape(
+ FloatArray(8) { R.dimen.add_desktop_button_size.toFloat() },
+ null,
+ null,
+ )
+ setTint(
+ resources.getColor(android.R.color.system_surface_bright_light, context.theme)
+ )
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index cfad303..e037045 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -547,6 +547,8 @@
private final int mSplitPlaceholderSize;
private final int mSplitPlaceholderInset;
private final ClearAllButton mClearAllButton;
+ @Nullable
+ private AddDesktopButton mAddDesktopButton = null;
private final Rect mClearAllButtonDeadZoneRect = new Rect();
private final Rect mTaskViewDeadZoneRect = new Rect();
private final Rect mTopRowDeadZoneRect = new Rect();
@@ -902,6 +904,12 @@
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
.inflate(R.layout.overview_clear_all_button, this, false);
mClearAllButton.setOnClickListener(this::dismissAllTasks);
+
+ if (DesktopModeStatus.enableMultipleDesktops(mContext)) {
+ mAddDesktopButton = (AddDesktopButton) LayoutInflater.from(context).inflate(
+ R.layout.overview_add_desktop_button, this, false);
+ }
+
mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
10 /* initial size */);
mGroupedTaskViewPool = new ViewPool<>(context, this,
@@ -1880,7 +1888,7 @@
}
mLoadPlanEverApplied = true;
if (taskGroups == null || taskGroups.isEmpty()) {
- removeTasksViewsAndClearAllButton();
+ removeAllTaskViews();
onTaskStackUpdated();
// With all tasks removed, touch handling in PagedView is disabled and we need to reset
// touch state or otherwise values will be obsolete.
@@ -1943,6 +1951,11 @@
taskGroups = mUtils.sortDesktopTasksToFront(taskGroups);
}
+ if (mAddDesktopButton != null) {
+ // Add `mAddDesktopButton` as the first child.
+ addView(mAddDesktopButton);
+ }
+
// 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--) {
@@ -2088,13 +2101,14 @@
return mModel.isLoadingTasksInBackground();
}
- private void removeTasksViewsAndClearAllButton() {
+ private void removeAllTaskViews() {
// This handles an edge case where applyLoadPlan happens during a gesture when the only
// Task is one with excludeFromRecents, in which case we should not remove it.
CollectionsKt
.filter(getTaskViews(), taskView -> !isGestureActive() || !taskView.isRunningTask())
.forEach(this::removeView);
if (!hasTaskViews()) {
+ removeView(mAddDesktopButton);
removeView(mClearAllButton);
}
}
@@ -2324,6 +2338,9 @@
float taskAlignmentTranslationY = getTaskAlignmentTranslationY();
mClearAllButton.setTaskAlignmentTranslationY(taskAlignmentTranslationY);
+ if (mAddDesktopButton != null) {
+ mAddDesktopButton.setTranslationY(taskAlignmentTranslationY);
+ }
updateGridProperties();
}
@@ -3006,6 +3023,9 @@
taskView = getTaskViewFromPool(TaskViewType.SINGLE);
taskView.bind(runningTasks[0], mOrientationState, mTaskOverlayFactory);
}
+ if (mAddDesktopButton != null && wasEmpty) {
+ addView(mAddDesktopButton);
+ }
addView(taskView, getRunningTaskExpectedIndex(taskView));
runningTaskViewId = taskView.getTaskViewId();
if (wasEmpty) {
@@ -3426,6 +3446,12 @@
+ snappedTaskNonGridScrollAdjustment);
}
+ if (mAddDesktopButton != null) {
+ mAddDesktopButton.setTranslationX(
+ gridTranslations.get(getFirstTaskView()) - snappedTaskGridTranslationX
+ + snappedTaskNonGridScrollAdjustment);
+ }
+
final TaskView runningTask = getRunningTaskView();
if (showAsGrid() && enableGridOnlyOverview() && runningTask != null) {
runActionOnRemoteHandles(
@@ -4137,6 +4163,7 @@
if (taskCount == 1) {
removeViewInLayout(mClearAllButton);
+ removeViewInLayout(mAddDesktopButton);
if (isHomeTaskDismissed) {
updateEmptyMessage();
} else if (!mSplitSelectStateController.isSplitSelectActive()) {
@@ -4468,7 +4495,7 @@
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> {
UI_HELPER_EXECUTOR.getHandler().post(
ActivityManagerWrapper.getInstance()::removeAllRecentTasks);
- removeTasksViewsAndClearAllButton();
+ removeAllTaskViews();
startHome();
});
}
@@ -4627,6 +4654,11 @@
taskView.setStableAlpha(alpha);
}
mClearAllButton.setContentAlpha(mContentAlpha);
+
+ // TODO(b/389209338): Handle the visibility of the `mAddDesktopButton`.
+ if (mAddDesktopButton != null) {
+ mAddDesktopButton.setAlpha(mContentAlpha);
+ }
int alphaInt = Math.round(alpha * 255);
mEmptyMessagePaint.setAlpha(alphaInt);
mEmptyIcon.setAlpha(alphaInt);
@@ -4923,9 +4955,11 @@
+ carouselHiddenOffsetSize;
if (child instanceof TaskView taskView) {
taskView.getPrimaryTaskOffsetTranslationProperty().set(taskView, totalTranslationX);
- } else {
+ } else if (child instanceof ClearAllButton) {
getPagedOrientationHandler().getPrimaryViewTranslate().set(child,
totalTranslationX);
+ } else {
+ // TODO(b/389209581): Handle the page offsets update of the 'mAddDesktopButton'.
}
if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) {
runActionOnRemoteHandles(
@@ -6104,6 +6138,13 @@
outPageScrolls[clearAllIndex] = clearAllScroll;
}
+ int addDesktopButtonIndex = indexOfChild(mAddDesktopButton);
+ if (addDesktopButtonIndex != -1 && addDesktopButtonIndex < outPageScrolls.length) {
+ outPageScrolls[addDesktopButtonIndex] =
+ newPageScrolls[addDesktopButtonIndex] + Math.round(
+ mAddDesktopButton.getTranslationX());
+ }
+
int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
getTaskViews().forEachWithIndexInParent((index, taskView) -> {
float scrollDiff = taskView.getScrollAdjustment(showAsGrid);
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index 13880f1..9ca8a1b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -19,9 +19,11 @@
import android.animation.AnimatorTestRule
import android.content.ComponentName
import android.content.Intent
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR
import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
@@ -64,6 +66,7 @@
FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
FLAG_ENABLE_BUBBLE_BAR,
)
+@DisableFlags(FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR)
class TaskbarOverflowTest {
@get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index ed0c928..fbfc40b 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -23,10 +23,12 @@
import android.content.res.Resources
import android.os.Process
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import android.platform.test.rule.TestWatcher
import android.testing.AndroidTestingRunner
import com.android.internal.R
import com.android.launcher3.BubbleTextView.RunningAppState
+import com.android.launcher3.Flags
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
import com.android.launcher3.model.data.AppInfo
@@ -57,6 +59,7 @@
import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
+@EnableFlags(Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR)
class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
@get:Rule val mockitoRule = MockitoJUnit.rule()
@@ -323,8 +326,12 @@
assertThat(hotseatItem1.taskId).isEqualTo(1)
}
+ /**
+ * Tests that in desktop mode, when two tasks have the same package name and one is in the
+ * hotseat, only the hotseat item represents the app, and no duplicate is shown in recent apps.
+ */
@Test
- fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_hotseatCoversFirstTask() {
+ fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_onlyHotseatCoversTask() {
setInDesktopMode(true)
val newHotseatItems =
@@ -338,16 +345,16 @@
recentTaskPackages = emptyList(),
)
- // First task is in Hotseat Items
+ // The task is in Hotseat Items
assertThat(newHotseatItems).hasLength(2)
assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
assertThat(newHotseatItems[1]).isNotInstanceOf(TaskItemInfo::class.java)
val hotseatItem1 = newHotseatItems[0] as TaskItemInfo
- assertThat(hotseatItem1.taskId).isEqualTo(1)
- // Second task is in shownTasks
+ assertThat(hotseatItem1.targetPackage).isEqualTo(HOTSEAT_PACKAGE_1)
+
+ // The other task of the same package is not in shownTasks
val shownTasks = recentAppsController.shownTasks.map { it.task1 }
- assertThat(shownTasks)
- .containsExactlyElementsIn(listOf(createTask(id = 2, HOTSEAT_PACKAGE_1)))
+ assertThat(shownTasks).isEmpty()
}
@Test
@@ -530,8 +537,12 @@
assertThat(shownTasks).isEqualTo(listOf(task1, task2))
}
+ /**
+ * Tests that when multiple instances of the same app are running in desktop mode and the app is
+ * not in the hotseat, only one instance is shown in the recent apps section.
+ */
@Test
- fun onRecentTasksChanged_inDesktopMode_multiInstance_shownTasks_maintainsOrder() {
+ fun onRecentTasksChanged_inDesktopMode_multiInstance_noHotseat_shownTasksHasOneInstance() {
setInDesktopMode(true)
val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
@@ -541,43 +552,10 @@
recentTaskPackages = emptyList(),
)
- prepareHotseatAndRunningAndRecentApps(
- hotseatPackages = emptyList(),
- runningTasks = listOf(task2, task1),
- recentTaskPackages = emptyList(),
- )
-
+ // Assert that shownTasks contains only one instance of the app
val shownTasks = recentAppsController.shownTasks.map { it.task1 }
- assertThat(shownTasks).isEqualTo(listOf(task1, task2))
- }
-
- @Test
- fun updateHotseatItems_inDesktopMode_multiInstanceHotseatPackage_shownItems_maintainsOrder() {
- setInDesktopMode(true)
- val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
- val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
- prepareHotseatAndRunningAndRecentApps(
- hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
- runningTasks = listOf(task1, task2),
- recentTaskPackages = emptyList(),
- )
- updateRecentTasks( // Trigger a recent-tasks change before calling updateHotseatItems()
- runningTasks = listOf(task2, task1),
- recentTaskPackages = emptyList(),
- )
-
- prepareHotseatAndRunningAndRecentApps(
- hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
- runningTasks = listOf(task2, task1),
- recentTaskPackages = emptyList(),
- )
-
- val newHotseatItems = recentAppsController.shownHotseatItems
- assertThat(newHotseatItems).hasSize(1)
- assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
- assertThat((newHotseatItems[0] as TaskItemInfo).taskId).isEqualTo(1)
- val shownTasks = recentAppsController.shownTasks.map { it.task1 }
- assertThat(shownTasks).isEqualTo(listOf(task2))
+ assertThat(shownTasks).hasSize(1)
+ assertThat(shownTasks[0].key.packageName).isEqualTo(RUNNING_APP_PACKAGE_1)
}
@Test
diff --git a/res/drawable/ic_desktop_add.xml b/res/drawable/ic_desktop_add.xml
index fa5e0de..d31b04b 100644
--- a/res/drawable/ic_desktop_add.xml
+++ b/res/drawable/ic_desktop_add.xml
@@ -14,8 +14,8 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48dp"
- android:height="48dp"
+ android:width="24dp"
+ android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
diff --git a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
index a78da23..225e12f 100644
--- a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
+++ b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
@@ -112,7 +112,9 @@
?.let { d ->
li.createBadgedIconBitmap(
d,
- IconOptions().setExtractedColor(Themes.getColorAccent(context)),
+ IconOptions()
+ .setExtractedColor(Themes.getColorAccent(context))
+ .setSourceHint(getSourceHint(info, cache)),
)
} ?: BitmapInfo.LOW_RES_INFO
}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 88a60ea..9f99e8f 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -594,7 +594,8 @@
@VisibleForTesting
synchronized boolean isItemInDb(ComponentKey cacheKey) {
- return getEntryFromDBLocked(cacheKey, new CacheEntry(), DEFAULT_LOOKUP_FLAG);
+ return getEntryFromDBLocked(cacheKey, new CacheEntry(), DEFAULT_LOOKUP_FLAG,
+ LauncherActivityCachingLogic.INSTANCE);
}
/**
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
index 4af564e..12c14fb 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
@@ -16,9 +16,11 @@
package com.android.launcher3.icons.mono
+import android.content.ComponentName
import android.graphics.Color
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.ColorDrawable
+import android.os.Process
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
@@ -29,6 +31,9 @@
import com.android.launcher3.Flags
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.SourceHint
+import com.android.launcher3.icons.cache.LauncherActivityCachingLogic
+import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
@@ -45,6 +50,12 @@
private val iconFactory = BaseIconFactory(context, DisplayMetrics.DENSITY_MEDIUM, 30)
+ private val sourceHint =
+ SourceHint(
+ key = ComponentKey(ComponentName("a", "a"), Process.myUserHandle()),
+ logic = LauncherActivityCachingLogic,
+ )
+
@Test
fun `createThemedBitmap when mono drawable is present`() {
val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
@@ -81,7 +92,8 @@
val themeBitmap =
MonoIconThemeController().createThemedBitmap(icon, iconInfo, iconFactory)!!
assertNotNull(
- MonoIconThemeController().decode(themeBitmap.serialize(), iconInfo, iconFactory)
+ MonoIconThemeController()
+ .decode(themeBitmap.serialize(), iconInfo, iconFactory, sourceHint)
)
}
@@ -90,7 +102,10 @@
ensureBitmapSerializationSupported()
val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
val iconInfo = iconFactory.createBadgedIconBitmap(icon)
- assertNull(MonoIconThemeController().decode(byteArrayOf(1, 1, 1, 1), iconInfo, iconFactory))
+ assertNull(
+ MonoIconThemeController()
+ .decode(byteArrayOf(1, 1, 1, 1), iconInfo, iconFactory, sourceHint)
+ )
}
@Test