Merge "Add new quickstep test for many tasks open at once." into sc-v2-dev
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 6ccb152..95ab62f 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -63,6 +63,17 @@
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, focusedTaskRect.height());
                 return response;
             }
+
+            case TestProtocol.REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET: {
+                if (!mDeviceProfile.isTablet) {
+                    return null;
+                }
+                Rect gridTaskRect = new Rect();
+                LauncherActivityInterface.INSTANCE.calculateGridTaskSize(mContext, mDeviceProfile,
+                        gridTaskRect, PagedOrientationHandler.PORTRAIT);
+                response.putParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD, gridTaskRect);
+                return response;
+            }
         }
 
         return super.call(method, arg);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 4d488db..292e9d7 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1484,6 +1484,20 @@
         return groupViewCount;
     }
 
+    /**
+     * Returns the number of tasks in the top row of the overview grid.
+     */
+    public int getTopRowTaskCountForTablet() {
+        return mTopRowIdSet.size();
+    }
+
+    /**
+     * Returns the number of tasks in the bottom row of the overview grid.
+     */
+    public int getBottomRowTaskCountForTablet() {
+        return getTaskViewCount() - mTopRowIdSet.size() - 1;
+    }
+
     protected void onTaskStackUpdated() {
         // Lazily update the empty message only when the task stack is reapplied
         updateEmptyMessage();
@@ -3170,7 +3184,7 @@
      * Returns all the tasks in the bottom row, without the focused task
      */
     private IntArray getBottomRowIdArray() {
-        int bottomRowIdArraySize = getTaskViewCount() - mTopRowIdSet.size() - 1;
+        int bottomRowIdArraySize = getBottomRowTaskCountForTablet();
         if (bottomRowIdArraySize <= 0) {
             return new IntArray(0);
         }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 3b7370f..4895b10 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -146,7 +146,8 @@
 
         // Test dismissing all tasks.
         mLauncher.pressHome().switchToOverview().dismissAllTasks();
-        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+        assertTrue("Launcher internal state is not Home",
+                isInState(() -> LauncherState.NORMAL));
         executeOnLauncher(
                 launcher -> assertEquals("Still have tasks after dismissing all",
                         0, getTaskCount(launcher)));
@@ -180,6 +181,14 @@
         return launcher.<RecentsView>getOverviewPanel().getTaskViewCount();
     }
 
+    private int getTopRowTaskCountForTablet(Launcher launcher) {
+        return launcher.<RecentsView>getOverviewPanel().getTopRowTaskCountForTablet();
+    }
+
+    private int getBottomRowTaskCountForTablet(Launcher launcher) {
+        return launcher.<RecentsView>getOverviewPanel().getBottomRowTaskCountForTablet();
+    }
+
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
@@ -276,4 +285,70 @@
                 isTestActivityRunning(2));
         getAndAssertBackground();
     }
+
+    @Test
+    @PortraitLandscape
+    public void testOverviewForTablet() throws Exception {
+        if (!mLauncher.isTablet()) {
+            return;
+        }
+        for (int i = 2; i <= 12; i++) {
+            startTestActivity(i);
+        }
+
+        Overview overview = mLauncher.pressHome().switchToOverview();
+        executeOnLauncher(
+                launcher -> assertTrue("Don't have at least 11 tasks",
+                        getTaskCount(launcher) >= 11));
+
+        // Test scroll the first task off screen
+        overview.scrollCurrentTaskOffScreen();
+        assertTrue("Launcher internal state is not Overview",
+                isInState(() -> LauncherState.OVERVIEW));
+        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
+                getCurrentOverviewPage(launcher) > 0));
+
+        // Test opening the task.
+        overview.getCurrentTask().open();
+        assertTrue("Test activity didn't open from Overview",
+                mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity8")),
+                        DEFAULT_UI_TIMEOUT));
+
+        // Scroll the task offscreen as it is now first
+        overview = mLauncher.pressHome().switchToOverview();
+        overview.scrollCurrentTaskOffScreen();
+        assertTrue("Launcher internal state is not Overview",
+                isInState(() -> LauncherState.OVERVIEW));
+        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
+                getCurrentOverviewPage(launcher) > 0));
+
+        // Test dismissing the later task.
+        final Integer numTasks = getFromLauncher(this::getTaskCount);
+        overview.getCurrentTask().dismiss();
+        executeOnLauncher(
+                launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
+                        numTasks - 1, getTaskCount(launcher)));
+        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after dismissal",
+                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
+                        launcher)) <= 1)));
+
+        // Test dismissing more tasks.
+        assertTrue("Launcher internal state didn't remain in Overview",
+                isInState(() -> LauncherState.OVERVIEW));
+        overview.getCurrentTask().dismiss();
+        assertTrue("Launcher internal state didn't remain in Overview",
+                isInState(() -> LauncherState.OVERVIEW));
+        overview.getCurrentTask().dismiss();
+        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple dismissals",
+                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
+                        launcher)) <= 1)));
+
+        // Test dismissing all tasks.
+        mLauncher.pressHome().switchToOverview().dismissAllTasks();
+        assertTrue("Launcher internal state is not Home",
+                isInState(() -> LauncherState.NORMAL));
+        executeOnLauncher(
+                launcher -> assertEquals("Still have tasks after dismissing all",
+                        0, getTaskCount(launcher)));
+    }
 }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 893a215..5bf0342 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -104,6 +104,8 @@
     public static final String REQUEST_GET_ACTIVITIES = "get-activities";
     public static final String REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET =
             "get-focused-task-height-for-tablet";
