Merge "Change work paused label font" into ub-launcher3-master
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index d82e9f0..801408f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -48,6 +48,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.allapps.AllAppsStore;
@@ -325,7 +326,7 @@
             mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
         }
         predictionLog.append("]");
-        if (false) FileLog.d(TAG, predictionLog.toString());
+        if (Utilities.IS_DEBUG_DEVICE) FileLog.d(TAG, predictionLog.toString());
         updateDependencies();
         if (isReady()) {
             fillGapsWithPrediction();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e6f8a85..21a8fd4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.IconShape;
@@ -65,7 +66,7 @@
  * too aggressive.
  */
 public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
-        IconLabelDotView {
+        IconLabelDotView, DraggableView {
 
     private static final int DISPLAY_WORKSPACE = 0;
     private static final int DISPLAY_ALL_APPS = 1;
@@ -103,7 +104,7 @@
 
     private final ActivityContext mActivity;
     private Drawable mIcon;
-    private final boolean mCenterVertically;
+    private boolean mCenterVertically;
 
     private final int mDisplay;
 
@@ -701,4 +702,24 @@
     public int getIconSize() {
         return mIconSize;
     }
+
+    @Override
+    public int getViewType() {
+        return DRAGGABLE_ICON;
+    }
+
+    @Override
+    public void getVisualDragBounds(Rect bounds) {
+        DeviceProfile grid = mActivity.getDeviceProfile();
+        BubbleTextView.getIconBounds(this, bounds, grid.iconSizePx);
+    }
+
+    @Override
+    public void prepareDrawDragView() {
+        if (getIcon() instanceof FastBitmapDrawable) {
+            FastBitmapDrawable icon = (FastBitmapDrawable) getIcon();
+            icon.setScale(1f);
+        }
+        setForceHideDot(true);
+    }
 }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index f2d07f2..9682d09 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -56,6 +56,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.RotationMode;
@@ -100,6 +101,11 @@
     @Thunk final int[] mTmpPoint = new int[2];
     @Thunk final int[] mTempLocation = new int[2];
 
+    // Used to visualize / debug the Grid of the CellLayout
+    private static final boolean VISUALIZE_GRID = false;
+    private Rect mVisualizeGridRect = new Rect();
+    private Paint mVisualizeGridPaint = new Paint();
+
     private GridOccupancy mOccupied;
     private GridOccupancy mTmpOccupied;
 
@@ -462,6 +468,37 @@
             mFolderLeaveBehind.drawLeaveBehind(canvas);
             canvas.restore();
         }
+
+        if (VISUALIZE_GRID) {
+            visualizeGrid(canvas);
+        }
+    }
+
+    protected void visualizeGrid(Canvas canvas) {
+        mVisualizeGridRect.set(0, 0, mCellWidth, mCellHeight);
+        mVisualizeGridPaint.setStrokeWidth(4);
+
+        for (int i = 0; i < mCountX; i++) {
+            for (int j = 0; j < mCountY; j++) {
+                canvas.save();
+
+                int transX = i * mCellWidth;
+                int transY = j * mCellHeight;
+
+                canvas.translate(getPaddingLeft() + transX, getPaddingTop() + transY);
+
+                mVisualizeGridPaint.setStyle(Paint.Style.FILL);
+                mVisualizeGridPaint.setColor(Color.argb(80, 255, 100, 100));
+
+                canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
+
+                mVisualizeGridPaint.setStyle(Paint.Style.STROKE);
+                mVisualizeGridPaint.setColor(Color.argb(255, 255, 100, 100));
+
+                canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
+                canvas.restore();
+            }
+        }
     }
 
     @Override
@@ -928,8 +965,8 @@
         return false;
     }
 
-    void visualizeDropLocation(View v, DragPreviewProvider outlineProvider, int cellX, int cellY,
-            int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
+    void visualizeDropLocation(DraggableView v, DragPreviewProvider outlineProvider, int cellX, int
+            cellY, int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
         final int oldDragCellX = mDragCell[0];
         final int oldDragCellY = mDragCell[1];
 
@@ -939,9 +976,6 @@
 
         Bitmap dragOutline = outlineProvider.generatedDragOutline;
         if (cellX != oldDragCellX || cellY != oldDragCellY) {
-            Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
-            Rect dragRegion = dragObject.dragView.getDragRegion();
-
             mDragCell[0] = cellX;
             mDragCell[1] = cellY;
 
@@ -950,50 +984,27 @@
             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
             Rect r = mDragOutlines[mDragOutlineCurrent];
 
+            cellToRect(cellX, cellY, spanX, spanY, r);
+            int left = r.left;
+            int top = r.top;
+
+            int width = dragOutline.getWidth();
+            int height = dragOutline.getHeight();
+
             if (resize) {
-                cellToRect(cellX, cellY, spanX, spanY, r);
-                if (v instanceof LauncherAppWidgetHostView) {
-                    DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
-                    Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
-                }
-            } else {
-                // Find the top left corner of the rect the object will occupy
-                final int[] topLeft = mTmpPoint;
-                cellToPoint(cellX, cellY, topLeft);
-
-                int left = topLeft[0];
-                int top = topLeft[1];
-
-                if (v != null && dragOffset == null) {
-                    // When drawing the drag outline, it did not account for margin offsets
-                    // added by the view's parent.
-                    MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
-                    left += lp.leftMargin;
-                    top += lp.topMargin;
-
-                    // Offsets due to the size difference between the View and the dragOutline.
-                    // There is a size difference to account for the outer blur, which may lie
-                    // outside the bounds of the view.
-                    top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
-                    // We center about the x axis
-                    left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
-                } else {
-                    if (dragOffset != null && dragRegion != null) {
-                        // Center the drag region *horizontally* in the cell and apply a drag
-                        // outline offset
-                        left += dragOffset.x + ((mCellWidth * spanX) - dragRegion.width()) / 2;
-                        int cHeight = getShortcutsAndWidgets().getCellContentHeight();
-                        int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
-                        top += dragOffset.y + cellPaddingY;
-                    } else {
-                        // Center the drag outline in the cell
-                        left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
-                        top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
-                    }
-                }
-                r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
+                width = r.width();
+                height = r.height();
             }
 
+            if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) {
+                left +=  ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
+                int cHeight = getShortcutsAndWidgets().getCellContentHeight();
+                int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
+                top += cellPaddingY;
+            }
+
+            r.set(left, top, left + width, top + height);
+
             Utilities.scaleRectAboutCenter(r, mChildScale);
             mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
             mDragOutlineAnims[mDragOutlineCurrent].animateIn();
@@ -1879,7 +1890,7 @@
 
     // This method starts or changes the reorder preview animations
     private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
-            View dragView, int delay, int mode) {
+            View dragView, int mode) {
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
@@ -1946,6 +1957,8 @@
 
             this.child = child;
             this.mode = mode;
+
+            // TODO issue!
             setInitialAnimationValues(false);
             finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
             finalDeltaX = initDeltaX;
@@ -2141,6 +2154,8 @@
     */
     private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
             int spanY, View dragView, int[] resultDirection) {
+
+        //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
         int[] targetDestination = new int[2];
 
         findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
@@ -2251,7 +2266,7 @@
                 setItemPlacementDirty(false);
             } else {
                 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
-                        REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
+                        ReorderPreviewAnimation.MODE_PREVIEW);
             }
             mShortcutsAndWidgets.requestLayout();
         }
