Merge "Fix Fail to Launch work tab in launcher" into tm-dev
diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index 2cd48d5..0cd9b2b 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -15,7 +15,6 @@
 */
 -->
 <resources>
+    <!--  Overview actions  -->
     <dimen name="overview_actions_top_margin">12dp</dimen>
-    <dimen name="overview_grid_side_margin">52dp</dimen>
-    <dimen name="overview_page_spacing">38dp</dimen>
 </resources>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
index 5153afa..7494683 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -17,11 +17,13 @@
 <resources>
     <dimen name="navigation_key_padding">25dp</dimen>
 
+    <!--  Task View  -->
+    <dimen name="task_thumbnail_icon_size">48dp</dimen>
+    <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
     <dimen name="overview_task_margin">12dp</dimen>
-    <dimen name="overview_task_margin_grid">4dp</dimen>
-    <dimen name="overview_actions_button_spacing">36dp</dimen>
-    <dimen name="overview_grid_side_margin">60dp</dimen>
-    <dimen name="overview_grid_row_spacing">36dp</dimen>
+    <dimen name="task_thumbnail_icon_drawable_size_grid">44dp</dimen>
+    <dimen name="overview_task_margin_grid">12dp</dimen>
+    <dimen name="overview_grid_row_spacing">28dp</dimen>
     <dimen name="overview_page_spacing">36dp</dimen>
-    <dimen name="task_thumbnail_icon_drawable_size_grid">32dp</dimen>
+    <dimen name="overview_grid_side_margin">64dp</dimen>
 </resources>
diff --git a/quickstep/res/values-sw720dp-land/dimens.xml b/quickstep/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..02d1189
--- /dev/null
+++ b/quickstep/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+*/
+-->
+<resources>
+    <!--  Overview actions  -->
+    <dimen name="overview_actions_top_margin">20dp</dimen>
+</resources>
diff --git a/quickstep/res/values-sw720dp/dimens.xml b/quickstep/res/values-sw720dp/dimens.xml
index e381cb0..ceaa8f8 100644
--- a/quickstep/res/values-sw720dp/dimens.xml
+++ b/quickstep/res/values-sw720dp/dimens.xml
@@ -15,11 +15,13 @@
 */
 -->
 <resources>
+    <!--  Task View  -->
+    <dimen name="task_thumbnail_icon_size">48dp</dimen>
+    <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
     <dimen name="overview_task_margin">16dp</dimen>
+    <dimen name="task_thumbnail_icon_drawable_size_grid">44dp</dimen>
     <dimen name="overview_task_margin_grid">16dp</dimen>
-    <dimen name="overview_grid_side_margin">64dp</dimen>
     <dimen name="overview_grid_row_spacing">36dp</dimen>
     <dimen name="overview_page_spacing">44dp</dimen>
-    <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
-    <dimen name="task_thumbnail_icon_drawable_size_grid">44dp</dimen>
+    <dimen name="overview_grid_side_margin">64dp</dimen>
 </resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 4210052..3072a3e 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -15,8 +15,6 @@
 -->
 
 <resources>
-    <dimen name="task_thumbnail_icon_size">48dp</dimen>
-    <dimen name="task_thumbnail_icon_drawable_size">48dp</dimen>
     <!-- For screens without rounded corners -->
     <dimen name="task_corner_radius_small">2dp</dimen>
     <!-- For Launchers that want to override the default dialog corner radius -->
@@ -32,18 +30,20 @@
     <dimen name="overview_proactive_row_bottom_margin">16dp</dimen>
 
     <dimen name="overview_minimum_next_prev_size">50dp</dimen>
+
+    <!--  Task View  -->
+    <dimen name="task_thumbnail_icon_size">48dp</dimen>
+    <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
     <dimen name="overview_task_margin">16dp</dimen>
-    <dimen name="overview_task_margin_grid">0dp</dimen>
+    <dimen name="overview_page_spacing">16dp</dimen>
+
     <item name="overview_max_scale" format="float" type="dimen">0.7</item>
     <item name="overview_modal_max_scale" format="float" type="dimen">1.1</item>
 
     <!-- Overrideable in overlay that provides the Overview Actions. -->
-    <dimen name="overview_actions_height">48dp</dimen>
-    <dimen name="overview_actions_button_spacing">32dp</dimen>
     <dimen name="overview_actions_top_margin">24dp</dimen>
-    <dimen name="overview_actions_horizontal_margin">16dp</dimen>
-
-    <dimen name="overview_page_spacing">16dp</dimen>
+    <dimen name="overview_actions_height">48dp</dimen>
+    <dimen name="overview_actions_button_spacing">36dp</dimen>
 
     <!-- These speeds are in dp/s -->
     <dimen name="max_task_dismiss_drag_velocity">2.25dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java b/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
index 6c4c601..9c3b881 100644
--- a/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
+++ b/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
@@ -19,6 +19,7 @@
 import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 
 import com.android.launcher3.LauncherSettings;
@@ -38,8 +39,8 @@
     }
 
     @Override
-    public WorkspaceItemInfo makeWorkspaceItem() {
-        WorkspaceItemInfo workspaceItemInfo = super.makeWorkspaceItem();
+    public WorkspaceItemInfo makeWorkspaceItem(Context context) {
+        WorkspaceItemInfo workspaceItemInfo = super.makeWorkspaceItem(context);
         workspaceItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
         workspaceItemInfo.status = WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON
                 | WorkspaceItemInfo.FLAG_RESTORE_STARTED
diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
index 9c3daea..7e3ee7d 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -85,7 +85,7 @@
                         .filter(info -> user.equals(info.user) && cn.equals(info.componentName))
                         .map(ai -> {
                             app.getIconCache().getTitleAndIcon(ai, false);
-                            return ai.makeWorkspaceItem();
+                            return ai.makeWorkspaceItem(context);
                         })
                         .findAny()
                         .orElseGet(() -> {
@@ -96,7 +96,7 @@
                             }
                             AppInfo ai = new AppInfo(context, lai, user);
                             app.getIconCache().getTitleAndIcon(ai, lai, false);
-                            return ai.makeWorkspaceItem();
+                            return ai.makeWorkspaceItem(context);
                         });
 
                 if (itemInfo == null) {
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 0e534f4..770dfb2 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -460,7 +460,7 @@
                     AppInfo info = new AppInfo(lai, user, mUMS.isUserQuiet(user));
                     mAppState.getIconCache().getTitleAndIcon(info, lai, false);
                     mReadCount++;
-                    return info.makeWorkspaceItem();
+                    return info.makeWorkspaceItem(mAppState.getContext());
                 }
                 case ITEM_TYPE_DEEP_SHORTCUT: {
                     ShortcutKey key = ShortcutKey.fromIntent(intent, user);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index c6dbc87..7b4501a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.SystemUiProxy;
@@ -136,7 +137,7 @@
             return null;
         }
         ItemInfo item = (ItemInfo) icon.getTag();
-        if (!PopupContainerWithArrow.canShow(icon, item)) {
+        if (!ShortcutUtil.supportsShortcuts(item)) {
             return null;
         }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 351ec4a..5f3a990 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -180,7 +180,7 @@
                 : null;
         super.applyFromWorkspaceItem(info, animate, staggerIndex);
         int oldPlateColor = mPlateColor;
-        int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
+        int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.appColor, 200);
         if (!animate) {
             mPlateColor = newPlateColor;
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 699ce97..5f62749 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -66,9 +66,9 @@
     @Override
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
         RecentsView recentsView = launcher.getOverviewPanel();
-        float workspacePageWidth = launcher.getDeviceProfile().getWorkspaceWidth();
+        float workspacePageHeight = launcher.getDeviceProfile().getCellLayoutHeight();
         recentsView.getTaskSize(sTempRect);
-        float scale = (float) sTempRect.width() / workspacePageWidth;
+        float scale = (float) sTempRect.height() / workspacePageHeight;
         float parallaxFactor = 0.5f;
         return new ScaleAndTranslation(scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor);
     }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 6745246..2fcd286 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -222,32 +222,27 @@
      */
     public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) {
         Resources res = context.getResources();
+        float maxScale = res.getFloat(R.dimen.overview_max_scale);
         if (dp.isTablet) {
             Rect gridRect = new Rect();
             calculateGridSize(dp, gridRect);
 
-            PointF taskDimension = getTaskDimension(context, dp);
-            float scale = gridRect.height() / taskDimension.y;
-            scale = Math.min(scale, res.getFloat(R.dimen.overview_max_scale));
-            int outWidth = Math.round(scale * taskDimension.x);
-            int outHeight = Math.round(scale * taskDimension.y);
-
-            int gravity = Gravity.CENTER;
-            Gravity.apply(gravity, outWidth, outHeight, gridRect, outRect);
+            calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect);
         } else {
             int taskMargin = dp.overviewTaskMarginPx;
             calculateTaskSizeInternal(context, dp,
                     dp.overviewTaskThumbnailTopMarginPx,
                     dp.getOverviewActionsClaimedSpace(),
                     res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
+                    maxScale,
                     Gravity.CENTER,
                     outRect);
         }
     }
 
     private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove,
-            int claimedSpaceBelow, int minimumHorizontalPadding, int gravity, Rect outRect) {
-        PointF taskDimension = getTaskDimension(context, dp);
+            int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity,
+            Rect outRect) {
         Rect insets = dp.getInsets();
 
         Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx);
@@ -258,9 +253,17 @@
                 minimumHorizontalPadding,
                 claimedSpaceBelow);
 
+        calculateTaskSizeInternal(context, dp, potentialTaskRect, maxScale, gravity, outRect);
+    }
+
+    private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
+            Rect potentialTaskRect, float maxScale, int gravity, Rect outRect) {
+        PointF taskDimension = getTaskDimension(context, dp);
+
         float scale = Math.min(
                 potentialTaskRect.width() / taskDimension.x,
                 potentialTaskRect.height() / taskDimension.y);
+        scale = Math.min(scale, maxScale);
         int outWidth = Math.round(scale * taskDimension.x);
         int outHeight = Math.round(scale * taskDimension.y);
 
@@ -359,6 +362,7 @@
                 dp.overviewTaskMarginPx,
                 dp.heightPx - outRect.bottom - dp.getInsets().bottom,
                 Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2),
+                1f /*maxScale*/,
                 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
                 outRect);
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index db19c45..269b3c2 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -54,6 +54,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Executors;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.TouchInteractionService.TISBinder;
@@ -136,6 +137,10 @@
         startBackgroundAnimation();
     }
 
+    private void runOnUiHelperThread(Runnable runnable) {
+        Executors.UI_HELPER_EXECUTOR.execute(runnable);
+    }
+
     private void startBackgroundAnimation() {
         if (Utilities.ATLEAST_S && mVibrator != null && mVibrator.areAllPrimitivesSupported(
                 VibrationEffect.Composition.PRIMITIVE_THUD)) {
@@ -144,22 +149,22 @@
                         new Animator.AnimatorListener() {
                             @Override
                             public void onAnimationStart(Animator animation) {
-                                mVibrator.vibrate(getVibrationEffect());
+                                runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect()));
                             }
 
                             @Override
                             public void onAnimationRepeat(Animator animation) {
-                                mVibrator.vibrate(getVibrationEffect());
+                                runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect()));
                             }
 
                             @Override
                             public void onAnimationEnd(Animator animation) {
-                                mVibrator.cancel();
+                                runOnUiHelperThread(mVibrator::cancel);
                             }
 
                             @Override
                             public void onAnimationCancel(Animator animation) {
-                                mVibrator.cancel();
+                                runOnUiHelperThread(mVibrator::cancel);
                             }
                         };
             }
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
index dce09e3..63970cd 100644
--- a/res/values-sw600dp-land/dimens.xml
+++ b/res/values-sw600dp-land/dimens.xml
@@ -16,6 +16,9 @@
   -->
 
 <resources>
