Merge "Hide taskviews when animating to split" into tm-qpr-dev
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 21728ad..d676f7d 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1996,9 +1996,11 @@
      * Applies the transform on the recents animation
      */
     protected void applyScrollAndTransform() {
-        // No need to apply any transform if there is ongoing swipe-pip-to-home animator since
-        // that animator handles the leash solely.
-        boolean notSwipingPipToHome = mRecentsAnimationTargets != null && !mIsSwipingPipToHome;
+        // No need to apply any transform if there is ongoing swipe-to-home animator
+        //    swipe-to-pip handles the leash solely
+        //    swipe-to-icon animation is handled by RectFSpringAnim anim
+        boolean notSwipingToHome = mRecentsAnimationTargets != null
+                && mGestureState.getEndTarget() != HOME;
         boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
         for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
             AnimatorControllerWithResistance playbackController =
@@ -2008,7 +2010,7 @@
                         getScaleProgressDueToScroll()), mDragLengthFactor);
             }
 
-            if (notSwipingPipToHome) {
+            if (notSwipingToHome) {
                 TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
                 if (setRecentsScroll) {
                     taskViewSimulator.setScroll(mRecentsView.getScrollOffset());
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index c16792a..7b2ed8b 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -31,6 +31,7 @@
     <dimen name="drop_target_button_drawable_horizontal_padding">24dp</dimen>
     <dimen name="drop_target_button_drawable_vertical_padding">20dp</dimen>
     <dimen name="drop_target_button_gap">32dp</dimen>
+    <dimen name="drop_target_button_workspace_edge_gap">32dp</dimen>
     <dimen name="drop_target_top_margin">110dp</dimen>
     <dimen name="drop_target_bottom_margin">48dp</dimen>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index e7b3375..39f0a2b 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -241,6 +241,7 @@
     <dimen name="drop_target_button_drawable_horizontal_padding">16dp</dimen>
     <dimen name="drop_target_button_drawable_vertical_padding">8dp</dimen>
     <dimen name="drop_target_button_gap">28dp</dimen>
+    <dimen name="drop_target_button_workspace_edge_gap">0dp</dimen>
 
     <!-- the distance an icon must be dragged before button drop targets accept it -->
     <dimen name="drag_distanceThreshold">30dp</dimen>
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 8da4f05..3b24df2 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -179,7 +179,12 @@
 
     @Override
     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-        mActive = !options.isKeyboardDrag && supportsDrop(dragObject.dragInfo);
+        if (options.isKeyboardDrag) {
+            mActive = false;
+        } else {
+            setupItemInfo(dragObject.dragInfo);
+            mActive = supportsDrop(dragObject.dragInfo);
+        }
         setVisibility(mActive ? View.VISIBLE : View.GONE);
 
         mAccessibleDrag = options.isAccessibleDrag;
@@ -191,6 +196,11 @@
         return supportsDrop(dragObject.dragInfo);
     }
 
+    /**
+     * Setups button for the specified ItemInfo.
+     */
+    protected abstract void setupItemInfo(ItemInfo info);
+
     protected abstract boolean supportsDrop(ItemInfo info);
 
     public abstract boolean supportsAccessibilityDrop(ItemInfo info, View view);
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 371bb80..95d3ad9 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -85,6 +85,9 @@
     }
 
     @Override
+    protected void setupItemInfo(ItemInfo info) {}
+
+    @Override
     protected boolean supportsDrop(ItemInfo info) {
         return true;
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 1cffa2a..212c3b0 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -217,6 +217,7 @@
     public int dropTargetHorizontalPaddingPx;
     public int dropTargetVerticalPaddingPx;
     public int dropTargetGapPx;
+    public int dropTargetButtonWorkspaceEdgeGapPx;
 
     // Insets
     private final Rect mInsets = new Rect();
@@ -348,6 +349,8 @@
         dropTargetVerticalPaddingPx = res.getDimensionPixelSize(
                 R.dimen.drop_target_button_drawable_vertical_padding);
         dropTargetGapPx = res.getDimensionPixelSize(R.dimen.drop_target_button_gap);
+        dropTargetButtonWorkspaceEdgeGapPx = res.getDimensionPixelSize(
+                R.dimen.drop_target_button_workspace_edge_gap);
 
         workspaceSpringLoadedBottomSpace =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index dbddb26..d908440 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -186,14 +186,13 @@
                 availableWidth = scaledPanelWidth - halfButtonGap / 2;
             } else {
                 // Both buttons plus the button gap do not display past the edge of the scaled
-                // workspace.
-                availableWidth = (scaledPanelWidth - dp.dropTargetGapPx) / 2;
+                // workspace, less a pre-defined gap from the edge of the workspace.
+                availableWidth = scaledPanelWidth - dp.dropTargetGapPx
+                        - 2 * dp.dropTargetButtonWorkspaceEdgeGapPx;
             }
 
             int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
             firstButton.measure(widthSpec, heightSpec);
-            secondButton.measure(widthSpec, heightSpec);
-
             if (!mIsVertical) {
                 // Remove icons and put the button's text on two lines if text is truncated.
                 if (firstButton.isTextTruncated(availableWidth)) {
@@ -202,6 +201,14 @@
                     firstButton.setPadding(horizontalPadding, verticalPadding / 2,
                             horizontalPadding, verticalPadding / 2);
                 }
+            }
+
+            if (!dp.isTwoPanels) {
+                availableWidth -= firstButton.getMeasuredWidth() + dp.dropTargetGapPx;
+                widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
+            }
+            secondButton.measure(widthSpec, heightSpec);
+            if (!mIsVertical) {
                 if (secondButton.isTextTruncated(availableWidth)) {
                     secondButton.setIconVisible(false);
                     secondButton.setTextMultiLine(true);
@@ -243,13 +250,30 @@
             int buttonGap = dp.dropTargetGapPx;
 
             ButtonDropTarget leftButton = mTempTargets[0];
-            leftButton.layout(barCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), 0,
-                    barCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
-
             ButtonDropTarget rightButton = mTempTargets[1];
-            rightButton.layout(barCenter + (buttonGap / 2), 0,
-                    barCenter + (buttonGap / 2) + rightButton.getMeasuredWidth(),
-                    rightButton.getMeasuredHeight());
+            if (dp.isTwoPanels) {
+                leftButton.layout(barCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), 0,
+                        barCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
+                rightButton.layout(barCenter + (buttonGap / 2), 0,
+                        barCenter + (buttonGap / 2) + rightButton.getMeasuredWidth(),
+                        rightButton.getMeasuredHeight());
+            } else {
+                int scaledPanelWidth = (int) (dp.getCellLayoutWidth() * scale);
+
+                int leftButtonWidth = leftButton.getMeasuredWidth();
+                int rightButtonWidth = rightButton.getMeasuredWidth();
+                int extraSpace = scaledPanelWidth - leftButtonWidth - rightButtonWidth - buttonGap;
+
+                int leftButtonStart = barCenter - (scaledPanelWidth / 2) + extraSpace / 2;
+                int leftButtonEnd = leftButtonStart + leftButtonWidth;
+                int rightButtonStart = leftButtonEnd + buttonGap;
+                int rightButtonEnd = rightButtonStart + rightButtonWidth;
+
+                leftButton.layout(leftButtonStart, 0, leftButtonEnd,
+                        leftButton.getMeasuredHeight());
+                rightButton.layout(rightButtonStart, 0, rightButtonEnd,
+                        rightButton.getMeasuredHeight());
+            }
         }
     }
 
@@ -318,7 +342,7 @@
     }
 
     public ButtonDropTarget[] getDropTargets() {
-        return mDropTargets;
+        return getVisibility() == View.VISIBLE ? mDropTargets : new ButtonDropTarget[0];
     }
 
     @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 31c1eff..1846383 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -536,6 +536,7 @@
         if (Utilities.ATLEAST_R) {
             getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
         }