@@ -2305,7 +2320,7 @@
 
         if (mode == MODE_SHOW_REORDER_HINT) {
             if (finalSolution != null) {
-                beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
+                beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
                         ReorderPreviewAnimation.MODE_HINT);
                 result[0] = finalSolution.cellX;
                 result[1] = finalSolution.cellY;
@@ -2345,7 +2360,7 @@
                     setItemPlacementDirty(false);
                 } else {
                     beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
-                            REORDER_ANIMATION_DURATION,  ReorderPreviewAnimation.MODE_PREVIEW);
+                            ReorderPreviewAnimation.MODE_PREVIEW);
                 }
             }
         } else {
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index c03011b..ef02e87 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -23,6 +23,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.FolderNameProvider;
 
 /**
@@ -69,6 +70,10 @@
 
         public FolderNameProvider folderNameProvider;
 
+        /** The source view (ie. icon, widget etc.) that is being dragged and which the
+         * DragView represents. May be an actual View class or a virtual stand-in */
+        public DraggableView originalView = null;
+
         public DragObject(Context context) {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 folderNameProvider = FolderNameProvider.newInstance(context);
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 336e423..b75a5e7 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -79,7 +79,7 @@
      * Add an app or shortcut for a specified rank.
      */
     public void add(WorkspaceItemInfo item, int rank, boolean animate) {
-        rank = Utilities.boundToRange(rank, 0, contents.size() + 1);
+        rank = Utilities.boundToRange(rank, 0, contents.size());
         contents.add(rank, item);
         for (int i = 0; i < mListeners.size(); i++) {
             mListeners.get(i).onAdd(item, rank);
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 24d0c41..e071777 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -324,6 +324,7 @@
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
             StateAnimationConfig config) {
+        config.userControlled = true;
         mConfig.reset();
         config.copyTo(mConfig);
         mConfig.playbackController = createAnimationToNewWorkspaceInternal(state)
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 1bd8263..c07dd9d 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -145,38 +145,46 @@
             final View child = getChildAt(i);
             if (child.getVisibility() != GONE) {
                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-
-                if (child instanceof LauncherAppWidgetHostView) {
-                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
-
-                    // Scale and center the widget to fit within its cells.
-                    DeviceProfile profile = mActivity.getDeviceProfile();
-                    float scaleX = profile.appWidgetScale.x;
-                    float scaleY = profile.appWidgetScale.y;
-
-                    lahv.setScaleToFit(Math.min(scaleX, scaleY));
-                    lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
-                            -(lp.height - (lp.height * scaleY)) / 2.0f);
-                }
-
-                int childLeft = lp.x;
-                int childTop = lp.y;
-                child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
-
-                if (lp.dropped) {
-                    lp.dropped = false;
-
-                    final int[] cellXY = mTmpCellXY;
-                    getLocationOnScreen(cellXY);
-                    mWallpaperManager.sendWallpaperCommand(getWindowToken(),
-                            WallpaperManager.COMMAND_DROP,
-                            cellXY[0] + childLeft + lp.width / 2,
-                            cellXY[1] + childTop + lp.height / 2, 0, null);
-                }
+                layoutChild(child);
             }
         }
     }
 
+    /**
+     * Core logic to layout a child for this ViewGroup.
+     */
+    public void layoutChild(View child) {
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        if (child instanceof LauncherAppWidgetHostView) {
+            LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
+
+            // Scale and center the widget to fit within its cells.
+            DeviceProfile profile = mActivity.getDeviceProfile();
+            float scaleX = profile.appWidgetScale.x;
+            float scaleY = profile.appWidgetScale.y;
+
+            lahv.setScaleToFit(Math.min(scaleX, scaleY));
+            lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
+                    -(lp.height - (lp.height * scaleY)) / 2.0f);
+        }
+
+        int childLeft = lp.x;
+        int childTop = lp.y;
+        child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
+
+        if (lp.dropped) {
+            lp.dropped = false;
+
+            final int[] cellXY = mTmpCellXY;
+            getLocationOnScreen(cellXY);
+            mWallpaperManager.sendWallpaperCommand(getWindowToken(),
+                    WallpaperManager.COMMAND_DROP,
+                    cellXY[0] + childLeft + lp.width / 2,
+                    cellXY[1] + childTop + lp.height / 2, 0, null);
+        }
+    }
+
+
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == ACTION_DOWN && getAlpha() == 0) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 46493b7..c42e480 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -70,6 +70,7 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.dragndrop.SpringLoadedDragController;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
@@ -81,7 +82,6 @@
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.WorkspaceTouchListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -1452,12 +1452,17 @@
                     + "View: " + child + "  tag: " + child.getTag();
             throw new IllegalStateException(msg);
         }
-        beginDragShared(child, source, (ItemInfo) dragObject,
+        beginDragShared(child, null, source, (ItemInfo) dragObject,
                 new DragPreviewProvider(child), options);
     }
 
-    public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
-            DragPreviewProvider previewProvider, DragOptions dragOptions) {
+    /**
+     * Core functionality for beginning a drag operation for an item that will be dropped within
+     * the workspace
+     */
+    public DragView beginDragShared(View child, DraggableView draggableView, DragSource source,
+            ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) {
+
         float iconScale = 1f;
         if (child instanceof BubbleTextView) {
             Drawable icon = ((BubbleTextView) child).getIcon();
@@ -1466,41 +1471,36 @@
             }
         }
 
+        // Clear the pressed state if necessary
         child.clearFocus();
         child.setPressed(false);
+        if (child instanceof BubbleTextView) {
+            BubbleTextView icon = (BubbleTextView) child;
+            icon.clearPressedBackground();
+        }
+
         mOutlineProvider = previewProvider;
 
         // The drag bitmap follows the touch point around on the screen
         final Bitmap b = previewProvider.createDragBitmap();
         int halfPadding = previewProvider.previewPadding / 2;
-
         float scale = previewProvider.getScaleAndPosition(b, mTempXY);
         int dragLayerX = mTempXY[0];
         int dragLayerY = mTempXY[1];
 
-        DeviceProfile grid = mLauncher.getDeviceProfile();
         Point dragVisualizeOffset = null;
-        Rect dragRect = null;
-        if (child instanceof BubbleTextView) {
-            dragRect = new Rect();
-            BubbleTextView.getIconBounds(child, dragRect, grid.iconSizePx);
+        Rect dragRect = new Rect();
+
+        if (draggableView == null && child instanceof DraggableView) {
+            draggableView = (DraggableView) child;
+        }
+
+        if (draggableView != null) {
+            draggableView.getVisualDragBounds(dragRect);
             dragLayerY += dragRect.top;
-            // Note: The dragRect is used to calculate drag layer offsets, but the
-            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
-            dragVisualizeOffset = new Point(- halfPadding, halfPadding);
-        } else if (child instanceof FolderIcon) {
-            int previewSize = grid.folderIconSizePx;
-            dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop());
-            dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
-        } else if (previewProvider instanceof ShortcutDragPreviewProvider) {
             dragVisualizeOffset = new Point(- halfPadding, halfPadding);
         }
 
-        // Clear the pressed state if necessary
-        if (child instanceof BubbleTextView) {
-            BubbleTextView icon = (BubbleTextView) child;
-            icon.clearPressedBackground();
-        }
 
         if (child.getParent() instanceof ShortcutAndWidgetContainer) {
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
@@ -1511,13 +1511,13 @@
                     .showForIcon((BubbleTextView) child);
             if (popupContainer != null) {
                 dragOptions.preDragCondition = popupContainer.createPreDragCondition();
-
                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("dragging started");
             }
         }
 
-        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
-                dragObject, dragVisualizeOffset, dragRect, scale * iconScale, scale, dragOptions);
+        DragView dv = mDragController.startDrag(b, draggableView, dragLayerX, dragLayerY, source,
+                dragObject, dragVisualizeOffset, dragRect, scale * iconScale,
+                scale, dragOptions);
         dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
         return dv;
     }
@@ -2186,7 +2186,7 @@
                     item.spanY, child, mTargetCell);
 
             if (!nearestDropOccupied) {
-                mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
+                mDragTargetLayout.visualizeDropLocation(d.originalView, mOutlineProvider,
                         mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d);
             } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
                     && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
@@ -2279,7 +2279,8 @@
 
     private void manageFolderFeedback(float distance, DragObject dragObject) {
         if (distance > mMaxDistanceForFolderCreation) {
-            if (mDragMode != DRAG_MODE_NONE) {
+            if ((mDragMode == DRAG_MODE_ADD_TO_FOLDER
+                    || mDragMode == DRAG_MODE_CREATE_FOLDER)) {
                 setDragMode(DRAG_MODE_NONE);
             }
             return;
@@ -2368,7 +2369,7 @@
             }
 
             boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
-            mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
+            mDragTargetLayout.visualizeDropLocation(dragObject.originalView, mOutlineProvider,
                 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject);
         }
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 10a3060..dd0212a 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -38,6 +38,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -610,6 +611,7 @@
         final Rect padding = new Rect();
         AllAppsRecyclerView recyclerView;
         boolean verticalFadingEdge;
+        private View mOverlay;
 
         boolean mWorkDisabled;
 
@@ -646,13 +648,20 @@
             if (mWorkDisabled == workDisabled) return;
             recyclerView.setContentDescription(
                     workDisabled ? mLauncher.getString(R.string.work_apps_paused_title) : null);
+            View overlayView = getOverlayView();
+            recyclerView.setItemAnimator(new DefaultItemAnimator());
             if (workDisabled) {
+                overlayView.setAlpha(0);
                 appsList.updateItemFilter((info, cn) -> false);
-                recyclerView.addAutoSizedOverlay(
-                        mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null));
+                recyclerView.addAutoSizedOverlay(overlayView);
+                overlayView.animate().alpha(1).withEndAction(
+                        () -> recyclerView.setItemAnimator(null)).start();
             } else if (mInfoMatcher != null) {
                 appsList.updateItemFilter(mInfoMatcher);
-                recyclerView.clearAutoSizedOverlays();
+                overlayView.animate().alpha(0).withEndAction(() -> {
+                    recyclerView.setItemAnimator(null);
+                    recyclerView.clearAutoSizedOverlays();
+                }).start();
             }
             mWorkDisabled = workDisabled;
         }
@@ -671,5 +680,12 @@
             mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs
                     && verticalFadingEdge);
         }