+<!-- PagedView  -->
+    <dimen name="min_page_snap_velocity">3600dp</dimen>
+
 <!-- Hotseat -->
     <dimen name="spring_loaded_hotseat_top_margin">44dp</dimen>
 
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..072b92d
--- /dev/null
+++ b/res/values-sw600dp/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+
+    <!-- The duration of the PagedView page snap animation -->
+    <integer name="config_pageSnapAnimationDuration">550</integer>
+
+</resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index eb347f2..d69e777 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -15,6 +15,9 @@
 -->
 
 <resources>
+<!-- PagedView-->
+    <dimen name="min_page_snap_velocity">3000dp</dimen>
+
 <!-- DragController -->
     <dimen name="drag_flingToDeleteMinVelocity">-1000dp</dimen>
 
diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
index 439ea93..235631d 100644
--- a/res/values-sw720dp-land/dimens.xml
+++ b/res/values-sw720dp-land/dimens.xml
@@ -15,6 +15,9 @@
 -->
 
 <resources>
+<!-- PagedView  -->
+    <dimen name="min_page_snap_velocity">5300dp</dimen>
+
 <!-- Dynamic grid -->
     <dimen name="dynamic_grid_edge_margin">21.93dp</dimen>
     <dimen name="cell_layout_padding">29.33dp</dimen>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index fad8c95..7b2ed8b 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -15,6 +15,9 @@
 -->
 
 <resources>
+<!-- PagedView  -->
+    <dimen name="min_page_snap_velocity">3400dp</dimen>
+
 <!-- AllApps -->
     <dimen name="all_apps_bottom_sheet_horizontal_padding">28dp</dimen>
 
@@ -28,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/config.xml b/res/values/config.xml
index 5ecd929..9aa1f03 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -1,3 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
 <resources>
     <!-- Miscellaneous -->
     <bool name="config_largeHeap">false</bool>
@@ -25,6 +40,9 @@
     <!-- The duration of the animation from search hint to text entry -->
     <integer name="config_searchHintAnimationDuration">50</integer>
 
+    <!-- The duration of the PagedView page snap animation -->
+    <integer name="config_pageSnapAnimationDuration">750</integer>
+
     <!-- View tag key used to store SpringAnimation data. -->
     <item type="id" name="spring_animation_tag" />
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 098c694..dd99a7a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -24,6 +24,8 @@
     <dimen name="dynamic_grid_icon_drawable_padding">7dp</dimen>
     <!-- Minimum space between workspace and hotseat in spring loaded mode -->
     <dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
+    <!-- Minimum amount of next page visible in spring loaded mode -->
+    <dimen name="dynamic_grid_spring_loaded_min_next_space_visible">24dp</dimen>
 
     <dimen name="dynamic_grid_cell_border_spacing">16dp</dimen>
     <dimen name="cell_layout_padding">10.77dp</dimen>
@@ -58,13 +60,9 @@
 <!-- Drop target bar -->
     <dimen name="dynamic_grid_drop_target_size">56dp</dimen>
     <dimen name="drop_target_vertical_gap">20dp</dimen>
-    <dimen name="drop_target_top_margin">36dp</dimen>
+    <dimen name="drop_target_top_margin">32dp</dimen>
     <dimen name="drop_target_bottom_margin">16dp</dimen>
 
-    <!-- Button drop target bar -->
-    <dimen name="button_drop_target_min_text_size">10sp</dimen>
-    <dimen name="button_drop_target_resize_text_increment">1sp</dimen>
-
 <!-- App Widget resize frame -->
     <dimen name="widget_handle_margin">13dp</dimen>
     <dimen name="resize_frame_background_padding">24dp</dimen>
@@ -99,6 +97,13 @@
     <dimen name="fastscroll_width">58dp</dimen>
     <dimen name="fastscroll_end_margin">-26dp</dimen>
 
+    <!-- PagedView -->
+    <dimen name="fling_threshold_velocity">500dp</dimen>
+    <dimen name="easy_fling_threshold_velocity">400dp</dimen>
+    <dimen name="min_fling_velocity">250dp</dimen>
+    <!-- The minimum velocity of a page snap after a fling gesture -->
+    <dimen name="min_page_snap_velocity">1500dp</dimen>
+
     <!-- All Apps -->
     <dimen name="all_apps_starting_vertical_translate">300dp</dimen>
     <dimen name="all_apps_search_bar_field_height">48dp</dimen>
@@ -229,7 +234,9 @@
     <dimen name="drop_target_button_drawable_padding">8dp</dimen>
     <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">22dp</dimen>
+    <dimen name="drop_target_button_gap">28dp</dimen>
+    <dimen name="drop_target_button_workspace_edge_gap">0dp</dimen>
+    <dimen name="drop_target_button_screen_edge_gap">28dp</dimen>
 
     <!-- the distance an icon must be dragged before button drop targets accept it -->
     <dimen name="drag_distanceThreshold">30dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ee5e024..829a21d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -277,6 +277,9 @@
     <!-- Summary for Notification dots setting. Tapping this will link enable/disable notification dots feature on the home screen. [CHAR LIMIT=50] -->
     <string name="notification_dots_service_title">Show notification dots</string>
 
+    <!-- Title for Developer Options setting. [CHAR LIMIT=50] -->
+    <string name="developer_options_title">Developer Options</string>
+
     <!-- Label for the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=60] -->
     <string name="auto_add_shortcuts_label">Add app icons to home screen</string>
     <!-- Text description of the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=NONE] -->
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 90de498..8a0c909 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -53,7 +53,7 @@
     <androidx.preference.PreferenceScreen
         android:key="pref_developer_options"
         android:persistent="false"
-        android:title="Developer Options"
+        android:title="@string/developer_options_title"
         android:fragment="com.android.launcher3.settings.DeveloperOptionsFragment"/>
 
 </androidx.preference.PreferenceScreen>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 6302739..878ac3b 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -53,6 +53,7 @@
 
 import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
 import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.IconShape;
@@ -65,12 +66,11 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.model.data.SearchActionItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.BubbleTextHolder;
 import com.android.launcher3.views.IconLabelDotView;
 
 import java.text.NumberFormat;
@@ -174,7 +174,6 @@
     private HandlerRunnable mIconLoadRequest;
 
     private boolean mEnableIconUpdateAnimation = false;
-    private BubbleTextHolder mBubbleTextHolder;
 
     public BubbleTextView(Context context) {
         this(context, null, 0);
@@ -253,7 +252,8 @@
      */
     public void reset() {
         mDotInfo = null;
-        mDotParams.color = Color.TRANSPARENT;
+        mDotParams.dotColor = Color.TRANSPARENT;
+        mDotParams.appColor = Color.TRANSPARENT;
         cancelDotScaleAnim();
         mDotParams.scale = 0f;
         mForceHideDot = false;
@@ -361,15 +361,8 @@
         setDownloadStateContentDescription(info, info.getProgressLevel());
     }
 
-    private void setItemInfo(ItemInfoWithIcon itemInfo) {
+    protected void setItemInfo(ItemInfoWithIcon itemInfo) {
         setTag(itemInfo);
-        if (mBubbleTextHolder != null) {
-            mBubbleTextHolder.onItemInfoUpdated(itemInfo);
-        }
-    }
-
-    public void setBubbleTextHolder(BubbleTextHolder bubbleTextHolder) {
-        mBubbleTextHolder = bubbleTextHolder;
     }
 
     @UiThread
@@ -381,7 +374,9 @@
             flags |= FLAG_NO_BADGE;
         }
         FastBitmapDrawable iconDrawable = info.newIcon(getContext(), flags);
-        mDotParams.color = iconDrawable.getIconColor();
+        mDotParams.appColor = iconDrawable.getIconColor();
+        mDotParams.dotColor = getContext().getResources()
+                .getColor(android.R.color.system_accent3_100, getContext().getTheme());
         setIcon(iconDrawable);
         applyLabel(info);
     }
@@ -917,10 +912,8 @@
             } else if (info instanceof WorkspaceItemInfo) {
                 applyFromWorkspaceItem((WorkspaceItemInfo) info);
                 mActivity.invalidateParent(info);
-            } else if (info instanceof PackageItemInfo) {
-                applyFromItemInfoWithIcon((PackageItemInfo) info);
-            } else if (info instanceof SearchActionItemInfo) {
-                applyFromItemInfoWithIcon((SearchActionItemInfo) info);
+            } else if (info != null) {
+                applyFromItemInfoWithIcon(info);
             }
 
             mDisableRelayout = false;
@@ -1058,4 +1051,19 @@
         args.put("count", notificationCount);
         return icuCountFormat.format(args);
     }
+
+    /**
+     * Starts a long press action and returns the corresponding pre-drag condition
+     */
+    public PreDragCondition startLongPressAction() {
+        PopupContainerWithArrow popup = PopupContainerWithArrow.showForIcon(this);
+        return popup != null ? popup.createPreDragCondition(true) : null;
+    }
+
+    /**
+     * Returns true if the view can show long-press popup
+     */
+    public boolean canShowLongPressPopup() {
+        return getTag() instanceof ItemInfo && ShortcutUtil.supportsShortcuts((ItemInfo) getTag());
+    }
 }
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 0b07c95..8da4f05 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -24,6 +24,7 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.text.InputType;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -49,6 +50,8 @@
     private static final int[] sTempCords = new int[2];
     private static final int DRAG_VIEW_DROP_DURATION = 285;
     private static final float DRAG_VIEW_HOVER_OVER_OPACITY = 0.65f;
+    private static final int MAX_LINES_TEXT_MULTI_LINE = 2;
+    private static final int MAX_LINES_TEXT_SINGLE_LINE = 1;
 
     public static final int TOOLTIP_DEFAULT = 0;
     public static final int TOOLTIP_LEFT = 1;
@@ -72,6 +75,8 @@
     protected CharSequence mText;
     protected Drawable mDrawable;
     private boolean mTextVisible = true;
+    private boolean mIconVisible = true;
+    private boolean mTextMultiLine = true;
 
     private PopupWindow mToolTip;
     private int mToolTipLocation;
@@ -109,8 +114,7 @@
         // drawableLeft and drawableStart.
         mDrawable = getContext().getDrawable(resId).mutate();
         mDrawable.setTintList(getTextColors());
-        centerIcon();
-        setCompoundDrawablesRelative(mDrawable, null, null, null);
+        updateIconVisibility();
     }
 
     public void setDropTargetBar(DropTargetBar dropTargetBar) {
@@ -306,13 +310,49 @@
         if (mTextVisible != isVisible || !TextUtils.equals(newText, getText())) {
             mTextVisible = isVisible;
             setText(newText);
-            centerIcon();
-            setCompoundDrawablesRelative(mDrawable, null, null, null);
-            int drawablePadding = mTextVisible ? mDrawablePadding : 0;
-            setCompoundDrawablePadding(drawablePadding);
+            updateIconVisibility();
         }
     }
 
+    /**
+     * Display button text over multiple lines when isMultiLine is true, single line otherwise.
+     */
+    public void setTextMultiLine(boolean isMultiLine) {
+        if (mTextMultiLine != isMultiLine) {
+            mTextMultiLine = isMultiLine;
+            setSingleLine(!isMultiLine);
+            setMaxLines(isMultiLine ? MAX_LINES_TEXT_MULTI_LINE : MAX_LINES_TEXT_SINGLE_LINE);
+            int inputType = InputType.TYPE_CLASS_TEXT;
+            if (isMultiLine) {
+                inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE;
+
+            }
+            setInputType(inputType);
+        }
+    }
+
+    protected boolean isTextMultiLine() {
+        return mTextMultiLine;
+    }
+
+    /**
+     * Sets the button icon visible when isVisible is true, hides it otherwise.
+     */
+    public void setIconVisible(boolean isVisible) {
+        if (mIconVisible != isVisible) {
+            mIconVisible = isVisible;
+            updateIconVisibility();
+        }
+    }
+
+    private void updateIconVisibility() {
+        if (mIconVisible) {
+            centerIcon();
+        }
+        setCompoundDrawablesRelative(mIconVisible ? mDrawable : null, null, null, null);
+        setCompoundDrawablePadding(mIconVisible && mTextVisible ? mDrawablePadding : 0);
+    }
+
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
@@ -324,40 +364,6 @@
         hideTooltip();
     }
 