+    public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET =
+            "get-grid-task-size-rect-for-tablet";
 
     public static Long sForcePauseTimeout;
     public static final String REQUEST_SET_FORCE_PAUSE_TIMEOUT = "set-force-pause-timeout";
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 8222f75..aae8fb5 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -145,6 +145,16 @@
             <meta-data android:name="android.app.shortcuts"
                        android:resource="@xml/shortcuts"/>
         </activity>
+        <activity
+            android:name="com.android.launcher3.testcomponent.OtherBaseTestingActivity"
+            android:label="OtherLauncherTestApp"
+            android:exported="true"
+            android:taskAffinity="com.android.launcher3.testcomponent.Affinity2">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
         <activity-alias android:name="Activity2"
                         android:label="TestActivity2"
                         android:exported="true"
@@ -208,31 +218,36 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
-        <activity-alias android:name="Activity9"
-                        android:label="TestActivity9"
-                        android:exported="true"
-                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+        <activity-alias android:name="Activity9" android:exported="true"
+            android:label="TestActivity9"
+            android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity-alias>
-        <activity-alias android:name="Activity10"
-                        android:label="TestActivity10"
-                        android:exported="true"
-                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+        <activity-alias android:name="Activity10" android:exported="true"
+            android:label="TestActivity10"
+            android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity-alias>
-        <activity-alias android:name="Activity11"
-                        android:label="TestActivity11"
-                        android:exported="true"
-                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+        <activity-alias android:name="Activity11" android:exported="true"
+            android:label="TestActivity11"
+            android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Activity12" android:exported="true"
+            android:label="TestActivity12"
+            android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity-alias>
     </application>
diff --git a/tests/src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java b/tests/src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java
new file mode 100644
index 0000000..8bcab62
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2018 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.testcomponent;
+
+/**
+ * Extension of BaseTestingActivity to help test many activities open at once.
+ */
+public class OtherBaseTestingActivity extends BaseTestingActivity {}
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index b037be4..0e3b501 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -25,6 +25,7 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Common overview panel for both Launcher and fallback recents
@@ -53,14 +54,18 @@
     }
 
     private void flingForwardImpl() {
+        flingForwardImpl(0);
+    }
+
+    private void flingForwardImpl(int rightMargin) {
         try (LauncherInstrumentation.Closable c =
                      mLauncher.addContextLayer("want to fling forward in overview")) {
             LauncherInstrumentation.log("Overview.flingForward before fling");
             final UiObject2 overview = verifyActiveContainer();
             final int leftMargin =
                     mLauncher.getTargetInsets().left + mLauncher.getEdgeSensitivityWidth();
-            mLauncher.scroll(
-                    overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20, false);
+            mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin + 1, 0, rightMargin, 0),
+                    20, false);
             try (LauncherInstrumentation.Closable c2 =
                          mLauncher.addContextLayer("flung forwards")) {
                 verifyActiveContainer();
@@ -86,6 +91,8 @@
 
             mLauncher.clickLauncherObject(
                     mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector));
+
+            mLauncher.waitUntilLauncherObjectGone(clearAllSelector);
         }
     }
 