+
+        private View getOverlayView() {
+            if (mOverlay == null) {
+                mOverlay = mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null);
+            }
+            return mOverlay;
+        }
     }
 }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4df3b0a..b3f87f0 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -163,7 +163,7 @@
             "Always use hardware optimization for folder animations.");
 
     public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag(
-            FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true,
+            FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, false,
             "Launch/close apps without rotation animation. Fix Launcher to portrait");
 
     public static void initialize(Context context) {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 1539747..d0d9aaf 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -137,6 +137,8 @@
      *
      * @param b The bitmap to display as the drag image.  It will be re-scaled to the
      *          enlarged size.
+     * @param originalView The source view (ie. icon, widget etc.) that is being dragged
+     *          and which the DragView represents
      * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
      * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
      * @param source An object representing where the drag originated
@@ -144,7 +146,7 @@
      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
      */
-    public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
+    public DragView startDrag(Bitmap b, DraggableView originalView, int dragLayerX, int dragLayerY,
             DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion,
             float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) {
         if (PROFILE_DRAWING_DURING_DRAG) {
@@ -170,6 +172,7 @@
         mLastDropTarget = null;
 
         mDragObject = new DropTarget.DragObject(mLauncher.getApplicationContext());
+        mDragObject.originalView = originalView;
 
         mIsInPreDrag = mOptions.preDragCondition != null
                 && !mOptions.preDragCondition.shouldStartDrag(0);
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 9d07595..e18ca54 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -41,7 +41,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
-import android.widget.TextView;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.CellLayout;
@@ -52,7 +51,6 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
@@ -90,6 +88,8 @@
     private int mTopViewIndex;
     private int mChildCountOnLastUpdate = -1;
 
+    private Rect mTmpRect = new Rect();
+
     // Related to adjacent page hints
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
     private final WorkspaceAndHotseatScrim mWorkspaceScrim;
@@ -254,59 +254,46 @@
 
     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
             View anchorView) {
+
         ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
         CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
         parentChildren.measureChild(child);
+        parentChildren.layoutChild(child);
 
-        Rect r = new Rect();
-        getViewRectRelativeToSelf(dragView, r);
+        getViewRectRelativeToSelf(dragView, mTmpRect);
+        final int fromX = mTmpRect.left;
+        final int fromY = mTmpRect.top;
 
         float coord[] = new float[2];
         float childScale = child.getScaleX();
+
         coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2);
         coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2);
 
         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
         // the correct coordinates (above) and use these to determine the final location
         float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
+
         // We need to account for the scale of the child itself, as the above only accounts for
         // for the scale in parents.
         scale *= childScale;
         int toX = Math.round(coord[0]);
         int toY = Math.round(coord[1]);
         float toScale = scale;
-        if (child instanceof TextView) {
-            TextView tv = (TextView) child;
-            // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
-            // the workspace may have smaller icon bounds).
-            toScale = scale / dragView.getIntrinsicIconScaleFactor();
 
-            // The child may be scaled (always about the center of the view) so to account for it,
-            // we have to offset the position by the scaled size.  Once we do that, we can center
-            // the drag view about the scaled child view.
-            // padding will remain constant (does not scale with size)
-            toY += tv.getPaddingTop();
-            toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
-            if (dragView.getDragVisualizeOffset() != null) {
-                toY -=  Math.round(toScale * dragView.getDragVisualizeOffset().y);
-            }
+        if (child instanceof DraggableView) {
+            DraggableView d = (DraggableView) child;
+            d.getVisualDragBounds(mTmpRect);
 
-            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
-        } else if (child instanceof FolderIcon) {
-            // Account for holographic blur padding on the drag view
-            toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
-            toY -= scale * dragView.getBlurSizeOutline() / 2;
-            toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
-            // Center in the x coordinate about the target's drawable
-            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
-        } else {
-            toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
-            toX -= (Math.round(scale * (dragView.getMeasuredWidth()
-                    - child.getMeasuredWidth()))) / 2;
+            // This accounts for the offset of the DragView created by scaling it about its
+            // center as it animates into place.
+            float scaleShiftX = dragView.getMeasuredWidth() * (1 - scale) / 2;
+            float scaleShiftY = dragView.getMeasuredHeight() * (1 - scale) / 2;
+
+            toX += scale * (mTmpRect.left - dragView.getBlurSizeOutline() / 2) - scaleShiftX;
+            toY += scale * (mTmpRect.top - dragView.getBlurSizeOutline() / 2) - scaleShiftY;
         }
 
-        final int fromX = r.left;
-        final int fromY = r.top;
         child.setVisibility(INVISIBLE);
         Runnable onCompleteRunnable = () -> child.setVisibility(VISIBLE);
         animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
diff --git a/src/com/android/launcher3/dragndrop/DraggableView.java b/src/com/android/launcher3/dragndrop/DraggableView.java
new file mode 100644
index 0000000..df99902
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/DraggableView.java
@@ -0,0 +1,57 @@
+/*
+ * 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.dragndrop;
+
+import android.graphics.Rect;
+
+/**
+ * Interface defining methods required for drawing and previewing DragViews, drag previews, and
+ * related animations
+ */
+public interface DraggableView {
+    int DRAGGABLE_ICON = 0;
+    int DRAGGABLE_WIDGET = 1;
+
+    /**
+     * Static ctr for a simple instance which just returns the view type.
+     */
+    static DraggableView ofType(int type) {
+        return () -> type;
+    }
+
+    /**
+     * Certain handling of DragViews depend only on whether this is an Icon Type item or a Widget
+     * Type item.
+     *
+     * @return DRAGGABLE_ICON or DRAGGABLE_WIDGET as appropriate
+     */
+    int getViewType();
+
+    /**
+     * Before rendering as a DragView bitmap, some views need a preparation step.
+     */
+    default void prepareDrawDragView() { }
+
+    /**
+     * If an actual View subclass, this method returns the rectangle (within the View's coordinates)
+     * of the visual region that should get dragged. This is used to extract exactly that element
+     * as well as to offset that element as appropriate for various animations
+     *
+     * @param bounds Visual bounds in the views coordinates will be written here.
+     */
+    default void getVisualDragBounds(Rect bounds) { }
+}
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 0bb3fba..ea1fbdb 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -20,10 +20,10 @@
 
 import android.annotation.TargetApi;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Path;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -33,7 +33,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.ShiftedBitmapDrawable;
@@ -50,6 +49,7 @@
     private final Drawable mBadge;
     private final Path mMask;
     private final ConstantState mConstantState;
+    private static final Rect sTmpRect = new Rect();
 
     private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) {
         super(bg, fg);
@@ -72,23 +72,14 @@
     public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
             Launcher launcher, int folderId, Point dragViewSize) {
         Preconditions.assertNonUiThread();
-        int margin = launcher.getResources()
-                .getDimensionPixelSize(R.dimen.blur_size_medium_outline);
-
-        // Allocate various bitmaps on the background thread, because why not!
-        int width = dragViewSize.x - margin;
-        int height = dragViewSize.y - margin;
-        if (width <= 0 || height <= 0) {
-            return null;
-        }
-        final Bitmap badge = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
 
         // Create the actual drawable on the UI thread to avoid race conditions with
         // FolderIcon draw pass
         try {
             return MAIN_EXECUTOR.submit(() -> {
                 FolderIcon icon = launcher.findFolderIcon(folderId);
-                return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize);
+                return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize);
+
             }).get();
         } catch (Exception e) {
             Log.e(TAG, "Unable to create folder icon", e);
@@ -96,49 +87,50 @@
         }
     }
 
-    /**
-     * Initializes various bitmaps on the UI thread and returns the final drawable.
-     */
     private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon,
-            Bitmap badgeBitmap, Point dragViewSize) {
+                                                               Point dragViewSize) {
         Preconditions.assertUIThread();
-        float margin = icon.getResources().getDimension(R.dimen.blur_size_medium_outline) / 2;
 
-        Canvas c = new Canvas();
+        icon.getPreviewBounds(sTmpRect);
+
         PreviewBackground bg = icon.getFolderBackground();
 
-        // Initialize badge
-        c.setBitmap(badgeBitmap);
-        bg.drawShadow(c);
-        bg.drawBackgroundStroke(c);
-        icon.drawDot(c);
+        // assume square
+        assert (dragViewSize.x == dragViewSize.y);
+        final int previewSize = sTmpRect.width();
 
-        // Initialize preview
-        final float sizeScaleFactor = 1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction();
-        final int previewWidth = (int) (dragViewSize.x * sizeScaleFactor);
-        final int previewHeight = (int) (dragViewSize.y * sizeScaleFactor);
+        final int margin = (dragViewSize.x - previewSize) / 2;
+        final float previewShiftX = -sTmpRect.left + margin;
+        final float previewShiftY = -sTmpRect.top + margin;
 
-        final float shiftFactor = AdaptiveIconDrawable.getExtraInsetFraction() / sizeScaleFactor;
-        final float previewShiftX = shiftFactor * previewWidth;
-        final float previewShiftY = shiftFactor * previewHeight;
-
-        Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight,
+        // Initialize badge, which consists of the outline stroke, shadow and dot; these
+        // must be rendered above the foreground
+        Bitmap badgeBmp = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
                 (canvas) -> {
-                    int count = canvas.save();
+                    canvas.save();
                     canvas.translate(previewShiftX, previewShiftY);
-                    icon.getPreviewItemManager().draw(canvas);
-                    canvas.restoreToCount(count);
+                    bg.drawShadow(canvas);
+                    bg.drawBackgroundStroke(canvas);
+                    icon.drawDot(canvas);
+                    canvas.restore();
                 });
 
         // Initialize mask
         Path mask = new Path();
         Matrix m = new Matrix();
-        m.setTranslate(margin, margin);
+        m.setTranslate(previewShiftX, previewShiftY);
         bg.getClipPath().transform(m, mask);
 
-        ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBitmap, margin, margin);
-        ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap,
-                margin - previewShiftX, margin - previewShiftY);
+        Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
+                (canvas) -> {
+                    canvas.save();
+                    canvas.translate(previewShiftX, previewShiftY);
+                    icon.getPreviewItemManager().draw(canvas);
+                    canvas.restore();
+                });
+
+        ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBmp, 0, 0);
+        ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 0, 0);
 
         return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask);
     }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 14fa1f4..365e76f 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -81,6 +81,7 @@
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.WorkspaceItemInfo;
@@ -962,6 +963,7 @@
             View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
                     ? mCurrentDragView : mContent.createNewView(info);
             ArrayList<View> views = getIconsInReadingOrder();
+            info.rank = Utilities.boundToRange(info.rank, 0, views.size());
             views.add(info.rank, icon);
             mContent.arrangeChildren(views);
             mItemsInvalidated = true;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index ab1ff10..f0d18ae 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Executors;