-
-    /**
-     * Reduce the size of the text until it fits or reaches a minimum.
-     *
-     * The minimum size is defined by {@code R.dimen.button_drop_target_min_text_size} and
-     * it diminishes by intervals defined by
-     * {@code R.dimen.button_drop_target_resize_text_increment}
-     * This functionality is very similar to the option
-     * {@link TextView#setAutoSizeTextTypeWithDefaults(int)} but can't be used in this view because
-     * the layout width is {@code WRAP_CONTENT}.
-     *
-     * @param availableWidth Available width in the button to fit the text, used in
-     *        {@code ButtonDropTarget#isTextTruncated(int)}
-     * @return The biggest text size in SP that makes the text fit or if the text can't fit returns
-     *         the min available value
-     */
-    public float resizeTextToFit(int availableWidth) {
-        float minSize = Utilities.pxToSp(getResources()
-                .getDimensionPixelSize(R.dimen.button_drop_target_min_text_size));
-        float step = Utilities.pxToSp(getResources()
-                .getDimensionPixelSize(R.dimen.button_drop_target_resize_text_increment));
-        float textSize = Utilities.pxToSp(getTextSize());
-
-        while (textSize > minSize) {
-            if (isTextTruncated(availableWidth)) {
-                textSize -= step;
-                setTextSize(textSize);
-            } else {
-                return textSize;
-            }
-        }
-        return minSize;
-    }
-
     public boolean isTextTruncated(int availableWidth) {
         availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth()
                 + getCompoundDrawablePadding());
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 88030ae..f028d3c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -108,6 +108,7 @@
     public float workspaceSpringLoadShrunkTop;
     public float workspaceSpringLoadShrunkBottom;
     public final int workspaceSpringLoadedBottomSpace;
+    public final int workspaceSpringLoadedMinNextPageVisiblePx;
 
     private final int extraSpace;
     public int workspaceTopPadding;
@@ -214,6 +215,8 @@
     public int dropTargetHorizontalPaddingPx;
     public int dropTargetVerticalPaddingPx;
     public int dropTargetGapPx;
+    public int dropTargetButtonWorkspaceEdgeGapPx;
+    public int dropTargetButtonScreenEdgeGapPx;
 
     // Insets
     private final Rect mInsets = new Rect();
@@ -343,9 +346,15 @@
         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);
+        dropTargetButtonScreenEdgeGapPx = res.getDimensionPixelSize(
+                R.dimen.drop_target_button_screen_edge_gap);
 
         workspaceSpringLoadedBottomSpace =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
+        workspaceSpringLoadedMinNextPageVisiblePx = res.getDimensionPixelSize(
+                R.dimen.dynamic_grid_spring_loaded_min_next_space_visible);
 
         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
 
@@ -501,7 +510,7 @@
      */
     private int calculateQsbWidth() {
         if (isQsbInline) {
-            int columns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
+            int columns = getPanelCount() * inv.numColumns;
             return getIconToIconWidthForColumns(columns)
                     - iconSizePx * numShownHotseatIcons
                     - hotseatBorderSpace * numShownHotseatIcons;
@@ -665,11 +674,10 @@
         updateIconSize(1f, res);
 
         updateWorkspacePadding();
-        Point workspacePadding = getTotalWorkspacePadding();
 
         // Check to see if the icons fit within the available height.
         float usedHeight = getCellLayoutHeightSpecification();
-        final int maxHeight = getWorkspaceHeight(workspacePadding);
+        final int maxHeight = getCellLayoutHeight();
         float extraHeight = Math.max(0, maxHeight - usedHeight);
         float scaleY = maxHeight / usedHeight;
         boolean shouldScale = scaleY < 1f;
@@ -702,7 +710,7 @@
     }
 
     private int getCellLayoutWidthSpecification() {
-        int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
+        int numColumns = getPanelCount() * inv.numColumns;
         return (cellWidthPx * numColumns) + (cellLayoutBorderSpacePx.x * (numColumns - 1))
                 + cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
     }
@@ -902,19 +910,25 @@
             result = new Point();
         }
 
-        // Since we are only concerned with the overall padding, layout direction does
-        // not matter.
-        Point padding = getTotalWorkspacePadding();
-
-        int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
-        int screenWidthPx = getWorkspaceWidth(padding);
-        result.x = calculateCellWidth(screenWidthPx, cellLayoutBorderSpacePx.x, numColumns);
-        int screenHeightPx = getWorkspaceHeight(padding);
-        result.y = calculateCellHeight(screenHeightPx, cellLayoutBorderSpacePx.y, inv.numRows);
+        int shortcutAndWidgetContainerWidth =
+                getCellLayoutWidth() - (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right);
+        result.x = calculateCellWidth(shortcutAndWidgetContainerWidth, cellLayoutBorderSpacePx.x,
+                inv.numColumns);
+        int shortcutAndWidgetContainerHeight =
+                getCellLayoutHeight() - (cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom);
+        result.y = calculateCellHeight(shortcutAndWidgetContainerHeight, cellLayoutBorderSpacePx.y,
+                inv.numRows);
         return result;
     }
 
     /**
+     * Gets the number of panels within the workspace.
+     */
+    public int getPanelCount() {
+        return isTwoPanels ? 2 : 1;
+    }
+
+    /**
      * Gets the space in px from the bottom of last item in the vertical-bar hotseat to the
      * bottom of the screen.
      */
@@ -932,7 +946,7 @@
     /**
      * Gets the scaled top of the workspace in px for the spring-loaded edit state.
      */
-    public float getWorkspaceSpringLoadShrunkTop() {
+    public float getCellLayoutSpringLoadShrunkTop() {
         workspaceSpringLoadShrunkTop = mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx
                 + dropTargetBarBottomMarginPx;
         return workspaceSpringLoadShrunkTop;
@@ -941,7 +955,7 @@
     /**
      * Gets the scaled bottom of the workspace in px for the spring-loaded edit state.
      */
-    private float getWorkspaceSpringLoadShrunkBottom() {
+    private float getCellLayoutSpringLoadShrunkBottom() {
         int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx;
         workspaceSpringLoadShrunkBottom =
                 heightPx - (isVerticalBarLayout() ? getVerticalHotseatLastItemBottomOffset()
@@ -950,45 +964,40 @@
     }
 
     /**
-     * Gets the minimum visible amount of the next workspace page when in the spring-loaded state.
-     */
-    private float getWorkspaceSpringLoadedMinimumNextPageVisible() {
-        return getCellSize().x / 2f;
-    }
-
-    /**
      * Gets the scale of the workspace for the spring-loaded edit state.
      */
     public float getWorkspaceSpringLoadScale() {
-        float cellLayoutHeight = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
-        float scale = (getWorkspaceSpringLoadShrunkBottom() - getWorkspaceSpringLoadShrunkTop())
-                / cellLayoutHeight;
+        float scale = (getCellLayoutSpringLoadShrunkBottom() - getCellLayoutSpringLoadShrunkTop())
+                / getCellLayoutHeight();
         scale = Math.min(scale, 1f);
 
         // Reduce scale if next pages would not be visible after scaling the workspace
-        int workspaceWidth = getWorkspaceWidth();
+        int workspaceWidth = availableWidthPx;
         float scaledWorkspaceWidth = workspaceWidth * scale;
-        float maxAvailableWidth =
-                workspaceWidth - (2 * getWorkspaceSpringLoadedMinimumNextPageVisible());
+        float maxAvailableWidth = workspaceWidth - (2 * workspaceSpringLoadedMinNextPageVisiblePx);
         if (scaledWorkspaceWidth > maxAvailableWidth) {
             scale *= maxAvailableWidth / scaledWorkspaceWidth;
         }
         return scale;
     }
 
-    public int getWorkspaceWidth() {
-        return getWorkspaceWidth(getTotalWorkspacePadding());
+    /**
+     * Gets the width of a single Cell Layout, aka a single panel within a Workspace.
+     *
+     * <p>This is the width of a Workspace, less its horizontal padding. Note that two-panel
+     * layouts have two Cell Layouts per workspace.
+     */
+    public int getCellLayoutWidth() {
+        return (availableWidthPx - getTotalWorkspacePadding().x) / getPanelCount();
     }
 
-    public int getWorkspaceWidth(Point workspacePadding) {
-        int cellLayoutTotalPadding =
-                (isTwoPanels ? 2 : 1) * (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right);
-        return availableWidthPx - workspacePadding.x - cellLayoutTotalPadding;
-    }
-
-    private int getWorkspaceHeight(Point workspacePadding) {
-        return availableHeightPx - workspacePadding.y - (cellLayoutPaddingPx.top
-                + cellLayoutPaddingPx.bottom);
+    /**
+     * Gets the height of a single Cell Layout, aka a single panel within a Workspace.
+     *
+     * <p>This is the height of a Workspace, less its vertical padding.
+     */
+    public int getCellLayoutHeight() {
+        return availableHeightPx - getTotalWorkspacePadding().y;
     }
 
     public Point getTotalWorkspacePadding() {
@@ -1160,7 +1169,7 @@
             return  ((taskbarSize - overviewActionsHeight) / 2) + getTaskbarOffsetY();
         }
 
-        return 0;
+        return isTaskbarPresent ? stashedTaskbarSize : mInsets.bottom;
     }
 
     /** Gets the space that the overview actions will take, including bottom margin. */
@@ -1404,11 +1413,19 @@
         writer.println(prefix + pxToDpStr("dropTargetBarSizePx", dropTargetBarSizePx));
         writer.println(
                 prefix + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx));
+        writer.println(prefix + pxToDpStr("dropTargetButtonWorkspaceEdgeGapPx",
+                dropTargetButtonWorkspaceEdgeGapPx));
+        writer.println(prefix + pxToDpStr("dropTargetButtonScreenEdgeGapPx",
+                dropTargetButtonScreenEdgeGapPx));
 
         writer.println(
                 prefix + pxToDpStr("workspaceSpringLoadShrunkTop", workspaceSpringLoadShrunkTop));
         writer.println(prefix + pxToDpStr("workspaceSpringLoadShrunkBottom",
                 workspaceSpringLoadShrunkBottom));
+        writer.println(prefix + pxToDpStr("workspaceSpringLoadedBottomSpace",
+                workspaceSpringLoadedBottomSpace));
+        writer.println(prefix + pxToDpStr("workspaceSpringLoadedMinNextPageVisiblePx",
+                workspaceSpringLoadedMinNextPageVisiblePx));
         writer.println(
                 prefix + pxToDpStr("getWorkspaceSpringLoadScale()", getWorkspaceSpringLoadScale()));
     }
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 2e3f26c..6a8ba1b 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -39,8 +39,6 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.testing.TestProtocol;
 
-import java.util.Arrays;
-
 /*
  * The top bar containing various drop targets: Delete/App Info/Uninstall.
  */
@@ -53,6 +51,8 @@
     private final Runnable mFadeAnimationEndRunnable =
             () -> updateVisibility(DropTargetBar.this);
 
+    private final Launcher mLauncher;
+
     @ViewDebug.ExportedProperty(category = "launcher")
     protected boolean mDeferOnDragEnd;
 
@@ -60,16 +60,19 @@
     protected boolean mVisible = false;
 
     private ButtonDropTarget[] mDropTargets;
+    private ButtonDropTarget[] mTempTargets;
     private ViewPropertyAnimator mCurrentAnimation;
 
     private boolean mIsVertical = true;
 
     public DropTargetBar(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mLauncher = Launcher.getLauncher(context);
     }
 
     public DropTargetBar(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        mLauncher = Launcher.getLauncher(context);
     }
 
     @Override
@@ -80,12 +83,13 @@
             mDropTargets[i] = (ButtonDropTarget) getChildAt(i);
             mDropTargets[i].setDropTargetBar(this);
         }
+        mTempTargets = new ButtonDropTarget[getChildCount()];
     }
 
     @Override
     public void setInsets(Rect insets) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+        DeviceProfile grid = mLauncher.getDeviceProfile();
         mIsVertical = grid.isVerticalBarLayout();
 
         lp.leftMargin = insets.left;
@@ -116,10 +120,15 @@
         lp.height = grid.dropTargetBarSizePx;
         lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
 
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        int horizontalPadding = dp.dropTargetHorizontalPaddingPx;
+        int verticalPadding = dp.dropTargetVerticalPaddingPx;
         setLayoutParams(lp);
         for (ButtonDropTarget button : mDropTargets) {
             button.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.dropTargetTextSizePx);
             button.setToolTipLocation(tooltipLocation);
+            button.setPadding(horizontalPadding, verticalPadding, horizontalPadding,
+                    verticalPadding);
         }
     }
 
