Merge "Add tracing for opening/unminimizing app in Desktop Windowing" into main
diff --git a/Android.bp b/Android.bp
index 6bd8602..5b986ab 100644
--- a/Android.bp
+++ b/Android.bp
@@ -389,6 +389,7 @@
"//frameworks/libs/systemui:contextualeducationlib",
"//frameworks/libs/systemui:msdl",
"SystemUI-statsd",
+ "WindowManager-Shell-shared-AOSP",
"launcher-testing-shared",
"androidx.lifecycle_lifecycle-common-java8",
"androidx.lifecycle_lifecycle-extensions",
diff --git a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
index a833ccf..b33fd38 100644
--- a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -60,9 +61,9 @@
}
@Override
- public void showAppBubble(Intent intent) {
+ public void showAppBubble(Intent intent, UserHandle user) {
if (intent == null || intent.getPackage() == null) return;
- SystemUiProxy.INSTANCE.get(this).showAppBubble(intent);
+ SystemUiProxy.INSTANCE.get(this).showAppBubble(intent, user);
}
/** Callback invoked when a drag is initiated within this context. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
index 3bff31f..b7000db 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
@@ -108,7 +108,7 @@
revealHoverToolTip();
mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, true);
}
- return true;
+ return false;
}
private void revealHoverToolTip() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index d733dc6..21c8255 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -687,6 +687,20 @@
}
}
+ /**
+ * Signal from SysUI indicating that a non-mirroring display was just connected to the
+ * primary device.
+ */
+ public void onDisplayReady(int displayId) {
+ }
+
+ /**
+ * Signal from SysUI indicating that a previously connected non-mirroring display was just
+ * removed from the primary device.
+ */
+ public void onDisplayRemoved(int displayId) {
+ }
+
private void removeActivityCallbacksAndListeners() {
if (mActivity != null) {
mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index e704691..feb9b33 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -56,6 +56,7 @@
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.LogUtils;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import java.io.PrintWriter;
@@ -219,7 +220,7 @@
.getAreDesktopTasksVisibleAndNotInOverview()) {
shortcuts.addAll(mControllers.uiController.getSplitMenuOptions().toList());
}
- if (com.android.wm.shell.Flags.enableBubbleAnything()) {
+ if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
shortcuts.add(BUBBLE);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index caac35e..b0cb484 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -36,6 +36,7 @@
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
+import android.util.Log;
import android.util.Property;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -110,6 +111,8 @@
private boolean mForceHideRing = false;
private Animator mRingScaleAnim;
+ private int mWidth;
+
private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y =
new FloatProperty<PredictedAppIcon>("slotMachineTranslationY") {
@Override
@@ -300,7 +303,13 @@
}
private int getOutlineOffsetX() {
- return (getMeasuredWidth() - mNormalizedIconSize) / 2;
+ int measuredWidth = getMeasuredWidth();
+ if (mDisplay != DISPLAY_TASKBAR) {
+ Log.d("b/387844520", "getOutlineOffsetX: measured width = " + measuredWidth
+ + ", mNormalizedIconSize = " + mNormalizedIconSize
+ + ", last updated width = " + mWidth);
+ }
+ return (mWidth - mNormalizedIconSize) / 2;
}
private int getOutlineOffsetY() {
@@ -313,7 +322,11 @@
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
+ mWidth = w;
mSlotIconBound.offsetTo((w - getIconSize()) / 2, (h - getIconSize()) / 2);
+ if (mDisplay != DISPLAY_TASKBAR) {
+ Log.d("b/387844520", "calling updateRingPath from onSizeChanged");
+ }
updateRingPath();
}
@@ -325,6 +338,7 @@
private void updateRingPath() {
mRingPath.reset();
+ mTmpMatrix.reset();
mTmpMatrix.setTranslate(getOutlineOffsetX(), getOutlineOffsetY());
mRingPath.addPath(mShapePath, mTmpMatrix);
@@ -339,6 +353,7 @@
mTmpMatrix.preTranslate(-mNormalizedIconSize, -mNormalizedIconSize);
mRingPath.addPath(mShapePath, mTmpMatrix);
}
+ invalidate();
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 810325c..58ebc50 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -64,7 +64,6 @@
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
-import static com.android.wm.shell.Flags.enableBubbleAnything;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import android.animation.Animator;
@@ -85,6 +84,7 @@
import android.os.IRemoteCallback;
import android.os.SystemProperties;
import android.os.Trace;
+import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.Display;
import android.view.HapticFeedbackConstants;
@@ -200,6 +200,7 @@
import com.android.systemui.unfold.dagger.UnfoldMain;
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
import com.android.systemui.unfold.updates.RotationChangeProvider;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -467,7 +468,7 @@
if (Flags.enablePrivateSpace()) {
shortcuts.add(UNINSTALL_APP);
}
- if (enableBubbleAnything()) {
+ if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
shortcuts.add(BUBBLE_SHORTCUT);
}
return shortcuts.stream();
@@ -1431,9 +1432,9 @@
}
@Override
- public void showAppBubble(Intent intent) {
+ public void showAppBubble(Intent intent, UserHandle user) {
if (intent == null || intent.getPackage() == null) return;
- SystemUiProxy.INSTANCE.get(this).showAppBubble(intent);
+ SystemUiProxy.INSTANCE.get(this).showAppBubble(intent, user);
}
/** Sets the location of the bubble bar */
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3dd1473..8b76ce9 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1826,9 +1826,7 @@
final Rect hotseatKeepClearArea = getKeepClearAreaForHotseat();
final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
- .startSwipePipToHome(taskInfo.topActivity,
- taskInfo.topActivityInfo,
- runningTaskTarget.taskInfo.pictureInPictureParams,
+ .startSwipePipToHome(taskInfo,
homeRotation,
hotseatKeepClearArea);
if (destinationBounds == null) {
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index e0fa77a..e1d4536 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -28,7 +28,6 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
-import android.app.TaskInfo;
import android.content.Intent;
import android.os.SystemClock;
import android.view.MotionEvent;
@@ -47,7 +46,6 @@
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.wm.shell.shared.GroupedTaskInfo;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -334,13 +332,7 @@
return new int[]{INVALID_TASK_ID, INVALID_TASK_ID};
} else {
if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
- if (mRunningTask.getVisibleTasks().isEmpty()) {
- return new int[0];
- }
- GroupedTaskInfo topRunningTask = mRunningTask.getVisibleTasks().getFirst();
- List<TaskInfo> groupedTasks = topRunningTask.getTaskInfoList();
- return groupedTasks.stream().mapToInt(
- groupedTask -> groupedTask.taskId).toArray();
+ return mRunningTask.topGroupedTaskIds();
} else {
int cachedTasksSize = mRunningTask.mAllCachedTasks.size();
int count = Math.min(cachedTasksSize, getMultipleTasks ? 2 : 1);
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 32ccd72..ee4ee38 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM;
+import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.KeyguardManager;
@@ -39,6 +40,7 @@
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.shared.GroupedTaskInfo;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -116,7 +118,8 @@
@Override
public void onTaskMovedToFront(GroupedTaskInfo taskToFront) {
mMainThreadExecutor.execute(() -> {
- topTaskTracker.handleTaskMovedToFront(taskToFront.getTaskInfo1());
+ topTaskTracker.handleTaskMovedToFront(
+ taskToFront.getBaseGroupedTask().getTaskInfo1());
});
}
@@ -340,50 +343,74 @@
int numVisibleTasks = 0;
for (GroupedTaskInfo rawTask : rawTasks) {
- if (rawTask.getType() == TYPE_FREEFORM) {
+ if (rawTask.isBaseType(TYPE_FREEFORM)) {
// TYPE_FREEFORM tasks is only created when desktop mode can be entered,
// leftover TYPE_FREEFORM tasks created when flag was on should be ignored.
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- GroupTask desktopTask = createDesktopTask(rawTask);
+ GroupTask desktopTask = createDesktopTask(rawTask.getBaseGroupedTask());
if (desktopTask != null) {
allTasks.add(desktopTask);
}
}
continue;
}
- TaskInfo taskInfo1 = rawTask.getTaskInfo1();
- TaskInfo taskInfo2 = rawTask.getTaskInfo2();
- Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
- Task task1 = loadKeysOnly
- ? new Task(task1Key)
- : Task.from(task1Key, taskInfo1,
- tmpLockedUsers.get(task1Key.userId) /* isLocked */);
- Task task2 = null;
- if (taskInfo2 != null) {
- // Is split task
- Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
- task2 = loadKeysOnly
- ? new Task(task2Key)
- : Task.from(task2Key, taskInfo2,
- tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+
+ if (Flags.enableShellTopTaskTracking()) {
+ final TaskInfo taskInfo1 = rawTask.getBaseGroupedTask().getTaskInfo1();
+ final Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
+ final Task task1 = Task.from(task1Key, taskInfo1,
+ tmpLockedUsers.get(task1Key.userId) /* isLocked */);
+ final Task task2;
+ final SplitConfigurationOptions.SplitBounds launcherSplitBounds;
+
+ if (rawTask.isBaseType(TYPE_SPLIT)) {
+ final TaskInfo taskInfo2 = rawTask.getBaseGroupedTask().getTaskInfo2();
+ final Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
+ task2 = Task.from(task2Key, taskInfo2,
+ tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+ launcherSplitBounds =
+ convertShellSplitBoundsToLauncher(
+ rawTask.getBaseGroupedTask().getSplitBounds());
+ } else {
+ task2 = null;
+ launcherSplitBounds = null;
+ }
+ allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
} else {
- // Is fullscreen task
- if (numVisibleTasks > 0) {
- boolean isExcluded = (taskInfo1.baseIntent.getFlags()
- & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
- if (taskInfo1.isTopActivityTransparent && isExcluded) {
- // If there are already visible tasks, then ignore the excluded tasks and
- // don't add them to the returned list
- continue;
+ TaskInfo taskInfo1 = rawTask.getTaskInfo1();
+ TaskInfo taskInfo2 = rawTask.getTaskInfo2();
+ Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
+ Task task1 = loadKeysOnly
+ ? new Task(task1Key)
+ : Task.from(task1Key, taskInfo1,
+ tmpLockedUsers.get(task1Key.userId) /* isLocked */);
+ Task task2 = null;
+ if (taskInfo2 != null) {
+ // Is split task
+ Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
+ task2 = loadKeysOnly
+ ? new Task(task2Key)
+ : Task.from(task2Key, taskInfo2,
+ tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+ } else {
+ // Is fullscreen task
+ if (numVisibleTasks > 0) {
+ boolean isExcluded = (taskInfo1.baseIntent.getFlags()
+ & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+ if (taskInfo1.isTopActivityTransparent && isExcluded) {
+ // If there are already visible tasks, then ignore the excluded tasks
+ // and don't add them to the returned list
+ continue;
+ }
}
}
+ if (taskInfo1.isVisible) {
+ numVisibleTasks++;
+ }
+ final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
+ convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
+ allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
}
- if (taskInfo1.isVisible) {
- numVisibleTasks++;
- }
- final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
- convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
- allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
}
return allTasks;
@@ -409,14 +436,6 @@
return new DesktopTask(tasks);
}
- private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
- ArrayList<GroupTask> newTasks = new ArrayList<>();
- for (int i = 0; i < tasks.size(); i++) {
- newTasks.add(tasks.get(i).copy());
- }
- return newTasks;
- }
-
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "RecentTasksList:");
writer.println(prefix + " mChangeId=" + mChangeId);
@@ -439,14 +458,7 @@
}
writer.println(prefix + " rawTasks=[");
for (GroupedTaskInfo task : rawTasks) {
- TaskInfo taskInfo1 = task.getTaskInfo1();
- TaskInfo taskInfo2 = task.getTaskInfo2();
- ComponentName cn1 = taskInfo1.topActivity;
- ComponentName cn2 = taskInfo2 != null ? taskInfo2.topActivity : null;
- writer.println(prefix + " t1: (id=" + taskInfo1.taskId
- + "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
- + " t2: (id=" + (taskInfo2 != null ? taskInfo2.taskId : "-1")
- + "; package=" + (cn2 != null ? cn2.getPackageName() + ")" : "no package)"));
+ writer.println(prefix + task);
}
writer.println(prefix + " ]");
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index 474ede0..f679fec 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -19,11 +19,9 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
import android.app.PendingIntent
-import android.app.PictureInPictureParams
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.content.pm.ActivityInfo
import android.content.pm.ShortcutInfo
import android.graphics.Point
import android.graphics.Rect
@@ -499,20 +497,12 @@
/** @return Destination bounds of auto-pip animation, `null` if the animation is not ready. */
fun startSwipePipToHome(
- componentName: ComponentName?,
- activityInfo: ActivityInfo?,
- pictureInPictureParams: PictureInPictureParams?,
+ taskInfo: RunningTaskInfo,
launcherRotation: Int,
hotseatKeepClearArea: Rect?,
): Rect? {
executeWithErrorLog({ "Failed call startSwipePipToHome" }) {
- return pip?.startSwipePipToHome(
- componentName,
- activityInfo,
- pictureInPictureParams,
- launcherRotation,
- hotseatKeepClearArea,
- )
+ return pip?.startSwipePipToHome(taskInfo, launcherRotation, hotseatKeepClearArea)
}
return null
}
@@ -673,8 +663,10 @@
*
* @param intent the intent used to create the bubble.
*/
- fun showAppBubble(intent: Intent?) =
- executeWithErrorLog({ "Failed call showAppBubble" }) { bubbles?.showAppBubble(intent) }
+ fun showAppBubble(intent: Intent?, user: UserHandle) =
+ executeWithErrorLog({ "Failed call showAppBubble" }) {
+ bubbles?.showAppBubble(intent, user)
+ }
/** Tells SysUI to show the expanded view. */
fun showExpandedView() =
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index bfd6107..b3d9da3 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -24,12 +24,12 @@
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_A;
+import static com.android.wm.shell.Flags.enableShellTopTaskTracking;
import static com.android.wm.shell.Flags.enableFlexibleSplit;
import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
-import android.content.Context;
import android.util.ArrayMap;
import android.util.Log;
@@ -37,7 +37,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import com.android.launcher3.dagger.ApplicationContext;
import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.util.DaggerSingletonObject;
import com.android.launcher3.util.DaggerSingletonTracker;
@@ -77,8 +76,6 @@
private static final int HISTORY_SIZE = 5;
- private final Context mContext;
-
// Only used when Flags.enableShellTopTaskTracking() is disabled
// Ordered list with first item being the most recent task.
private final LinkedList<TaskInfo> mOrderedTaskList = new LinkedList<>();
@@ -87,20 +84,13 @@
private int mPinnedTaskId = INVALID_TASK_ID;
// Only used when Flags.enableShellTopTaskTracking() is enabled
- // Mapping of display id to running tasks. Running tasks are ordered from top most to
- // bottom most.
- private ArrayMap<Integer, ArrayList<GroupedTaskInfo>> mVisibleTasks = new ArrayMap<>();
+ // Mapping of display id to visible tasks. Visible tasks are ordered from top most to bottom
+ // most.
+ private ArrayMap<Integer, GroupedTaskInfo> mVisibleTasks = new ArrayMap<>();
@Inject
- public TopTaskTracker(@ApplicationContext Context context, DaggerSingletonTracker tracker,
- SystemUiProxy systemUiProxy) {
- mContext = context;
-
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
- // Just prepopulate a list for the default display tasks so we don't need to add null
- // checks everywhere
- mVisibleTasks.put(DEFAULT_DISPLAY, new ArrayList<>());
- } else {
+ public TopTaskTracker(DaggerSingletonTracker tracker, SystemUiProxy systemUiProxy) {
+ if (!enableShellTopTaskTracking()) {
mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
@@ -109,7 +99,7 @@
}
tracker.addCloseable(() -> {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (enableShellTopTaskTracking()) {
return;
}
@@ -120,7 +110,7 @@
@Override
public void onTaskRemoved(int taskId) {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (enableShellTopTaskTracking()) {
return;
}
@@ -133,7 +123,7 @@
}
void handleTaskMovedToFront(TaskInfo taskInfo) {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (enableShellTopTaskTracking()) {
return;
}
@@ -187,32 +177,25 @@
* Called when the set of visible tasks have changed.
*/
public void onVisibleTasksChanged(GroupedTaskInfo[] visibleTasks) {
- if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (!enableShellTopTaskTracking()) {
return;
}
- // TODO(346588978): Per-display info, just have everything in order by display
-
// Clear existing tasks for each display
- mVisibleTasks.forEach((displayId, visibleTasksOnDisplay) -> visibleTasksOnDisplay.clear());
+ mVisibleTasks.clear();
// Update the visible tasks on each display
- for (int i = 0; i < visibleTasks.length; i++) {
- final int displayId = visibleTasks[i].getTaskInfo1().getDisplayId();
- final ArrayList<GroupedTaskInfo> displayTasks;
- if (mVisibleTasks.containsKey(displayId)) {
- displayTasks = mVisibleTasks.get(displayId);
- } else {
- displayTasks = new ArrayList<>();
- mVisibleTasks.put(displayId, displayTasks);
- }
- displayTasks.add(visibleTasks[i]);
+ Log.d(TAG, "onVisibleTasksChanged:");
+ for (GroupedTaskInfo groupedTask : visibleTasks) {
+ Log.d(TAG, "\t" + groupedTask);
+ final int displayId = groupedTask.getBaseGroupedTask().getTaskInfo1().getDisplayId();
+ mVisibleTasks.put(displayId, groupedTask);
}
}
@Override
public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (enableShellTopTaskTracking()) {
return;
}
@@ -224,7 +207,7 @@
}
public void onTaskChanged(RunningTaskInfo taskInfo) {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (enableShellTopTaskTracking()) {
return;
}
@@ -238,7 +221,7 @@
@Override
public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (enableShellTopTaskTracking()) {
return;
}
@@ -262,7 +245,7 @@
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (enableShellTopTaskTracking()) {
return;
}
@@ -271,7 +254,7 @@
@Override
public void onActivityUnpinned() {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (enableShellTopTaskTracking()) {
return;
}
@@ -279,16 +262,17 @@
}
/**
- * @return index 0 will be task in left/top position, index 1 in right/bottom position.
- * Will return empty array if device is not in staged split
+ * Return the running split task ids. Index 0 will be task in left/top position, index 1 in
+ * right/bottom position, or and empty array if device is not in splitscreen.
*/
public int[] getRunningSplitTaskIds() {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
- // TODO(346588978): This assumes default display for now
- final ArrayList<GroupedTaskInfo> visibleTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
- final GroupedTaskInfo splitTaskInfo = visibleTasks.stream()
- .filter(taskInfo -> taskInfo.getType() == TYPE_SPLIT)
- .findFirst().orElse(null);
+ if (enableShellTopTaskTracking()) {
+ // TODO(346588978): This assumes default display as splitscreen is only currently there
+ final GroupedTaskInfo visibleTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
+ final GroupedTaskInfo splitTaskInfo =
+ visibleTasks != null && visibleTasks.isBaseType(TYPE_SPLIT)
+ ? visibleTasks.getBaseGroupedTask()
+ : null;
if (splitTaskInfo != null && splitTaskInfo.getSplitBounds() != null) {
return new int[] {
splitTaskInfo.getSplitBounds().leftTopTaskId,
@@ -317,24 +301,13 @@
* Dumps the list of tasks in top task tracker.
*/
public void dump(PrintWriter pw) {
- if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (!enableShellTopTaskTracking()) {
return;
}
- // TODO(346588978): This assumes default display for now
- final ArrayList<GroupedTaskInfo> displayTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
pw.println("TopTaskTracker:");
- pw.println(" tasks: [");
- for (GroupedTaskInfo taskInfo : displayTasks) {
- final TaskInfo info = taskInfo.getTaskInfo1();
- final boolean isExcluded = (info.baseIntent.getFlags()
- & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
- pw.println(" " + info.taskId + ": excluded=" + isExcluded
- + " visibleRequested=" + info.isVisibleRequested
- + " visible=" + info.isVisible
- + " " + info.baseIntent.getComponent());
- }
- pw.println(" ]");
+ mVisibleTasks.forEach((displayId, tasks) ->
+ pw.println(" visibleTasks(" + displayId + "): " + tasks));
}
/**
@@ -343,13 +316,12 @@
@NonNull
@UiThread
public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (enableShellTopTaskTracking()) {
// TODO(346588978): Currently ignore filterOnlyVisibleRecents, but perhaps make this an
// explicit filter For things to ignore (ie. PIP/Bubbles/Assistant/etc/so that this is
// explicit)
- // TODO(346588978): This assumes default display for now (as does all of Launcher)
- final ArrayList<GroupedTaskInfo> displayTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
- return new CachedTaskInfo(new ArrayList<>(displayTasks));
+ // TODO(346588978): This assumes default display as gesture nav is only supported there
+ return new CachedTaskInfo(mVisibleTasks.get(DEFAULT_DISPLAY));
} else {
if (filterOnlyVisibleRecents) {
// Since we only know about the top most task, any filtering may not be applied on
@@ -374,6 +346,11 @@
}
}
+ private static boolean isHomeTask(TaskInfo task) {
+ return task != null && task.configuration.windowConfiguration
+ .getActivityType() == ACTIVITY_TYPE_HOME;
+ }
+
private static boolean isRecentsTask(TaskInfo task) {
return task != null && task.configuration.windowConfiguration
.getActivityType() == ACTIVITY_TYPE_RECENTS;
@@ -384,7 +361,6 @@
* during the lifecycle of the task.
*/
public static class CachedTaskInfo {
-
// Only used when enableShellTopTaskTracking() is disabled
@Nullable
private final TaskInfo mTopTask;
@@ -393,40 +369,48 @@
// Only used when enableShellTopTaskTracking() is enabled
@Nullable
- private final GroupedTaskInfo mTopGroupedTask;
- @Nullable
- private final ArrayList<GroupedTaskInfo> mVisibleTasks;
+ private final GroupedTaskInfo mVisibleTasks;
// Only used when enableShellTopTaskTracking() is enabled
- CachedTaskInfo(@NonNull ArrayList<GroupedTaskInfo> visibleTasks) {
+ CachedTaskInfo(@Nullable GroupedTaskInfo visibleTasks) {
mAllCachedTasks = null;
mTopTask = null;
mVisibleTasks = visibleTasks;
- mTopGroupedTask = !mVisibleTasks.isEmpty() ? mVisibleTasks.getFirst() : null;
}
// Only used when enableShellTopTaskTracking() is disabled
CachedTaskInfo(@NonNull List<TaskInfo> allCachedTasks) {
mVisibleTasks = null;
- mTopGroupedTask = null;
mAllCachedTasks = allCachedTasks;
mTopTask = allCachedTasks.isEmpty() ? null : allCachedTasks.get(0);
}
/**
- * @return The list of visible tasks
+ * Returns the "base" task that is used the as the representative running task of the set
+ * of tasks initially provided.
+ *
+ * Not for general use, as in other windowing modes (ie. split/desktop) the caller should
+ * not make assumptions about there being a single base task.
+ * TODO(346588978): Try to remove all usage of this if possible
*/
- public ArrayList<GroupedTaskInfo> getVisibleTasks() {
- return mVisibleTasks;
+ @Nullable
+ private TaskInfo getLegacyBaseTask() {
+ if (enableShellTopTaskTracking()) {
+ return mVisibleTasks != null
+ ? mVisibleTasks.getBaseGroupedTask().getTaskInfo1()
+ : null;
+ } else {
+ return mTopTask;
+ }
}
/**
- * @return The top task id
+ * Returns the top task id.
*/
public int getTaskId() {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (enableShellTopTaskTracking()) {
// Callers should use topGroupedTaskContainsTask() instead
return INVALID_TASK_ID;
} else {
@@ -435,29 +419,58 @@
}
/**
- * @return Whether the top grouped task contains the given {@param taskId} if
- * Flags.enableShellTopTaskTracking() is true, otherwise it checks the top
- * task as reported from TaskStackListener.
+ * Returns the top grouped task ids if Flags.enableShellTopTaskTracking() is true, otherwise
+ * an empty array.
+ */
+ public int[] topGroupedTaskIds() {
+ if (enableShellTopTaskTracking()) {
+ if (mVisibleTasks == null) {
+ return new int[0];
+ }
+ List<TaskInfo> groupedTasks = mVisibleTasks.getTaskInfoList();
+ return groupedTasks.stream().mapToInt(
+ groupedTask -> groupedTask.taskId).toArray();
+ } else {
+ // Not used
+ return new int[0];
+ }
+ }
+
+ /**
+ * Returns whether the top grouped task contains the given {@param taskId} if
+ * Flags.enableShellTopTaskTracking() is true, otherwise it checks the top task as reported
+ * from TaskStackListener.
*/
public boolean topGroupedTaskContainsTask(int taskId) {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
- return mTopGroupedTask != null && mTopGroupedTask.containsTask(taskId);
+ if (enableShellTopTaskTracking()) {
+ return mVisibleTasks != null && mVisibleTasks.containsTask(taskId);
} else {
return mTopTask != null && mTopTask.taskId == taskId;
}
}
/**
- * Returns true if the root of the task chooser activity
+ * Returns true if this represents the task chooser activity
*/
public boolean isRootChooseActivity() {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
- // TODO(346588978): Update this to not make an assumption on a specific task info
- return mTopGroupedTask != null && ACTION_CHOOSER.equals(
- mTopGroupedTask.getTaskInfo1().baseIntent.getAction());
- } else {
- return mTopTask != null && ACTION_CHOOSER.equals(mTopTask.baseIntent.getAction());
- }
+ final TaskInfo baseTask = getLegacyBaseTask();
+ return baseTask != null && ACTION_CHOOSER.equals(baseTask.baseIntent.getAction());
+ }
+
+ /**
+ * Returns true if this represents the HOME activity type task
+ */
+ public boolean isHomeTask() {
+ final TaskInfo baseTask = getLegacyBaseTask();
+ return baseTask != null && TopTaskTracker.isHomeTask(baseTask);
+ }
+
+ /**
+ * Returns true if this represents the RECENTS activity type task
+ */
+ public boolean isRecentsTask() {
+ final TaskInfo baseTask = getLegacyBaseTask();
+ return baseTask != null && TopTaskTracker.isRecentsTask(baseTask);
}
/**
@@ -465,7 +478,7 @@
* is another running task that is not excluded from recents, returns that underlying task.
*/
public @Nullable CachedTaskInfo getVisibleNonExcludedTask() {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ if (enableShellTopTaskTracking()) {
// Callers should not need this when the full set of visible tasks are provided
return null;
}
@@ -485,49 +498,16 @@
}
/**
- * Returns true if this represents the HOME activity type task
- */
- public boolean isHomeTask() {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
- // TODO(346588978): Update this to not make an assumption on a specific task info
- return mTopGroupedTask != null
- && mTopGroupedTask.getTaskInfo1().getActivityType() == ACTIVITY_TYPE_HOME;
- } else {
- return mTopTask != null && mTopTask.configuration.windowConfiguration
- .getActivityType() == ACTIVITY_TYPE_HOME;
- }
- }
-
- /**
- * Returns true if this represents the RECENTS activity type task
- */
- public boolean isRecentsTask() {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
- // TODO(346588978): Update this to not make an assumption on a specific task info
- return mTopGroupedTask != null
- && TopTaskTracker.isRecentsTask(mTopGroupedTask.getTaskInfo1());
- } else {
- return TopTaskTracker.isRecentsTask(mTopTask);
- }
- }
-
- /**
* Returns {@link Task} array which can be used as a placeholder until the true object
* is loaded by the model
*/
public Task[] getPlaceholderTasks() {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
- // TODO(346588978): Update this to return more than a single task once the callers
- // are refactored
- if (mVisibleTasks.isEmpty()) {
- return new Task[0];
- }
- final TaskInfo info = mVisibleTasks.getFirst().getTaskInfo1();
- return new Task[]{Task.from(new TaskKey(info), info, false)};
- } else {
- return mTopTask == null ? new Task[0]
- : new Task[]{Task.from(new TaskKey(mTopTask), mTopTask, false)};
- }
+ final TaskInfo baseTask = getLegacyBaseTask();
+ // TODO(346588978): Update this to return more than a single task once the callers
+ // are refactored
+ return baseTask == null
+ ? new Task[0]
+ : new Task[]{Task.from(new TaskKey(baseTask), baseTask, false)};
}
/**
@@ -535,13 +515,12 @@
* placeholder until the true object is loaded by the model
*/
public Task[] getSplitPlaceholderTasks(int[] taskIds) {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
- if (mVisibleTasks.isEmpty()
- || mVisibleTasks.getFirst().getType() != TYPE_SPLIT) {
+ if (enableShellTopTaskTracking()) {
+ if (mVisibleTasks == null || !mVisibleTasks.isBaseType(TYPE_SPLIT)) {
return new Task[0];
}
- GroupedTaskInfo splitTask = mVisibleTasks.getFirst();
+ GroupedTaskInfo splitTask = mVisibleTasks.getBaseGroupedTask();
Task[] result = new Task[taskIds.length];
for (int i = 0; i < taskIds.length; i++) {
TaskInfo info = splitTask.getTaskById(taskIds[i]);
@@ -572,22 +551,11 @@
@Nullable
public String getPackageName() {
- if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
- // TODO(346588978): Update this to not make an assumption on a specific task info
- if (mTopGroupedTask == null) {
- return null;
- }
- final TaskInfo info = mTopGroupedTask.getTaskInfo1();
- if (info.baseActivity == null) {
- return null;
- }
- return info.baseActivity.getPackageName();
- } else {
- if (mTopTask == null || mTopTask.baseActivity == null) {
- return null;
- }
- return mTopTask.baseActivity.getPackageName();
+ final TaskInfo baseTask = getLegacyBaseTask();
+ if (baseTask == null || baseTask.baseActivity == null) {
+ return null;
}
+ return baseTask.baseActivity.getPackageName();
}
}
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index f7fb18b..15f320d 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -301,6 +301,22 @@
@BinderThread
@Override
+ public void onDisplayReady(int displayId) {
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
+ tis -> executeForTaskbarManager(
+ taskbarManager -> taskbarManager.onDisplayReady(displayId))));
+ }
+
+ @BinderThread
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
+ tis -> executeForTaskbarManager(
+ taskbarManager -> taskbarManager.onDisplayRemoved(displayId))));
+ }
+
+ @BinderThread
+ @Override
public void updateWallpaperVisibility(int displayId, boolean visible) {
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
tis -> executeForTaskbarManager(
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 703d631..2f95413 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -79,6 +79,10 @@
}
}
tasks.value = MapForStateFlow(recentTasks)
+ Log.d(
+ TAG,
+ "getAllTaskData: oldTasks ${oldTaskMap.keys}, newTasks: ${recentTasks.keys}",
+ )
}
}
return tasks.map { it.values.toList() }
diff --git a/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt b/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt
new file mode 100644
index 0000000..3823100
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 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.quickstep.recents.domain.model
+
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.recents.model.ThumbnailData
+
+/**
+ * Data class representing a task in the application.
+ *
+ * This class holds the essential information about a task, including its unique identifier, display
+ * title, associated icon, optional thumbnail data, and background color.
+ *
+ * @property id The unique identifier for this task. Must be an integer.
+ * @property title The display title of the task.
+ * @property titleDescription A content description of the task.
+ * @property icon An optional drawable resource representing an icon for the task. Can be null if no
+ * icon is required.
+ * @property thumbnail An optional [ThumbnailData] object containing thumbnail information. Can be
+ * null if no thumbnail is needed.
+ * @property backgroundColor The background color of the task, represented as an integer color
+ * value.
+ * @property isLocked Indicates whether the [Task] is locked.
+ */
+data class TaskModel(
+ val id: TaskId,
+ val title: String,
+ val titleDescription: String?,
+ val icon: Drawable?,
+ val thumbnail: ThumbnailData?,
+ val backgroundColor: Int,
+ val isLocked: Boolean,
+)
+
+typealias TaskId = Int
diff --git a/quickstep/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCase.kt b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCase.kt
new file mode 100644
index 0000000..a60144b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCase.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 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.quickstep.recents.domain.usecase
+
+import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.domain.model.TaskModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class GetTaskUseCase(private val repository: RecentTasksRepository) {
+ operator fun invoke(taskId: Int): Flow<TaskModel?> =
+ repository.getTaskDataById(taskId).map { task ->
+ if (task != null) {
+ TaskModel(
+ id = task.key.id,
+ title = task.title,
+ titleDescription = task.titleDescription,
+ icon = task.icon,
+ thumbnail = task.thumbnail,
+ backgroundColor = task.colorBackground,
+ isLocked = task.isLocked,
+ )
+ } else {
+ null
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt
new file mode 100644
index 0000000..5f98479
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2025 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.quickstep.recents.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.recents.model.ThumbnailData
+
+/**
+ * This class represents the UI state to be consumed by TaskView, GroupTaskView and DesktopTaskView.
+ * Data class representing the state of a list of tasks.
+ *
+ * This class encapsulates a list of [TaskTileUiState] objects, along with a flag indicating whether
+ * the data is being used for a live tile display.
+ *
+ * @property tasks The list of [TaskTileUiState] objects representing the individual tasks.
+ * @property isLiveTile Indicates whether this data is intended for a live tile. If `true`, the
+ * running app will be displayed instead of the thumbnail.
+ */
+data class TaskTileUiState(val tasks: List<TaskData>, val isLiveTile: Boolean)
+
+sealed interface TaskData {
+ /** When no data was found for the TaskId provided */
+ data class NoData(val taskId: Int) : TaskData
+
+ /**
+ * This class provides UI information related to a Task (App) to be displayed within a TaskView.
+ *
+ * @property taskId Identifier of the task
+ * @property title App title
+ * @property icon App icon
+ * @property thumbnailData Information related to the last snapshot retrieved from the app
+ * @property backgroundColor The background color of the task.
+ * @property isLocked Indicates whether the task is locked or not.
+ */
+ data class Data(
+ val taskId: Int,
+ val title: String,
+ val icon: Drawable?,
+ val thumbnailData: ThumbnailData?,
+ val backgroundColor: Int,
+ val isLocked: Boolean,
+ ) : TaskData
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
new file mode 100644
index 0000000..2e51a8a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2025 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.quickstep.recents.ui.viewmodel
+
+import android.util.Log
+import com.android.launcher3.util.coroutines.DispatcherProvider
+import com.android.quickstep.recents.domain.model.TaskId
+import com.android.quickstep.recents.domain.model.TaskModel
+import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/**
+ * ViewModel used for [com.android.quickstep.views.TaskView],
+ * [com.android.quickstep.views.DesktopTaskView] and [com.android.quickstep.views.GroupedTaskView].
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class TaskViewModel(
+ recentsViewData: RecentsViewData,
+ private val getTaskUseCase: GetTaskUseCase,
+ dispatcherProvider: DispatcherProvider,
+) {
+ private var taskIds = MutableStateFlow(emptySet<Int>())
+
+ private val isLiveTile =
+ combine(
+ taskIds,
+ recentsViewData.runningTaskIds,
+ recentsViewData.runningTaskShowScreenshot,
+ ) { taskIds, runningTaskIds, runningTaskShowScreenshot ->
+ runningTaskIds == taskIds && !runningTaskShowScreenshot
+ }
+ .distinctUntilChanged()
+
+ val state: Flow<TaskTileUiState> =
+ taskIds
+ .flatMapLatest { ids ->
+ // Combine Tasks requests
+ combine(
+ ids.map { id -> getTaskUseCase(id).map { taskModel -> id to taskModel } },
+ ::mapToUiState,
+ )
+ }
+ .combine(isLiveTile) { tasks, isLiveTile -> TaskTileUiState(tasks, isLiveTile) }
+ .flowOn(dispatcherProvider.background)
+
+ fun bind(vararg taskId: TaskId) {
+ Log.d(TAG, "bind: $taskId")
+ taskIds.value = taskId.toSet()
+ }
+
+ private fun mapToUiState(result: Array<Pair<TaskId, TaskModel?>>): List<TaskData> =
+ result.map { mapToUiState(it.first, it.second) }
+
+ private fun mapToUiState(taskId: TaskId, result: TaskModel?): TaskData =
+ result?.let {
+ TaskData.Data(
+ taskId = taskId,
+ title = result.title,
+ icon = result.icon,
+ thumbnailData = result.thumbnail,
+ backgroundColor = result.backgroundColor,
+ isLocked = result.isLocked,
+ )
+ } ?: TaskData.NoData(taskId)
+
+ private companion object {
+ const val TAG = "TaskViewModel"
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index dc849f3..99df84c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -613,6 +613,7 @@
borderEnabled = false
hoverBorderVisible = false
taskViewId = UNBOUND_TASK_VIEW_ID
+ // TODO(b/390583187): Clean the components UI State when TaskView is recycled.
taskContainers.forEach { it.destroy() }
}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
similarity index 98%
rename from quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
index df98606..b39c3f1 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
@@ -17,7 +17,7 @@
package com.android.launcher3.taskbar
-import androidx.test.runner.AndroidJUnit4
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.statemanager.StateManager
import com.android.quickstep.RecentsActivity
import com.android.quickstep.fallback.RecentsState
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
similarity index 98%
rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
index d064f4a..26f1197 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
@@ -99,7 +99,7 @@
keyboardQuickSwitchController,
taskbarPinningController,
optionalBubbleControllers,
- taskbarDesktopModeController
+ taskbarDesktopModeController,
)
}
}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
similarity index 97%
rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
index e619e7c..2431020 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar
import android.app.KeyguardManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING
@@ -23,6 +24,7 @@
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
@@ -30,6 +32,7 @@
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+@RunWith(AndroidJUnit4::class)
class TaskbarKeyguardControllerTest : TaskbarBaseTestCase() {
private val baseDragLayer: TaskbarDragLayer = mock()
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
similarity index 99%
rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index fbfc40b..0bae42c 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -24,8 +24,7 @@
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 androidx.test.annotation.UiThreadTest
import com.android.internal.R
import com.android.launcher3.BubbleTextView.RunningAppState
import com.android.launcher3.Flags
@@ -35,6 +34,7 @@
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.TaskItemInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.quickstep.RecentsModel
import com.android.quickstep.RecentsModel.RecentTasksChangedListener
import com.android.quickstep.TaskIconCache
@@ -46,6 +46,7 @@
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import org.junit.rules.TestWatcher
import org.junit.runner.Description
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -58,7 +59,8 @@
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@RunWith(LauncherMultivalentJUnit::class)
@EnableFlags(Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR)
class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
index e6c5a6c..66c4ab5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
@@ -33,11 +33,13 @@
import com.android.quickstep.views.RecentsView;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@SmallTest
@RunWith(LauncherMultivalentJUnit.class)
+@Ignore
public class LauncherSwipeHandlerV2TestCase extends AbsSwipeUpHandlerTestCase<
LauncherState,
QuickstepLauncher,
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
similarity index 97%
rename from quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
index ccc0311..ccfe36d 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
@@ -21,7 +21,7 @@
import static junit.framework.TestCase.assertNull;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -34,6 +34,7 @@
import android.content.Context;
import android.content.res.Resources;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
@@ -45,6 +46,7 @@
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -56,6 +58,7 @@
import java.util.stream.Collectors;
@SmallTest
+@RunWith(AndroidJUnit4.class)
public class RecentTasksListTest {
@Mock
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
similarity index 72%
rename from quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
index 80fbce7..0245908 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -1,13 +1,16 @@
package com.android.quickstep
import android.content.Context
-import android.testing.AndroidTestingRunner
+import androidx.test.annotation.UiThreadTest
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
import com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE
import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
import com.android.launcher3.util.DisplayController.Info
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.NavigationMode
import com.android.quickstep.util.GestureExclusionManager
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY
@@ -22,6 +25,7 @@
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,12 +34,13 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
/** Unit test for [RecentsAnimationDeviceState]. */
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@RunWith(LauncherMultivalentJUnit::class)
class RecentsAnimationDeviceStateTest {
@Mock private lateinit var exclusionManager: GestureExclusionManager
@@ -50,6 +55,13 @@
underTest = RecentsAnimationDeviceState(context, exclusionManager)
}
+ @After
+ fun tearDown() {
+ underTest.close()
+ UI_HELPER_EXECUTOR.submit {}.get()
+ MAIN_EXECUTOR.submit {}.get()
+ }
+
@Test
fun registerExclusionListener_success() {
underTest.registerExclusionListener()
@@ -64,7 +76,7 @@
underTest.registerExclusionListener()
- verifyZeroInteractions(exclusionManager)
+ verifyNoMoreInteractions(exclusionManager)
}
@Test
@@ -85,7 +97,7 @@
underTest.unregisterExclusionListener()
- verifyZeroInteractions(exclusionManager)
+ verifyNoMoreInteractions(exclusionManager)
}
@Test
@@ -116,13 +128,13 @@
underTest.onDisplayInfoChanged(context, info, CHANGE_DENSITY)
- verifyZeroInteractions(exclusionManager)
+ verifyNoMoreInteractions(exclusionManager)
}
@Test
fun trackpadGesturesNotAllowedForSelectedStates() {
- val disablingStates = GESTURE_DISABLING_SYSUI_STATES +
- SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
+ val disablingStates =
+ GESTURE_DISABLING_SYSUI_STATES + SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
allSysUiStates().forEach { state ->
val canStartGesture = !disablingStates.contains(state)
@@ -133,13 +145,13 @@
@Test
fun trackpadGesturesNotAllowedIfHomeAndOverviewIsDisabled() {
- val stateToExpectedResult = mapOf(
- SYSUI_STATE_HOME_DISABLED to true,
- SYSUI_STATE_OVERVIEW_DISABLED to true,
- DEFAULT_STATE
- .enable(SYSUI_STATE_OVERVIEW_DISABLED)
- .enable(SYSUI_STATE_HOME_DISABLED) to false
- )
+ val stateToExpectedResult =
+ mapOf(
+ SYSUI_STATE_HOME_DISABLED to true,
+ SYSUI_STATE_OVERVIEW_DISABLED to true,
+ DEFAULT_STATE.enable(SYSUI_STATE_OVERVIEW_DISABLED)
+ .enable(SYSUI_STATE_HOME_DISABLED) to false,
+ )
stateToExpectedResult.forEach { (state, allowed) ->
underTest.setSystemUiFlags(state)
@@ -160,22 +172,19 @@
@Test
fun systemGesturesNotAllowedWhenGestureStateDisabledAndNavBarVisible() {
- val stateToExpectedResult = mapOf(
- DEFAULT_STATE
- .enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
- .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
- DEFAULT_STATE
- .enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
- .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
- DEFAULT_STATE
- .disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
- .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
- DEFAULT_STATE
- .disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
- .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to false,
- )
+ val stateToExpectedResult =
+ mapOf(
+ DEFAULT_STATE.enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+ .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+ DEFAULT_STATE.enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+ .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+ DEFAULT_STATE.disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+ .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+ DEFAULT_STATE.disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+ .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to false,
+ )
- stateToExpectedResult.forEach {(state, gestureAllowed) ->
+ stateToExpectedResult.forEach { (state, gestureAllowed) ->
underTest.setSystemUiFlags(state)
assertThat(underTest.canStartSystemGesture()).isEqualTo(gestureAllowed)
}
@@ -187,14 +196,15 @@
}
companion object {
- private val GESTURE_DISABLING_SYSUI_STATES = listOf(
- SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
- SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
- SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
- SYSUI_STATE_MAGNIFICATION_OVERLAP,
- SYSUI_STATE_DEVICE_DREAMING,
- SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION,
- )
+ private val GESTURE_DISABLING_SYSUI_STATES =
+ listOf(
+ SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
+ SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
+ SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
+ SYSUI_STATE_MAGNIFICATION_OVERLAP,
+ SYSUI_STATE_DEVICE_DREAMING,
+ SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION,
+ )
private const val SYSUI_STATES_COUNT = 33
private const val DEFAULT_STATE = 0L
}
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
similarity index 97%
rename from quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
index 4e34e11..a5c60ce 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
@@ -35,6 +35,7 @@
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.Flags;
@@ -48,6 +49,7 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -56,6 +58,7 @@
import java.util.function.Consumer;
@SmallTest
+@RunWith(AndroidJUnit4.class)
public class RecentsModelTest {
@Mock
private Context mContext;
diff --git a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
similarity index 96%
rename from quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
index a87c328..6e9885a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -27,16 +27,19 @@
import android.content.Context;
import android.content.Intent;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
+@RunWith(AndroidJUnit4.class)
public class TaskAnimationManagerTest {
protected final Context mContext =
diff --git a/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java
similarity index 96%
rename from quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java
index 4e04261..3686c16 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.content.res.Resources;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.R;
@@ -35,12 +36,14 @@
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.Executor;
@SmallTest
+@RunWith(AndroidJUnit4.class)
public class TaskThumbnailCacheTest {
@Mock
private Context mContext;
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index 1c9ce0b..35af29f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -58,10 +58,10 @@
tasks.value.map {
it.apply {
thumbnail = thumbnailDataMap[it.key.id]
- taskIconDataMap[it.key.id].let { data ->
- title = data?.title
- titleDescription = data?.titleDescription
- icon = data?.icon
+ taskIconDataMap[it.key.id]?.let { data ->
+ title = data.title
+ titleDescription = data.titleDescription
+ icon = data.icon
}
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt
new file mode 100644
index 0000000..b036bce
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2025 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.quickstep.recents.domain.usecase
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.domain.model.TaskModel
+import com.android.systemui.shared.recents.model.Task
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class GetTaskUseCaseTest {
+ private val unconfinedTestDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(unconfinedTestDispatcher)
+
+ private val tasksRepository = FakeTasksRepository()
+ private val sut = GetTaskUseCase(repository = tasksRepository)
+
+ @Before
+ fun setUp() {
+ tasksRepository.seedTasks(listOf(TASK_1))
+ }
+
+ @Test
+ fun taskNotSeeded_returnsNull() =
+ testScope.runTest {
+ val result = sut.invoke(NOT_FOUND_TASK_ID).firstOrNull()
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun taskNotVisible_returnsNull() =
+ testScope.runTest {
+ val result = sut.invoke(TASK_1_ID).firstOrNull()
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun taskVisible_returnsData() =
+ testScope.runTest {
+ tasksRepository.setVisibleTasks(setOf(TASK_1_ID))
+ val expectedResult =
+ TaskModel(
+ id = TASK_1_ID,
+ title = "Title $TASK_1_ID",
+ titleDescription = "Content Description $TASK_1_ID",
+ icon = TASK_1_ICON,
+ thumbnail = null,
+ backgroundColor = Color.BLACK,
+ isLocked = false,
+ )
+ val result = sut.invoke(TASK_1_ID).firstOrNull()
+ assertThat(result).isEqualTo(expectedResult)
+ }
+
+ private companion object {
+ const val NOT_FOUND_TASK_ID = 404
+ private const val TASK_1_ID = 1
+ private val TASK_1_ICON = ShapeDrawable()
+ private val TASK_1 =
+ Task(
+ Task.TaskKey(
+ /* id = */ TASK_1_ID,
+ /* windowingMode = */ 0,
+ /* intent = */ Intent(),
+ /* sourceComponent = */ ComponentName("", ""),
+ /* userId = */ 0,
+ /* lastActiveTime = */ 2000,
+ )
+ )
+ .apply {
+ title = "Title 1"
+ titleDescription = "Content Description 1"
+ colorBackground = Color.BLACK
+ icon = TASK_1_ICON
+ thumbnail = null
+ isLocked = false
+ }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
new file mode 100644
index 0000000..54a27e9
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2025 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.quickstep.recents.ui.viewmodel
+
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.TestDispatcherProvider
+import com.android.quickstep.recents.domain.model.TaskModel
+import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class TaskViewModelTest {
+ private val unconfinedTestDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(unconfinedTestDispatcher)
+
+ private val recentsViewData = RecentsViewData()
+ private val getTaskUseCase = mock<GetTaskUseCase>()
+ private val sut =
+ TaskViewModel(
+ recentsViewData = recentsViewData,
+ getTaskUseCase = getTaskUseCase,
+ dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
+ )
+
+ @Before
+ fun setUp() {
+ whenever(getTaskUseCase.invoke(TASK_MODEL_1.id)).thenReturn(flow { emit(TASK_MODEL_1) })
+ whenever(getTaskUseCase.invoke(TASK_MODEL_2.id)).thenReturn(flow { emit(TASK_MODEL_2) })
+ whenever(getTaskUseCase.invoke(TASK_MODEL_3.id)).thenReturn(flow { emit(TASK_MODEL_3) })
+ whenever(getTaskUseCase.invoke(INVALID_TASK_ID)).thenReturn(flow { emit(null) })
+ recentsViewData.runningTaskIds.value = emptySet()
+ }
+
+ @Test
+ fun singleTaskRetrieved_when_validTaskId() =
+ testScope.runTest {
+ sut.bind(TASK_MODEL_1.id)
+ val expectedResult = TaskTileUiState(listOf(TASK_MODEL_1.toUiState()), false)
+ assertThat(sut.state.first()).isEqualTo(expectedResult)
+ }
+
+ @Test
+ fun multipleTasksRetrieved_when_validTaskIds() =
+ testScope.runTest {
+ sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id, INVALID_TASK_ID)
+ val expectedResult =
+ TaskTileUiState(
+ tasks =
+ listOf(
+ TASK_MODEL_1.toUiState(),
+ TASK_MODEL_2.toUiState(),
+ TASK_MODEL_3.toUiState(),
+ TaskData.NoData(INVALID_TASK_ID),
+ ),
+ isLiveTile = false,
+ )
+ assertThat(sut.state.first()).isEqualTo(expectedResult)
+ }
+
+ @Test
+ fun isLiveTile_when_runningTasksMatchTasks() =
+ testScope.runTest {
+ recentsViewData.runningTaskShowScreenshot.value = false
+ recentsViewData.runningTaskIds.value =
+ setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+ sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+ val expectedResult =
+ TaskTileUiState(
+ tasks =
+ listOf(
+ TASK_MODEL_1.toUiState(),
+ TASK_MODEL_2.toUiState(),
+ TASK_MODEL_3.toUiState(),
+ ),
+ isLiveTile = true,
+ )
+ assertThat(sut.state.first()).isEqualTo(expectedResult)
+ }
+
+ @Test
+ fun isNotLiveTile_when_runningTaskShowScreenshotIsTrue() =
+ testScope.runTest {
+ recentsViewData.runningTaskShowScreenshot.value = true
+ recentsViewData.runningTaskIds.value =
+ setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+ sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+ val expectedResult =
+ TaskTileUiState(
+ tasks =
+ listOf(
+ TASK_MODEL_1.toUiState(),
+ TASK_MODEL_2.toUiState(),
+ TASK_MODEL_3.toUiState(),
+ ),
+ isLiveTile = false,
+ )
+ assertThat(sut.state.first()).isEqualTo(expectedResult)
+ }
+
+ @Test
+ fun isNotLiveTile_when_runningTasksMatchPartialTasks_lessRunningTasks() =
+ testScope.runTest {
+ recentsViewData.runningTaskShowScreenshot.value = false
+ recentsViewData.runningTaskIds.value = setOf(TASK_MODEL_1.id, TASK_MODEL_2.id)
+ sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+ val expectedResult =
+ TaskTileUiState(
+ tasks =
+ listOf(
+ TASK_MODEL_1.toUiState(),
+ TASK_MODEL_2.toUiState(),
+ TASK_MODEL_3.toUiState(),
+ ),
+ isLiveTile = false,
+ )
+ assertThat(sut.state.first()).isEqualTo(expectedResult)
+ }
+
+ @Test
+ fun isNotLiveTile_when_runningTasksMatchPartialTasks_moreRunningTasks() =
+ testScope.runTest {
+ recentsViewData.runningTaskShowScreenshot.value = false
+ recentsViewData.runningTaskIds.value =
+ setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+ sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id)
+ val expectedResult =
+ TaskTileUiState(
+ tasks = listOf(TASK_MODEL_1.toUiState(), TASK_MODEL_2.toUiState()),
+ isLiveTile = false,
+ )
+ assertThat(sut.state.first()).isEqualTo(expectedResult)
+ }
+
+ @Test
+ fun noDataAvailable_when_InvalidTaskId() =
+ testScope.runTest {
+ sut.bind(INVALID_TASK_ID)
+ val expectedResult =
+ TaskTileUiState(listOf(TaskData.NoData(INVALID_TASK_ID)), isLiveTile = false)
+ assertThat(sut.state.first()).isEqualTo(expectedResult)
+ }
+
+ private fun TaskModel.toUiState() =
+ TaskData.Data(
+ taskId = id,
+ title = title,
+ icon = icon!!,
+ thumbnailData = thumbnail,
+ backgroundColor = backgroundColor,
+ isLocked = isLocked,
+ )
+
+ companion object {
+ const val INVALID_TASK_ID = -1
+ val TASK_MODEL_1 =
+ TaskModel(
+ 1,
+ "Title 1",
+ "Content Description 1",
+ ShapeDrawable(),
+ ThumbnailData(),
+ Color.BLACK,
+ false,
+ )
+ val TASK_MODEL_2 =
+ TaskModel(
+ 2,
+ "Title 2",
+ "Content Description 2",
+ ShapeDrawable(),
+ ThumbnailData(),
+ Color.RED,
+ true,
+ )
+ val TASK_MODEL_3 =
+ TaskModel(
+ 3,
+ "Title 3",
+ "Content Description 3",
+ ShapeDrawable(),
+ ThumbnailData(),
+ Color.BLUE,
+ false,
+ )
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
index 73aa460..0044631 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
@@ -76,7 +76,7 @@
assertThat(systemUnderTest.run(TASK_ID)).isEqualTo(thumbnailData.thumbnail)
}
- companion object {
+ private companion object {
const val TASK_ID = 0
const val THUMBNAIL_WIDTH = 100
const val THUMBNAIL_HEIGHT = 200
diff --git a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
similarity index 97%
rename from quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
index 3148737..5b42d6c 100644
--- a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
@@ -56,9 +56,9 @@
private val taskbarSharedState = mock<TaskbarSharedState>()
private var isInDesktopMode = false
private val launcherPrefs =
- mock<LauncherPrefs> {
- on { get(TASKBAR_PINNING) } doReturn false
- on { get(TASKBAR_PINNING_IN_DESKTOP_MODE) } doReturn false
+ mock<LauncherPrefs>().apply {
+ doReturn(false).whenever(this).get(TASKBAR_PINNING)
+ doReturn(false).whenever(this).get(TASKBAR_PINNING_IN_DESKTOP_MODE)
}
private val statsLogger = mock<StatsLogManager.StatsLogger>()
private val statsLogManager = mock<StatsLogManager> { on { logger() } doReturn statsLogger }
diff --git a/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
similarity index 90%
rename from quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
index c190cfe..555e62b 100644
--- a/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
@@ -18,11 +18,12 @@
import android.graphics.Rect
import android.graphics.Region
-import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
+import androidx.test.annotation.UiThreadTest
import androidx.test.filters.SmallTest
import com.android.launcher3.util.Executors
+import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.quickstep.util.GestureExclusionManager.ExclusionListener
import org.junit.Before
import org.junit.Test
@@ -31,11 +32,12 @@
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.reset
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
/** Unit test for [GestureExclusionManager]. */
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@RunWith(LauncherMultivalentJUnit::class)
class GestureExclusionManagerTest {
@Mock private lateinit var windowManager: IWindowManager
@@ -72,7 +74,7 @@
underTest.addListener(listener2)
awaitTasksCompleted()
- verifyZeroInteractions(windowManager)
+ verifyNoMoreInteractions(windowManager)
}
@Test
@@ -98,7 +100,7 @@
underTest.removeListener(listener1)
awaitTasksCompleted()
- verifyZeroInteractions(windowManager)
+ verifyNoMoreInteractions(windowManager)
}
@Test
@@ -119,12 +121,12 @@
awaitTasksCompleted()
underTest.addListener(listener1)
awaitTasksCompleted()
- verifyZeroInteractions(listener1)
+ verifyNoMoreInteractions(listener1)
underTest.addListener(listener2)
awaitTasksCompleted()
- verifyZeroInteractions(listener1)
+ verifyNoMoreInteractions(listener1)
verify(listener2).onGestureExclusionChanged(r1, r2)
}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
index 67a0ee4..3f7c85c 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
@@ -127,11 +127,11 @@
when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
- boolean hoverHandled =
+ boolean hoverConsumed =
mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
waitForIdleSync();
- assertThat(hoverHandled).isTrue();
+ assertThat(hoverConsumed).isFalse();
verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
true);
}
@@ -141,11 +141,11 @@
when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
- boolean hoverHandled =
+ boolean hoverConsumed =
mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
waitForIdleSync();
- assertThat(hoverHandled).isTrue();
+ assertThat(hoverConsumed).isFalse();
verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
false);
}
@@ -155,11 +155,11 @@
when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
- boolean hoverHandled =
+ boolean hoverConsumed =
mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
waitForIdleSync();
- assertThat(hoverHandled).isTrue();
+ assertThat(hoverConsumed).isFalse();
verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
true);
}
@@ -169,11 +169,11 @@
when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
- boolean hoverHandled =
+ boolean hoverConsumed =
mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
waitForIdleSync();
- assertThat(hoverHandled).isTrue();
+ assertThat(hoverConsumed).isFalse();
verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
false);
}
@@ -184,11 +184,11 @@
when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
doReturn(true).when(mSpyFolderView).isOpen();
- boolean hoverHandled =
+ boolean hoverConsumed =
mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
waitForIdleSync();
- assertThat(hoverHandled).isTrue();
+ assertThat(hoverConsumed).isFalse();
verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
false);
}
@@ -199,10 +199,10 @@
when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
doReturn(true).when(mSpyFolderView).isOpen();
- boolean hoverHandled =
+ boolean hoverConsumed =
mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
- assertThat(hoverHandled).isTrue();
+ assertThat(hoverConsumed).isFalse();
}
@Test
@@ -210,10 +210,10 @@
when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_MOVE);
when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_MOVE);
- boolean hoverHandled =
+ boolean hoverConsumed =
mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
- assertThat(hoverHandled).isTrue();
+ assertThat(hoverConsumed).isFalse();
}
@Test
@@ -221,11 +221,11 @@
when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
- boolean hoverHandled =
+ boolean hoverConsumed =
mTaskbarHoverToolTipController.onHover(mAppPairIcon, mMotionEvent);
waitForIdleSync();
- assertThat(hoverHandled).isTrue();
+ assertThat(hoverConsumed).isFalse();
verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
true);
}
@@ -235,11 +235,11 @@
when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
- boolean hoverHandled =
+ boolean hoverConsumed =
mTaskbarHoverToolTipController.onHover(mAppPairIcon, mMotionEvent);
waitForIdleSync();
- assertThat(hoverHandled).isTrue();
+ assertThat(hoverConsumed).isFalse();
verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
false);
}
@@ -250,11 +250,11 @@
when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
when(taskbarActivityContext.isIconAlignedWithHotseat()).thenReturn(true);
- boolean hoverHandled =
+ boolean hoverConsumed =
mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
waitForIdleSync();
- assertThat(hoverHandled).isTrue();
+ assertThat(hoverConsumed).isFalse();
verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
true);
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java
new file mode 100644
index 0000000..8fedf5c
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2025 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.quickstep;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP;
+
+import android.app.WindowConfiguration;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.util.rule.SetPropRule;
+import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
+import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
+import com.android.window.flags.Flags;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsLockedTaskbar extends AbstractTaplTestsTaskbar {
+ private static final String TAG = "TaplTestsLockedTaskbar";
+
+ @Rule
+ public SetPropRule mSetPropRule =
+ new SetPropRule(ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP, "true");
+
+ @Override
+ public void setUp() throws Exception {
+ Assume.assumeTrue(mLauncher.isTablet());
+ Assume.assumeTrue(Flags.enterDesktopByDefaultOnFreeformDisplays());
+ Assume.assumeTrue(DesktopModeStatus.canEnterDesktopMode(getTargetContext()));
+ super.setUp();
+
+ // Default-to-desktop feature requires the display to be freeform mode.
+ setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ // Reset the display windowing mode to the device default.
+ setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_UNDEFINED);
+
+ mLauncher.recreateTaskbar();
+
+ super.tearDown();
+ }
+
+ @Test
+ @PortraitLandscape
+ @NavigationModeSwitch
+ @TaskbarModeSwitch(mode = PERSISTENT)
+ public void testTaskbarVisibility() {
+ // The taskbar should be visible on home.
+ mDevice.pressHome();
+ waitForResumed("Launcher internal state is still Background");
+ mLauncher.getLaunchedAppState().assertTaskbarVisible();
+
+ // The taskbar should be visible when a freeform task is active.
+ startAppFast(CALCULATOR_APP_PACKAGE);
+ mLauncher.getLaunchedAppState().assertTaskbarVisible();
+ }
+
+ private void setDisplayWindowingMode(int windowingMode) {
+ try {
+ WindowManagerGlobal.getWindowManagerService().setWindowingMode(
+ DEFAULT_DISPLAY, windowingMode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error setting windowing mode", e);
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 76c0f90..753b2e2 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -75,7 +75,8 @@
TYPE_ADD_TO_HOME_CONFIRMATION,
TYPE_TASKBAR_OVERLAY_PROXY,
TYPE_TASKBAR_PINNING_POPUP,
- TYPE_PIN_IME_POPUP
+ TYPE_PIN_IME_POPUP,
+ TYPE_ONE_GRID_MIGRATION_EDU,
})
@Retention(RetentionPolicy.SOURCE)
public @interface FloatingViewType {}
@@ -104,6 +105,7 @@
public static final int TYPE_TASKBAR_OVERLAY_PROXY = 1 << 20;
public static final int TYPE_TASKBAR_PINNING_POPUP = 1 << 21;
public static final int TYPE_PIN_IME_POPUP = 1 << 22;
+ public static final int TYPE_ONE_GRID_MIGRATION_EDU = 1 << 23;
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
@@ -112,19 +114,19 @@
| TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP
| TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG
| TYPE_ADD_TO_HOME_CONFIRMATION | TYPE_TASKBAR_OVERLAY_PROXY
- | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP;
+ | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP | TYPE_ONE_GRID_MIGRATION_EDU;
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
| TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_TASKBAR_EDUCATION_DIALOG
| TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG | TYPE_TASKBAR_OVERLAY_PROXY
- | TYPE_PIN_IME_POPUP;
+ | TYPE_PIN_IME_POPUP | TYPE_ONE_GRID_MIGRATION_EDU;
/** Type of popups that should get exclusive accessibility focus. */
public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
& ~TYPE_ALL_APPS_EDU & ~TYPE_TASKBAR_ALL_APPS & ~TYPE_PIN_IME_POPUP
- & ~TYPE_WIDGET_RESIZE_FRAME;
+ & ~TYPE_WIDGET_RESIZE_FRAME & ~TYPE_ONE_GRID_MIGRATION_EDU & ~TYPE_ON_BOARD_POPUP;
// These view all have particular operation associated with swipe down interaction.
public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 175d6ec..468cee8 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -261,6 +261,13 @@
return count;
}
+ private void addProfileId(XmlPullParser parser) {
+ Long profileId = mUserTypeToSerial.get(getAttributeValue(parser, ATTR_USER_TYPE));
+ if (profileId != null) {
+ mValues.put(Favorites.PROFILE_ID, profileId);
+ }
+ }
+
/**
* Parses container and screenId attribute from the current tag, and puts it in the out.
* @param out array of size 2.
@@ -305,10 +312,6 @@
convertToDistanceFromEnd(getAttributeValue(parser, ATTR_X), mColumnCount));
mValues.put(Favorites.CELLY,
convertToDistanceFromEnd(getAttributeValue(parser, ATTR_Y), mRowCount));
- Long profileId = mUserTypeToSerial.get(getAttributeValue(parser, ATTR_USER_TYPE));
- if (profileId != null) {
- mValues.put(Favorites.PROFILE_ID, profileId);
- }
TagParser tagParser = tagParserMap.get(parser.getName());
if (tagParser == null) {
@@ -382,7 +385,7 @@
public int parseAndAdd(XmlPullParser parser) {
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
-
+ addProfileId(parser);
if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
ActivityInfo info;
try {
@@ -431,6 +434,7 @@
public int parseAndAdd(XmlPullParser parser) {
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
+ addProfileId(parser);
if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
return -1;
@@ -452,7 +456,7 @@
public int parseAndAdd(XmlPullParser parser) {
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
final String shortcutId = getAttributeValue(parser, ATTR_SHORTCUT_ID);
-
+ addProfileId(parser);
try {
LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
launcherApps.pinShortcuts(packageName, Collections.singletonList(shortcutId),
@@ -482,13 +486,13 @@
public ComponentName getComponentName(XmlPullParser parser) {
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
+ addProfileId(parser);
if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
return null;
}
return new ComponentName(packageName, className);
}
-
@Override
public int parseAndAdd(XmlPullParser parser)
throws XmlPullParserException, IOException {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 9aa06bf..315096c 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -75,6 +75,7 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
@@ -448,7 +449,9 @@
@VisibleForTesting
@UiThread
public void applyIconAndLabel(ItemInfoWithIcon info) {
- int flags = shouldUseTheme() ? FLAG_THEMED : 0;
+ ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
+ int flags = (shouldUseTheme()
+ && themeManager.isMonoThemeEnabled()) ? FLAG_THEMED : 0;
// Remove badge on icons smaller than 48dp.
if (mHideBadge || mDisplay == DISPLAY_SEARCH_RESULT_SMALL) {
flags |= FLAG_NO_BADGE;
@@ -1236,6 +1239,7 @@
mIcon = icon;
if (mIcon != null) {
mIcon.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
+ mIcon.setHoverScaleEnabledForDisplay(mDisplay != DISPLAY_TASKBAR);
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 0d050b2..86c49d0 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3572,7 +3572,7 @@
int nScreens = getChildCount();
int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
if (extraScreenId >= 0 && nScreens > 1) {
- if (page == extraScreenId) {
+ if (page == extraScreenId || (isTwoPanelEnabled() && page == extraScreenId + 1)) {
return getContext().getString(R.string.workspace_new_page);
}
nScreens--;
@@ -3584,6 +3584,11 @@
int panelCount = getPanelCount();
int currentPage = (page / panelCount) + 1;
int totalPages = nScreens / panelCount + nScreens % panelCount;
+
+ // When dragging, a blank screen is added. This increases the total page count, but we still
+ // want to describe the original page count where icons are currently pinned
+ if (extraScreenId > 0) totalPages--;
+
return getContext().getString(R.string.workspace_scroll_format, currentPage, totalPages);
}
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 0a5dd62..9a7c347 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -36,6 +36,7 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.ContentWriter;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import java.util.Arrays;
@@ -178,7 +179,7 @@
public void updateFromDeepShortcutInfo(@NonNull final ShortcutInfo shortcutInfo,
@NonNull final Context context) {
- if (com.android.wm.shell.Flags.enableBubbleAnything()) {
+ if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
mShortcutInfo = shortcutInfo;
}
// {@link ShortcutInfo#getActivity} can change during an update. Recreate the intent
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 329d9df..7e08c6e 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -431,7 +431,7 @@
&& !(itemInfo instanceof WorkspaceItemInfo)) {
return null;
}
- return new BubbleShortcut(activity, itemInfo, originalView);
+ return new BubbleShortcut<>(activity, itemInfo, originalView);
};
public interface BubbleActivityStarter {
@@ -439,7 +439,7 @@
void showShortcutBubble(ShortcutInfo info);
/** Tell SysUI to show the provided intent in a bubble. */
- void showAppBubble(Intent intent);
+ void showAppBubble(Intent intent, UserHandle user);
}
public static class BubbleShortcut<T extends ActivityContext> extends SystemShortcut<T> {
@@ -476,7 +476,7 @@
if (intent.getPackage() == null) {
intent.setPackage(mItemInfo.getTargetPackage());
}
- mStarter.showAppBubble(intent);
+ mStarter.showAppBubble(intent, mItemInfo.user);
} else {
Log.w(TAG, "unable to bubble, no intent: " + mItemInfo);
}
diff --git a/src/com/android/launcher3/util/LayoutImportExportHelper.kt b/src/com/android/launcher3/util/LayoutImportExportHelper.kt
index 4033f60..0df9dae 100644
--- a/src/com/android/launcher3/util/LayoutImportExportHelper.kt
+++ b/src/com/android/launcher3/util/LayoutImportExportHelper.kt
@@ -136,4 +136,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 5cf96c8..862b862 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -29,6 +29,8 @@
android:functionalTest="false"
android:handleProfiling="false"
android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.launcher3" >
+ android:targetPackage="com.android.launcher3" >
+ <meta-data android:name="listener"
+ android:value="com.android.launcher3.util.GlobalTestRunListener"/>
</instrumentation>
</manifest>
diff --git a/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt b/tests/multivalentTests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
similarity index 83%
rename from tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
index cf03adc..7ddd859 100644
--- a/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
@@ -20,28 +20,33 @@
import android.view.ViewGroup.MarginLayoutParams
import android.widget.ImageView
import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.util.ActivityContextWrapper
import com.google.common.truth.Truth
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
+@RunWith(AndroidJUnit4::class)
class FloatingMaskViewTest {
- @Mock
- private val mockAllAppsRecyclerView: AllAppsRecyclerView? = null
+ @Mock private val mockAllAppsRecyclerView: AllAppsRecyclerView? = null
- @Mock
- private val mockBottomBox: ImageView? = null
+ @Mock private val mockBottomBox: ImageView? = null
private var mVut: FloatingMaskView? = null
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
val context: Context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
mVut = FloatingMaskView(context)
- mVut!!.layoutParams = MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT)
+ mVut!!.layoutParams =
+ MarginLayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ )
}
@Test
diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
similarity index 99%
rename from tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
index 430e496..d757d10 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
@@ -42,7 +42,7 @@
import android.os.UserHandle;
import android.os.UserManager;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.pm.UserCache;
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
index 398f9c5..23b00c2 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
@@ -52,8 +52,8 @@
import android.widget.RelativeLayout;
import android.widget.TextView;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.R;
import com.android.launcher3.logging.StatsLogManager;
@@ -300,7 +300,7 @@
&& info.user.equals(MAIN_HANDLE));
int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
- int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+ int position = rows * NUM_APP_COLS - (NUM_APP_COLS - 1) + 1;
assertEquals(NUM_PRIVATE_SPACE_APPS + MAIN_USER_APP_COUNT
+ CONTAINER_HEADER_ELEMENT_COUNT + VIEW_AT_END_OF_APP_LIST,
@@ -335,7 +335,7 @@
&& info.user.equals(MAIN_HANDLE));
int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT) - 1;
- int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+ int position = rows * NUM_APP_COLS - (NUM_APP_COLS - 1) + 1;
assertEquals(NUM_PRIVATE_SPACE_APPS + MAIN_USER_APP_COUNT
+ CONTAINER_HEADER_ELEMENT_COUNT + VIEW_AT_END_OF_APP_LIST,
@@ -370,7 +370,7 @@
&& info.user.equals(MAIN_HANDLE));
int rows = (int) (ALL_APPS_HEIGHT - BIGGER_PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
- int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+ int position = rows * NUM_APP_COLS - (NUM_APP_COLS - 1) + 1;
assertEquals(NUM_PRIVATE_SPACE_APPS + MAIN_USER_APP_COUNT
+ CONTAINER_HEADER_ELEMENT_COUNT + VIEW_AT_END_OF_APP_LIST,
diff --git a/tests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/GlobalTestRunListener.java b/tests/multivalentTests/src/com/android/launcher3/util/GlobalTestRunListener.java
new file mode 100644
index 0000000..667f540
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/GlobalTestRunListener.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 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.util;
+
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunListener;
+import org.mockito.Mockito;
+
+public class GlobalTestRunListener extends RunListener {
+ /**
+ * See {@link RunListener#testFinished} which executes per atomic test.
+ * {@link RunListener#testSuiteFinished} which executes per test suite. Test suite = test class
+ * in this context.
+ * {@link RunListener#testRunFinished} which executes after everything (all test suites) is
+ * completed.
+ */
+ @Override
+ public void testSuiteFinished(Description description) throws Exception {
+ // This method runs after every test class and will clear mocks after every test class
+ // execution is completed.
+ Mockito.framework().clearInlineMocks();
+ super.testSuiteFinished(description);
+ }
+}
diff --git a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
index 38fad6b..1e54603 100644
--- a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
+++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
@@ -32,6 +32,7 @@
import com.android.launcher3.util.rule.BackAndRestoreRule
import com.android.launcher3.util.rule.setFlags
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,6 +70,7 @@
}
@Test
+ @Ignore("b/385147987")
fun oldDatabasesNotPresentAfterRestore() {
val dbController = ModelDbController(getInstrumentation().targetContext)
if (Flags.gridMigrationRefactor()) {
diff --git a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
index 94b2c7e..c7db741 100644
--- a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
+++ b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
@@ -35,6 +35,7 @@
import com.android.launcher3.util.TestUtil
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -166,6 +167,7 @@
}
@Test
+ @Ignore("b/388258969")
fun `promiseIconAddedForId returns true if there is a promiseIcon with the session id`() {
// Given
val expectedIdString = IntArray().apply { add(1) }.toConcatString()
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
index bbcc6a8..033cfb0 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
@@ -63,5 +63,12 @@
return new SplitScreenMenuItem(mLauncher, menuItem);
}
+ /** Returns the Bubble menu item. */
+ public BubbleMenuItem getBubbleMenuItem() {
+ final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer,
+ AppIcon.getMenuItemSelector("Bubble", mLauncher));
+ return new BubbleMenuItem(mLauncher, menuItem);
+ }
+
protected abstract AppIconMenuItem createMenuItem(UiObject2 menuItem);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/BubbleMenuItem.kt b/tests/tapl/com/android/launcher3/tapl/BubbleMenuItem.kt
new file mode 100644
index 0000000..77391f1
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/BubbleMenuItem.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2025 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.tapl
+
+import androidx.test.uiautomator.UiObject2
+
+/**
+ * A class representing the Bubble menu item in the app long-press menu, which moves the app into a
+ * bubble.
+ */
+class BubbleMenuItem(
+ private val launcher: LauncherInstrumentation,
+ private val uiObject: UiObject2,
+) {
+
+ fun click() {
+ launcher.addContextLayer("want to create bubble from app long-press menu").use {
+ LauncherInstrumentation.log(
+ "clicking on bubble menu item ${uiObject.visibleCenter} in ${
+ launcher.getVisibleBounds(
+ uiObject
+ )
+ }"
+ )
+ launcher.clickLauncherObject(uiObject)
+ }
+ }
+}