@@ -78,7 +79,8 @@
 /**
  * An icon that can appear on in the workspace representing an {@link Folder}.
  */
-public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView {
+public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
+        DraggableView {
 
     @Thunk ActivityContext mActivity;
     @Thunk Folder mFolder;
@@ -230,6 +232,16 @@
         mBackground.getBounds(outBounds);
     }
 
+    @Override
+    public int getViewType() {
+        return DRAGGABLE_ICON;
+    }
+
+    @Override
+    public void getVisualDragBounds(Rect bounds) {
+        getPreviewBounds(bounds);
+    }
+
     public float getBackgroundStrokeWidth() {
         return mBackground.getStrokeWidth();
     }
@@ -525,6 +537,10 @@
         invalidate();
     }
 
+    public boolean getIconVisible() {
+        return mBackgroundIsVisible;
+    }
+
     public PreviewBackground getFolderBackground() {
         return mBackground;
     }
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 94d30f6..ed9dfbb 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -30,14 +30,12 @@
 import android.view.View;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
 
 import java.nio.ByteBuffer;
 
@@ -53,7 +51,7 @@
     // The padding added to the drag view during the preview generation.
     public final int previewPadding;
 
-    protected final int blurSizeOutline;
+    public final int blurSizeOutline;
 
     private OutlineGeneratorCallback mOutlineGeneratorCallback;
     public Bitmap generatedDragOutline;
@@ -66,56 +64,25 @@
         mView = view;
         blurSizeOutline =
                 context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
-
-        if (mView instanceof BubbleTextView) {
-            Drawable d = ((BubbleTextView) mView).getIcon();
-            Rect bounds = getDrawableBounds(d);
-            previewPadding = blurSizeOutline - bounds.left - bounds.top;
-        } else {
-            previewPadding = blurSizeOutline;
-        }
+        previewPadding = blurSizeOutline;
     }
 
     /**
      * Draws the {@link #mView} into the given {@param destCanvas}.
      */
     protected void drawDragView(Canvas destCanvas, float scale) {
-        destCanvas.save();
+        int saveCount = destCanvas.save();
         destCanvas.scale(scale, scale);
 
-        if (mView instanceof BubbleTextView) {
-            Drawable d = ((BubbleTextView) mView).getIcon();
-            Rect bounds = getDrawableBounds(d);
-            destCanvas.translate(blurSizeOutline / 2 - bounds.left,
-                    blurSizeOutline / 2 - bounds.top);
-            if (d instanceof FastBitmapDrawable) {
-                ((FastBitmapDrawable) d).setScale(1);
-            }
-            d.draw(destCanvas);
-        } else {
-            final Rect clipRect = mTempRect;
-            mView.getDrawingRect(clipRect);
-
-            boolean textVisible = false;
-            if (mView instanceof FolderIcon) {
-                // For FolderIcons the text can bleed into the icon area, and so we need to
-                // hide the text completely (which can't be achieved by clipping).
-                if (((FolderIcon) mView).getTextVisible()) {
-                    ((FolderIcon) mView).setTextVisible(false);
-                    textVisible = true;
-                }
-            }
-            destCanvas.translate(-mView.getScrollX() + blurSizeOutline / 2,
-                    -mView.getScrollY() + blurSizeOutline / 2);
-            destCanvas.clipRect(clipRect);
+        if (mView instanceof DraggableView) {
+            DraggableView dv = (DraggableView) mView;
+            dv.prepareDrawDragView();
+            dv.getVisualDragBounds(mTempRect);
+            destCanvas.translate(blurSizeOutline / 2 - mTempRect.left,
+                    blurSizeOutline / 2 - mTempRect.top);
             mView.draw(destCanvas);
-
-            // Restore text visibility of FolderIcon if necessary
-            if (textVisible) {
-                ((FolderIcon) mView).setTextVisible(true);
-            }
         }
-        destCanvas.restore();
+        destCanvas.restoreToCount(saveCount);
     }
 
     /**
@@ -123,28 +90,15 @@
      * Responsibility for the bitmap is transferred to the caller.
      */
     public Bitmap createDragBitmap() {
-        int width = mView.getWidth();
-        int height = mView.getHeight();
-
-        if (mView instanceof BubbleTextView) {
-            Drawable d = ((BubbleTextView) mView).getIcon();
-            Rect bounds = getDrawableBounds(d);
-            width = bounds.width();
-            height = bounds.height();
-        } else if (mView instanceof LauncherAppWidgetHostView) {
-            float scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
-            width = (int) (mView.getWidth() * scale);
-            height = (int) (mView.getHeight() * scale);
-
-            if (mView instanceof PendingAppWidgetHostView) {
-                // Use hardware renderer as the icon for the pending app widget may be a hw bitmap
-                return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
-                        height + blurSizeOutline, (c) -> drawDragView(c, scale));
-            } else {
-                // Use software renderer for widgets as we know that they already work
-                return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline,
-                        height + blurSizeOutline, (c) -> drawDragView(c, scale));
-            }
+        int width = 0;
+        int height = 0;
+        if (mView instanceof DraggableView) {
+            ((DraggableView) mView).getVisualDragBounds(mTempRect);
+            width = mTempRect.width();
+            height = mTempRect.height();
+        } else {
+            width = mView.getWidth();
+            height = mView.getHeight();
         }
 
         return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 3fb2bf6..7b7ab5e 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -40,12 +40,14 @@
 import android.util.DisplayMetrics;
 import android.util.FloatProperty;
 import android.view.View;
+import android.view.WindowInsets;
 
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.util.Themes;
@@ -198,9 +200,22 @@
      * Determines whether to draw the top and/or bottom scrim based on new insets.
      */
     public void onInsetsChanged(Rect insets, boolean allowSysuiScrims) {
-        mDrawTopScrim = allowSysuiScrims && mTopScrim != null && insets.top > 0;
-        mDrawBottomScrim = allowSysuiScrims && mBottomMask != null
-                && !mLauncher.getDeviceProfile().isVerticalBarLayout();
+        mDrawTopScrim = allowSysuiScrims
+                && mTopScrim != null
+                && insets.top > 0;
+        mDrawBottomScrim = allowSysuiScrims
+                && mBottomMask != null
+                && !mLauncher.getDeviceProfile().isVerticalBarLayout()
+                && hasBottomNavButtons();
+    }
+
+    private boolean hasBottomNavButtons() {
+        if (Utilities.ATLEAST_Q && mLauncher.getRootView() != null
+                && mLauncher.getRootView().getRootWindowInsets() != null) {
+            WindowInsets windowInsets = mLauncher.getRootView().getRootWindowInsets();
+            return windowInsets.getTappableElementInsets().bottom > 0;
+        }
+        return true;
     }
 
     @Override
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index a9d10d7..1b70fde 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.logging;
 
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR;
-
 import android.util.ArrayMap;
 import android.util.SparseArray;
 import android.view.View;
@@ -27,12 +25,9 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.userevent.nano.LauncherLogExtensions.TargetExtension;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
 import com.android.launcher3.util.InstantAppResolver;
 
 import java.lang.reflect.Field;
@@ -70,93 +65,6 @@
         return result != null ? result : UNKNOWN;
     }
 
