Merge "App Pairs (behind flag): Add Overview menu item, icon, tests" into udc-dev
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 0000000..4a7ee1a
--- /dev/null
+++ b/quickstep/res/drawable/ic_save_app_pair.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+     Copyright (C) 2023 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M13.329,2.305H4.242C2.751,2.305 1.542,3.514 1.542,5.005V13.005C1.542,14.496 2.751,15.705 4.242,15.705H7.875V19.011C7.875,20.502 9.084,21.711 10.575,21.711H19.662C21.153,21.711 22.362,20.502 22.362,19.011V10.011C22.362,8.52 21.153,7.311 19.662,7.311H16.029V5.005C16.029,3.514 14.821,2.305 13.329,2.305ZM14.329,7.311V5.005C14.329,4.452 13.882,4.005 13.329,4.005H4.242C3.69,4.005 3.242,4.452 3.242,5.005V13.005C3.242,13.557 3.69,14.005 4.242,14.005H7.875V10.011C7.875,8.52 9.084,7.311 10.575,7.311H14.329ZM9.575,14.005V10.011C9.575,9.611 9.81,9.266 10.15,9.106C10.285,9.037 10.438,8.999 10.6,8.999H19.687C20.239,8.999 20.687,9.447 20.687,9.999V18.999C20.687,19.399 20.452,19.744 20.113,19.904C19.977,19.972 19.824,20.011 19.662,20.011H10.575C10.023,20.011 9.575,19.563 9.575,19.011V15.705H9.6V14.005H9.575ZM15.542,11.996V14H17.588V15H15.542V16.996H14.542V15H12.464V14H14.542V11.996H15.542Z"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 6e47ff4..2aa0be6 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -129,7 +129,8 @@
             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 255b910..fd7b343 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -40,6 +40,7 @@
 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.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 @@
         }
     }
 
+    /**
+     * 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<BaseDraggingActivity> {
         private static final String TAG = "FreeformSystemShortcut";
 
@@ -257,9 +279,6 @@
             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 @@
             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 @@
         }
     };
 
+    TaskShortcutFactory SAVE_APP_PAIR = new TaskShortcutFactory() {
+        @Nullable
+        @Override
+        public List<SystemShortcut> 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<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 9a65b4f..c39d095 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -186,35 +186,6 @@
         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.<RecentsView>getOverviewPanel().getCurrentPage();
     }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index d3fbe93..2ae512a 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 @@
         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();
@@ -58,14 +55,29 @@
     }
 
     @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 @@
                 .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 f2fb8f5..7552b22 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -42,6 +42,9 @@
     <string name="recent_task_option_split_screen">Split screen</string>
     <string name="split_app_info_accessibility">App info for %1$s</string>
 
+    <!-- App pairs -->
+    <string name="save_app_pair">Save app pair</string>
+
     <!-- Widgets -->
     <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
     <string name="long_press_widget_to_add">Touch &amp; hold to move a widget.</string>
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
index 8cdc8a0..54be3c3 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
@@ -49,4 +49,10 @@
             }
         }
     }
+
+    /** 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;
+    }
 }