Merge "Don't animate the bubble that was dismissed by drag" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 31a9009..88804b7 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -23,13 +23,6 @@
}
flag {
- name: "enable_grid_only_overview"
- namespace: "launcher"
- description: "Enable a grid-only overview without a focused task."
- bug: "257950105"
-}
-
-flag {
name: "enable_cursor_hover_states"
namespace: "launcher"
description: "Enables cursor hover states for certain elements."
@@ -44,13 +37,6 @@
}
flag {
- name: "enable_overview_icon_menu"
- namespace: "launcher"
- description: "Enable updated overview icon and menu within task."
- bug: "257950105"
-}
-
-flag {
name: "enable_focus_outline"
namespace: "launcher"
description: "Enables focus states outline for launcher."
@@ -238,13 +224,6 @@
}
flag {
- name: "enable_refactor_task_thumbnail"
- namespace: "launcher"
- description: "Enables rewritten version of TaskThumbnailViews in Overview"
- bug: "331753115"
-}
-
-flag {
name: "enable_handle_delayed_gesture_callbacks"
namespace: "launcher"
description: "Enables additional handling for delayed mid-gesture callbacks"
diff --git a/aconfig/launcher_overview.aconfig b/aconfig/launcher_overview.aconfig
new file mode 100644
index 0000000..f9327fe
--- /dev/null
+++ b/aconfig/launcher_overview.aconfig
@@ -0,0 +1,23 @@
+package: "com.android.launcher3"
+container: "system_ext"
+
+flag {
+ name: "enable_grid_only_overview"
+ namespace: "launcher_overview"
+ description: "Enable a grid-only overview without a focused task."
+ bug: "257950105"
+}
+
+flag {
+ name: "enable_overview_icon_menu"
+ namespace: "launcher_overview"
+ description: "Enable updated overview icon and menu within task."
+ bug: "257950105"
+}
+
+flag {
+ name: "enable_refactor_task_thumbnail"
+ namespace: "launcher_overview"
+ description: "Enables rewritten version of TaskThumbnailViews in Overview"
+ bug: "331753115"
+}
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 7cdca74..7235b63 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -51,9 +51,11 @@
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -86,6 +88,12 @@
private static final String EXTRA_UI_SURFACE = "ui_surface";
private static final Pattern UI_SURFACE_PATTERN =
Pattern.compile("^(widgets|widgets_hub)$");
+
+ /**
+ * User ids that should be filtered out of the widget lists created by this activity.
+ */
+ private static final String EXTRA_USER_ID_FILTER = "filtered_user_ids";
+
private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
private WidgetsModel mModel;
private LauncherAppState mApp;
@@ -101,6 +109,10 @@
@NonNull
private List<AppWidgetProviderInfo> mAddedWidgets = new ArrayList<>();
+ /** A set of user ids that should be filtered out from the selected widgets. */
+ @NonNull
+ Set<Integer> mFilteredUserIds = new HashSet<>();
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -145,6 +157,12 @@
if (addedWidgets != null) {
mAddedWidgets = addedWidgets;
}
+ ArrayList<Integer> filteredUsers = getIntent().getIntegerArrayListExtra(
+ EXTRA_USER_ID_FILTER);
+ mFilteredUserIds.clear();
+ if (filteredUsers != null) {
+ mFilteredUserIds.addAll(filteredUsers);
+ }
}
@NonNull
@@ -289,6 +307,13 @@
return rejectWidget(widget, "shortcut");
}
+ if (mFilteredUserIds.contains(widget.user.getIdentifier())) {
+ return rejectWidget(
+ widget,
+ "widget user: %d is being filtered",
+ widget.user.getIdentifier());
+ }
+
if (mWidgetCategoryFilter > 0 && (info.widgetCategory & mWidgetCategoryFilter) == 0) {
return rejectWidget(
widget,
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index b647a3e..0fa3fbc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -247,7 +247,9 @@
? context.getColor(R.color.taskbar_nav_icon_light_color)
: context.getColor(R.color.taskbar_nav_icon_dark_color);
- mTaskbarTransitions = new TaskbarTransitions(mContext, mNavButtonsView);
+ if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ mTaskbarTransitions = new TaskbarTransitions(mContext, mNavButtonsView);
+ }
}
/**
@@ -359,7 +361,9 @@
R.bool.floating_rotation_button_position_left);
mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
mRotationButtonListener);
- mTaskbarTransitions.init();
+ if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ mTaskbarTransitions.init();
+ }
applyState();
mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
@@ -621,7 +625,9 @@
}
public void setWallpaperVisible(boolean isVisible) {
- mTaskbarTransitions.setWallpaperVisibility(isVisible);
+ if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ mTaskbarTransitions.setWallpaperVisibility(isVisible);
+ }
}
public void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
@@ -632,25 +638,32 @@
}
public void checkNavBarModes() {
- boolean isBarHidden = (mSysuiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
- mTaskbarTransitions.transitionTo(mTransitionMode, !isBarHidden);
- }
-
- public void finishBarAnimations() {
- mTaskbarTransitions.finishAnimations();
- }
-
- public void touchAutoDim(boolean reset) {
- mTaskbarTransitions.setAutoDim(false);
- mHandler.removeCallbacks(mAutoDim);
- if (reset) {
- mHandler.postDelayed(mAutoDim, AUTODIM_TIMEOUT_MS);
+ if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ boolean isBarHidden = (mSysuiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
+ mTaskbarTransitions.transitionTo(mTransitionMode, !isBarHidden);
}
}
- public void transitionTo(@BarTransitions.TransitionMode int barMode,
- boolean animate) {
- mTaskbarTransitions.transitionTo(barMode, animate);
+ public void finishBarAnimations() {
+ if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ mTaskbarTransitions.finishAnimations();
+ }
+ }
+
+ public void touchAutoDim(boolean reset) {
+ if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ mTaskbarTransitions.setAutoDim(false);
+ mHandler.removeCallbacks(mAutoDim);
+ if (reset) {
+ mHandler.postDelayed(mAutoDim, AUTODIM_TIMEOUT_MS);
+ }
+ }
+ }
+
+ public void transitionTo(@BarTransitions.TransitionMode int barMode, boolean animate) {
+ if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ mTaskbarTransitions.transitionTo(barMode, animate);
+ }
}
/** Use to set the translationY for the all nav+contextual buttons */
@@ -752,7 +765,9 @@
private void onDarkIntensityChanged() {
updateNavButtonColor();
- mTaskbarTransitions.onDarkIntensityChanged(mTaskbarNavButtonDarkIntensity.value);
+ if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ mTaskbarTransitions.onDarkIntensityChanged(mTaskbarNavButtonDarkIntensity.value);
+ }
}
protected ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
@@ -1100,7 +1115,9 @@
+ mOnBackgroundNavButtonColorOverrideMultiplier.value);
mNavButtonsView.dumpLogs(prefix + "\t", pw);
- mTaskbarTransitions.dumpLogs(prefix + "\t", pw);
+ if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ mTaskbarTransitions.dumpLogs(prefix + "\t", pw);
+ }
}
private static String getStateString(int flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
index 0443197..abd5fae 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
@@ -40,6 +40,7 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.views.ArrowTipView;
@@ -73,6 +74,8 @@
} else if (mHoverView instanceof FolderIcon
&& ((FolderIcon) mHoverView).mInfo.title != null) {
mToolTipText = ((FolderIcon) mHoverView).mInfo.title.toString();
+ } else if (mHoverView instanceof AppPairIcon) {
+ mToolTipText = ((AppPairIcon) mHoverView).getTitleTextView().getText().toString();
} else {
mToolTipText = null;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index b294208..ee79fbf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -649,7 +649,7 @@
}
@VisibleForTesting
- void addTaskbarRootViewToWindow() {
+ public void addTaskbarRootViewToWindow() {
if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) {
mWindowManager.addView(mTaskbarRootLayout,
mTaskbarActivityContext.getWindowLayoutParams());
@@ -658,7 +658,7 @@
}
@VisibleForTesting
- void removeTaskbarRootViewFromWindow() {
+ public void removeTaskbarRootViewFromWindow() {
if (enableTaskbarNoRecreate() && mAddedWindow) {
mWindowManager.removeViewImmediate(mTaskbarRootLayout);
mAddedWindow = false;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index fc3b4c7..0a81f78 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -149,20 +149,39 @@
taskListChangeId =
recentsModel.getTasks { tasks ->
allRecentTasks = tasks
+ val oldRunningPackages = runningAppPackages
+ val oldMinimizedPackages = minimizedAppPackages
desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
- onRecentsOrHotseatChanged()
- controllers.taskbarViewController.commitRunningAppsToUI()
+ val runningPackagesChanged = oldRunningPackages != runningAppPackages
+ val minimizedPackagessChanged = oldMinimizedPackages != minimizedAppPackages
+ if (
+ onRecentsOrHotseatChanged() ||
+ runningPackagesChanged ||
+ minimizedPackagessChanged
+ ) {
+ controllers.taskbarViewController.commitRunningAppsToUI()
+ }
}
}
}
- private fun onRecentsOrHotseatChanged() {
+ /**
+ * Updates [shownTasks] when Recents or Hotseat changes.
+ *
+ * @return Whether [shownTasks] changed.
+ */
+ private fun onRecentsOrHotseatChanged(): Boolean {
+ val oldShownTasks = shownTasks
shownTasks =
if (isInDesktopMode) {
computeShownRunningTasks()
} else {
computeShownRecentTasks()
}
+ val shownTasksChanged = oldShownTasks != shownTasks
+ if (!shownTasksChanged) {
+ return shownTasksChanged
+ }
for (groupTask in shownTasks) {
for (task in groupTask.tasks) {
@@ -174,6 +193,7 @@
}
}
}
+ return shownTasksChanged
}
private fun computeShownRunningTasks(): List<GroupTask> {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
index dc6365b..181cba0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
@@ -79,6 +79,7 @@
import com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT
import com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN
import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
+import com.android.launcher3.util.OnboardingPrefs.TASKBAR_SEARCH_EDU_SEEN
import com.android.launcher3.util.PluginManagerWrapper
import com.android.launcher3.util.StartActivityParams
import com.android.launcher3.util.UserIconInfo
@@ -394,6 +395,7 @@
HOTSEAT_LONGPRESS_TIP_SEEN.sharedPrefKey
)
addOnboardPref("Taskbar Education", TASKBAR_EDU_TOOLTIP_STEP.sharedPrefKey)
+ addOnboardPref("Taskbar Search Education", TASKBAR_SEARCH_EDU_SEEN.sharedPrefKey)
addOnboardPref("All Apps Visited Count", ALL_APPS_VISITED_COUNT.sharedPrefKey)
}
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index bdbe826..b6d83d3 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -2445,16 +2445,15 @@
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
- Optional<RemoteAnimationTarget> taskTargetOptional =
- Arrays.stream(appearedTaskTargets)
- .filter(mGestureState.mLastStartedTaskIdPredicate)
- .findFirst();
- if (!taskTargetOptional.isPresent()) {
+ RemoteAnimationTarget[] taskTargets = Arrays.stream(appearedTaskTargets)
+ .filter(mGestureState.mLastStartedTaskIdPredicate)
+ .toArray(RemoteAnimationTarget[]::new);
+ if (taskTargets.length == 0) {
ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
- RemoteAnimationTarget taskTarget = taskTargetOptional.get();
+ RemoteAnimationTarget taskTarget = taskTargets[0];
TaskView taskView = mRecentsView == null
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
if (taskView == null
@@ -2468,13 +2467,13 @@
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
- animateSplashScreenExit(mContainer, appearedTaskTargets, taskTarget.leash);
+ animateSplashScreenExit(mContainer, appearedTaskTargets, taskTargets);
}
private void animateSplashScreenExit(
@NonNull T activity,
@NonNull RemoteAnimationTarget[] appearedTaskTargets,
- @NonNull SurfaceControl leash) {
+ @NonNull RemoteAnimationTarget[] animatingTargets) {
ViewGroup splashView = activity.getDragLayer();
final QuickstepLauncher quickstepLauncher = activity instanceof QuickstepLauncher
? (QuickstepLauncher) activity : null;
@@ -2492,26 +2491,28 @@
}
surfaceApplier.scheduleApply(transaction);
- SplashScreenExitAnimationUtils.startAnimations(splashView, leash,
- mSplashMainWindowShiftLength, new TransactionPool(), new Rect(),
- SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION,
- /* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0,
- SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION,
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Hiding launcher which shows the app surface behind, then
- // finishing recents to the app. After transition finish, showing
- // the views on launcher again, so it can be visible when next
- // animation starts.
- splashView.setAlpha(0);
- if (quickstepLauncher != null) {
- quickstepLauncher.getDepthController()
- .pauseBlursOnWindows(false);
+ for (RemoteAnimationTarget target : animatingTargets) {
+ SplashScreenExitAnimationUtils.startAnimations(splashView, target.leash,
+ mSplashMainWindowShiftLength, new TransactionPool(), target.screenSpaceBounds,
+ SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION,
+ /* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0,
+ SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION,
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Hiding launcher which shows the app surface behind, then
+ // finishing recents to the app. After transition finish, showing
+ // the views on launcher again, so it can be visible when next
+ // animation starts.
+ splashView.setAlpha(0);
+ if (quickstepLauncher != null) {
+ quickstepLauncher.getDepthController()
+ .pauseBlursOnWindows(false);
+ }
+ finishRecentsAnimationOnTasksAppeared(() -> splashView.setAlpha(1));
}
- finishRecentsAnimationOnTasksAppeared(() -> splashView.setAlpha(1));
- }
- });
+ });
+ }
}
private void finishRecentsAnimationOnTasksAppeared(Runnable onFinishComplete) {
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
index 8d99069..307b2fa 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.java
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java
@@ -22,6 +22,7 @@
import com.android.systemui.shared.recents.model.Task;
import java.util.List;
+import java.util.Objects;
/**
* A {@link Task} container that can contain N number of tasks that are part of the desktop in
@@ -68,4 +69,16 @@
return "type=" + taskViewType + " tasks=" + tasks;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DesktopTask that)) return false;
+ if (!super.equals(o)) return false;
+ return Objects.equals(tasks, that.tasks);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), tasks);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
index 945ffe3..e8b611c 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -26,6 +26,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* A {@link Task} container that can contain one or two tasks, depending on if the two tasks
@@ -91,4 +92,17 @@
return "type=" + taskViewType + " task1=" + task1 + " task2=" + task2;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof GroupTask that)) return false;
+ return taskViewType == that.taskViewType && Objects.equals(task1,
+ that.task1) && Objects.equals(task2, that.task2)
+ && Objects.equals(mSplitBounds, that.mSplitBounds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(task1, task2, mSplitBounds, taskViewType);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 88c3a08..48ed67b 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -453,8 +453,9 @@
}
// adjust the mSourceRectHint / mAppBounds by display cutout if applicable.
if (mSourceRectHint != null && mDisplayCutoutInsets != null) {
- if (mFromRotation == Surface.ROTATION_0 && mDisplayCutoutInsets.top >= 0) {
- // TODO: this is to special case the issues on Pixel Foldable device(s).
+ if (mFromRotation == Surface.ROTATION_0) {
+ // TODO: this is to special case the issues on Foldable device
+ // with display cutout.
mSourceRectHint.offset(mDisplayCutoutInsets.left, mDisplayCutoutInsets.top);
} else if (mFromRotation == Surface.ROTATION_90) {
mSourceRectHint.offset(mDisplayCutoutInsets.left, mDisplayCutoutInsets.top);
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
index bfad697..9ecd935 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
@@ -27,9 +27,10 @@
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.notification.NotificationKeyData
-import com.android.launcher3.taskbar.TaskbarUnitTestRule
-import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.android.launcher3.util.PackageUserKey
@@ -43,7 +44,11 @@
class TaskbarAllAppsControllerTest {
@get:Rule
- val taskbarUnitTestRule = TaskbarUnitTestRule(this, getInstrumentation().targetContext)
+ val taskbarUnitTestRule =
+ TaskbarUnitTestRule(
+ this,
+ TaskbarWindowSandboxContext.create(getInstrumentation().targetContext),
+ )
@get:Rule val animatorTestRule = AnimatorTestRule(this)
@InjectController lateinit var allAppsController: TaskbarAllAppsController
@@ -65,9 +70,8 @@
}
@Test
- @UiThreadTest
fun testToggle_taskbarRecreated_allAppsReopened() {
- allAppsController.toggle()
+ getInstrumentation().runOnMainSync { allAppsController.toggle() }
taskbarUnitTestRule.recreateTaskbar()
assertThat(allAppsController.isOpen).isTrue()
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
index 72bdc16..fae5562 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
@@ -26,8 +26,9 @@
import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY
import com.android.launcher3.AbstractFloatingView.hasOpenView
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.taskbar.TaskbarUnitTestRule
-import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.android.launcher3.views.BaseDragLayer
@@ -42,7 +43,11 @@
class TaskbarOverlayControllerTest {
@get:Rule
- val taskbarUnitTestRule = TaskbarUnitTestRule(this, getInstrumentation().targetContext)
+ val taskbarUnitTestRule =
+ TaskbarUnitTestRule(
+ this,
+ TaskbarWindowSandboxContext.create(getInstrumentation().targetContext),
+ )
@InjectController lateinit var overlayController: TaskbarOverlayController
private val taskbarContext: TaskbarActivityContext
@@ -150,9 +155,10 @@
}
@Test
- @UiThreadTest
fun testRecreateTaskbar_closesWindow() {
- TestOverlayView.show(overlayController.requestWindow())
+ getInstrumentation().runOnMainSync {
+ TestOverlayView.show(overlayController.requestWindow())
+ }
taskbarUnitTestRule.recreateTaskbar()
assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse()
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
similarity index 89%
rename from quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
index 3b53cdc..6638736 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.launcher3.taskbar
+package com.android.launcher3.taskbar.rules
-import com.android.launcher3.taskbar.TaskbarModeRule.Mode
-import com.android.launcher3.taskbar.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
import com.android.launcher3.util.NavigationMode
import org.junit.rules.TestRule
import org.junit.runner.Description
@@ -40,7 +39,7 @@
* Make sure this rule precedes any rules that depend on [DisplayController], or else the instance
* might be inconsistent across the test lifecycle.
*/
-class TaskbarModeRule(private val context: SandboxContext) : TestRule {
+class TaskbarModeRule(private val context: TaskbarWindowSandboxContext) : TestRule {
/** The selected Taskbar mode. */
enum class Mode {
TRANSIENT,
@@ -60,7 +59,7 @@
override fun evaluate() {
val mode = taskbarMode.mode
- context.putObject(
+ context.applicationContext.putObject(
DisplayController.INSTANCE,
object : DisplayController(context) {
override fun getInfo(): Info {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt
similarity index 87%
rename from quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt
index 7dfbb9a..f75e542 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt
@@ -14,17 +14,16 @@
* limitations under the License.
*/
-package com.android.launcher3.taskbar
+package com.android.launcher3.taskbar.rules
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.InvariantDeviceProfile
-import com.android.launcher3.taskbar.TaskbarModeRule.Mode.PINNED
-import com.android.launcher3.taskbar.TaskbarModeRule.Mode.THREE_BUTTONS
-import com.android.launcher3.taskbar.TaskbarModeRule.Mode.TRANSIENT
-import com.android.launcher3.taskbar.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.THREE_BUTTONS
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.LauncherMultivalentJUnit
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
import com.android.launcher3.util.NavigationMode
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -34,7 +33,7 @@
@RunWith(LauncherMultivalentJUnit::class)
class TaskbarModeRuleTest {
- private val context = SandboxContext(getInstrumentation().targetContext)
+ private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
@get:Rule val taskbarModeRule = TaskbarModeRule(context)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
similarity index 94%
rename from quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
index bbf738e..12f946e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-package com.android.launcher3.taskbar
+package com.android.launcher3.taskbar.rules
import android.app.Instrumentation
import android.app.PendingIntent
-import android.content.Context
import android.content.IIntentSender
import android.content.Intent
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ServiceTestRule
import com.android.launcher3.LauncherAppState
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
@@ -62,7 +63,11 @@
* }
* ```
*/
-class TaskbarUnitTestRule(private val testInstance: Any, private val context: Context) : TestRule {
+class TaskbarUnitTestRule(
+ private val testInstance: Any,
+ private val context: TaskbarWindowSandboxContext,
+) : TestRule {
+
private val instrumentation = InstrumentationRegistry.getInstrumentation()
private val serviceTestRule = ServiceTestRule()
@@ -133,7 +138,7 @@
/** Simulates Taskbar recreation lifecycle. */
fun recreateTaskbar() {
- taskbarManager.recreateTaskbar()
+ instrumentation.runOnMainSync { taskbarManager.recreateTaskbar() }
injectControllers()
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt
new file mode 100644
index 0000000..8262e0f
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.rules
+
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarKeyguardController
+import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.taskbar.TaskbarStashController
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runners.model.Statement
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023"])
+class TaskbarUnitTestRuleTest {
+
+ private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
+
+ @Test
+ fun testSetup_taskbarInitialized() {
+ onSetup { assertThat(activityContext).isInstanceOf(TaskbarActivityContext::class.java) }
+ }
+
+ @Test
+ fun testRecreateTaskbar_activityContextChanged() {
+ onSetup {
+ val context1 = activityContext
+ recreateTaskbar()
+ val context2 = activityContext
+ assertThat(context1).isNotSameInstanceAs(context2)
+ }
+ }
+
+ @Test
+ fun testTeardown_taskbarDestroyed() {
+ val testRule = TaskbarUnitTestRule(this, context)
+ testRule.apply(EMPTY_STATEMENT, DESCRIPTION).evaluate()
+ assertThrows(RuntimeException::class.java) { testRule.activityContext }
+ }
+
+ @Test
+ fun testInjectController_validControllerType_isInjected() {
+ val testClass =
+ object {
+ @InjectController lateinit var controller: TaskbarStashController
+ val isInjected: Boolean
+ get() = ::controller.isInitialized
+ }
+
+ TaskbarUnitTestRule(testClass, context).apply(EMPTY_STATEMENT, DESCRIPTION).evaluate()
+
+ onSetup(TaskbarUnitTestRule(testClass, context)) {
+ assertThat(testClass.isInjected).isTrue()
+ }
+ }
+
+ @Test
+ fun testInjectController_multipleControllers_areInjected() {
+ val testClass =
+ object {
+ @InjectController lateinit var controller1: TaskbarStashController
+ @InjectController lateinit var controller2: TaskbarKeyguardController
+ val areInjected: Boolean
+ get() = ::controller1.isInitialized && ::controller2.isInitialized
+ }
+
+ onSetup(TaskbarUnitTestRule(testClass, context)) {
+ assertThat(testClass.areInjected).isTrue()
+ }
+ }
+
+ @Test
+ fun testInjectController_invalidControllerType_exceptionThrown() {
+ val testClass =
+ object {
+ @InjectController lateinit var manager: TaskbarManager // Not a controller.
+ }
+
+ // We cannot use #assertThrows because we also catch an assumption violated exception when
+ // running #evaluate on devices that do not support Taskbar.
+ val result =
+ try {
+ TaskbarUnitTestRule(testClass, context)
+ .apply(EMPTY_STATEMENT, DESCRIPTION)
+ .evaluate()
+ } catch (e: NoSuchElementException) {
+ e
+ }
+ assertThat(result).isInstanceOf(NoSuchElementException::class.java)
+ }
+
+ @Test
+ fun testInjectController_recreateTaskbar_controllerChanged() {
+ val testClass =
+ object {
+ @InjectController lateinit var controller: TaskbarStashController
+ }
+
+ onSetup(TaskbarUnitTestRule(testClass, context)) {
+ val controller1 = testClass.controller
+ recreateTaskbar()
+ val controller2 = testClass.controller
+ assertThat(controller1).isNotSameInstanceAs(controller2)
+ }
+ }
+
+ /** Executes [runTest] after the [testRule] setup phase completes. */
+ private fun onSetup(
+ testRule: TaskbarUnitTestRule = TaskbarUnitTestRule(this, context),
+ runTest: TaskbarUnitTestRule.() -> Unit,
+ ) {
+ testRule
+ .apply(
+ object : Statement() {
+ override fun evaluate() = runTest(testRule)
+ },
+ DESCRIPTION,
+ )
+ .evaluate()
+ }
+
+ private companion object {
+ private val EMPTY_STATEMENT =
+ object : Statement() {
+ override fun evaluate() = Unit
+ }
+ private val DESCRIPTION =
+ Description.createSuiteDescription(TaskbarUnitTestRuleTest::class.java)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
new file mode 100644
index 0000000..321e7a9
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.rules
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Bundle
+import android.view.Display
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+
+/**
+ * Sandbox wrapper where [createWindowContext] provides contexts that are still sandboxed within
+ * [application].
+ *
+ * Taskbar can create window contexts, which need to operate under the same sandbox application, but
+ * [Context.getApplicationContext] by default returns the actual application. For this reason,
+ * [SandboxContext] overrides [getApplicationContext] to return itself, which prevents leaving the
+ * sandbox. [SandboxContext] and the real application have different sets of
+ * [MainThreadInitializedObject] instances, so overriding the application prevents the latter set
+ * from leaking into the sandbox. Similarly, this implementation overrides [getApplicationContext]
+ * to return the original sandboxed [application], and it wraps created windowed contexts to
+ * propagate this [application].
+ */
+class TaskbarWindowSandboxContext
+private constructor(private val application: SandboxContext, base: Context) : ContextWrapper(base) {
+
+ override fun createWindowContext(type: Int, options: Bundle?): Context {
+ return TaskbarWindowSandboxContext(application, super.createWindowContext(type, options))
+ }
+
+ override fun createWindowContext(display: Display, type: Int, options: Bundle?): Context {
+ return TaskbarWindowSandboxContext(
+ application,
+ super.createWindowContext(display, type, options),
+ )
+ }
+
+ override fun getApplicationContext() = application
+
+ companion object {
+ /** Creates a [TaskbarWindowSandboxContext] to sandbox [base] for Taskbar tests. */
+ fun create(base: Context): TaskbarWindowSandboxContext {
+ return SandboxContext(base).let { TaskbarWindowSandboxContext(it, it) }
+ }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt
new file mode 100644
index 0000000..ad4b4de
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.rules
+
+import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+class TaskbarWindowSandboxContextTest {
+
+ private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
+
+ @Test
+ fun testCreateWindowContext_applicationContextSandboxed() {
+ val windowContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null)
+ assertThat(windowContext.applicationContext).isInstanceOf(SandboxContext::class.java)
+ }
+
+ @Test
+ fun testCreateWindowContext_nested_applicationContextSandboxed() {
+ val windowContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null)
+ val nestedContext = windowContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null)
+ assertThat(nestedContext.applicationContext).isInstanceOf(SandboxContext::class.java)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt
new file mode 100644
index 0000000..7aed579
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.content.ComponentName
+import android.content.Intent
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.systemui.shared.recents.model.Task
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+class DesktopTaskTest {
+
+ @Test
+ fun testDesktopTask_sameInstance_isEqual() {
+ val task = DesktopTask(createTasks(1))
+ assertThat(task).isEqualTo(task)
+ }
+
+ @Test
+ fun testDesktopTask_identicalConstructor_isEqual() {
+ val task1 = DesktopTask(createTasks(1))
+ val task2 = DesktopTask(createTasks(1))
+ assertThat(task1).isEqualTo(task2)
+ }
+
+ @Test
+ fun testDesktopTask_copy_isEqual() {
+ val task1 = DesktopTask(createTasks(1))
+ val task2 = task1.copy()
+ assertThat(task1).isEqualTo(task2)
+ }
+
+ @Test
+ fun testDesktopTask_differentId_isNotEqual() {
+ val task1 = DesktopTask(createTasks(1))
+ val task2 = DesktopTask(createTasks(2))
+ assertThat(task1).isNotEqualTo(task2)
+ }
+
+ @Test
+ fun testDesktopTask_differentLength_isNotEqual() {
+ val task1 = DesktopTask(createTasks(1))
+ val task2 = DesktopTask(createTasks(1, 2))
+ assertThat(task1).isNotEqualTo(task2)
+ }
+
+ private fun createTasks(vararg ids: Int): List<Task> {
+ return ids.map { Task(Task.TaskKey(it, 0, Intent(), ComponentName("", ""), 0, 0)) }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
new file mode 100644
index 0000000..a6d3887
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Rect
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.quickstep.views.TaskView
+import com.android.systemui.shared.recents.model.Task
+import com.android.wm.shell.common.split.SplitScreenConstants
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+class GroupTaskTest {
+
+ @Test
+ fun testGroupTask_sameInstance_isEqual() {
+ val task = GroupTask(createTask(1))
+ assertThat(task).isEqualTo(task)
+ }
+
+ @Test
+ fun testGroupTask_identicalConstructor_isEqual() {
+ val task1 = GroupTask(createTask(1))
+ val task2 = GroupTask(createTask(1))
+ assertThat(task1).isEqualTo(task2)
+ }
+
+ @Test
+ fun testGroupTask_copy_isEqual() {
+ val task1 = GroupTask(createTask(1))
+ val task2 = task1.copy()
+ assertThat(task1).isEqualTo(task2)
+ }
+
+ @Test
+ fun testGroupTask_differentId_isNotEqual() {
+ val task1 = GroupTask(createTask(1))
+ val task2 = GroupTask(createTask(2))
+ assertThat(task1).isNotEqualTo(task2)
+ }
+
+ @Test
+ fun testGroupTask_equalSplitTasks_isEqual() {
+ val splitBounds =
+ SplitConfigurationOptions.SplitBounds(
+ Rect(),
+ Rect(),
+ 1,
+ 2,
+ SplitScreenConstants.SNAP_TO_50_50
+ )
+ val task1 = GroupTask(createTask(1), createTask(2), splitBounds, TaskView.Type.GROUPED)
+ val task2 = GroupTask(createTask(1), createTask(2), splitBounds, TaskView.Type.GROUPED)
+ assertThat(task1).isEqualTo(task2)
+ }
+
+ @Test
+ fun testGroupTask_differentSplitTasks_isNotEqual() {
+ val splitBounds1 =
+ SplitConfigurationOptions.SplitBounds(
+ Rect(),
+ Rect(),
+ 1,
+ 2,
+ SplitScreenConstants.SNAP_TO_50_50
+ )
+ val splitBounds2 =
+ SplitConfigurationOptions.SplitBounds(
+ Rect(),
+ Rect(),
+ 1,
+ 2,
+ SplitScreenConstants.SNAP_TO_30_70
+ )
+ val task1 = GroupTask(createTask(1), createTask(2), splitBounds1, TaskView.Type.GROUPED)
+ val task2 = GroupTask(createTask(1), createTask(2), splitBounds2, TaskView.Type.GROUPED)
+ assertThat(task1).isNotEqualTo(task2)
+ }
+
+ @Test
+ fun testGroupTask_differentType_isNotEqual() {
+ val task1 = GroupTask(createTask(1), null, null, TaskView.Type.SINGLE)
+ val task2 = GroupTask(createTask(1), null, null, TaskView.Type.DESKTOP)
+ assertThat(task1).isNotEqualTo(task2)
+ }
+
+ private fun createTask(id: Int): Task {
+ return Task(Task.TaskKey(id, 0, Intent(), ComponentName("", ""), 0, 0))
+ }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
index 9ed3906..3232bdb 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.FolderInfo;
@@ -66,6 +67,7 @@
@Mock private MotionEvent mMotionEvent;
@Mock private BubbleTextView mHoverBubbleTextView;
@Mock private FolderIcon mHoverFolderIcon;
+ @Mock private AppPairIcon mAppPairIcon;
@Mock private Display mDisplay;
@Mock private TaskbarDragLayer mTaskbarDragLayer;
private Folder mSpyFolderView;
@@ -213,6 +215,34 @@
assertThat(hoverHandled).isFalse();
}
+ @Test
+ public void onHover_hoverEnterAppPair_revealToolTip() {
+ when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+ when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+
+ boolean hoverHandled =
+ mTaskbarHoverToolTipController.onHover(mAppPairIcon, mMotionEvent);
+ waitForIdleSync();
+
+ assertThat(hoverHandled).isTrue();
+ verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
+ true);
+ }
+
+ @Test
+ public void onHover_hoverExitAppPair_closeToolTip() {
+ when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
+ when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
+
+ boolean hoverHandled =
+ mTaskbarHoverToolTipController.onHover(mAppPairIcon, mMotionEvent);
+ waitForIdleSync();
+
+ assertThat(hoverHandled).isTrue();
+ verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
+ false);
+ }
+
private void waitForIdleSync() {
mTestableLooper.processAllMessages();
}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index 486dc68..13c4f72 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -44,6 +44,7 @@
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -56,7 +57,6 @@
@Mock private lateinit var mockRecentsModel: RecentsModel
@Mock private lateinit var mockDesktopVisibilityController: DesktopVisibilityController
- private var nextTaskId: Int = 500
private var taskListChangeId: Int = 1
private lateinit var recentAppsController: TaskbarRecentAppsController
@@ -478,6 +478,82 @@
assertThat(shownPackages).containsExactlyElementsIn(expectedPackages)
}
+ @Test
+ fun onRecentTasksChanged_notInDesktopMode_noActualChangeToRecents_commitRunningAppsToUI_notCalled() {
+ setInDesktopMode(false)
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = emptyList(),
+ runningTaskPackages = emptyList(),
+ recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+ )
+ verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+ // Call onRecentTasksChanged() again with the same tasks, verify it's a no-op.
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = emptyList(),
+ runningTaskPackages = emptyList(),
+ recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+ )
+ verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+ }
+
+ @Test
+ fun onRecentTasksChanged_inDesktopMode_noActualChangeToRunning_commitRunningAppsToUI_notCalled() {
+ setInDesktopMode(true)
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = emptyList(),
+ runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ recentTaskPackages = emptyList()
+ )
+ verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+ // Call onRecentTasksChanged() again with the same tasks, verify it's a no-op.
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = emptyList(),
+ runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ recentTaskPackages = emptyList()
+ )
+ verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+ }
+
+ @Test
+ fun onRecentTasksChanged_onlyMinimizedChanges_commitRunningAppsToUI_isCalled() {
+ setInDesktopMode(true)
+ val runningTasks = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = emptyList(),
+ runningTaskPackages = runningTasks,
+ minimizedTaskIndices = setOf(0),
+ recentTaskPackages = emptyList()
+ )
+ verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+ // Call onRecentTasksChanged() again with a new minimized app, verify we update UI.
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = emptyList(),
+ runningTaskPackages = runningTasks,
+ minimizedTaskIndices = setOf(0, 1),
+ recentTaskPackages = emptyList()
+ )
+ verify(taskbarViewController, times(2)).commitRunningAppsToUI()
+ }
+
+ @Test
+ fun onRecentTasksChanged_hotseatAppStartsRunning_commitRunningAppsToUI_isCalled() {
+ setInDesktopMode(true)
+ val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = hotseatPackages,
+ runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1),
+ recentTaskPackages = emptyList()
+ )
+ verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+ // Call onRecentTasksChanged() again with a new running app, verify we update UI.
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = hotseatPackages,
+ runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, HOTSEAT_PACKAGE_1),
+ recentTaskPackages = emptyList()
+ )
+ verify(taskbarViewController, times(2)).commitRunningAppsToUI()
+ }
+
private fun prepareHotseatAndRunningAndRecentApps(
hotseatPackages: List<String>,
runningTaskPackages: List<String>,
@@ -556,9 +632,11 @@
}
private fun createTask(packageName: String, isVisible: Boolean = true): Task {
+ // Use the number at the end of the test packageName as the id.
+ val id = packageName[packageName.length - 1].code
return Task(
Task.TaskKey(
- nextTaskId++,
+ id,
WINDOWING_MODE_FREEFORM,
Intent().apply { `package` = packageName },
ComponentName(packageName, "TestActivity"),
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index dcc55e6..d3c1a02 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -65,6 +65,7 @@
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.core.content.res.ResourcesCompat;
import com.android.launcher3.AbstractFloatingView;
@@ -1693,6 +1694,11 @@
return windowBottomPx - folderBottomPx;
}
+ @VisibleForTesting
+ public boolean getDeleteFolderOnDropCompleted() {
+ return mDeleteFolderOnDropCompleted;
+ }
+
/**
* Save this listener for the special case of when we update the state and concurrently
* add another listener to {@link #mOnFolderStateChangedListeners} to avoid a
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 35372d3..b7ad95e 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -40,7 +40,6 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
@@ -500,12 +499,6 @@
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
- }
-
- @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ViewGroup.LayoutParams containerLp = mWidgetImageContainer.getLayoutParams();
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
diff --git a/tests/src/com/android/launcher3/folder/FolderTest.kt b/tests/src/com/android/launcher3/folder/FolderTest.kt
new file mode 100644
index 0000000..e1daa74
--- /dev/null
+++ b/tests/src/com/android/launcher3/folder/FolderTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.folder
+
+import android.content.Context
+import android.graphics.Point
+import android.view.View
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DropTarget.DragObject
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.celllayout.board.FolderPoint
+import com.android.launcher3.celllayout.board.TestWorkspaceBuilder
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.ModelTestExtensions.clearModelDb
+import junit.framework.TestCase.assertEquals
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+/** Tests for [Folder] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FolderTest {
+
+ private val context: Context =
+ ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+ private val workspaceBuilder = TestWorkspaceBuilder(context)
+ private val folder: Folder = Mockito.spy(Folder(context, null))
+
+ @After
+ fun tearDown() {
+ LauncherAppState.getInstance(context).model.clearModelDb()
+ }
+
+ @Test
+ fun `Undo a folder with 1 icon when onDropCompleted is called`() {
+ val folderInfo =
+ workspaceBuilder.createFolderInCell(FolderPoint(Point(1, 0), TWO_ICON_FOLDER_TYPE), 0)
+ folder.mInfo = folderInfo
+ folder.mInfo.getContents().removeAt(0)
+ folder.mContent = Mockito.mock(FolderPagedView::class.java)
+ val dragLayout = Mockito.mock(View::class.java)
+ val dragObject = Mockito.mock(DragObject::class.java)
+ assertEquals(folder.deleteFolderOnDropCompleted, false)
+ folder.onDropCompleted(dragLayout, dragObject, true)
+ verify(folder, times(1)).replaceFolderWithFinalItem()
+ assertEquals(folder.deleteFolderOnDropCompleted, false)
+ }
+
+ @Test
+ fun `Do not undo a folder with 2 icons when onDropCompleted is called`() {
+ val folderInfo =
+ workspaceBuilder.createFolderInCell(FolderPoint(Point(1, 0), TWO_ICON_FOLDER_TYPE), 0)
+ folder.mInfo = folderInfo
+ folder.mContent = Mockito.mock(FolderPagedView::class.java)
+ val dragLayout = Mockito.mock(View::class.java)
+ val dragObject = Mockito.mock(DragObject::class.java)
+ assertEquals(folder.deleteFolderOnDropCompleted, false)
+ folder.onDropCompleted(dragLayout, dragObject, true)
+ verify(folder, times(0)).replaceFolderWithFinalItem()
+ assertEquals(folder.deleteFolderOnDropCompleted, false)
+ }
+
+ companion object {
+ const val TWO_ICON_FOLDER_TYPE = 'A'
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java
index 28d1faa..d40d3bc 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java
@@ -23,8 +23,6 @@
import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -56,7 +54,6 @@
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
@@ -143,7 +140,6 @@
}
@Test
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/310242894
public void testPendingWidget_withConfigScreen() {
// A non-restored widget with config screen get bound and shows a 'Click to setup' UI.
// Do not bind the widget
@@ -193,7 +189,6 @@
}
@Test
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/310242894
public void testPendingWidget_notRestored_brokenInstall() {
// A widget which is was being installed once, even if its not being
// installed at the moment is not removed.
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index ae24a57..bc26c00 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -352,8 +352,11 @@
}
private void assertPagesExist(Launcher launcher, int... pageIds) {
+ waitForLauncherCondition("Existing page count does NOT match. "
+ + "Expected: " + pageIds.length
+ + ". Actual: " + launcher.getWorkspace().getPageCount(),
+ l -> pageIds.length == l.getWorkspace().getPageCount());
int pageCount = launcher.getWorkspace().getPageCount();
- assertEquals("Existing page count does NOT match.", pageIds.length, pageCount);
for (int i = 0; i < pageCount; i++) {
CellLayout page = (CellLayout) launcher.getWorkspace().getPageAt(i);
int pageId = launcher.getWorkspace().getCellLayoutId(page);