-    public static String getActionStr(Action action) {
-        String str = "";
-        switch (action.type) {
-            case Action.Type.TOUCH:
-                str += getFieldName(action.touch, Action.Touch.class);
-                if (action.touch == Action.Touch.SWIPE || action.touch == Action.Touch.FLING) {
-                    str += " direction=" + getFieldName(action.dir, Action.Direction.class);
-                }
-                break;
-            case Action.Type.COMMAND:
-                str += getFieldName(action.command, Action.Command.class);
-                break;
-            default: return getFieldName(action.type, Action.Type.class);
-        }
-        if (action.touch == Action.Touch.SWIPE || action.touch == Action.Touch.FLING ||
-                (action.command == Action.Command.BACK && action.dir != Action.Direction.NONE)) {
-            str += " direction=" + getFieldName(action.dir, Action.Direction.class);
-        }
-        return str;
-    }
-
-    public static String getTargetStr(Target t) {
-        if (t == null) {
-            return "";
-        }
-        String str = "";
-        switch (t.type) {
-            case Target.Type.ITEM:
-                str = getItemStr(t);
-                break;
-            case Target.Type.CONTROL:
-                str = getFieldName(t.controlType, ControlType.class);
-                break;
-            case Target.Type.CONTAINER:
-                str = getFieldName(t.containerType, ContainerType.class);
-                if (t.containerType == ContainerType.WORKSPACE ||
-                        t.containerType == ContainerType.HOTSEAT ||
-                        t.containerType == NAVBAR) {
-                    str += " id=" + t.pageIndex;
-                } else if (t.containerType == ContainerType.FOLDER) {
-                    str += " grid(" + t.gridX + "," + t.gridY + ")";
-                }
-                break;
-            default:
-                str += "UNKNOWN TARGET TYPE";
-        }
-
-        if (t.spanX != 1 || t.spanY != 1) {
-            str += " span(" + t.spanX + "," + t.spanY + ")";
-        }
-
-        if (t.tipType != TipType.DEFAULT_NONE) {
-            str += " " + getFieldName(t.tipType, TipType.class);
-        }
-
-        return str;
-    }
-
-    private static String getItemStr(Target t) {
-        String typeStr = getFieldName(t.itemType, ItemType.class);
-        if (t.packageNameHash != 0) {
-            typeStr += ", packageHash=" + t.packageNameHash;
-        }
-        if (t.componentHash != 0) {
-            typeStr += ", componentHash=" + t.componentHash;
-        }
-        if (t.intentHash != 0) {
-            typeStr += ", intentHash=" + t.intentHash;
-        }
-        if (t.itemType == ItemType.FOLDER_ICON) {
-            typeStr += ", grid(" + t.gridX + "," + t.gridY + ")";
-        } else if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0)
-                && t.itemType != ItemType.TASK) {
-            typeStr +=
-                    ", isWorkApp=" + t.isWorkApp + ", predictiveRank=" + t.predictedRank + ", grid("
-                            + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
-                            + "), pageIdx=" + t.pageIndex;
-        }
-        if (t.searchQueryLength != 0) {
-            typeStr += ", searchQueryLength=" + t.searchQueryLength;
-        }
-        if (t.itemType == ItemType.TASK) {
-            typeStr += ", pageIdx=" + t.pageIndex;
-        }
-        return typeStr;
-    }
-
     public static Target newItemTarget(int itemType) {
         Target t = newTarget(Target.Type.ITEM);
         t.itemType = itemType;
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 32fce0b..fdfcef1 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -424,7 +424,8 @@
         // Now add the new shortcuts to the map.
         for (ShortcutInfo shortcut : shortcuts) {
             boolean shouldShowInContainer = shortcut.isEnabled()
-                    && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
+                    && (shortcut.isDeclaredInManifest() || shortcut.isDynamic())
+                    && shortcut.getActivity() != null;
             if (shouldShowInContainer) {
                 ComponentKey targetComponent
                         = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 5af5ebb..9bac259 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -59,6 +59,7 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationItemView;
 import com.android.launcher3.notification.NotificationKeyData;
@@ -662,7 +663,8 @@
             iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
             iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
 
-            DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+            DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
+            DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), draggableView,
                     mContainer, sv.getFinalInfo(),
                     new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
                     new DragOptions());
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index fae0fe2..8c8aa9b 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -134,7 +134,7 @@
      */
     private void updateForcedRotation(boolean setValueFromPrefs) {
         boolean isForcedRotation = mFeatureFlagsPrefs
-                .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
+                .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, false)
                 && !getAllowRotationDefaultValue();
         if (mForcedRotation == isForcedRotation) {
             return;
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
new file mode 100644
index 0000000..478141a
--- /dev/null
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -0,0 +1,349 @@
+/*
+ * 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.views;
+
+import static com.android.launcher3.Utilities.mapToRange;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InsettableFrameLayout.LayoutParams;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
+import com.android.launcher3.graphics.IconShape;
+
+/**
+ * A view used to draw both layers of an {@link AdaptiveIconDrawable}.
+ * Supports springing just the foreground layer.
+ * Supports clipping the icon to/from its icon shape.
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public class ClipIconView extends View implements ClipPathView {
+
+    private static final Rect sTmpRect = new Rect();
+
+    // We spring the foreground drawable relative to the icon's movement in the DragLayer.
+    // We then use these two factor values to scale the movement of the fg within this view.
+    private static final int FG_TRANS_X_FACTOR = 60;
+    private static final int FG_TRANS_Y_FACTOR = 75;
+
+    private static final FloatPropertyCompat<ClipIconView> mFgTransYProperty =
+            new FloatPropertyCompat<ClipIconView>("ClipIconViewFgTransY") {
+                @Override
+                public float getValue(ClipIconView view) {
+                    return view.mFgTransY;
+                }
+
+                @Override
+                public void setValue(ClipIconView view, float transY) {
+                    view.mFgTransY = transY;
+                    view.invalidate();
+                }
+            };
+
+    private static final FloatPropertyCompat<ClipIconView> mFgTransXProperty =
+            new FloatPropertyCompat<ClipIconView>("ClipIconViewFgTransX") {
+                @Override
+                public float getValue(ClipIconView view) {
+                    return view.mFgTransX;
+                }
+
+                @Override
+                public void setValue(ClipIconView view, float transX) {
+                    view.mFgTransX = transX;
+                    view.invalidate();
+                }
+            };
+
+    private final Launcher mLauncher;
+    private final int mBlurSizeOutline;
+    private final boolean mIsRtl;
+
+    private @Nullable Drawable mForeground;
+    private @Nullable Drawable mBackground;
+
+    private boolean mIsVerticalBarLayout = false;
+    private boolean mIsAdaptiveIcon = false;
+
+    private ValueAnimator mRevealAnimator;
+
+    private final Rect mStartRevealRect = new Rect();
+    private final Rect mEndRevealRect = new Rect();
+    private Path mClipPath;
+    private float mTaskCornerRadius;
+
+    private final Rect mOutline = new Rect();
+    private final Rect mFinalDrawableBounds = new Rect();
+
+    private final SpringAnimation mFgSpringY;
+    private float mFgTransY;
+    private final SpringAnimation mFgSpringX;
+    private float mFgTransX;
+
+    public ClipIconView(Context context) {
+        this(context, null);
+    }
+
+    public ClipIconView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ClipIconView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+        mBlurSizeOutline = getResources().getDimensionPixelSize(
+                R.dimen.blur_size_medium_outline);
+        mIsRtl = Utilities.isRtl(getResources());
+
+        mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
+                .setSpring(new SpringForce()
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                        .setStiffness(SpringForce.STIFFNESS_LOW));
+        mFgSpringY = new SpringAnimation(this, mFgTransYProperty)
+                .setSpring(new SpringForce()
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                        .setStiffness(SpringForce.STIFFNESS_LOW));
+    }
+
+    void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
+            boolean isOpening, float scale, float minSize, LayoutParams parentLp) {
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        float dX = mIsRtl
+                ? rect.left - (dp.widthPx - parentLp.getMarginStart() - parentLp.width)
+                : rect.left - parentLp.getMarginStart();
+        float dY = rect.top - parentLp.topMargin;
+
+        // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION
+        float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f;
+        float shapeRevealProgress = Utilities.boundToRange(mapToRange(
+                Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
+                LINEAR), 0, 1);
+
+        if (mIsVerticalBarLayout) {
+            mOutline.right = (int) (rect.width() / scale);
+        } else {
+            mOutline.bottom = (int) (rect.height() / scale);
+        }
+
+        mTaskCornerRadius = cornerRadius / scale;
+        if (mIsAdaptiveIcon) {
+            if (!isOpening && progress >= shapeProgressStart) {
+                if (mRevealAnimator == null) {
+                    mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator(
+                            this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening);
+                    mRevealAnimator.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mRevealAnimator = null;
+                        }
+                    });
+                    mRevealAnimator.start();
+                    // We pause here so we can set the current fraction ourselves.
+                    mRevealAnimator.pause();
+                }
+                mRevealAnimator.setCurrentFraction(shapeRevealProgress);
+            }
+
+            float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height())
+                    / minSize;
+            setBackgroundDrawableBounds(drawableScale);
+            if (isOpening) {
+                // Center align foreground
+                int height = mFinalDrawableBounds.height();
+                int width = mFinalDrawableBounds.width();
+                int diffY = mIsVerticalBarLayout ? 0
+                        : (int) (((height * drawableScale) - height) / 2);
+                int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2)
+                        : 0;
+                sTmpRect.set(mFinalDrawableBounds);
+                sTmpRect.offset(diffX, diffY);
+                mForeground.setBounds(sTmpRect);
+            } else {
+                // Spring the foreground relative to the icon's movement within the DragLayer.
+                int diffX = (int) (dX / dp.availableWidthPx * FG_TRANS_X_FACTOR);
+                int diffY = (int) (dY / dp.availableHeightPx * FG_TRANS_Y_FACTOR);
+
+                mFgSpringX.animateToFinalPosition(diffX);
+                mFgSpringY.animateToFinalPosition(diffY);
+            }
+        }
+        invalidate();
+        invalidateOutline();
+    }
+
+    private void setBackgroundDrawableBounds(float scale) {
+        sTmpRect.set(mFinalDrawableBounds);
+        Utilities.scaleRectAboutCenter(sTmpRect, scale);
+        // Since the drawable is at the top of the view, we need to offset to keep it centered.
+        if (mIsVerticalBarLayout) {
+            sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top);
+        } else {
+            sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale));
+        }
+        mBackground.setBounds(sTmpRect);
+    }
+
+    protected void endReveal() {
+        if (mRevealAnimator != null) {
+            mRevealAnimator.end();
+        }
+    }
+
+    void setIcon(@Nullable Drawable drawable, int iconOffset, LayoutParams lp, boolean isOpening) {
+        mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable;
+        if (mIsAdaptiveIcon) {
+            boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon;
+
+            AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable;
+            Drawable background = adaptiveIcon.getBackground();
+            if (background == null) {
+                background = new ColorDrawable(Color.TRANSPARENT);
+            }
+            mBackground = background;
+            Drawable foreground = adaptiveIcon.getForeground();
+            if (foreground == null) {
+                foreground = new ColorDrawable(Color.TRANSPARENT);
+            }
+            mForeground = foreground;
+
+            final int originalHeight = lp.height;
+            final int originalWidth = lp.width;
+
+            int blurMargin = mBlurSizeOutline / 2;
+            mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight);
+
+            if (!isFolderIcon) {
+                mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin);
+            }
+            mForeground.setBounds(mFinalDrawableBounds);
+            mBackground.setBounds(mFinalDrawableBounds);
+
+            mStartRevealRect.set(0, 0, originalWidth, originalHeight);
+
+            if (!isFolderIcon) {
+                Utilities.scaleRectAboutCenter(mStartRevealRect, IconShape.getNormalizationScale());
+            }
+
+            float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
+            if (mIsVerticalBarLayout) {
+                lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
+            } else {
+                lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
+            }
+
+            int left = mIsRtl
+                    ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+                    : lp.leftMargin;
+            layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
+
+            float scale = Math.max((float) lp.height / originalHeight,
+                    (float) lp.width / originalWidth);
+            float bgDrawableStartScale;
+            if (isOpening) {
+                bgDrawableStartScale = 1f;
+                mOutline.set(0, 0, originalWidth, originalHeight);
+            } else {
+                bgDrawableStartScale = scale;
+                mOutline.set(0, 0, lp.width, lp.height);
+            }
+            setBackgroundDrawableBounds(bgDrawableStartScale);
+            mEndRevealRect.set(0, 0, lp.width, lp.height);
+            setOutlineProvider(new ViewOutlineProvider() {
+                @Override
+                public void getOutline(View view, Outline outline) {
+                    outline.setRoundRect(mOutline, mTaskCornerRadius);
+                }
+            });
+            setClipToOutline(true);
+        } else {
+            setBackground(drawable);
+            setClipToOutline(false);
+        }
+
+        invalidate();
+        invalidateOutline();
+    }
+
+    @Override
+    public void setClipPath(Path clipPath) {
+        mClipPath = clipPath;
+        invalidate();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int count = canvas.save();
+        if (mClipPath != null) {
+            canvas.clipPath(mClipPath);
+        }
+        super.draw(canvas);
+        if (mBackground != null) {
+            mBackground.draw(canvas);
+        }
+        if (mForeground != null) {
+            int count2 = canvas.save();
+            canvas.translate(mFgTransX, mFgTransY);
+            mForeground.draw(canvas);
+            canvas.restoreToCount(count2);
+        }
+        canvas.restoreToCount(count);
+    }
+
+    void recycle() {
+        setBackground(null);
+        mIsAdaptiveIcon = false;
+        mForeground = null;
+        mBackground = null;
+        mClipPath = null;
+        mFinalDrawableBounds.setEmpty();
+        if (mRevealAnimator != null) {
+            mRevealAnimator.cancel();
+        }
+        mRevealAnimator = null;
+        mTaskCornerRadius = 0;
+        mOutline.setEmpty();
+        mFgTransY = 0;
+        mFgSpringX.cancel();
+        mFgTransX = 0;
+        mFgSpringY.cancel();
+    }
+}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index fa625ed..3e2560f 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -18,8 +18,6 @@
 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.Utilities.getBadge;
 import static com.android.launcher3.Utilities.getFullDrawable;
-import static com.android.launcher3.Utilities.mapToRange;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -28,17 +26,12 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Outline;
-import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.CancellationSignal;
@@ -46,19 +39,17 @@
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.InsettableFrameLayout.LayoutParams;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
@@ -66,8 +57,6 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.ShiftedBitmapDrawable;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -76,8 +65,8 @@
  * A view that is created to look like another view with the purpose of creating fluid animations.
  */
 @TargetApi(Build.VERSION_CODES.Q)
-public class FloatingIconView extends View implements
-        Animator.AnimatorListener, ClipPathView, OnGlobalLayoutListener {
+public class FloatingIconView extends FrameLayout implements
+        Animator.AnimatorListener, OnGlobalLayoutListener {
 
     private static final String TAG = FloatingIconView.class.getSimpleName();
 
@@ -86,81 +75,34 @@
 
     public static final float SHAPE_PROGRESS_DURATION = 0.10f;
     private static final int FADE_DURATION_MS = 200;
-    private static final Rect sTmpRect = new Rect();
     private static final RectF sTmpRectF = new RectF();
     private static final Object[] sTmpObjArray = new Object[1];
 
-    // We spring the foreground drawable relative to the icon's movement in the DragLayer.
-    // We then use these two factor values to scale the movement of the fg within this view.
-    private static final int FG_TRANS_X_FACTOR = 60;
-    private static final int FG_TRANS_Y_FACTOR = 75;
-
-    private static final FloatPropertyCompat<FloatingIconView> mFgTransYProperty
-            = new FloatPropertyCompat<FloatingIconView>("FloatingViewFgTransY") {
-        @Override
-        public float getValue(FloatingIconView view) {
-            return view.mFgTransY;
-        }
-
-        @Override
-        public void setValue(FloatingIconView view, float transY) {
-            view.mFgTransY = transY;
-            view.invalidate();
-        }
-    };
-
-    private static final FloatPropertyCompat<FloatingIconView> mFgTransXProperty
-            = new FloatPropertyCompat<FloatingIconView>("FloatingViewFgTransX") {
-        @Override
-        public float getValue(FloatingIconView view) {
-            return view.mFgTransX;
-        }
-
-        @Override
-        public void setValue(FloatingIconView view, float transX) {
-            view.mFgTransX = transX;
-            view.invalidate();
-        }
-    };
-
     private Runnable mEndRunnable;
     private CancellationSignal mLoadIconSignal;
 
     private final Launcher mLauncher;
-    private final int mBlurSizeOutline;
     private final boolean mIsRtl;
 
     private boolean mIsVerticalBarLayout = false;
-    private boolean mIsAdaptiveIcon = false;
     private boolean mIsOpening;
 
     private IconLoadResult mIconLoadResult;
 
+    private ClipIconView mClipIconView;
     private @Nullable Drawable mBadge;
-    private @Nullable Drawable mForeground;
-    private @Nullable Drawable mBackground;
+
     private float mRotation;
-    private ValueAnimator mRevealAnimator;
-    private final Rect mStartRevealRect = new Rect();
-    private final Rect mEndRevealRect = new Rect();
-    private Path mClipPath;
-    private float mTaskCornerRadius;
 
     private View mOriginalIcon;
     private RectF mPositionOut;
     private Runnable mOnTargetChangeRunnable;
 
-    private final Rect mOutline = new Rect();
     private final Rect mFinalDrawableBounds = new Rect();
 
     private AnimatorSet mFadeAnimatorSet;
     private ListenerView mListenerView;
 
-    private final SpringAnimation mFgSpringY;
-    private float mFgTransY;
-    private final SpringAnimation mFgSpringX;
-    private float mFgTransX;
-
     public FloatingIconView(Context context) {
         this(context, null);
     }
@@ -172,19 +114,11 @@
     public FloatingIconView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mLauncher = Launcher.getLauncher(context);
-        mBlurSizeOutline = getResources().getDimensionPixelSize(
-                R.dimen.blur_size_medium_outline);
         mIsRtl = Utilities.isRtl(getResources());
         mListenerView = new ListenerView(context, attrs);
-
-        mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
-                .setSpring(new SpringForce()
-                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
-                .setStiffness(SpringForce.STIFFNESS_LOW));
-        mFgSpringY = new SpringAnimation(this, mFgTransYProperty)
-                .setSpring(new SpringForce()
-                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
-                        .setStiffness(SpringForce.STIFFNESS_LOW));
+        mClipIconView = new ClipIconView(context, attrs);
+        addView(mClipIconView);
+        setWillNotDraw(false);
     }
 
     @Override
@@ -213,10 +147,12 @@
             float cornerRadius, boolean isOpening) {
         setAlpha(alpha);
 
-        LayoutParams lp = (LayoutParams) getLayoutParams();
+        InsettableFrameLayout.LayoutParams lp =
+                (InsettableFrameLayout.LayoutParams) getLayoutParams();
+
+        DeviceProfile dp = mLauncher.getDeviceProfile();
         float dX = mIsRtl
-                ? rect.left
-                - (mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width)
+                ? rect.left - (dp.widthPx - lp.getMarginStart() - lp.width)
                 : rect.left - lp.getMarginStart();
         float dY = rect.top - lp.topMargin;
         setTranslationX(dX);
@@ -227,69 +163,15 @@
         float scaleY = rect.height() / minSize;
         float scale = Math.max(1f, Math.min(scaleX, scaleY));
 
+        mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale,
+                minSize, lp);
+
         setPivotX(0);
         setPivotY(0);
         setScaleX(scale);
         setScaleY(scale);
 
-        // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION
-        float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f;
-        float shapeRevealProgress = Utilities.boundToRange(mapToRange(
-                Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
-                LINEAR), 0, 1);
-
-        if (mIsVerticalBarLayout) {
-            mOutline.right = (int) (rect.width() / scale);
-        } else {
-            mOutline.bottom = (int) (rect.height() / scale);
-        }
-
-        mTaskCornerRadius = cornerRadius / scale;
-        if (mIsAdaptiveIcon) {
-            if (!isOpening && progress >= shapeProgressStart) {
-                if (mRevealAnimator == null) {
-                    mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator(
-                            this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening);
-                    mRevealAnimator.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mRevealAnimator = null;
-                        }
-                    });
-                    mRevealAnimator.start();
-                    // We pause here so we can set the current fraction ourselves.
-                    mRevealAnimator.pause();
-                }
-                mRevealAnimator.setCurrentFraction(shapeRevealProgress);
-            }
-
-            float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height())
-                    / minSize;
-            setBackgroundDrawableBounds(drawableScale);
-            if (isOpening) {
-                // Center align foreground
-                int height = mFinalDrawableBounds.height();
-                int width = mFinalDrawableBounds.width();
-                int diffY = mIsVerticalBarLayout ? 0
-                        : (int) (((height * drawableScale) - height) / 2);
-                int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2)
-                        : 0;
-                sTmpRect.set(mFinalDrawableBounds);
-                sTmpRect.offset(diffX, diffY);
-                mForeground.setBounds(sTmpRect);
-            } else {
-                // Spring the foreground relative to the icon's movement within the DragLayer.
-                int diffX = (int) (dX / mLauncher.getDeviceProfile().availableWidthPx
-                        * FG_TRANS_X_FACTOR);
-                int diffY = (int) (dY / mLauncher.getDeviceProfile().availableHeightPx
-                        * FG_TRANS_Y_FACTOR);
-
-                mFgSpringX.animateToFinalPosition(diffX);
-                mFgSpringY.animateToFinalPosition(diffY);
-            }
-        }
         invalidate();