@@ -135,36 +144,83 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
+        int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
 
-        int visibleCount = getVisibleButtonsCount();
-        if (visibleCount > 0) {
-            int availableWidth = width / visibleCount;
-            boolean textVisible = true;
-            boolean textResized = false;
-            float textSize = mDropTargets[0].getTextSize();
-            for (ButtonDropTarget button : mDropTargets) {
-                if (button.getVisibility() == GONE) {
-                    continue;
-                }
-                if (button.isTextTruncated(availableWidth)) {
-                    textSize = Math.min(textSize, button.resizeTextToFit(availableWidth));
-                    textResized = true;
-                }
-                textVisible = textVisible && !button.isTextTruncated(availableWidth);
-            }
+        int visibleCount = getVisibleButtons(mTempTargets);
+        if (visibleCount == 1) {
+            int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
 
-            if (textResized) {
-                for (ButtonDropTarget button : mDropTargets) {
-                    button.setTextSize(textSize);
-                }
-            }
+            ButtonDropTarget firstButton = mTempTargets[0];
+            firstButton.setTextVisible(true);
+            firstButton.setIconVisible(true);
+            firstButton.measure(widthSpec, heightSpec);
+        } else if (visibleCount == 2) {
+            DeviceProfile dp = mLauncher.getDeviceProfile();
+            int verticalPadding = dp.dropTargetVerticalPaddingPx;
+            int horizontalPadding = dp.dropTargetHorizontalPaddingPx;
 
-            int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
-            int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
-            for (ButtonDropTarget button : mDropTargets) {
-                if (button.getVisibility() != GONE) {
-                    button.setTextVisible(textVisible);
-                    button.measure(widthSpec, heightSpec);
+            ButtonDropTarget firstButton = mTempTargets[0];
+            firstButton.setTextVisible(true);
+            firstButton.setIconVisible(true);
+
+            ButtonDropTarget secondButton = mTempTargets[1];
+            secondButton.setTextVisible(true);
+            secondButton.setIconVisible(true);
+            secondButton.setTextMultiLine(false);
+            // Reset second button padding in case it was previously changed to multi-line text.
+            secondButton.setPadding(horizontalPadding, verticalPadding, horizontalPadding,
+                    verticalPadding);
+
+            if (dp.isTwoPanels) {
+                // Both buttons for two panel fit to the width of one Cell Layout (less
+                // half of the center gap between the buttons).
+                float scale = dp.getWorkspaceSpringLoadScale();
+                int scaledPanelWidth = (int) (dp.getCellLayoutWidth() * scale);
+                int halfButtonGap = dp.dropTargetGapPx / 2;
+                scaledPanelWidth -= halfButtonGap / 2;
+
+                int widthSpec = MeasureSpec.makeMeasureSpec(scaledPanelWidth, MeasureSpec.AT_MOST);
+                firstButton.measure(widthSpec, heightSpec);
+                secondButton.measure(widthSpec, heightSpec);
+            } else {
+                int availableWidth;
+                int buttonGap = dp.dropTargetGapPx;
+                if (mIsVertical) {
+                    // Both buttons plus the button gap do not display past the edge of the
+                    // scaled workspace, less a pre-defined gap from the edge of the workspace.
+                    float scale = dp.getWorkspaceSpringLoadScale();
+                    int panelWidth = (int) (dp.getCellLayoutWidth() * scale);
+                    availableWidth = Math.min(
+                            panelWidth - (2 * dp.dropTargetButtonWorkspaceEdgeGapPx), width);
+                } else {
+                    // Both buttons plus the button gap display up to a pre-defined margin of
+                    // the unscaled workspace edge.
+                    availableWidth = Math.min(
+                            dp.availableWidthPx - (2 * dp.dropTargetButtonScreenEdgeGapPx),
+                            width);
+                }
+                int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth - buttonGap,
+                        MeasureSpec.AT_MOST);
+
+                // First button's width is at most the drop target bar's total width less the button
+                // gap.
+                firstButton.measure(widthSpec, heightSpec);
+
+                int usedWidth = firstButton.getMeasuredWidth() + buttonGap;
+                int remainingWidth = availableWidth - usedWidth;
+                widthSpec = MeasureSpec.makeMeasureSpec(remainingWidth, MeasureSpec.AT_MOST);
+                secondButton.measure(widthSpec, heightSpec);
+
+                // Remove both icons and put the second button's text on two lines if text is
+                // truncated on phones. We assume first button's text is never truncated, so it
+                // remains single-line.
+                if (secondButton.isTextTruncated(remainingWidth) && !mIsVertical) {
+                    firstButton.setIconVisible(false);
+                    secondButton.setIconVisible(false);
+                    secondButton.setTextMultiLine(true);
+                    secondButton.setPadding(secondButton.getPaddingLeft(),
+                            secondButton.getPaddingTop() / 2, secondButton.getPaddingRight(),
+                            secondButton.getPaddingBottom() / 2);
                 }
             }
         }
@@ -173,98 +229,79 @@
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        int visibleCount = getVisibleButtonsCount();
+        int visibleCount = getVisibleButtons(mTempTargets);
         if (visibleCount == 0) {
             return;
         }
 
-        Launcher launcher = Launcher.getLauncher(getContext());
-        Workspace<?> workspace = launcher.getWorkspace();
-        DeviceProfile dp = launcher.getDeviceProfile();
-        int buttonHorizontalPadding = dp.dropTargetHorizontalPaddingPx;
-        int buttonVerticalPadding = dp.dropTargetVerticalPaddingPx;
+        DeviceProfile dp = mLauncher.getDeviceProfile();
         int barCenter = (right - left) / 2;
-
-        ButtonDropTarget[] visibleButtons = Arrays.stream(mDropTargets)
-                .filter(b -> b.getVisibility() != GONE)
-                .toArray(ButtonDropTarget[]::new);
-        Arrays.stream(visibleButtons).forEach(
-                b -> b.setPadding(buttonHorizontalPadding, buttonVerticalPadding,
-                        buttonHorizontalPadding, buttonVerticalPadding));
+        if (mIsVertical) {
+            // Center vertical bar over scaled workspace, accounting for hotseat offset.
+            float scale = dp.getWorkspaceSpringLoadScale();
+            Workspace<?> ws = mLauncher.getWorkspace();
+            int workspaceCenter = (ws.getLeft() + ws.getRight()) / 2;
+            int cellLayoutCenter = ((dp.getInsets().left + dp.workspacePadding.left) + (dp.widthPx
+                    - dp.getInsets().right - dp.workspacePadding.right)) / 2;
+            int cellLayoutCenterOffset = (int) ((cellLayoutCenter - workspaceCenter) * scale);
+            barCenter = workspaceCenter + cellLayoutCenterOffset;
+        }
 
         if (visibleCount == 1) {
-            ButtonDropTarget button = visibleButtons[0];
+            ButtonDropTarget button = mTempTargets[0];
             button.layout(barCenter - (button.getMeasuredWidth() / 2), 0,
                     barCenter + (button.getMeasuredWidth() / 2), button.getMeasuredHeight());
         } else if (visibleCount == 2) {
             int buttonGap = dp.dropTargetGapPx;
 
             if (dp.isTwoPanels) {
-                ButtonDropTarget leftButton = visibleButtons[0];
+                ButtonDropTarget leftButton = mTempTargets[0];
                 leftButton.layout(barCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), 0,
                         barCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
 
-                ButtonDropTarget rightButton = visibleButtons[1];
+                ButtonDropTarget rightButton = mTempTargets[1];
                 rightButton.layout(barCenter + (buttonGap / 2), 0,
-                        barCenter + rightButton.getMeasuredWidth() + (buttonGap / 2),
+                        barCenter + (buttonGap / 2) + rightButton.getMeasuredWidth(),
                         rightButton.getMeasuredHeight());
-            } else if (dp.isTablet) {
-                int numberOfMargins = visibleCount - 1;
-                int buttonWidths = Arrays.stream(mDropTargets)
-                        .filter(b -> b.getVisibility() != GONE)
-                        .mapToInt(ButtonDropTarget::getMeasuredWidth)
-                        .sum();
-                int totalWidth = buttonWidths + (numberOfMargins * buttonGap);
-                int buttonsStartMargin = barCenter - (totalWidth / 2);
-
-                int start = buttonsStartMargin;
-                for (ButtonDropTarget button : visibleButtons) {
-                    int margin = (start != buttonsStartMargin) ? buttonGap : 0;
-                    button.layout(start + margin, 0, start + margin + button.getMeasuredWidth(),
-                            button.getMeasuredHeight());
-                    start += button.getMeasuredWidth() + margin;
-                }
-            } else if (mIsVertical) {
-                // Center buttons over workspace, not screen.
-                int verticalCenter = (workspace.getRight() - workspace.getLeft()) / 2;
-                ButtonDropTarget leftButton = visibleButtons[0];
-                leftButton.layout(verticalCenter - leftButton.getMeasuredWidth() - (buttonGap / 2),
-                        0, verticalCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
-
-                ButtonDropTarget rightButton = visibleButtons[1];
-                rightButton.layout(verticalCenter + (buttonGap / 2), 0,
-                        verticalCenter + rightButton.getMeasuredWidth() + (buttonGap / 2),
-                        rightButton.getMeasuredHeight());
-            } else if (dp.isPhone) {
-                // Buttons aligned to outer edges of scaled workspace.
-                float scale = dp.getWorkspaceSpringLoadScale();
-
-                int workspaceWidth = (int) (launcher.getWorkspace().getNormalChildWidth() * scale);
-                int start = barCenter - (workspaceWidth / 2);
-                int end = barCenter + (workspaceWidth / 2);
-
-                ButtonDropTarget leftButton = visibleButtons[0];
-                ButtonDropTarget rightButton = visibleButtons[1];
-
-                // If the text within the buttons is too long, the buttons can overlap
-                int overlap = start + leftButton.getMeasuredWidth() + rightButton.getMeasuredWidth()
-                        - end;
-                if (overlap > 0) {
-                    end += overlap;
+            } else {
+                int start;
+                int end;
+                if (mIsVertical) {
+                    // Scaled CellLayout width is assumed to not exceed the bounds of left/right.
+                    float scale = dp.getWorkspaceSpringLoadScale();
+                    int panelWidth = (int) (dp.getCellLayoutWidth() * scale);
+                    start = barCenter - (panelWidth / 2) + dp.dropTargetButtonWorkspaceEdgeGapPx;
+                    end = barCenter + (panelWidth / 2) - dp.dropTargetButtonWorkspaceEdgeGapPx;
+                } else {
+                    start = Math.max(dp.dropTargetButtonScreenEdgeGapPx, left);
+                    end = Math.min(dp.availableWidthPx - dp.dropTargetButtonScreenEdgeGapPx, right);
                 }
 
-                leftButton.layout(start, 0, start + leftButton.getMeasuredWidth(),
+                ButtonDropTarget leftButton = mTempTargets[0];
+                ButtonDropTarget rightButton = mTempTargets[1];
+
+                int leftButtonWidth = leftButton.getMeasuredWidth();
+                int rightButtonWidth = rightButton.getMeasuredWidth();
+                int buttonPlusGapWidth = leftButtonWidth + buttonGap + rightButtonWidth;
+
+                int extraSpace = end - start - buttonPlusGapWidth;
+                start = (start - left) + (extraSpace / 2);
+
+                leftButton.layout(start, 0, start + leftButtonWidth,
                         leftButton.getMeasuredHeight());
-                rightButton.layout(end - rightButton.getMeasuredWidth(), 0, end,
+
+                int rightButtonStart = start + leftButtonWidth + buttonGap;
+                rightButton.layout(rightButtonStart, 0, rightButtonStart + rightButtonWidth,
                         rightButton.getMeasuredHeight());
             }
         }
     }
 
-    private int getVisibleButtonsCount() {
+    private int getVisibleButtons(ButtonDropTarget[] outVisibleButtons) {
         int visibleCount = 0;
-        for (ButtonDropTarget buttons : mDropTargets) {
-            if (buttons.getVisibility() != GONE) {
+        for (ButtonDropTarget button : mDropTargets) {
+            if (button.getVisibility() != GONE) {
+                outVisibleButtons[visibleCount] = button;
                 visibleCount++;
             }
         }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ad87451..135b88d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1169,6 +1169,7 @@
         }
 
         AbstractFloatingView.closeAllOpenViewsExcept(this, false, TYPE_REBIND_SAFE);
+        DragView.removeAllViews(this);
         TraceHelper.INSTANCE.endSection(traceToken);
     }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 95a8a2a..4d33eae 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -28,6 +28,8 @@
 import android.animation.LayoutTransition;
 import android.annotation.SuppressLint;
 import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Rect;
@@ -78,27 +80,19 @@
     public static final int INVALID_PAGE = -1;
     protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
 
-    public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
-
     private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
     // The page is moved more than halfway, automatically move to the next page on touch up.
     private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
 
     private static final float MAX_SCROLL_PROGRESS = 1.0f;
 
-    // The following constants need to be scaled based on density. The scaled versions will be
-    // assigned to the corresponding member variables below.
-    private static final int FLING_THRESHOLD_VELOCITY = 500;
-    private static final int EASY_FLING_THRESHOLD_VELOCITY = 400;
-    private static final int MIN_SNAP_VELOCITY = 1500;
-    private static final int MIN_FLING_VELOCITY = 250;
-
     private boolean mFreeScroll = false;
 
-    protected final int mFlingThresholdVelocity;
-    protected final int mEasyFlingThresholdVelocity;
-    protected final int mMinFlingVelocity;
-    protected final int mMinSnapVelocity;
+    private int mFlingThresholdVelocity;
+    private int mEasyFlingThresholdVelocity;
+    private int mMinFlingVelocity;
+    private int mMinSnapVelocity;
+    private int mPageSnapAnimationDuration;
 
     protected boolean mFirstLayout = true;
 
@@ -192,11 +186,7 @@
         mPageSlop = configuration.getScaledPagingTouchSlop();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
 
-        float density = getResources().getDisplayMetrics().density;
-        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density);
-        mEasyFlingThresholdVelocity = (int) (EASY_FLING_THRESHOLD_VELOCITY * density);
-        mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density);
-        mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density);
+        updateVelocityValues();
 
         initEdgeEffect();
         setDefaultFocusHighlightEnabled(false);
@@ -628,6 +618,22 @@
                 - mInsets.left - mInsets.right;
     }
 
+    private void updateVelocityValues() {
+        Resources res = getResources();
+        mFlingThresholdVelocity = res.getDimensionPixelSize(R.dimen.fling_threshold_velocity);
+        mEasyFlingThresholdVelocity =
+                res.getDimensionPixelSize(R.dimen.easy_fling_threshold_velocity);
+        mMinFlingVelocity = res.getDimensionPixelSize(R.dimen.min_fling_velocity);
+        mMinSnapVelocity = res.getDimensionPixelSize(R.dimen.min_page_snap_velocity);
+        mPageSnapAnimationDuration = res.getInteger(R.integer.config_pageSnapAnimationDuration);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateVelocityValues();
+    }
+
     @Override
     public void requestLayout() {
         mIsLayoutValid = false;
@@ -1616,7 +1622,7 @@
     }
 
     protected void snapToDestination() {
-        snapToPage(getDestinationPage(), PAGE_SNAP_ANIMATION_DURATION);
+        snapToPage(getDestinationPage(), mPageSnapAnimationDuration);
     }
 
     // We want the duration of the page snap animation to be influenced by the distance that
@@ -1640,7 +1646,7 @@
         if (Math.abs(velocity) < mMinFlingVelocity) {
             // If the velocity is low enough, then treat this more as an automatic page advance
             // as opposed to an apparent physical response to flinging
-            return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+            return snapToPage(whichPage, mPageSnapAnimationDuration);
         }
 
         // Here we compute a "distance" that will be used in the computation of the overall
@@ -1663,11 +1669,11 @@
     }
 
     public boolean snapToPage(int whichPage) {
-        return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+        return snapToPage(whichPage, mPageSnapAnimationDuration);
     }
 
     public boolean snapToPageImmediately(int whichPage) {
-        return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true);
+        return snapToPage(whichPage, mPageSnapAnimationDuration, true);
     }
 
     public boolean snapToPage(int whichPage, int duration) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index fb028b9..6c091f0 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -85,14 +85,12 @@
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
-import com.android.launcher3.model.data.AppInfo;
 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.SearchActionItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemFactory;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pageindicators.PageIndicator;
-import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
@@ -1680,11 +1678,7 @@
         }
 
         if (child instanceof BubbleTextView && !dragOptions.isAccessibleDrag) {
-            PopupContainerWithArrow<Launcher> popupContainer = PopupContainerWithArrow
-                    .showForIcon((BubbleTextView) child);
-            if (popupContainer != null) {
-                dragOptions.preDragCondition = popupContainer.createPreDragCondition(true);
-            }
+            dragOptions.preDragCondition = ((BubbleTextView) child).startLongPressAction();
         }
 
         final DragView dv;
