diff --git a/quickstep/res/drawable/ic_save_app_pair.xml b/quickstep/res/drawable/ic_save_app_pair.xml new file mode 100644 index 0000000000..4a7ee1ac70 --- /dev/null +++ b/quickstep/res/drawable/ic_save_app_pair.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java index 6e47ff4716..2aa0be6b2b 100644 --- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java @@ -129,7 +129,8 @@ public class TaskOverlayFactory implements ResourceBasedOverride { TaskShortcutFactory.PIN, TaskShortcutFactory.INSTALL, TaskShortcutFactory.FREE_FORM, - TaskShortcutFactory.WELLBEING + TaskShortcutFactory.WELLBEING, + TaskShortcutFactory.SAVE_APP_PAIR }; /** diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index 255b9107b9..fd7b3434bd 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -40,6 +40,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.StatsLogManager.LauncherEvent; import com.android.launcher3.model.WellbeingModel; import com.android.launcher3.popup.SystemShortcut; @@ -63,7 +64,8 @@ import java.util.function.Function; import java.util.stream.Collectors; /** - * Represents a system shortcut that can be shown for a recent task. + * Represents a system shortcut that can be shown for a recent task. Appears as a single entry in + * the dropdown menu that shows up when you tap an app icon in Overview. */ public interface TaskShortcutFactory { @Nullable @@ -122,6 +124,26 @@ public interface TaskShortcutFactory { } } + /** + * A menu item, "Save app pair", that allows the user to preserve the current app combination as + * a single persistent icon on the Home screen, allowing for quick split screen initialization. + */ + class SaveAppPairSystemShortcut extends SystemShortcut { + + private final TaskView mTaskView; + + public SaveAppPairSystemShortcut(BaseDraggingActivity target, TaskView taskView) { + super(R.drawable.ic_save_app_pair, R.string.save_app_pair, target, + taskView.getItemInfo(), taskView); + mTaskView = taskView; + } + + @Override + public void onClick(View view) { + // TODO (b/274189428): Call "saveAppPair" function in new AppPairController class + } + } + class FreeformSystemShortcut extends SystemShortcut { private static final String TAG = "FreeformSystemShortcut"; @@ -257,9 +279,6 @@ public interface TaskShortcutFactory { final PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); - int[] taskViewTaskIds = taskView.getTaskIds(); - boolean taskViewHasMultipleTasks = taskViewTaskIds[0] != -1 && - taskViewTaskIds[1] != -1; boolean notEnoughTasksToSplit = recentsView.getTaskViewCount() < 2; boolean isFocusedTask = deviceProfile.isTablet && taskView.isFocusedTask(); boolean isTaskInExpectedScrollPosition = @@ -267,11 +286,11 @@ public interface TaskShortcutFactory { boolean isTaskSplitNotSupported = !task.isDockable; boolean hideForExistingMultiWindow = activity.getDeviceProfile().isMultiWindowMode; - if (taskViewHasMultipleTasks || - notEnoughTasksToSplit || - isTaskSplitNotSupported || - hideForExistingMultiWindow || - (isFocusedTask && isTaskInExpectedScrollPosition)) { + if (taskView.containsMultipleTasks() + || notEnoughTasksToSplit + || isTaskSplitNotSupported + || hideForExistingMultiWindow + || (isFocusedTask && isTaskInExpectedScrollPosition)) { return null; } @@ -283,6 +302,26 @@ public interface TaskShortcutFactory { } }; + TaskShortcutFactory SAVE_APP_PAIR = new TaskShortcutFactory() { + @Nullable + @Override + public List getShortcuts(BaseDraggingActivity activity, + TaskIdAttributeContainer taskContainer) { + final TaskView taskView = taskContainer.getTaskView(); + + if (!FeatureFlags.ENABLE_APP_PAIRS.get() || !taskView.containsMultipleTasks()) { + return null; + } + + return Collections.singletonList(new SaveAppPairSystemShortcut(activity, taskView)); + } + + @Override + public boolean showForSplitscreen() { + return true; + } + }; + TaskShortcutFactory FREE_FORM = new TaskShortcutFactory() { @Override public List getShortcuts(BaseDraggingActivity activity, diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index 9a65b4f213..c39d095068 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -186,35 +186,6 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { actionsView.clickAndDismissScreenshot(); } - @Test - @PortraitLandscape - public void testSplitFromOverview() { - assumeTrue(!mLauncher.isTablet()); - - startTestActivity(2); - startTestActivity(3); - - mLauncher.goHome().switchToOverview().getCurrentTask() - .tapMenu() - .tapSplitMenuItem() - .getCurrentTask() - .open(); - } - - @Test - @PortraitLandscape - public void testSplitFromOverviewForTablet() { - assumeTrue(mLauncher.isTablet()); - - startTestActivity(2); - startTestActivity(3); - - mLauncher.goHome().switchToOverview().getOverviewActions() - .clickSplit() - .getTestActivityTask(2) - .open(); - } - private int getCurrentOverviewPage(Launcher launcher) { return launcher.getOverviewPanel().getCurrentPage(); } diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java index d3fbe93632..2ae512a1f4 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java @@ -15,14 +15,18 @@ */ package com.android.quickstep; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + import android.content.Intent; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.ui.TaplTestsLauncher3; import com.android.launcher3.util.rule.TestStabilityRule; import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch; import org.junit.After; -import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -37,13 +41,6 @@ public class TaplTestsSplitscreen extends AbstractQuickStepTest { super.setUp(); TaplTestsLauncher3.initialize(this); - mLauncher.getWorkspace() - .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0)) - .switchToAllApps() - .getAppIcon(CALCULATOR_APP_NAME) - .dragToHotseat(0); - - startAppFast(CALCULATOR_APP_PACKAGE); if (mLauncher.isTablet()) { mLauncher.enableBlockTimeout(true); mLauncher.showTaskbarIfHidden(); @@ -57,15 +54,30 @@ public class TaplTestsSplitscreen extends AbstractQuickStepTest { } } + @Test + @PortraitLandscape + public void testSplitFromOverview() { + createAndLaunchASplitPair(); + } + @Test // TODO (b/270201357): When this test is proven stable, remove this TestStabilityRule and - // introduce into presubmit as well. + // introduce into presubmit as well. @TestStabilityRule.Stability( flavors = TestStabilityRule.LOCAL | TestStabilityRule.PLATFORM_POSTSUBMIT) @PortraitLandscape @TaskbarModeSwitch public void testSplitAppFromHomeWithItself() throws Exception { - Assume.assumeTrue(mLauncher.isTablet()); + // Currently only tablets have Taskbar in Overview, so test is only active on tablets + assumeTrue(mLauncher.isTablet()); + + mLauncher.getWorkspace() + .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0)) + .switchToAllApps() + .getAppIcon(CALCULATOR_APP_NAME) + .dragToHotseat(0); + + startAppFast(CALCULATOR_APP_PACKAGE); mLauncher.goHome() .switchToAllApps() @@ -79,4 +91,50 @@ public class TaplTestsSplitscreen extends AbstractQuickStepTest { .getAppIcon(CALCULATOR_APP_NAME) .launchIntoSplitScreen(); } + + @Test + public void testSaveAppPairMenuItemExistsOnSplitPair() throws Exception { + assumeTrue(FeatureFlags.ENABLE_APP_PAIRS.get()); + + createAndLaunchASplitPair(); + + assertTrue("Save app pair menu item is missing", + mLauncher.goHome() + .switchToOverview() + .getCurrentTask() + .tapMenu() + .hasMenuItem("Save app pair")); + } + + @Test + public void testSaveAppPairMenuItemDoesNotExistOnSingleTask() throws Exception { + assumeTrue(FeatureFlags.ENABLE_APP_PAIRS.get()); + + startAppFast(CALCULATOR_APP_PACKAGE); + + assertFalse("Save app pair menu item is erroneously appearing on single task", + mLauncher.goHome() + .switchToOverview() + .getCurrentTask() + .tapMenu() + .hasMenuItem("Save app pair")); + } + + private void createAndLaunchASplitPair() { + startTestActivity(2); + startTestActivity(3); + + if (mLauncher.isTablet()) { + mLauncher.goHome().switchToOverview().getOverviewActions() + .clickSplit() + .getTestActivityTask(2) + .open(); + } else { + mLauncher.goHome().switchToOverview().getCurrentTask() + .tapMenu() + .tapSplitMenuItem() + .getCurrentTask() + .open(); + } + } } diff --git a/res/values/strings.xml b/res/values/strings.xml index f2fb8f59d0..7552b22499 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -42,6 +42,9 @@ Split screen App info for %1$s + + Save app pair + Touch & hold to move a widget. diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java index 8cdc8a036e..54be3c324d 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java @@ -49,4 +49,10 @@ public class OverviewTaskMenu { } } } + + /** Returns true if an item matching the given string is present in the menu. */ + public boolean hasMenuItem(String expectedMenuItemText) { + UiObject2 menuItem = mLauncher.findObjectInContainer(mMenu, By.text(expectedMenuItemText)); + return menuItem != null; + } }