-        invalidateOutline();
     }
 
     @Override
@@ -301,9 +183,7 @@
             mEndRunnable.run();
         } else {
             // End runnable also ends the reveal animator, so we manually handle it here.
-            if (mRevealAnimator != null) {
-                mRevealAnimator.end();
-            }
+            mClipIconView.endReveal();
         }
     }
 
@@ -315,23 +195,25 @@
      */
     private void matchPositionOf(Launcher launcher, View v, boolean isOpening, RectF positionOut) {
         float rotation = getLocationBoundsForView(launcher, v, isOpening, positionOut);
-        final LayoutParams lp = new LayoutParams(
+        final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
                 Math.round(positionOut.width()),
                 Math.round(positionOut.height()));
         updatePosition(rotation, positionOut, lp);
         setLayoutParams(lp);
+
+        mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
     }
 
-    private void updatePosition(float rotation, RectF position, LayoutParams lp) {
+    private void updatePosition(float rotation, RectF pos, InsettableFrameLayout.LayoutParams lp) {
         mRotation = rotation;
-        mPositionOut.set(position);
+        mPositionOut.set(pos);
         lp.ignoreInsets = true;
         // Position the floating view exactly on top of the original
-        lp.topMargin = Math.round(position.top);
+        lp.topMargin = Math.round(pos.top);
         if (mIsRtl) {
-            lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - position.right));
+            lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right));
         } else {
-            lp.setMarginStart(Math.round(position.left));
+            lp.setMarginStart(Math.round(pos.left));
         }
         // Set the properties here already to make sure they are available when running the first
         // animation frame.