@@ -2777,9 +2771,9 @@
                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                 case LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION:
-                    if (info instanceof AppInfo) {
+                    if (info instanceof WorkspaceItemFactory) {
                         // Came from all apps -- make a copy
-                        info = ((AppInfo) info).makeWorkspaceItem();
+                        info = ((WorkspaceItemFactory) info).makeWorkspaceItem(mLauncher);
                         d.dragInfo = info;
                     }
                     if (info instanceof WorkspaceItemInfo
@@ -2788,11 +2782,6 @@
                         info = new WorkspaceItemInfo((WorkspaceItemInfo) info);
                         d.dragInfo = info;
                     }
-                    if (info instanceof SearchActionItemInfo) {
-                        info = ((SearchActionItemInfo) info).createWorkspaceItem(
-                                mLauncher.getModel());
-                        d.dragInfo = info;
-                    }
                     view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info);
                     break;
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
diff --git a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
index 14b2431..19d0421 100644
--- a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.dragndrop.DragController;
@@ -31,9 +32,9 @@
 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.popup.PopupContainerWithArrow;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BubbleTextHolder;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -72,7 +73,7 @@
             getSupportedActions(host, item, actions);
             actions.forEach(la -> info.addAction(la.accessibilityAction));
 
-            if (!itemSupportsLongClick(host, item)) {
+            if (!itemSupportsLongClick(host)) {
                 info.setLongClickable(false);
                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
             }
@@ -84,8 +85,15 @@
      */
     protected abstract void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out);
 
