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;
+ }
}