@@ -111,6 +118,40 @@
     }
 
     /**
+     * Scrolls the current task via flinging forward until it is off screen.
+     *
+     * If only one task is present, it is only partially scrolled off screen and will still be
+     * the current task.
+     */
+    public void scrollCurrentTaskOffScreen() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "want to scroll current task off screen in overview")) {
+            verifyActiveContainer();
+
+            OverviewTask task = getCurrentTask();
+            mLauncher.assertNotNull("current task is null", task);
+            flingForwardImpl(task.getTaskCenterX());
+
+            try (LauncherInstrumentation.Closable c2 =
+                         mLauncher.addContextLayer("scrolled task off screen")) {
+                verifyActiveContainer();
+                verifyActionsViewVisibility();
+
+                if (getTaskCount() > 1) {
+                    if (mLauncher.isTablet()) {
+                        mLauncher.assertTrue("current task is not grid height",
+                                getCurrentTask().getVisibleHeight() == mLauncher
+                                        .getGridTaskRectForTablet().height());
+                    }
+                    mLauncher.assertTrue("Current task not scrolled off screen",
+                            !getCurrentTask().equals(task));
+                }
+            }
+        }
+    }
+
+    /**
      * Gets the current task in the carousel, or fails if the carousel is empty.
      *
      * @return the task in the middle of the visible tasks list.
@@ -130,6 +171,20 @@
         return new OverviewTask(mLauncher, widestTask, this);
     }
 
+    /**
+     * Returns a list of all tasks fully visible in the tablet grid overview.
+     */
+    @NonNull
+    public List<OverviewTask> getCurrentTasksForTablet() {
+        final List<UiObject2> taskViews = getTasks();
+        mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
+
+        final int gridTaskWidth = mLauncher.getGridTaskRectForTablet().width();
+
+        return taskViews.stream().filter(t -> t.getVisibleBounds().width() == gridTaskWidth).map(
+                t -> new OverviewTask(mLauncher, t, this)).collect(Collectors.toList());
+    }
+
     @NonNull
     private List<UiObject2> getTasks() {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
@@ -166,11 +221,18 @@
         }
     }
 
+    /**
+     * Returns if clear all button is visible.
+     */
+    public boolean isClearAllVisible() {
+        return mLauncher.hasLauncherObject(mLauncher.getOverviewObjectSelector("clear_all"));
+    }
+
     /* TODO(b/197630182): Once b/188790554 is fixed, remove instanceof check. Currently, when
         swiping from app to overview in Fallback Recents, taskbar remains and no action buttons
         are visible, so we are only testing Overview for now, not BaseOverview. */
     private void verifyActionsViewVisibility() {
-        if (!(this instanceof Overview)) {
+        if (!(this instanceof Overview) || !hasTasks()) {
             return;
         }
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
@@ -198,8 +260,13 @@
 
     /**
      * Returns Overview focused task if it exists.
+     *
+     * @throws IllegalStateException if not run on a tablet device.
      */
     UiObject2 getFocusedTaskForTablet() {
+        if (!mLauncher.isTablet()) {
+            throw new IllegalStateException("Must be run on tablet device.");
+        }
         final List<UiObject2> taskViews = getTasks();
         if (taskViews.size() == 0) {
             return null;
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 55fb2c1..9ee0e25 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -326,6 +326,11 @@
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    Rect getGridTaskRectForTablet() {
+        return ((Rect) getTestInfo(TestProtocol.REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET)
+                .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD));
+    }
+
     float getExactScreenCenterX() {
         return getRealDisplaySize().x / 2f;
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 9419839..15bddd7 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -24,7 +24,9 @@
 
 import com.android.launcher3.testing.TestProtocol;
 
+import java.util.List;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * A recent task in the overview panel carousel.
@@ -47,10 +49,14 @@
         mOverview.verifyActiveContainer();
     }
 
-    private int getVisibleHeight() {
+    int getVisibleHeight() {
         return mTask.getVisibleBounds().height();
     }
 
+    int getTaskCenterX() {
+        return mTask.getVisibleCenter().x;
+    }
+
     /**
      * Dismisses the task by swiping up.
      */
@@ -68,6 +74,8 @@
 
             boolean taskWasFocused = mLauncher.isTablet() && getVisibleHeight() == mLauncher
                     .getFocusedTaskHeightForTablet();
+            List<Integer> originalTasksCenterX = getCurrentTasksCenterXList();
+            boolean isClearAllVisibleBeforeDismiss = mOverview.isClearAllVisible();
 
             dismissBySwipingUp();
 
@@ -76,6 +84,16 @@
                     mLauncher.assertNotNull("No task became focused",
                             mOverview.getFocusedTaskForTablet());
                 }
+                if (!isClearAllVisibleBeforeDismiss) {
+                    List<Integer> currentTasksCenterX = getCurrentTasksCenterXList();
+                    if (originalTasksCenterX.size() == currentTasksCenterX.size()) {
+                        // Check for the same number of visible tasks before and after to
+                        // avoid asserting on cases of shifting all tasks to close the distance
+                        // between clear all and tasks at the end of the grid.
+                        mLauncher.assertTrue("Task centers not aligned",
+                                originalTasksCenterX.equals(currentTasksCenterX));
+                    }
+                }
             }
         }
     }
@@ -94,6 +112,14 @@
                         + centerY, "swiping to dismiss");
     }
 
+    private List<Integer> getCurrentTasksCenterXList() {
+        return mLauncher.isTablet()
+                ? mOverview.getCurrentTasksForTablet().stream()
+                    .map(OverviewTask::getTaskCenterX)
+                    .collect(Collectors.toList())
+                : List.of(mOverview.getCurrentTask().getTaskCenterX());
+    }
+
     /**
      * Clicks at the task.
      */