-    private boolean itemSupportsLongClick(View host, ItemInfo info) {
-        return PopupContainerWithArrow.canShow(host, info);
+    private boolean itemSupportsLongClick(View host) {
+        if (host instanceof BubbleTextView) {
+            return ((BubbleTextView) host).canShowLongPressPopup();
+        } else if (host instanceof BubbleTextHolder) {
+            BubbleTextHolder holder = (BubbleTextHolder) host;
+            return holder.getBubbleText() != null && holder.getBubbleText().canShowLongPressPopup();
+        } else {
+            return false;
+        }
     }
 
     protected boolean itemSupportsAccessibleDrag(ItemInfo item) {
@@ -113,7 +121,6 @@
     @Thunk
     protected void announceConfirmation(String confirmation) {
         mContext.getDragLayer().announceForAccessibility(confirmation);
-
     }
 
     public boolean isInAccessibleDrag() {
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 12eb837..44d57d7 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -24,13 +24,14 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.keyboard.KeyboardDragAndDropView;
-import com.android.launcher3.model.data.AppInfo;
 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.WorkspaceItemFactory;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.popup.ArrowPopup;
@@ -40,6 +41,7 @@
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.BubbleTextHolder;
 import com.android.launcher3.views.OptionsPopupView;
 import com.android.launcher3.views.OptionsPopupView.OptionItem;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -118,7 +120,7 @@
             }
         }
 
-        if ((item instanceof AppInfo) || (item instanceof WorkspaceItemInfo)
+        if ((item instanceof WorkspaceItemFactory) || (item instanceof WorkspaceItemInfo)
                 || (item instanceof PendingAddItemInfo)) {
             out.add(mActions.get(ADD_TO_WORKSPACE));
         }
@@ -143,13 +145,18 @@
     protected boolean performAction(final View host, final ItemInfo item, int action,
             boolean fromKeyboard) {
         if (action == ACTION_LONG_CLICK) {
-            if (PopupContainerWithArrow.canShow(host, item)) {
-                // Long press should be consumed for workspace items, and it should invoke the
-                // Shortcuts / Notifications / Actions pop-up menu, and not start a drag as the
-                // standard long press path does.
-                PopupContainerWithArrow.showForIcon((BubbleTextView) host);
-                return true;
+            PreDragCondition dragCondition = null;
+            // Long press should be consumed for workspace items, and it should invoke the
+            // Shortcuts / Notifications / Actions pop-up menu, and not start a drag as the
+            // standard long press path does.
+            if (host instanceof BubbleTextView) {
+                dragCondition = ((BubbleTextView) host).startLongPressAction();
+            } else if (host instanceof BubbleTextHolder) {
+                BubbleTextHolder holder = (BubbleTextHolder) host;
+                dragCondition = holder.getBubbleText() == null ? null
+                        : holder.getBubbleText().startLongPressAction();
             }
+            return dragCondition != null;
         } else if (action == MOVE) {
             return beginAccessibleDrag(host, item, fromKeyboard);
         } else if (action == ADD_TO_WORKSPACE) {
@@ -166,7 +173,10 @@
             popup.setOnCloseCallback(host::requestFocus);
             return true;
         } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) {
-            return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
+            BubbleTextView btv = host instanceof BubbleTextView ? (BubbleTextView) host
+                    : (host instanceof BubbleTextHolder
+                            ? ((BubbleTextHolder) host).getBubbleText() : null);
+            return btv != null && PopupContainerWithArrow.showForIcon(btv) != null;
         } else {
             for (ButtonDropTarget dropTarget : mContext.getDropTargetBar().getDropTargets()) {
                 if (dropTarget.supportsAccessibilityDrop(item, host)
@@ -367,8 +377,8 @@
             return false;
         }
         mContext.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
-            if (item instanceof AppInfo) {
-                WorkspaceItemInfo info = ((AppInfo) item).makeWorkspaceItem();
+            if (item instanceof WorkspaceItemFactory) {
+                WorkspaceItemInfo info = ((WorkspaceItemFactory) item).makeWorkspaceItem(mContext);
                 mContext.getModelWriter().addItemToDatabase(info,
                         LauncherSettings.Favorites.CONTAINER_DESKTOP,
                         screenId, coordinates[0], coordinates[1]);
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index 95c67dd..7c1e152 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -388,34 +388,34 @@
         mInsets.set(insets);
         DeviceProfile grid = mActivityContext.getDeviceProfile();
 
-        for (int i = 0; i < mAH.size(); i++) {
-            mAH.get(i).mPadding.bottom = insets.bottom;
-            mAH.get(i).mPadding.left = mAH.get(i).mPadding.right = grid.allAppsLeftRightPadding;
-            mAH.get(i).applyPadding();
-        }
+        applyAdapterPaddings(grid);
 
         MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
-        int leftRightMargin = grid.allAppsLeftRightMargin;
-        mlp.leftMargin = insets.left + leftRightMargin;
-        mlp.rightMargin = insets.right + leftRightMargin;
+        mlp.leftMargin = insets.left;
+        mlp.rightMargin = insets.right;
         setLayoutParams(mlp);
 
         if (grid.isVerticalBarLayout()) {
             setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
         } else {
-            setPadding(0, grid.allAppsTopPadding, 0, 0);
+            setPadding(grid.allAppsLeftRightMargin, grid.allAppsTopPadding,
+                    grid.allAppsLeftRightMargin, 0);
         }
 
         InsettableFrameLayout.dispatchInsets(this, insets);
     }
 
+    /**
+     * Returns a padding in case a scrim is shown on the bottom of the view and a padding is needed.
+     */
+    protected int getNavBarScrimHeight(WindowInsets insets) {
+        return 0;
+    }
+
     @Override
     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
-        if (Utilities.ATLEAST_Q) {
-            mNavBarScrimHeight = insets.getTappableElementInsets().bottom;
-        } else {
-            mNavBarScrimHeight = insets.getStableInsetBottom();
-        }
+        mNavBarScrimHeight = getNavBarScrimHeight(insets);
+        applyAdapterPaddings(mActivityContext.getDeviceProfile());
         return super.dispatchApplyWindowInsets(insets);
     }
 
@@ -483,6 +483,15 @@
         mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
     }
 
+    private void applyAdapterPaddings(DeviceProfile grid) {
+        int bottomPadding = Math.max(mInsets.bottom, mNavBarScrimHeight);
+        for (int i = 0; i < mAH.size(); i++) {
+            mAH.get(i).mPadding.bottom = bottomPadding;
+            mAH.get(i).mPadding.left = mAH.get(i).mPadding.right = grid.allAppsLeftRightPadding;
+            mAH.get(i).applyPadding();
+        }
+    }
+
     private void setDeviceManagementResources() {
         if (mActivityContext.getStringCache() != null) {
             Button personalTab = findViewById(R.id.tab_personal);
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index 8601819..20f5e74 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -18,9 +18,11 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.WindowInsets;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
 
 /**
  * AllAppsContainerView with launcher specific callbacks
@@ -58,4 +60,13 @@
         }
         return super.onTouchEvent(ev);
     }
+
+    @Override
+    protected int getNavBarScrimHeight(WindowInsets insets) {
+        if (Utilities.ATLEAST_Q) {
+            return insets.getTappableElementInsets().bottom;
+        } else {
+            return insets.getStableInsetBottom();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index a3945fd..0264ae2 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -578,4 +578,19 @@
         iv.setImageDrawable(drawable);
         return iv;
     }
+
+    /**
+     * Removes any stray DragView from the DragLayer.
+     */
+    public static void removeAllViews(ActivityContext activity) {
+        BaseDragLayer dragLayer = activity.getDragLayer();
+        // Iterate in reverse order. DragView is added later to the dragLayer,
+        // and will be one of the last views.
+        for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
+            View child = dragLayer.getChildAt(i);
+            if (child instanceof DragView) {
+                dragLayer.removeView(child);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 8916519..e68ebdb 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -72,7 +72,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.OnAlarmListener;
-import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
@@ -88,10 +87,10 @@
 import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.FolderInfo.FolderListener;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemFactory;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
 import com.android.launcher3.util.Executors;
@@ -145,7 +144,7 @@
      * Time for which the scroll hint is shown before automatically changing page.
      */
     public static final int SCROLL_HINT_DURATION = 500;
-    public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
+    private static final int RESCROLL_EXTRA_DELAY = 150;
 
     public static final int SCROLL_NONE = -1;
     public static final int SCROLL_LEFT = 0;
@@ -1283,9 +1282,9 @@
             final WorkspaceItemInfo si;
             if (pasiSi != null) {
                 si = pasiSi;
-            } else if (d.dragInfo instanceof AppInfo) {
+            } else if (d.dragInfo instanceof WorkspaceItemFactory) {
                 // Came from all apps -- make a copy.
-                si = ((AppInfo) d.dragInfo).makeWorkspaceItem();
+                si = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(launcher);
             } else {
                 // WorkspaceItemInfo
                 si = (WorkspaceItemInfo) d.dragInfo;
@@ -1523,7 +1522,9 @@
 
             // Pause drag event until the scrolling is finished
             mScrollPauseAlarm.setOnAlarmListener(new OnScrollFinishedListener(mDragObject));
-            mScrollPauseAlarm.setAlarm(RESCROLL_DELAY);
+            int rescrollDelay = getResources().getInteger(
+                    R.integer.config_pageSnapAnimationDuration) + RESCROLL_EXTRA_DELAY;
+            mScrollPauseAlarm.setAlarm(rescrollDelay);
         }
     }
 
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 5fe2435..b1e2701 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -68,11 +68,11 @@
 import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.FolderInfo.FolderListener;
 import com.android.launcher3.model.data.FolderInfo.LabelState;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemFactory;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Executors;
@@ -284,7 +284,7 @@
         mBackground.animateToAccept(cl, lp.cellX, lp.cellY);
         mOpenAlarm.setOnAlarmListener(mOnOpenListener);
         if (SPRING_LOADING_ENABLED &&
-                ((dragInfo instanceof AppInfo)
+                ((dragInfo instanceof WorkspaceItemFactory)
                         || (dragInfo instanceof WorkspaceItemInfo)
                         || (dragInfo instanceof PendingAddShortcutInfo))) {
             mOpenAlarm.setAlarm(ON_OPEN_DELAY);
@@ -486,9 +486,9 @@
 
     public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
         WorkspaceItemInfo item;
-        if (d.dragInfo instanceof AppInfo) {
+        if (d.dragInfo instanceof WorkspaceItemFactory) {
             // Came from all apps -- make a copy
-            item = ((AppInfo) d.dragInfo).makeWorkspaceItem();
+            item = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(getContext());
         } else if (d.dragSource instanceof BaseItemDragListener){
             // Came from a different window -- make a copy
             item = new WorkspaceItemInfo((WorkspaceItemInfo) d.dragInfo);
@@ -637,7 +637,7 @@
 
             // If we are animating to the accepting state, animate the dot out.
             mDotParams.scale = Math.max(0, mDotScale - mBackground.getScaleProgress());
-            mDotParams.color = mBackground.getDotColor();
+            mDotParams.dotColor = mBackground.getDotColor();
             mDotRenderer.draw(canvas, mDotParams);
         }
     }
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index ca91296..31ef2e5 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -32,6 +32,7 @@
 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.WorkspaceItemFactory;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.PackageInstallInfo;
@@ -107,8 +108,8 @@
                 }
 
                 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                    if (item instanceof AppInfo) {
-                        item = ((AppInfo) item).makeWorkspaceItem();
+                    if (item instanceof WorkspaceItemFactory) {
+                        item = ((WorkspaceItemFactory) item).makeWorkspaceItem(app.getContext());
                     }
                 }
                 if (item != null) {
@@ -130,8 +131,8 @@
                 if (item instanceof WorkspaceItemInfo || item instanceof FolderInfo ||
                         item instanceof LauncherAppWidgetInfo) {
                     itemInfo = item;
-                } else if (item instanceof AppInfo) {
-                    itemInfo = ((AppInfo) item).makeWorkspaceItem();
+                } else if (item instanceof WorkspaceItemFactory) {
+                    itemInfo = ((WorkspaceItemFactory) item).makeWorkspaceItem(app.getContext());
                 } else {
                     throw new RuntimeException("Unexpected info type");
                 }
@@ -180,7 +181,7 @@
                         // App was installed while launcher was in the background,
                         // or app was already installed for another user.
                         itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user)
-                                .makeWorkspaceItem();
+                                .makeWorkspaceItem(app.getContext());
 
                         if (shortcutExists(dataModel, itemInfo.getIntent(), itemInfo.user)) {
                             // We need this additional check here since we treat all auto added
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 7f70bad..5b2bcf5 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -35,7 +35,6 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.pm.PackageInstallInfo;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageManagerHelper;
 
 import java.util.Comparator;
@@ -43,7 +42,7 @@
 /**
  * Represents an app in AllAppsView.
  */
-public class AppInfo extends ItemInfoWithIcon {
+public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
 
     public static final AppInfo[] EMPTY_ARRAY = new AppInfo[0];
     public static final Comparator<AppInfo> COMPONENT_KEY_COMPARATOR = (a, b) -> {
@@ -121,7 +120,8 @@
         return super.dumpProperties() + " componentName=" + componentName;
     }
 
-    public WorkspaceItemInfo makeWorkspaceItem() {
+    @Override
+    public WorkspaceItemInfo makeWorkspaceItem(Context context) {
         WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(this);
 
         if ((runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) {
@@ -139,10 +139,6 @@
         return workspaceItemInfo;
     }
 
-    public ComponentKey toComponentKey() {
-        return new ComponentKey(componentName, user);
-    }
-
     public static Intent makeLaunchIntent(LauncherActivityInfo info) {
         return makeLaunchIntent(info.getComponentName());
     }
diff --git a/src/com/android/launcher3/model/data/SearchActionItemInfo.java b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
index cc22601..e879313 100644
--- a/src/com/android/launcher3/model/data/SearchActionItemInfo.java
+++ b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.EXTENDED_CONTAINERS;
 
 import android.app.PendingIntent;
+import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.os.Process;
@@ -26,18 +27,14 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.logger.LauncherAtom.ItemInfo;
 import com.android.launcher3.logger.LauncherAtom.SearchActionItem;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
-import com.android.launcher3.model.BgDataModel;
 
 /**
  * Represents a SearchAction with in launcher
  */
-public class SearchActionItemInfo extends ItemInfoWithIcon {
+public class SearchActionItemInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
 
     public static final int FLAG_SHOULD_START = 1 << 1;
     public static final int FLAG_SHOULD_START_FOR_RESULT = FLAG_SHOULD_START | 1 << 2;
@@ -158,7 +155,8 @@
     /**
      * Creates a {@link WorkspaceItemInfo} coorsponding to search action to be stored in launcher db
      */
-    public WorkspaceItemInfo createWorkspaceItem(LauncherModel model) {
+    @Override
+    public WorkspaceItemInfo makeWorkspaceItem(Context context) {
         WorkspaceItemInfo info = new WorkspaceItemInfo();
         info.title = title;
         info.bitmap = bitmap;
@@ -167,18 +165,12 @@
         if (hasFlags(FLAG_SHOULD_START_FOR_RESULT)) {
             info.options |= WorkspaceItemInfo.FLAG_START_FOR_RESULT;
         }
-
-        model.enqueueModelUpdateTask(new BaseModelUpdateTask() {
-            @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-
-                model.updateAndBindWorkspaceItem(() -> {
-                    PackageItemInfo pkgInfo = new PackageItemInfo(getIntentPackageName(), user);
-                    app.getIconCache().getTitleAndIconForApp(pkgInfo, false);
-                    info.bitmap = info.bitmap.withBadgeInfo(pkgInfo.bitmap);
-                    return info;
-                });
-            }
+        LauncherAppState app = LauncherAppState.getInstance(context);
+        app.getModel().updateAndBindWorkspaceItem(() -> {
+            PackageItemInfo pkgInfo = new PackageItemInfo(getIntentPackageName(), user);
+            app.getIconCache().getTitleAndIconForApp(pkgInfo, false);
+            info.bitmap = info.bitmap.withBadgeInfo(pkgInfo.bitmap);
+            return info;
         });
         return info;
     }
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemFactory.java b/src/com/android/launcher3/model/data/WorkspaceItemFactory.java
new file mode 100644
index 0000000..47b9c6e
--- /dev/null
+++ b/src/com/android/launcher3/model/data/WorkspaceItemFactory.java
@@ -0,0 +1,29 @@
+/*
+ * 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.model.data;
+
+import android.content.Context;
+
+/**
+ * Interface to objects capable of generating workspace item
+ */
+public interface WorkspaceItemFactory {
+
+    /**
+     * Called to create a pinnable item info
+     */
+    WorkspaceItemInfo makeWorkspaceItem(Context context);
+}
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 484b879..49d97d2 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -187,7 +187,10 @@
 
     /**
      * Returns true if we can show the container.
+     *
+     * @deprecated Left here since some dependent projects are using this method
      */
+    @Deprecated
     public static boolean canShow(View icon, ItemInfo item) {
         return icon instanceof BubbleTextView && ShortcutUtil.supportsShortcuts(item);
     }
@@ -204,7 +207,7 @@
             return null;
         }
         ItemInfo item = (ItemInfo) icon.getTag();
-        if (!canShow(icon, item)) {
+        if (!ShortcutUtil.supportsShortcuts(item)) {
             return null;
         }
 
@@ -538,7 +541,7 @@
     public static void dismissInvalidPopup(BaseDraggingActivity activity) {
         PopupContainerWithArrow popup = getOpen(activity);
         if (popup != null && (!popup.mOriginalIcon.isAttachedToWindow()
-                || !canShow(popup.mOriginalIcon, (ItemInfo) popup.mOriginalIcon.getTag()))) {
+                || !ShortcutUtil.supportsShortcuts((ItemInfo) popup.mOriginalIcon.getTag()))) {
             popup.animateClose();
         }
     }
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 15cdc20..a205ab5 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -51,7 +51,7 @@
             return super.getWorkspaceScaleAndTranslation(launcher);
         }
 
-        float shrunkTop = grid.getWorkspaceSpringLoadShrunkTop();
+        float shrunkTop = grid.getCellLayoutSpringLoadShrunkTop();
         float scale = grid.getWorkspaceSpringLoadScale();
 
         float halfHeight = ws.getHeight() / 2;
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index cd00f15..c4d2bc4 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -100,8 +100,7 @@
                 onClickFolderIcon(v);
             }
         } else if (tag instanceof AppInfo) {
-            startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher
-            );
+            startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
         } else if (tag instanceof LauncherAppWidgetInfo) {
             if (v instanceof PendingAppWidgetHostView) {
                 onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index f876dd9..6bae745 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.views.BubbleTextHolder;
 
 /**
  * Class to handle long-clicks on workspace items and start drag as a result.
@@ -79,9 +80,12 @@
         launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
     }
 
-    private static boolean onAllAppsItemLongClick(View v) {
+    private static boolean onAllAppsItemLongClick(View view) {
         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onAllAppsItemLongClick");
-        v.cancelLongPress();
+        view.cancelLongPress();
+        View v = (view instanceof BubbleTextHolder)
+                ? ((BubbleTextHolder) view).getBubbleText()
+                : view;
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
         // When we have exited all apps or are in transition, disregard long clicks
diff --git a/src/com/android/launcher3/views/BubbleTextHolder.java b/src/com/android/launcher3/views/BubbleTextHolder.java
index 76c465c..84f8049 100644
--- a/src/com/android/launcher3/views/BubbleTextHolder.java
+++ b/src/com/android/launcher3/views/BubbleTextHolder.java
@@ -16,8 +16,6 @@
 package com.android.launcher3.views;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
 
 /**
  * Views that contain {@link BubbleTextView} should implement this interface.
@@ -25,14 +23,6 @@
 public interface BubbleTextHolder extends IconLabelDotView {
     BubbleTextView getBubbleText();
 
-    /**
-     * Called when new {@link ItemInfo} is set to {@link BubbleTextView}
-     *
-     * @param itemInfo the new itemInfo
-     */
-    default void onItemInfoUpdated(ItemInfoWithIcon itemInfo) {
-    }
-
     @Override
     default void setIconVisible(boolean visible) {
         getBubbleText().setIconVisible(visible);
diff --git a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
index d2d569f..9442734 100644
--- a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
@@ -18,7 +18,6 @@
 
 import static com.android.launcher3.Utilities.ATLEAST_R;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.widget.BaseWidgetSheet.MAX_WIDTH_SCALE_FOR_LARGER_SCREEN;
 
 import android.animation.PropertyValuesHolder;
 import android.annotation.SuppressLint;
@@ -106,7 +105,10 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
         int widthUsed;
-        if (mInsets.bottom > 0) {
+        if (deviceProfile.isTablet) {
+            int margin = deviceProfile.allAppsLeftRightMargin;
+            widthUsed = Math.max(2 * margin, 2 * (mInsets.left + mInsets.right));
+        } else if (mInsets.bottom > 0) {
             widthUsed = mInsets.left + mInsets.right;
         } else {
             Rect padding = deviceProfile.workspacePadding;
@@ -114,18 +116,8 @@
                     2 * (mInsets.left + mInsets.right));
         }
 
-        if (deviceProfile.isTablet || deviceProfile.isTwoPanels) {
-            // In large screen devices, we restrict the width of the widgets picker to show part of
-            // the home screen. Let's ensure the minimum width used is at least the minimum width
-            // that isn't taken by the widgets picker.
-            int minUsedWidth = (int) (deviceProfile.availableWidthPx
-                    * (1 - MAX_WIDTH_SCALE_FOR_LARGER_SCREEN));
-            widthUsed = Math.max(widthUsed, minUsedWidth);
-        }
-
-        int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
         measureChildWithMargins(mContent, widthMeasureSpec,
-                widthUsed, heightMeasureSpec, heightUsed);
+                widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding);
         setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
                 MeasureSpec.getSize(heightMeasureSpec));
     }
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index c7bb612..1bcba14 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -16,6 +16,8 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -23,6 +25,7 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
+import android.view.WindowInsets;
 import android.widget.Toast;
 
 import androidx.annotation.GuardedBy;
@@ -43,6 +46,7 @@
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.views.AbstractSlideInView;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.ArrowTipView;
@@ -55,11 +59,6 @@
         PopupDataProvider.PopupDataChangeListener, Insettable {
     /** The default number of cells that can fit horizontally in a widget sheet. */
     protected static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4;
-    /**
-     * The maximum scale, [0, 1], of the device screen width that the widgets picker can consume
-     * on large screen devices.
-     */
-    protected static final float MAX_WIDTH_SCALE_FOR_LARGER_SCREEN = 0.89f;
 
     protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN =
             "launcher.widgets_education_tip_seen";
@@ -70,10 +69,15 @@
 
     private int mContentHorizontalMarginInPx;
 
+    protected int mNavBarScrimHeight;
+    private final Paint mNavBarScrimPaint;
+
     public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mContentHorizontalMarginInPx = getResources().getDimensionPixelSize(
                 R.dimen.widget_list_horizontal_margin);
+        mNavBarScrimPaint = new Paint();
+        mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
     }
 
     protected int getScrimColor(Context context) {
@@ -83,6 +87,9 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
+        WindowInsets windowInsets = WindowManagerProxy.INSTANCE.get(getContext())
+                .normalizeWindowInsets(getContext(), getRootWindowInsets(), new Rect());
+        mNavBarScrimHeight = getNavBarScrimHeight(windowInsets);
         mActivityContext.getPopupDataProvider().setChangeListener(this);
     }
 
@@ -136,6 +143,30 @@
         }
     }
 
+    private int getNavBarScrimHeight(WindowInsets insets) {
+        if (Utilities.ATLEAST_Q) {
+            return insets.getTappableElementInsets().bottom;
+        } else {
+            return insets.getStableInsetBottom();
+        }
+    }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        mNavBarScrimHeight = getNavBarScrimHeight(insets);
+        return super.onApplyWindowInsets(insets);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        if (mNavBarScrimHeight > 0) {
+            canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
+                    mNavBarScrimPaint);
+        }
+    }
+
     /** Called when the horizontal margin of the content view has changed. */
     protected abstract void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx);
 
@@ -147,7 +178,10 @@
     protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
         int widthUsed;
-        if (mInsets.bottom > 0) {
+        if (deviceProfile.isTablet) {
+            int margin = deviceProfile.allAppsLeftRightMargin;
+            widthUsed = Math.max(2 * margin, 2 * (mInsets.left + mInsets.right));
+        } else if (mInsets.bottom > 0) {
             widthUsed = mInsets.left + mInsets.right;
         } else {
             Rect padding = deviceProfile.workspacePadding;
@@ -155,15 +189,6 @@
                     2 * (mInsets.left + mInsets.right));
         }
 
-        if (deviceProfile.isTablet || deviceProfile.isTwoPanels) {
-            // In large screen devices, we restrict the width of the widgets picker to show part of
-            // the home screen. Let's ensure the minimum width used is at least the minimum width
-            // that isn't taken by the widgets picker.
-            int minUsedWidth = (int) (deviceProfile.availableWidthPx
-                    * (1 - MAX_WIDTH_SCALE_FOR_LARGER_SCREEN));
-            widthUsed = Math.max(widthUsed, minUsedWidth);
-        }
-
         measureChildWithMargins(mContent, widthMeasureSpec,
                 widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding);
         setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index b152ddc..bf521cc 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -247,10 +247,12 @@
     @Override
     public void setInsets(Rect insets) {
         super.setInsets(insets);
+        int bottomPadding = Math.max(insets.bottom, mNavBarScrimHeight);
 
         mContent.setPadding(mContent.getPaddingStart(),
-                mContent.getPaddingTop(), mContent.getPaddingEnd(), insets.bottom);
-        if (insets.bottom > 0) {
+                mContent.getPaddingTop(), mContent.getPaddingEnd(),
+                bottomPadding);
+        if (bottomPadding > 0) {
             setupNavBarColor();
         } else {
             clearNavBarColor();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 341cb5c..a49cdc0 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -328,15 +328,15 @@
     @Override
     public void setInsets(Rect insets) {
         super.setInsets(insets);
-
-        setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, insets.bottom);
-        setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, insets.bottom);
+        int bottomPadding = Math.max(insets.bottom, mNavBarScrimHeight);
+        setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, bottomPadding);
+        setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, bottomPadding);
         if (mHasWorkProfile) {
-            setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
+            setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, bottomPadding);
         }
-        ((MarginLayoutParams) mNoWidgetsView.getLayoutParams()).bottomMargin = insets.bottom;
+        ((MarginLayoutParams) mNoWidgetsView.getLayoutParams()).bottomMargin = bottomPadding;
 
-        if (insets.bottom > 0) {
+        if (bottomPadding > 0) {
             setupNavBarColor();
         } else {
             clearNavBarColor();
diff --git a/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt b/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt
new file mode 100644
index 0000000..80259a5
--- /dev/null
+++ b/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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
+
+import android.graphics.PointF
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.WindowBounds
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Test for [DeviceProfile] grid dimensions.
+ *
+ * This includes workspace, cell layout, shortcut and widget container, cell sizes, etc.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileGridDimensionsTest : DeviceProfileBaseTest() {
+
+    @Test
+    fun getCellLayoutWidth_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelWidth() {
+        val tabletWidth = 2560
+        val tabletHeight = 1600
+        val availableWidth = 2560
+        val availableHeight = 1500
+        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+        useTwoPanels = true
+        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+        whenever(info.densityDpi).thenReturn(320)
+        inv = newScalableInvariantDeviceProfile()
+
+        val dp = newDP()
+
+        val expectedWorkspaceWidth = availableWidth
+        val expectedCellLayoutWidth =
+                (expectedWorkspaceWidth - (dp.workspacePadding.right + dp.workspacePadding.left)) /
+                        dp.panelCount
+        assertThat(dp.cellLayoutWidth).isEqualTo(expectedCellLayoutWidth)
+    }
+
+    @Test
+    fun getCellLayoutHeight_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelHeight() {
+        val tabletWidth = 2560
+        val tabletHeight = 1600
+        val availableWidth = 2560
+        val availableHeight = 1500
+        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+        useTwoPanels = true
+        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+        whenever(info.densityDpi).thenReturn(320)
+        inv = newScalableInvariantDeviceProfile()
+
+        val dp = newDP()
+
+        val expectedWorkspaceHeight = availableHeight
+        val expectedCellLayoutHeight =
+                expectedWorkspaceHeight - (dp.workspacePadding.top + dp.workspacePadding.bottom)
+        assertThat(dp.cellLayoutHeight).isEqualTo(expectedCellLayoutHeight)
+    }
+
+    @Test
+    fun getCellSize_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelWidth() {
+        val tabletWidth = 2560
+        val tabletHeight = 1600
+        val availableWidth = 2560
+        val availableHeight = 1500
+        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+        useTwoPanels = true
+        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+        whenever(info.densityDpi).thenReturn(320)
+        inv = newScalableInvariantDeviceProfile()
+
+        val dp = newDP()
+
+        val expectedWorkspaceWidth = availableWidth
+        val expectedCellLayoutWidth =
+                (expectedWorkspaceWidth - (dp.workspacePadding.right + dp.workspacePadding.left)) /
+                        dp.panelCount
+        val expectedShortcutAndWidgetContainerWidth =
+                expectedCellLayoutWidth -
+                        (dp.cellLayoutPaddingPx.left + dp.cellLayoutPaddingPx.right)
+        assertThat(dp.getCellSize().x).isEqualTo(
+                (expectedShortcutAndWidgetContainerWidth -
+                        ((inv!!.numColumns - 1) * dp.cellLayoutBorderSpacePx.x)) / inv!!.numColumns)
+        val expectedWorkspaceHeight = availableHeight
+        val expectedCellLayoutHeight =
+                expectedWorkspaceHeight - (dp.workspacePadding.top + dp.workspacePadding.bottom)
+        val expectedShortcutAndWidgetContainerHeight = expectedCellLayoutHeight -
+                (dp.cellLayoutPaddingPx.top + dp.cellLayoutPaddingPx.bottom)
+        assertThat(dp.getCellSize().y).isEqualTo(
+                (expectedShortcutAndWidgetContainerHeight -
+                        ((inv!!.numRows - 1) * dp.cellLayoutBorderSpacePx.y)) / inv!!.numRows)
+    }
+
+    @Test
+    fun getPanelCount_twoPanelLandscapeScalable4By4GridTablet_equalsTwoPanels() {
+        val tabletWidth = 2560
+        val tabletHeight = 1600
+        val availableWidth = 2560
+        val availableHeight = 1500
+        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+        useTwoPanels = true
+        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+        whenever(info.densityDpi).thenReturn(320)
+        inv = newScalableInvariantDeviceProfile()
+
+        val dp = newDP()
+
+        assertThat(dp.panelCount).isEqualTo(2)
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 15e8f68..a3b05f6 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -48,11 +48,13 @@
 import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
 import com.android.launcher3.widget.picker.WidgetsRecyclerView;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -66,6 +68,10 @@
     private static final String DUMMY_APP_NAME = "Aardwolf";
     private static final String MAPS_APP_NAME = "Maps";
     private static final String STORE_APP_NAME = "Play Store";
+    private static final String GMAIL_APP_NAME = "Gmail";
+
+    @Rule
+    public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule();
 
     @Before
     public void setUp() throws Exception {
@@ -374,28 +380,23 @@
 
     @Test
     @PortraitLandscape
-    public void testDragToFolder() throws Exception {
-        final HomeAppIcon playStoreIcon = createShortcutIfNotExist("Play Store", 0, 1);
-        final HomeAppIcon gmailIcon = createShortcutIfNotExist("Gmail", 1, 1);
+    @ScreenRecord
+    public void testDragToFolder() {
+        // TODO: add the use case to drag an icon to an existing folder. Currently it either fails
+        // on tablets or phones due to difference in resolution.
+        final HomeAppIcon playStoreIcon = createShortcutIfNotExist(STORE_APP_NAME, 0, 1);
+        final HomeAppIcon gmailIcon = createShortcutInCenterIfNotExist(GMAIL_APP_NAME);
 
         FolderIcon folderIcon = gmailIcon.dragToIcon(playStoreIcon);
-
         Folder folder = folderIcon.open();
-        folder.getAppIcon("Play Store");
-        folder.getAppIcon("Gmail");
+        folder.getAppIcon(STORE_APP_NAME);
+        folder.getAppIcon(GMAIL_APP_NAME);
         Workspace workspace = folder.close();
 
-        assertNull("Gmail should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon("Gmail"));
-        assertNull("Play Store should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon("Play Store"));
-
-        final HomeAppIcon youTubeIcon = createShortcutInCenterIfNotExist("YouTube");
-
-        folderIcon = youTubeIcon.dragToIcon(folderIcon);
-        folder = folderIcon.open();
-        folder.getAppIcon("YouTube");
-        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));
     }
 
     @Test
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 39cd4ff..91ab1bd 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.tapl;
 
-import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
 import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
 
 import android.graphics.Point;
@@ -64,35 +62,25 @@
     protected abstract String launchableType();
 
     private LaunchedAppState launch(BySelector selector) {
-        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+        try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                 "want to launch an app from " + launchableType())) {
             LauncherInstrumentation.log("Launchable.launch before click "
                     + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
             final String label = mObject.getText();
 
-            executeAndWaitForWindowChange(() -> {
-                mLauncher.clickLauncherObject(mObject);
-                expectActivityStartEvents();
-            }, label, "clicking " + launchableType());
+            mLauncher.clickLauncherObject(mObject);
 
-            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("clicked")) {
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
+                expectActivityStartEvents();
                 return assertAppLaunched(label, selector);
             }
         }
     }
 
-    protected void executeAndWaitForWindowChange(Runnable command, String label, String action) {
-        mLauncher.executeAndWaitForEvent(
-                command,
-                event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
-                () -> "Launching an app didn't open a new window: " + label,
-                action);
-    }
-
     protected LaunchedAppState assertAppLaunched(String label, BySelector selector) {
         mLauncher.assertTrue(
                 "App didn't start: " + label + " (" + selector + ")",
-                TestHelpers.wait(Until.hasObject(selector),
+                mLauncher.getDevice().wait(Until.hasObject(selector),
                         LauncherInstrumentation.WAIT_TIME_MS));
         return new LaunchedAppState(mLauncher);
     }
@@ -117,7 +105,6 @@
                     expectLongClickEvents);
         }
 
-
         return dragStartCenter;
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 2033a42..face02a 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -129,42 +129,25 @@
 
                 try (LauncherInstrumentation.Closable c3 = launcher.addContextLayer(
                         "moved pointer to drop point")) {
-                    dropDraggedItem(
-                            launcher,
-                            launchable,
-                            expectedNewPackageName,
-                            endPoint, downTime,
-                            itemVisibleCenter,
-                            itemVisibleBounds,
-                            itemLabel,
-                            expectedExistingPackageName);
+                    LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen: "
+                            + "before drop " + itemVisibleCenter + " in " + itemVisibleBounds);
+                    launcher.sendPointer(
+                            downTime,
+                            SystemClock.uptimeMillis(),
+                            MotionEvent.ACTION_UP,
+                            endPoint,
+                            LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER);
+                    LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen: "
+                            + "after drop");
+
+                    try (LauncherInstrumentation.Closable c4 = launcher.addContextLayer(
+                            "dropped item")) {
+                        launchable.assertAppLaunched(itemLabel, By.pkg(expectedNewPackageName));
+                        launchable.assertAppLaunched(
+                                itemLabel, By.pkg(expectedExistingPackageName));
+                    }
                 }
             }
         }
     }
-
-    private static void dropDraggedItem(
-            LauncherInstrumentation launcher, Launchable launchable, String expectedNewPackageName,
-            Point endPoint, long downTime, Point itemVisibleCenter, Rect itemVisibleBounds,
-            String itemLabel, String expectedExistingPackageName) {
-        LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen before drop "
-                + itemVisibleCenter + " in " + itemVisibleBounds);
-
-        launchable.executeAndWaitForWindowChange(() -> {
-            launcher.sendPointer(
-                    downTime,
-                    SystemClock.uptimeMillis(),
-                    MotionEvent.ACTION_UP,
-                    endPoint,
-                    LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER);
-            LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen: after "
-                    + "drop");
-        }, itemLabel, "dropping taskbar item");
-
-        try (LauncherInstrumentation.Closable c = launcher.addContextLayer("dropped item")) {
-            launchable.assertAppLaunched(itemLabel, By.pkg(expectedNewPackageName));
-            launcher.checkPackagesVisible(
-                    new String[] {expectedNewPackageName, expectedExistingPackageName});
-        }
-    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 2b3583e..9d25b1b 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -84,7 +84,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
-import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
@@ -92,7 +91,6 @@
 import java.util.function.Supplier;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * The main tapl object. The only object that can be explicitly constructed by the using code. It
@@ -501,29 +499,16 @@
         }
     }
 