@@ -412,9 +294,8 @@
                 drawable = originalView.getBackground();
             }
         } else {
-            boolean isFolderIcon = originalView instanceof FolderIcon;
-            int width = isFolderIcon ? originalView.getWidth() : (int) pos.width();
-            int height = isFolderIcon ? originalView.getHeight() : (int) pos.height();
+            int width = (int) pos.width();
+            int height = (int) pos.height();
             if (supportsAdaptiveIcons) {
                 drawable = getFullDrawable(l, info, width, height, sTmpObjArray);
                 if (drawable instanceof AdaptiveIconDrawable) {
@@ -451,110 +332,42 @@
     /**
      * Sets the drawables of the {@param originalView} onto this view.
      *
-     * @param originalView The View that the FloatingIconView will replace.
      * @param drawable The drawable of the original view.
      * @param badge The badge of the original view.
      * @param iconOffset The amount of offset needed to match this view with the original view.
      */
     @UiThread
-    private void setIcon(View originalView, @Nullable Drawable drawable, @Nullable Drawable badge,
-            int iconOffset) {
+    private void setIcon(@Nullable Drawable drawable, @Nullable Drawable badge, int iconOffset) {
+        final InsettableFrameLayout.LayoutParams lp =
+                (InsettableFrameLayout.LayoutParams) getLayoutParams();
         mBadge = badge;
-
-        mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable;
-        if (mIsAdaptiveIcon) {
-            boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon;
-
-            AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable;
-            Drawable background = adaptiveIcon.getBackground();
-            if (background == null) {
-                background = new ColorDrawable(Color.TRANSPARENT);
-            }
-            mBackground = background;
-            Drawable foreground = adaptiveIcon.getForeground();
-            if (foreground == null) {
-                foreground = new ColorDrawable(Color.TRANSPARENT);
-            }
-            mForeground = foreground;
-
-            final LayoutParams lp = (LayoutParams) getLayoutParams();
+        mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening);
+        if (drawable instanceof AdaptiveIconDrawable) {
             final int originalHeight = lp.height;
             final int originalWidth = lp.width;
 
-            int blurMargin = mBlurSizeOutline / 2;
             mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight);
 
-            if (!isFolderIcon) {
-                mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin);
-            }
-            mForeground.setBounds(mFinalDrawableBounds);
-            mBackground.setBounds(mFinalDrawableBounds);
-
-            mStartRevealRect.set(0, 0, originalWidth, originalHeight);
-
-            if (mBadge != null) {
-                mBadge.setBounds(mStartRevealRect);
-                if (!mIsOpening && !isFolderIcon) {
-                    DRAWABLE_ALPHA.set(mBadge, 0);
-                }
-            }
-
-            if (isFolderIcon) {
-                ((FolderIcon) originalView).getPreviewBounds(sTmpRect);
-                float bgStroke = ((FolderIcon) originalView).getBackgroundStrokeWidth();
-                if (mForeground instanceof ShiftedBitmapDrawable) {
-                    ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground;
-                    sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke);
-                    sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke);
-                }
-                if (mBadge instanceof ShiftedBitmapDrawable) {
-                    ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mBadge;
-                    sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke);
-                    sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke);
-                }
-            } else {
-                Utilities.scaleRectAboutCenter(mStartRevealRect,
-                        IconShape.getNormalizationScale());
-            }
-
             float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
             if (mIsVerticalBarLayout) {
                 lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
             } else {
                 lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
             }
+            setLayoutParams(lp);
 
-            int left = mIsRtl
-                    ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
-                    : lp.leftMargin;
-            layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
+            final LayoutParams clipViewLp = (LayoutParams) mClipIconView.getLayoutParams();
+            final int clipViewOgHeight = clipViewLp.height;
+            final int clipViewOgWidth = clipViewLp.width;
+            clipViewLp.width = lp.width;
+            clipViewLp.height = lp.height;
+            mClipIconView.setLayoutParams(clipViewLp);
 
-            float scale = Math.max((float) lp.height / originalHeight,
-                    (float) lp.width / originalWidth);
-            float bgDrawableStartScale;
-            if (mIsOpening) {
-                bgDrawableStartScale = 1f;
-                mOutline.set(0, 0, originalWidth, originalHeight);
-            } else {
-                bgDrawableStartScale = scale;
-                mOutline.set(0, 0, lp.width, lp.height);
+            if (mBadge != null) {
+                mBadge.setBounds(0, 0, clipViewOgWidth, clipViewOgHeight);
             }
-            setBackgroundDrawableBounds(bgDrawableStartScale);
-            mEndRevealRect.set(0, 0, lp.width, lp.height);
-            setOutlineProvider(new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    outline.setRoundRect(mOutline, mTaskCornerRadius);
-                }
-            });
-            setClipToOutline(true);
-        } else {
-            setBackground(drawable);
-            setClipToOutline(false);
         }
-
         invalidate();
-        invalidateOutline();
     }
 
     /**
@@ -571,7 +384,7 @@
 
         synchronized (mIconLoadResult) {
             if (mIconLoadResult.isIconLoaded) {
-                setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
+                setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
                         mIconLoadResult.iconOffset);
                 hideOriginalView(originalView);
             } else {
@@ -580,7 +393,7 @@
                         return;
                     }
 
-                    setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
+                    setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
                             mIconLoadResult.iconOffset);
 
                     setVisibility(VISIBLE);
@@ -600,23 +413,12 @@
         }
     }
 
-    private void setBackgroundDrawableBounds(float scale) {
-        sTmpRect.set(mFinalDrawableBounds);
-        Utilities.scaleRectAboutCenter(sTmpRect, scale);
-        // Since the drawable is at the top of the view, we need to offset to keep it centered.
-        if (mIsVerticalBarLayout) {
-            sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top);
-        } else {
-            sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale));
-        }
-        mBackground.setBounds(sTmpRect);
-    }
-
     @WorkerThread
     @SuppressWarnings("WrongThread")
     private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
-                !(drawable instanceof AdaptiveIconDrawable)) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O
+                || !(drawable instanceof AdaptiveIconDrawable)
+                || (drawable instanceof FolderAdaptiveIcon)) {
             return 0;
         }
         int blurSizeOutline =
@@ -640,29 +442,11 @@
     }
 
     @Override
-    public void setClipPath(Path clipPath) {
-        mClipPath = clipPath;
-        invalidate();
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
+    protected void dispatchDraw(Canvas canvas) {
         int count = canvas.save();
         canvas.rotate(mRotation,
                 mFinalDrawableBounds.exactCenterX(), mFinalDrawableBounds.exactCenterY());
-        if (mClipPath != null) {
-            canvas.clipPath(mClipPath);
-        }
-        super.draw(canvas);
-        if (mBackground != null) {
-            mBackground.draw(canvas);
-        }
-        if (mForeground != null) {
-            int count2 = canvas.save();
-            canvas.translate(mFgTransX, mFgTransY);
-            mForeground.draw(canvas);
-            canvas.restoreToCount(count2);
-        }
+        super.dispatchDraw(canvas);
         if (mBadge != null) {
             mBadge.draw(canvas);
         }
@@ -706,7 +490,8 @@
             float rotation = getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening,
                     sTmpRectF);
             if (rotation != mRotation || !sTmpRectF.equals(mPositionOut)) {
-                updatePosition(rotation, sTmpRectF, (LayoutParams) getLayoutParams());
+                updatePosition(rotation, sTmpRectF,
+                        (InsettableFrameLayout.LayoutParams) getLayoutParams());
                 if (mOnTargetChangeRunnable != null) {
                     mOnTargetChangeRunnable.run();
                 }
@@ -822,12 +607,6 @@
             }
         });
 
-        if (mBadge != null) {
-            ObjectAnimator badgeFade = ObjectAnimator.ofInt(mBadge, DRAWABLE_ALPHA, 255);
-            badgeFade.addUpdateListener(valueAnimator -> invalidate());
-            fade.play(badgeFade);
-        }
-
         if (originalView instanceof IconLabelDotView) {
             IconLabelDotView view = (IconLabelDotView) originalView;
             fade.addListener(new AnimatorListenerAdapter() {
@@ -869,21 +648,12 @@
         setScaleX(1);
         setScaleY(1);
         setAlpha(1);
-        setBackground(null);
         if (mLoadIconSignal != null) {
             mLoadIconSignal.cancel();
         }
         mLoadIconSignal = null;
         mEndRunnable = null;
-        mIsAdaptiveIcon = false;
-        mForeground = null;
-        mBackground = null;
-        mClipPath = null;
         mFinalDrawableBounds.setEmpty();
-        if (mRevealAnimator != null) {
-            mRevealAnimator.cancel();
-        }
-        mRevealAnimator = null;
         if (mFadeAnimatorSet != null) {
             mFadeAnimatorSet.cancel();
         }
@@ -892,15 +662,10 @@
         mListenerView.setListener(null);
         mOriginalIcon = null;
         mOnTargetChangeRunnable = null;
-        mTaskCornerRadius = 0;
-        mOutline.setEmpty();
-        mFgTransY = 0;
-        mFgSpringX.cancel();
-        mFgTransX = 0;
-        mFgSpringY.cancel();
         mBadge = null;
         sTmpObjArray[0] = null;
         mIconLoadResult = null;
+        mClipIconView.recycle();
     }
 
     private static class IconLoadResult {
@@ -911,7 +676,7 @@
         Runnable onIconLoaded;
         boolean isIconLoaded;
 
-        public IconLoadResult(ItemInfo itemInfo) {
+        IconLoadResult(ItemInfo itemInfo) {
             this.itemInfo = itemInfo;
         }
     }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index f3fd7ca..c1310e3 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.SparseBooleanArray;
@@ -44,6 +45,7 @@
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
@@ -52,7 +54,7 @@
  * {@inheritDoc}
  */
 public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
