Add TAPL API for adding app icon to hotseat

Please refer to go/tapl-add-icon-to-hotseat for more details.

Bug: 231400119
Test: atest NexusLauncherTests:TaplTestsQuickstep#testAddDeleteShortcutOnHotseat
Change-Id: I8cadecbe742413e3ce35ff269c0f78057331532c
Merged-In: I8cadecbe742413e3ce35ff269c0f78057331532c
diff --git a/src/com/android/launcher3/testing/HotseatCellCenterRequest.java b/src/com/android/launcher3/testing/HotseatCellCenterRequest.java
new file mode 100644
index 0000000..cbb847e
--- /dev/null
+++ b/src/com/android/launcher3/testing/HotseatCellCenterRequest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 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.testing;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Request object for querying a hotseat cell region in Rect.
+ */
+public class HotseatCellCenterRequest implements TestInformationRequest {
+    public final int cellInd;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(cellInd);
+    }
+
+    @Override
+    public String getRequestName() {
+        return TestProtocol.REQUEST_HOTSEAT_CELL_CENTER;
+    }
+
+    public static final Parcelable.Creator<HotseatCellCenterRequest> CREATOR =
+            new Parcelable.Creator<HotseatCellCenterRequest>() {
+
+                @Override
+                public HotseatCellCenterRequest createFromParcel(Parcel source) {
+                    return new HotseatCellCenterRequest(source);
+                }
+
+                @Override
+                public HotseatCellCenterRequest[] newArray(int size) {
+                    return new HotseatCellCenterRequest[size];
+                }
+            };
+
+    private HotseatCellCenterRequest(int cellInd) {
+        this.cellInd = cellInd;
+    }
+
+    private HotseatCellCenterRequest(Parcel in) {
+        this(in.readInt());
+    }
+
+    /**
+     * Create a builder for HotseatCellCenterRequest.
+     *
+     * @return HotseatCellCenterRequest builder.
+     */
+    public static HotseatCellCenterRequest.Builder builder() {
+        return new HotseatCellCenterRequest.Builder();
+    }
+
+    /**
+     * HotseatCellCenterRequest Builder.
+     */
+    public static final class Builder {
+        private int mCellInd;
+
+        private Builder() {
+            mCellInd = 0;
+        }
+
+        /**
+         * Set the index of hotseat cells.
+         */
+        public HotseatCellCenterRequest.Builder setCellInd(int i) {
+            this.mCellInd = i;
+            return this;
+        }
+
+        /**
+         * build the HotseatCellCenterRequest.
+         */
+        public HotseatCellCenterRequest build() {
+            return new HotseatCellCenterRequest(mCellInd);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 242d2d4..af4fb26 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -33,6 +33,7 @@
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
@@ -185,7 +186,7 @@
                     return new int[]{cellLayout.getCountX(), cellLayout.getCountY()};
                 });
 
-            case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER:
+            case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER: {
                 final WorkspaceCellCenterRequest request = extra.getParcelable(
                         TestProtocol.TEST_INFO_REQUEST_FIELD);
                 return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
@@ -197,6 +198,21 @@
                             cellLayout, request.cellX, request.cellY, request.spanX, request.spanY);
                     return new Point(cellRect.centerX(), cellRect.centerY());
                 });
+            }
+
+            case TestProtocol.REQUEST_HOTSEAT_CELL_CENTER: {
+                final HotseatCellCenterRequest request = extra.getParcelable(
+                        TestProtocol.TEST_INFO_REQUEST_FIELD);
+                return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
+                    final Hotseat hotseat = launcher.getHotseat();
+                    final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
+                            hotseat, request.cellInd, /* cellY= */ 0,
+                            /* spanX= */ 1, /* spanY= */ 1);
+                    // TODO(b/234322284): return the real center point.
+                    return new Point(cellRect.left + (cellRect.right - cellRect.left) / 3,
+                            cellRect.centerY());
+                });
+            }
 
             case TestProtocol.REQUEST_HAS_TIS: {
                 response.putBoolean(
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index b76e9d5..ca824e3 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -115,6 +115,8 @@
     public static final String REQUEST_WORKSPACE_CELL_LAYOUT_SIZE = "workspace-cell-layout-size";
     public static final String REQUEST_WORKSPACE_CELL_CENTER = "workspace-cell-center";
 
+    public static final String REQUEST_HOTSEAT_CELL_CENTER = "hotseat-cell-center";
+
     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 =
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 0d74f52..de55b4b 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -542,6 +542,18 @@
         }
     }
 