-    void checkPackagesVisible(String[] expectedVisiblePackages) {
-        Set<String> actualVisiblePackages =
-                getVisiblePackagesStream().collect(Collectors.toSet());
-
-        for (String expectedVisible : expectedVisiblePackages) {
-            assertTrue("Expected package not visible: " + expectedVisible,
-                    actualVisiblePackages.contains(expectedVisible));
-        }
-    }
-
     private String getVisiblePackages() {
-        final String apps = getVisiblePackagesStream().collect(Collectors.joining(", "));
-        return !apps.isEmpty()
-                ? "active app: " + apps
-                : "the test doesn't see views from any app, including Launcher";
-    }
-
-    private Stream<String> getVisiblePackagesStream() {
-        return mDevice.findObjects(getAnyObjectSelector())
+        final String apps = mDevice.findObjects(getAnyObjectSelector())
                 .stream()
                 .map(LauncherInstrumentation::getApplicationPackageSafe)
                 .distinct()
-                .filter(pkg -> pkg != null && !SYSTEMUI_PACKAGE.equals(pkg));
+                .filter(pkg -> pkg != null && !SYSTEMUI_PACKAGE.equals(pkg))
+                .collect(Collectors.joining(", "));
+        return !apps.isEmpty()
+                ? "active app: " + apps
+                : "the test doesn't see views from any app, including Launcher";
     }
 
     private static String getApplicationPackageSafe(UiObject2 object) {
@@ -1567,11 +1552,11 @@
 
             // vx0: initial speed at the x-dimension, set as twice the avg speed
             // dx: the constant deceleration at the x-dimension
-            double vx0 = 2 * (to.x - from.x) / duration;
+            double vx0 = 2.0 * (to.x - from.x) / duration;
             double dx = vx0 / duration;
             // vy0: initial speed at the y-dimension, set as twice the avg speed
             // dy: the constant deceleration at the y-dimension
-            double vy0 = 2 * (to.y - from.y) / duration;
+            double vy0 = 2.0 * (to.y - from.y) / duration;
             double dy = vy0 / duration;
 
             for (long i = 0; i < steps; ++i) {
diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
deleted file mode 100644
index 42b6bc9..0000000
--- a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2020 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.tapl;
-
-import androidx.annotation.NonNull;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
-import com.android.launcher3.testing.TestProtocol;
-
-public class OptionsPopupMenuItem {
-
-    private final LauncherInstrumentation mLauncher;
-    private final UiObject2 mObject;
-
-    OptionsPopupMenuItem(@NonNull LauncherInstrumentation launcher, @NonNull UiObject2 shortcut) {
-        mLauncher = launcher;
-        mObject = shortcut;
-    }
-
-    /**
-     * Clicks the option.
-     */
-    @NonNull
-    public void launch(@NonNull String expectedPackageName) {
-        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            LauncherInstrumentation.log("OptionsPopupMenuItem before click "
-                    + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
-            mLauncher.clickLauncherObject(mObject);
-            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START);
-            mLauncher.assertTrue(
-                    "App didn't start: " + By.pkg(expectedPackageName),
-                    mLauncher.getDevice().wait(Until.hasObject(By.pkg(expectedPackageName)),
-                            LauncherInstrumentation.WAIT_TIME_MS));
-        }
-    }
-}