-        implements TouchCompleteListener, View.OnLongClickListener {
+        implements TouchCompleteListener, View.OnLongClickListener, DraggableView {
 
     // Related to the auto-advancing of widgets
     private static final long ADVANCE_INTERVAL = 20000;
@@ -412,4 +414,18 @@
         }
         return false;
     }
+
+    @Override
+    public int getViewType() {
+        return DRAGGABLE_WIDGET;
+    }
+
+    @Override
+    public void getVisualDragBounds(Rect bounds) {
+        int x = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2;
+        int y = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2;
+        int width = (int) getScaleToFit() * getMeasuredWidth();
+        int height = (int) getScaleToFit() * getMeasuredHeight();
+        bounds.set(x, y , x + width, y + height);
+    }
 }
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 68b1595..104ad77 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -24,12 +24,15 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 
+import com.android.launcher3.dragndrop.DraggableView;
+
 import java.util.ArrayList;
 
 /**
  * Extension of AppWidgetHostView with support for controlled keyboard navigation.
  */
-public abstract class NavigableAppWidgetHostView extends AppWidgetHostView {
+public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
+        implements DraggableView {
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mChildrenFocused;
@@ -133,4 +136,14 @@
         // The host view's background changes when selected, to indicate the focus is inside.
         setSelected(childIsFocused);
     }
+
+    @Override
+    public int getViewType() {
+        return DRAGGABLE_WIDGET;
+    }
+
+    @Override
+    public void getVisualDragBounds(Rect bounds) {
+        bounds.set(0, 0 , getMeasuredWidth(), getMeasuredHeight());
+    }
 }
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 662e627..3c11274 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.icons.LauncherIcons;
@@ -79,6 +80,8 @@
 
         mEstimatedCellSize = launcher.getWorkspace().estimateItemSize(mAddInfo);
 
+        DraggableView draggableView;
+
         if (mAddInfo instanceof PendingAddWidgetInfo) {
             PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) mAddInfo;
 
@@ -110,6 +113,7 @@
 
             dragOffset = null;
             dragRegion = null;
+            draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_WIDGET);
         } else {
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
             Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
@@ -136,6 +140,7 @@
             dragRegion.top = (mEstimatedCellSize[1]
                     - iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2;
             dragRegion.bottom = dragRegion.top + iconSize;
+            draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
         }
 
         // Since we are not going through the workspace for starting the drag, set drag related
@@ -148,8 +153,8 @@
                 + (int) ((scale * preview.getHeight() - preview.getHeight()) / 2);
 
         // Start the drag
-        launcher.getDragController().startDrag(preview, dragLayerX, dragLayerY, source, mAddInfo,
-                dragOffset, dragRegion, scale, scale, options);
+        launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
+                source, mAddInfo, dragOffset, dragRegion, scale, scale, options);
     }
 
     @Override
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index f5dd995..7cd656e 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -18,7 +18,6 @@
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-import static com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
@@ -57,6 +56,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.testing.TestProtocol;
@@ -220,13 +220,19 @@
 
     @Before
     public void setUp() throws Exception {
+        Log.d(TAG, "Before disabling battery defender");
+        mDevice.executeShellCommand("setprop vendor.battery.defender.disable 1");
+        Log.d(TAG, "Before enabling stay awake");
         mDevice.executeShellCommand("settings put global stay_on_while_plugged_in 3");
-        if (hasSystemUiObject("keyguard_status_view")) {
+        for (int i = 0; i < 10 && hasSystemUiObject("keyguard_status_view"); ++i) {
             Log.d(TAG, "Before unlocking the phone");
             mDevice.executeShellCommand("input keyevent 82");
-        } else {
-            Log.d(TAG, "Phone isn't locked");
+            mDevice.waitForIdle();
         }
+        Assert.assertTrue("Keyguard still visible",
+                mDevice.wait(
+                        Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000));
+        Log.d(TAG, "Keyguard is not visible");
 
         final String launcherPackageName = mDevice.getLauncherPackageName();
         try {
@@ -288,7 +294,8 @@
     protected void resetLoaderState() {
         try {
             mMainThreadExecutor.execute(
-                    () -> LauncherAppState.getInstance(mTargetContext).getModel().forceReload());
+                    () -> LauncherAppState.getInstance(
+                            mTargetContext).getModel().forceReload());
         } catch (Throwable t) {
             throw new IllegalArgumentException(t);
         }
@@ -302,7 +309,8 @@
         ContentResolver resolver = mTargetContext.getContentResolver();
         int screenId = FIRST_SCREEN_ID;
         // Update the screen id counter for the provider.
-        LauncherSettings.Settings.call(resolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
+        LauncherSettings.Settings.call(resolver,
+                LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
 
         if (screenId > FIRST_SCREEN_ID) {
             screenId = FIRST_SCREEN_ID;
@@ -316,7 +324,8 @@
         item.screenId = screenId;
         item.onAddToDatabase(writer);
         writer.put(LauncherSettings.Favorites._ID, item.id);
-        resolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext));
+        resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
+                writer.getValues(mTargetContext));
         resetLoaderState();
 
         // Launch the home activity
@@ -347,7 +356,8 @@
         });
     }
 
-    // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting
+    // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
+    // expecting
     // the results of that gesture because the wait can hide flakeness.
     protected void waitForState(String message, Supplier<LauncherState> state) {
         waitForLauncherCondition(message,
@@ -360,7 +370,8 @@
 
     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
     // flakiness.
-    protected void waitForLauncherCondition(String message, Function<Launcher, Boolean> condition) {
+    protected void waitForLauncherCondition(String
+            message, Function<Launcher, Boolean> condition) {
         waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT);
     }
 
@@ -436,7 +447,8 @@
 
         public Intent blockingGetExtraIntent() throws InterruptedException {
             Intent intent = blockingGetIntent();
-            return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
+            return intent == null ? null : (Intent) intent.getParcelableExtra(
+                    Intent.EXTRA_INTENT);
         }
     }
 
@@ -463,7 +475,8 @@
         if (newTask) {
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         } else {
-            intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+            intent.addFlags(
+                    Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
         }
         getInstrumentation().getTargetContext().startActivity(intent);
         assertTrue("App didn't start: " + selector,
@@ -500,7 +513,8 @@
 
     protected boolean isInState(Supplier<LauncherState> state) {
         if (!TestHelpers.isInLauncherProcess()) return true;
-        return getFromLauncher(launcher -> launcher.getStateManager().getState() == state.get());
+        return getFromLauncher(
+                launcher -> launcher.getStateManager().getState() == state.get());
     }
 
     protected int getAllAppsScroll(Launcher launcher) {
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 7e80e5d..8d571ff 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.ui;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -158,9 +159,11 @@
 
         // dismiss personal edu
         mDevice.pressHome();
+        waitForState("Launcher did not go home", () -> NORMAL);
 
         // open work tab
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+        waitForState("Launcher did not switch to all apps", () -> ALL_APPS);
         executeOnLauncher(launcher -> {
             AllAppsPagedView pagedView = (AllAppsPagedView) launcher.getAppsView().getContentView();
             pagedView.setCurrentPage(WORK_PAGE);