+    @Test
+    @PortraitLandscape
+    public void testAddDeleteShortcutOnHotseat() {
+        mLauncher.getWorkspace()
+                .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
+                .switchToAllApps()
+                .getAppIcon(APP_NAME)
+                .dragToHotseat(0);
+        mLauncher.getWorkspace().deleteAppIcon(
+                mLauncher.getWorkspace().getHotseatAppIcon(APP_NAME));
+    }
+
     /**
      * @return List of workspace grid coordinates. Those are not pixels. See {@link
      *     Workspace#getIconGridDimensions()}
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java b/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
index 7546504..693baa0 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAppIcon.java
@@ -22,6 +22,8 @@
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.UiObject2;
 
+import java.util.function.Supplier;
+
 /**
  * App icon on the workspace or all apps.
  */
@@ -100,9 +102,46 @@
         }
     }
 
+    /**
+     * Drag an object to the given cell in hotseat. The target cell should be expected to be empty.
+     *
+     * @param cellInd zero based index number of the hotseat cells.
+     * @return the workspace app icon.
+     */
+    @NonNull
+    public WorkspaceAppIcon dragToHotseat(int cellInd) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     String.format("want to drag the icon to hotseat cell %d", cellInd))
+        ) {
+            final Supplier<Point> dest = () -> Workspace.getHotseatCellCenter(mLauncher, cellInd);
+
+            Workspace.dragIconToHotseat(
+                    mLauncher,
+                    this,
+                    dest,
+                    () -> addExpectedEventsForLongClick(),
+                    /*expectDropEvents= */ null);
+            try (LauncherInstrumentation.Closable ignore = mLauncher.addContextLayer("dragged")) {
+                WorkspaceAppIcon appIcon =
+                        (WorkspaceAppIcon) mLauncher.getWorkspace().getHotseatAppIcon(mAppName);
+                mLauncher.assertTrue(
+                        String.format("The %s icon should be in the hotseat cell %d.", mAppName,
+                                cellInd),
+                        appIcon.isInHotseatCell(cellInd));
+                return appIcon;
+            }
+        }
+    }
+
     /** This method requires public access, however should not be called in tests. */
     @Override
     public Launchable getLaunchable() {
         return this;
     }
+
+    boolean isInHotseatCell(int cellInd) {
+        final Point center = Workspace.getHotseatCellCenter(mLauncher, cellInd);
+        return mObject.getVisibleBounds().contains(center.x, center.y);
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 42ba18c..5e5fdec 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -39,6 +39,7 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.testing.HotseatCellCenterRequest;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.testing.WorkspaceCellCenterRequest;
 
@@ -251,6 +252,24 @@
     }
 
     /**
+     * Returns an icon for the given cell; fails if the icon doesn't exist.
+     *
+     * @param cellInd zero based index number of the hotseat cells.
+     * @return app icon.
+     */
+    @NonNull
+    public HomeAppIcon getHotseatAppIcon(int cellInd) {
+        List<UiObject2> icons = mHotseat.findObjects(AppIcon.getAnyAppIconSelector());
+        final Point center = getHotseatCellCenter(mLauncher, cellInd);
+        return icons.stream()
+                .filter(icon -> icon.getVisibleBounds().contains(center.x, center.y))
+                .findFirst()
+                .map(icon -> new WorkspaceAppIcon(mLauncher, icon))
+                .orElseThrow(() ->
+                        new AssertionError("Unable to get a hotseat icon on " + cellInd));
+    }
+
+    /**
      * @return map of text -> center of the view. In case of icons with the same name, the one with
      *     lower x coordinate is selected.
      */
@@ -360,6 +379,11 @@
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    static Point getHotseatCellCenter(LauncherInstrumentation launcher, int cellInd) {
+        return launcher.getTestInfo(HotseatCellCenterRequest.builder()
+                .setCellInd(cellInd).build()).getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     /**
      * Finds folder icons in the current workspace.
      *
@@ -471,6 +495,25 @@
                 () -> "Page scroll didn't happen", "Scrolling page");
     }
 
+    static void dragIconToHotseat(
+            LauncherInstrumentation launcher,
+            Launchable launchable,
+            Supplier<Point> dest,
+            Runnable expectLongClickEvents,
+            @Nullable Runnable expectDropEvents) {
+        final long downTime = SystemClock.uptimeMillis();
+        Point dragStart = launchable.startDrag(
+                downTime,
+                expectLongClickEvents,
+                /* runToSpringLoadedState= */ true);
+        Point targetDest = dest.get();
+
+        launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, true,
+                downTime, SystemClock.uptimeMillis(), false,
+                LauncherInstrumentation.GestureScope.INSIDE);
+        dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
+    }
+
     /**
      * Flings to get to screens on the right. Waits for scrolling and a possible overscroll
      * recoil to complete.