+        setTitle(R.string.home_screen);
     }
 
     protected LauncherOverlayManager getDefaultOverlay() {
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 5b037e4..f8bc1f4 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -6,6 +6,7 @@
 import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DISMISS_PREDICTION;
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.INVALID;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO;
@@ -69,6 +70,7 @@
     private boolean mHadPendingAlarm;
 
     protected int mCurrentAccessibilityAction = -1;
+
     public SecondaryDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -134,24 +136,33 @@
     }
 
     @Override
+    protected void setupItemInfo(ItemInfo info) {
+        int buttonType = getButtonType(info, getViewUnderDrag(info));
+        if (buttonType != INVALID) {
+            setupUi(buttonType);
+        }
+    }
+
+    @Override
     protected boolean supportsDrop(ItemInfo info) {
-        return supportsAccessibilityDrop(info, getViewUnderDrag(info));
+        return getButtonType(info, getViewUnderDrag(info)) != INVALID;
     }
 
     @Override
     public boolean supportsAccessibilityDrop(ItemInfo info, View view) {
+        return getButtonType(info, view) != INVALID;
+    }
+
+    private int getButtonType(ItemInfo info, View view) {
         if (view instanceof AppWidgetHostView) {
             if (getReconfigurableWidgetId(view) != INVALID_APPWIDGET_ID) {
-                setupUi(RECONFIGURE);
-                return true;
+                return RECONFIGURE;
             }
-            return false;
+            return INVALID;
         } else if (FeatureFlags.ENABLE_PREDICTION_DISMISS.get() && info.isPredictedItem()) {
-            setupUi(DISMISS_PREDICTION);
-            return true;
+            return DISMISS_PREDICTION;
         }
 
-        setupUi(UNINSTALL);
         Boolean uninstallDisabled = mUninstallDisabledCache.get(info.user);
         if (uninstallDisabled == null) {
             UserManager userManager =
@@ -165,16 +176,20 @@
         mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
         mCacheExpireAlarm.setOnAlarmListener(this);
         if (uninstallDisabled) {
-            return false;
+            return INVALID;
         }
 
         if (info instanceof ItemInfoWithIcon) {
             ItemInfoWithIcon iconInfo = (ItemInfoWithIcon) info;
-            if ((iconInfo.runtimeStatusFlags & FLAG_SYSTEM_MASK) != 0) {
-                return (iconInfo.runtimeStatusFlags & FLAG_SYSTEM_NO) != 0;
+            if ((iconInfo.runtimeStatusFlags & FLAG_SYSTEM_MASK) != 0
+                    && (iconInfo.runtimeStatusFlags & FLAG_SYSTEM_NO) == 0) {
+                return INVALID;
             }
         }
-        return getUninstallTarget(info) != null;
+        if (getUninstallTarget(info) == null) {
+            return INVALID;
+        }
+        return UNINSTALL;
     }
 
     /**
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 44d57d7..79214e8 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -60,6 +60,7 @@
     public static final int DISMISS_PREDICTION = R.id.action_dismiss_prediction;
     public static final int PIN_PREDICTION = R.id.action_pin_prediction;
     public static final int RECONFIGURE = R.id.action_reconfigure;
+    public static final int INVALID = -1;
     protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
     protected static final int MOVE = R.id.action_move;
     protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index 5fac7cf..9e91b9d 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -20,6 +20,7 @@
 import static android.os.Process.myUserHandle;
 
 import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.mapping;
@@ -31,13 +32,18 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.AnyThread;
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -78,6 +84,7 @@
      * Sends a broadcast to all package installers that have items with active sessions on the users
      * first screen.
      */
+    @WorkerThread
     public void sendBroadcasts(Context context, List<ItemInfo> firstScreenItems) {
         UserHandle myUser = myUserHandle();
         mSessionInfoForPackage
@@ -95,6 +102,7 @@
      * @param packages List of packages with active sessions for this package installer.
      * @param firstScreenItems List of items on the first screen.
      */
+    @WorkerThread
     private void sendBroadcastToInstaller(Context context, String installerPackageName,
             Set<String> packages, List<ItemInfo> firstScreenItems) {
         Set<String> folderItems = new HashSet<>();
@@ -106,7 +114,7 @@
             if (info instanceof FolderInfo) {
                 FolderInfo folderInfo = (FolderInfo) info;
                 String folderItemInfoPackage;
-                for (ItemInfo folderItemInfo : folderInfo.contents) {
+                for (ItemInfo folderItemInfo : cloneOnMainThread(folderInfo.contents)) {
                     folderItemInfoPackage = getPackageName(folderItemInfo);
                     if (folderItemInfoPackage != null
                             && packages.contains(folderItemInfoPackage)) {
@@ -170,4 +178,17 @@
             Log.d(TAG, packageInstaller + ":" + label + ":" + pkg);
         }
     }
+
+    /**
+     * Clone the provided list on UI thread. This is used for {@link FolderInfo#contents} which
+     * is always modified on UI thread.
+     */
+    @AnyThread
+    private static List<WorkspaceItemInfo> cloneOnMainThread(ArrayList<WorkspaceItemInfo> list) {
+        try {
+            return MAIN_EXECUTOR.submit(() -> new ArrayList(list)).get();
+        } catch (Exception e) {
+            return Collections.emptyList();
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 8a97c6b..c93be8b 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -53,7 +53,6 @@
 import com.android.launcher3.widget.picker.WidgetsRecyclerView;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -199,8 +198,8 @@
         // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
         executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
                 isWorkspaceScrollable(launcher)));
-        assertNull("Chrome app was found on empty workspace",
-                workspace.tryGetWorkspaceAppIcon("Chrome"));
+        workspace.verifyWorkspaceAppIconIsGone(
+                "Chrome app was found on empty workspace", "Chrome");
 
         workspace.ensureWorkspaceIsScrollable();
 
@@ -388,10 +387,10 @@
         folder.getAppIcon(GMAIL_APP_NAME);
         Workspace workspace = folder.close();
 
-        assertNull(STORE_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(STORE_APP_NAME));
-        assertNull(GMAIL_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(GMAIL_APP_NAME));
+        workspace.verifyWorkspaceAppIconIsGone(STORE_APP_NAME + " should be moved to a folder.",
+                STORE_APP_NAME);
+        workspace.verifyWorkspaceAppIconIsGone(GMAIL_APP_NAME + " should be moved to a folder.",
+                GMAIL_APP_NAME);
 
         final HomeAppIcon mapIcon = createShortcutInCenterIfNotExist(MAPS_APP_NAME);
         folderIcon = mapIcon.dragToIcon(folderIcon);
@@ -399,8 +398,8 @@
         folder.getAppIcon(MAPS_APP_NAME);
         workspace = folder.close();
 
-        assertNull(MAPS_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(MAPS_APP_NAME));
+        workspace.verifyWorkspaceAppIconIsGone(MAPS_APP_NAME + " should be moved to a folder.",
+                MAPS_APP_NAME);
     }
 
     @Test
@@ -424,8 +423,9 @@
         for (String appName : new String[]{"Gmail", "Play Store", APP_NAME}) {
             final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(appName);
             Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(homeAppIcon);
-            assertNull(appName + " app was found after being deleted from workspace",
-                    workspace.tryGetWorkspaceAppIcon(appName));
+            workspace.verifyWorkspaceAppIconIsGone(
+                    appName + " app was found after being deleted from workspace",
+                    appName);
         }
     }
 
@@ -509,10 +509,8 @@
                     .containsAtLeast(DUMMY_APP_NAME, MAPS_APP_NAME, STORE_APP_NAME);
 
             mLauncher.getWorkspace().getWorkspaceAppIcon(DUMMY_APP_NAME).uninstall();
-
-            assertNull(
-                    DUMMY_APP_NAME + " app was found after being uninstalled",
-                    mLauncher.getWorkspace().tryGetWorkspaceAppIcon(DUMMY_APP_NAME));
+            mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
+                    DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
 
             Map<String, Point> finalPositions =
                     mLauncher.getWorkspace().getWorkspaceIconsPositions();
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index eb7f05b..e919740 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -140,6 +140,20 @@
         }
     }
 
+    /**
+     * Waits for an app icon to be gone (e.g. after uninstall). Fails if it remains.
+     *
+     * @param errorMessage error message thrown then the icon doesn't disappear.
+     * @param appName      app that should be gone.
+     */
+    public void verifyWorkspaceAppIconIsGone(String errorMessage, String appName) {
+        final UiObject2 workspace = verifyActiveContainer();
+        assertTrue(errorMessage,
+                workspace.wait(
+                        Until.gone(AppIcon.getAppIconSelector(appName, mLauncher)),
+                        LauncherInstrumentation.WAIT_TIME_MS));
+    }
+
 
     /**
      * Returns an icon for the app; fails if the icon doesn't exist.