Removing Launcher dependency from Folders

This allows opening/closing folders without a Launcher context

Bug: 187353581
Test: Manual
Change-Id: Id73a40445a23004eb554f0422d286aa0ff6b3c41
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index f64ce5c..9778b61 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -132,6 +132,7 @@
 
     private final ViewCache mViewCache = new ViewCache();
 
+    @Override
     public ViewCache getViewCache() {
         return mViewCache;
     }
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 4312939..20c6938 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3;
 
+import static com.android.launcher3.util.UiThreadHelper.hideKeyboardAsync;
+
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -25,7 +27,7 @@
 import android.widget.EditText;
 
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.views.ActivityContext;
 
 
 /**
@@ -99,7 +101,7 @@
     }
 
     public void hideKeyboard() {
-        UiThreadHelper.hideKeyboardAsync(Launcher.getLauncher(getContext()), getWindowToken());
+        hideKeyboardAsync(ActivityContext.lookupContext(getContext()), getWindowToken());
     }
 
     private boolean showSoftInput() {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 92d1b11..ece0eb4 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -124,6 +124,7 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.LauncherDragController;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.BitmapRenderer;
@@ -428,7 +429,7 @@
         mIconCache = app.getIconCache();
         mAccessibilityDelegate = createAccessibilityDelegate();
 
-        mDragController = new DragController(this);
+        mDragController = new LauncherDragController(this);
         mAllAppsController = new AllAppsTransitionController(this);
         mStateManager = new StateManager<>(this, NORMAL);
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index fb21698..52a6cef 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -59,7 +59,7 @@
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
-import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * An abstraction of the original Workspace which supports browsing through a
@@ -295,18 +295,16 @@
     }
 
     /**
-     * Returns the currently visible pages.
+     * Executes the callback against each visible page
      */
-    public Iterable<View> getVisiblePages() {
+    public void forEachVisiblePage(Consumer<View> callback) {
         int panelCount = getPanelCount();
-        List<View> visiblePages = new ArrayList<>(panelCount);
         for (int i = mCurrentPage; i < mCurrentPage + panelCount; i++) {
             View page = getPageAt(i);
             if (page != null) {
-                visiblePages.add(page);
+                callback.accept(page);
             }
         }
-        return visiblePages;
     }
 
     /**
@@ -1034,7 +1032,7 @@
         // Try canceling the long press. It could also have been scheduled
         // by a distant descendant, so use the mAllowLongPress flag to block
         // everything
-        getVisiblePages().forEach(View::cancelLongPress);
+        forEachVisiblePage(View::cancelLongPress);
     }
 
     protected float getScrollProgress(int screenCenter, View v, int page) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 7c5f99e..f846d14 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -2974,7 +2974,7 @@
 
         List<CellLayout> cellLayouts = new ArrayList<>(getPanelCount() + 1);
         cellLayouts.add(getHotseat());
-        getVisiblePages().forEach(page -> cellLayouts.add((CellLayout) page));
+        forEachVisiblePage(page -> cellLayouts.add((CellLayout) page));
 
         // Order: App icons, app in folder. Items in hotseat get returned first.
         if (ADAPTIVE_ICON_WINDOW_ANIM.get()) {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 575b8fd..5731db4 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -16,45 +16,37 @@
 
 package com.android.launcher3.dragndrop;
 
-import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.Utilities.ATLEAST_Q;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.ComponentName;
-import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.view.DragEvent;
-import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
 import java.util.Optional;
 
 /**
  * Class for initiating a drag within a view or across multiple views.
+ * @param <T>
  */
-public class DragController implements DragDriver.EventListener, TouchController {
-    private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
+public abstract class DragController<T extends ActivityContext>
+        implements DragDriver.EventListener, TouchController {
 
     /**
      * When a drag is started from a deep press, you need to drag this much farther than normal to
@@ -62,8 +54,7 @@
      */
     private static final int DEEP_PRESS_DISTANCE_FACTOR = 3;
 
-    private final Launcher mLauncher;
-    private final FlingToDeleteHelper mFlingToDeleteHelper;
+    protected final T mActivity;
 
     // temporaries to avoid gc thrash
     private final Rect mRectTemp = new Rect();
@@ -73,30 +64,30 @@
      * Drag driver for the current drag/drop operation, or null if there is no active DND operation.
      * It's null during accessible drag operations.
      */
-    private DragDriver mDragDriver = null;
+    protected DragDriver mDragDriver = null;
 
     /** Options controlling the drag behavior. */
-    private DragOptions mOptions;
+    protected DragOptions mOptions;
 
     /** Coordinate for motion down event */
-    private final Point mMotionDown = new Point();
+    protected final Point mMotionDown = new Point();
     /** Coordinate for last touch event **/
-    private final Point mLastTouch = new Point();
+    protected final Point mLastTouch = new Point();
 
     private final Point mTmpPoint = new Point();
 
-    private DropTarget.DragObject mDragObject;
+    protected DropTarget.DragObject mDragObject;
 
     /** Who can receive drop events */
     private final ArrayList<DropTarget> mDropTargets = new ArrayList<>();
     private final ArrayList<DragListener> mListeners = new ArrayList<>();
 
-    private DropTarget mLastDropTarget;
+    protected DropTarget mLastDropTarget;
 
     private int mLastTouchClassification;
-    private int mDistanceSinceScroll = 0;
+    protected int mDistanceSinceScroll = 0;
 
-    private boolean mIsInPreDrag;
+    protected boolean mIsInPreDrag;
 
     /**
      * Interface to receive notifications when a drag starts or stops
@@ -119,9 +110,8 @@
     /**
      * Used to create a new DragLayer from XML.
      */
-    public DragController(Launcher launcher) {
-        mLauncher = launcher;
-        mFlingToDeleteHelper = new FlingToDeleteHelper(launcher);
+    public DragController(T activity) {
+        mActivity = activity;
     }
 
     /**
@@ -198,7 +188,7 @@
                 options);
     }
 
-    private DragView startDrag(
+    protected abstract DragView startDrag(
             @Nullable Drawable drawable,
             @Nullable View view,
             DraggableView originalView,
@@ -210,98 +200,9 @@
             Rect dragRegion,
             float initialDragViewScale,
             float dragViewScaleOnDrop,
-            DragOptions options) {
-        if (PROFILE_DRAWING_DURING_DRAG) {
-            android.os.Debug.startMethodTracing("Launcher");
-        }
+            DragOptions options);
 
-        mLauncher.hideKeyboard();
-        AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_DISCOVERY_BOUNCE);
-
-        mOptions = options;
-        if (mOptions.simulatedDndStartPoint != null) {
-            mLastTouch.x = mMotionDown.x = mOptions.simulatedDndStartPoint.x;
-            mLastTouch.y = mMotionDown.y = mOptions.simulatedDndStartPoint.y;
-        }
-
-        final int registrationX = mMotionDown.x - dragLayerX;
-        final int registrationY = mMotionDown.y - dragLayerY;
-
-        final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
-        final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
-
-        mLastDropTarget = null;
-
-        mDragObject = new DropTarget.DragObject(mLauncher.getApplicationContext());
-        mDragObject.originalView = originalView;
-
-        mIsInPreDrag = mOptions.preDragCondition != null
-                && !mOptions.preDragCondition.shouldStartDrag(0);
-
-        final Resources res = mLauncher.getResources();
-        final float scaleDps = mIsInPreDrag
-                ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
-        final DragView dragView = mDragObject.dragView = drawable != null
-                ? new DragView(
-                    mLauncher,
-                    drawable,
-                    registrationX,
-                    registrationY,
-                    initialDragViewScale,
-                    dragViewScaleOnDrop,
-                    scaleDps)
-                : new DragView(
-                    mLauncher,
-                    view,
-                    view.getMeasuredWidth(),
-                    view.getMeasuredHeight(),
-                    registrationX,
-                    registrationY,
-                    initialDragViewScale,
-                    dragViewScaleOnDrop,
-                    scaleDps);
-        dragView.setItemInfo(dragInfo);
-        mDragObject.dragComplete = false;
-
-        mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
-        mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
-
-        mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent);
-        if (!mOptions.isAccessibleDrag) {
-            mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
-        }
-
-        mDragObject.dragSource = source;
-        mDragObject.dragInfo = dragInfo;
-        mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
-
-        if (dragOffset != null) {
-            dragView.setDragVisualizeOffset(new Point(dragOffset));
-        }
-        if (dragRegion != null) {
-            dragView.setDragRegion(new Rect(dragRegion));
-        }
-
-        mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-        dragView.show(mLastTouch.x, mLastTouch.y);
-        mDistanceSinceScroll = 0;
-
-        if (!mIsInPreDrag) {
-            callOnDragStart();
-        } else if (mOptions.preDragCondition != null) {
-            mOptions.preDragCondition.onPreDragStart(mDragObject);
-        }
-
-        handleMoveEvent(mLastTouch.x, mLastTouch.y);
-
-        if (!mLauncher.isTouchInProgress() && options.simulatedDndStartPoint == null) {
-            // If it is an internal drag and the touch is already complete, cancel immediately
-            MAIN_EXECUTOR.submit(this::cancelDrag);
-        }
-        return dragView;
-    }
-
-    private void callOnDragStart() {
+    protected void callOnDragStart() {
         if (mOptions.preDragCondition != null) {
             mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
         }
@@ -357,13 +258,15 @@
         if (!accepted) {
             // If it was not accepted, cleanup the state. If it was accepted, it is the
             // responsibility of the drop target to cleanup the state.
-            mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+            exitDrag();
             mDragObject.deferDragViewCleanupPostAnimation = false;
         }
 
         mDragObject.dragSource.onDropCompleted(dropTarget, mDragObject, accepted);
     }
 
+    protected abstract void exitDrag();
+
     public void onAppsRemoved(ItemInfoMatcher matcher) {
         // Cancel the current drag if we are removing an app that we are dragging
         if (mDragObject != null) {
@@ -377,7 +280,7 @@
         }
     }
 
-    private void endDrag() {
+    protected void endDrag() {
         if (isDragging()) {
             mDragDriver = null;
             boolean isDeferred = false;
@@ -396,8 +299,6 @@
                 callOnDragEnd();
             }
         }
-
-        mFlingToDeleteHelper.releaseVelocityTracker();
     }
 
     public void animateDragViewToOriginalPosition(final Runnable onComplete,
@@ -443,7 +344,7 @@
      * Clamps the position to the drag layer bounds.
      */
     private Point getClampedDragLayerPos(float x, float y) {
-        mLauncher.getDragLayer().getLocalVisibleRect(mRectTemp);
+        mActivity.getDragLayer().getLocalVisibleRect(mRectTemp);
         mTmpPoint.x = (int) Math.max(mRectTemp.left, Math.min(x, mRectTemp.right - 1));
         mTmpPoint.y = (int) Math.max(mRectTemp.top, Math.min(y, mRectTemp.bottom - 1));
         return mTmpPoint;
@@ -465,19 +366,16 @@
 
     @Override
     public void onDriverDragEnd(float x, float y) {
-        DropTarget dropTarget;
-        Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject, mOptions);
-        if (flingAnimation != null) {
-            dropTarget = mFlingToDeleteHelper.getDropTarget();
-        } else {
-            dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
+        if (!endWithFlingAnimation()) {
+            drop(findDropTarget((int) x, (int) y, mCoordinatesTemp), null);
         }
-
-        drop(dropTarget, flingAnimation);
-
         endDrag();
     }
 
+    protected boolean endWithFlingAnimation() {
+        return false;
+    }
+
     @Override
     public void onDriverDragCancel() {
         cancelDrag();
@@ -520,7 +418,7 @@
         return mDragDriver != null && mDragDriver.onDragEvent(event);
     }
 
-    private void handleMoveEvent(int x, int y) {
+    protected void handleMoveEvent(int x, int y) {
         mDragObject.dragView.move(x, y);
 
         // Drop on someone?
@@ -592,7 +490,7 @@
         endDrag();
     }
 
-    private void drop(DropTarget dropTarget, Runnable flingAnimation) {
+    protected void drop(DropTarget dropTarget, Runnable flingAnimation) {
         final int[] coordinates = mCoordinatesTemp;
         mDragObject.x = coordinates[0];
         mDragObject.y = coordinates[1];
@@ -649,7 +547,7 @@
             if (r.contains(x, y)) {
                 dropCoordinates[0] = x;
                 dropCoordinates[1] = y;
-                mLauncher.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
+                mActivity.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
                 return target;
             }
         }
@@ -657,11 +555,11 @@
         // cell layout to drop to in the existing drag/drop logic.
         dropCoordinates[0] = x;
         dropCoordinates[1] = y;
-        mLauncher.getDragLayer().mapCoordInSelfToDescendant(mLauncher.getWorkspace(),
-                dropCoordinates);
-        return mLauncher.getWorkspace();
+        return getDefaultDropTarget(dropCoordinates);
     }
 
+    protected abstract DropTarget getDefaultDropTarget(int[] dropCoordinates);
+
     /**
      * Sets the drag listener which will be notified when a drag starts or ends.
      */
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
new file mode 100644
index 0000000..a98d70c
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2021 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 static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.model.data.ItemInfo;
+
+/**
+ * Drag controller for Launcher activity
+ */
+public class LauncherDragController extends DragController<Launcher> {
+
+    private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
+
+    private final FlingToDeleteHelper mFlingToDeleteHelper;
+
+    public LauncherDragController(Launcher launcher) {
+        super(launcher);
+        mFlingToDeleteHelper = new FlingToDeleteHelper(launcher);
+    }
+
+    @Override
+    protected DragView startDrag(
+            @Nullable Drawable drawable,
+            @Nullable View view,
+            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) {
+            android.os.Debug.startMethodTracing("Launcher");
+        }
+
+        mActivity.hideKeyboard();
+        AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_DISCOVERY_BOUNCE);
+
+        mOptions = options;
+        if (mOptions.simulatedDndStartPoint != null) {
+            mLastTouch.x = mMotionDown.x = mOptions.simulatedDndStartPoint.x;
+            mLastTouch.y = mMotionDown.y = mOptions.simulatedDndStartPoint.y;
+        }
+
+        final int registrationX = mMotionDown.x - dragLayerX;
+        final int registrationY = mMotionDown.y - dragLayerY;
+
+        final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
+        final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
+
+        mLastDropTarget = null;
+
+        mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext());
+        mDragObject.originalView = originalView;
+
+        mIsInPreDrag = mOptions.preDragCondition != null
+                && !mOptions.preDragCondition.shouldStartDrag(0);
+
+        final Resources res = mActivity.getResources();
+        final float scaleDps = mIsInPreDrag
+                ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
+        final DragView dragView = mDragObject.dragView = drawable != null
+                ? new DragView(
+                mActivity,
+                drawable,
+                registrationX,
+                registrationY,
+                initialDragViewScale,
+                dragViewScaleOnDrop,
+                scaleDps)
+                : new DragView(
+                        mActivity,
+                        view,
+                        view.getMeasuredWidth(),
+                        view.getMeasuredHeight(),
+                        registrationX,
+                        registrationY,
+                        initialDragViewScale,
+                        dragViewScaleOnDrop,
+                        scaleDps);
+        dragView.setItemInfo(dragInfo);
+        mDragObject.dragComplete = false;
+
+        mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
+        mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
+
+        mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent);
+        if (!mOptions.isAccessibleDrag) {
+            mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
+        }
+
+        mDragObject.dragSource = source;
+        mDragObject.dragInfo = dragInfo;
+        mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
+
+        if (dragOffset != null) {
+            dragView.setDragVisualizeOffset(new Point(dragOffset));
+        }
+        if (dragRegion != null) {
+            dragView.setDragRegion(new Rect(dragRegion));
+        }
+
+        mActivity.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        dragView.show(mLastTouch.x, mLastTouch.y);
+        mDistanceSinceScroll = 0;
+
+        if (!mIsInPreDrag) {
+            callOnDragStart();
+        } else if (mOptions.preDragCondition != null) {
+            mOptions.preDragCondition.onPreDragStart(mDragObject);
+        }
+
+        handleMoveEvent(mLastTouch.x, mLastTouch.y);
+
+        if (!mActivity.isTouchInProgress() && options.simulatedDndStartPoint == null) {
+            // If it is an internal drag and the touch is already complete, cancel immediately
+            MAIN_EXECUTOR.submit(this::cancelDrag);
+        }
+        return dragView;
+    }
+
+    @Override
+    protected void exitDrag() {
+        mActivity.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+    }
+
+    @Override
+    protected boolean endWithFlingAnimation() {
+        Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject, mOptions);
+        if (flingAnimation != null) {
+            drop(mFlingToDeleteHelper.getDropTarget(), flingAnimation);
+            return true;
+        }
+        return super.endWithFlingAnimation();
+    }
+
+    @Override
+    protected void endDrag() {
+        super.endDrag();
+        mFlingToDeleteHelper.releaseVelocityTracker();
+    }
+
+    @Override
+    protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
+        mActivity.getDragLayer().mapCoordInSelfToDescendant(mActivity.getWorkspace(),
+                dropCoordinates);
+        return mActivity.getWorkspace();
+    }
+}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 92d891f..22d1b1c 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -23,7 +23,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
 import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
@@ -109,7 +108,6 @@
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -177,8 +175,9 @@
     private boolean mIsAnimatingClosed = false;
 
     // Folder can be displayed in Launcher's activity or a separate window (e.g. Taskbar).
-    // Anything specific to Launcher should use mLauncher, otherwise should use mActivityContext.
-    protected final Launcher mLauncher;
+    // Anything specific to Launcher should use mLauncherDelegate, otherwise should
+    // use mActivityContext.
+    protected final LauncherDelegate mLauncherDelegate;
     protected final ActivityContext mActivityContext;
 
     protected DragController mDragController;
@@ -235,8 +234,6 @@
     // Wallpaper local color extraction
     @Nullable private LocalColorExtractor mColorExtractor;
     @Nullable private LocalColorExtractor.Listener mColorListener;
-    private final Rect mTempRect = new Rect();
-    private final RectF mTempRectF = new RectF();
 
     // For simplicity, we start the color change only after the open animation has started.
     private Runnable mColorChangeRunnable;
@@ -255,8 +252,9 @@
         super(context, attrs);
         setAlwaysDrawnWithCacheEnabled(false);
 
-        mLauncher = Launcher.getLauncher(context);
         mActivityContext = ActivityContext.lookupContext(context);
+        mLauncherDelegate = LauncherDelegate.from(mActivityContext);
+
         mStatsLogManager = StatsLogManager.newInstance(context);
         // We need this view to be focusable in touch mode so that when text editing of the folder
         // name is complete, we have something to focus on, thus hiding the cursor and giving
@@ -267,7 +265,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        final DeviceProfile dp = mLauncher.getDeviceProfile();
+        final DeviceProfile dp = mActivityContext.getDeviceProfile();
         final int paddingLeftRight = dp.folderContentPaddingLeftRight;
 
         mContent = findViewById(R.id.folder_content);
@@ -298,7 +296,7 @@
         }
 
         if (Utilities.ATLEAST_S) {
-            mColorExtractor = LocalColorExtractor.newInstance(mLauncher);
+            mColorExtractor = LocalColorExtractor.newInstance(getContext());
             mColorListener = (RectF rect, SparseIntArray extractedColors) -> {
                 mColorChangeRunnable = () -> {
                     mColorChangeRunnable = null;
@@ -339,7 +337,7 @@
 
     public boolean onLongClick(View v) {
         // Return if global dragging is not enabled
-        if (!mLauncher.isDraggingEnabled()) return true;
+        if (!mLauncherDelegate.isDraggingEnabled()) return true;
         return startDrag(v, new DragOptions());
     }
 
@@ -365,7 +363,7 @@
                         });
             }
 
-            mLauncher.getWorkspace().beginDragShared(v, this, options);
+            mLauncherDelegate.beginDragShared(v, this, options);
         }
         return true;
     }
@@ -421,7 +419,7 @@
         if (DEBUG) {
             Log.d(TAG, "onBackKey newTitle=" + newTitle);
         }
-        mInfo.setTitle(newTitle, mLauncher.getModelWriter());
+        mInfo.setTitle(newTitle, mLauncherDelegate.getModelWriter());
         mFolderIcon.onTitleChanged(newTitle);
 
         if (TextUtils.isEmpty(mInfo.title)) {
@@ -486,6 +484,7 @@
 
     public void setFolderIcon(FolderIcon icon) {
         mFolderIcon = icon;
+        mLauncherDelegate.init(this, icon);
     }
 
     @Override
@@ -592,8 +591,8 @@
     }
 
     private void startAnimation(final AnimatorSet a) {
-        mLauncher.getWorkspace().getVisiblePages()
-                .forEach(visiblePage -> addAnimatorListenerForPage(a, (CellLayout) visiblePage));
+        mLauncherDelegate.forEachVisibleWorkspacePage(
+                visiblePage -> addAnimatorListenerForPage(a, (CellLayout) visiblePage));
 
         a.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -754,12 +753,12 @@
                     mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION)
                         .translationX(0)
                         .setInterpolator(AnimationUtils.loadInterpolator(
-                                mLauncher, android.R.interpolator.fast_out_slow_in));
+                                getContext(), android.R.interpolator.fast_out_slow_in));
                     mPageIndicator.playEntryAnimation();
 
                     if (updateAnimationFlag) {
                         mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true,
-                                mLauncher.getModelWriter());
+                                mLauncherDelegate.getModelWriter());
                     }
                 }
             });
@@ -1115,7 +1114,7 @@
         if (getItemCount() <= mContent.itemsPerPage()) {
             // Show the animation, next time something is added to the folder.
             mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false,
-                    mLauncher.getModelWriter());
+                    mLauncherDelegate.getModelWriter());
         }
     }
 
@@ -1133,7 +1132,7 @@
         }
 
         if (!items.isEmpty()) {
-            mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
+            mLauncherDelegate.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
         }
         if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind
                 && total > 1 /* no need to update if there's one icon */) {
@@ -1190,12 +1189,7 @@
         if (mColorExtractor != null) {
             mColorExtractor.removeLocations();
             mColorExtractor.setListener(mColorListener);
-            mTempRect.set(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
-            mColorExtractor.getExtractedRectForViewRect(mLauncher,
-                    mLauncher.getWorkspace().getCurrentPage(), mTempRect, mTempRectF);
-            if (!mTempRectF.isEmpty()) {
-                mColorExtractor.addLocation(Arrays.asList(mTempRectF));
-            }
+            mLauncherDelegate.addRectForColorExtraction(lp, mColorExtractor);
         }
     }
 
@@ -1236,7 +1230,7 @@
 
         if (mContent.getChildCount() > 0) {
             int cellIconGap = (mContent.getPageAt(0).getCellWidth()
-                    - mLauncher.getDeviceProfile().iconSizePx) / 2;
+                    - mActivityContext.getDeviceProfile().iconSizePx) / 2;
             mFooter.setPadding(mContent.getPaddingLeft() + cellIconGap,
                     mFooter.getPaddingTop(),
                     mContent.getPaddingRight() + cellIconGap,
@@ -1266,56 +1260,7 @@
     }
 
     @Thunk void replaceFolderWithFinalItem() {
-        // Add the last remaining child to the workspace in place of the folder
-        Runnable onCompleteRunnable = new Runnable() {
-            @Override
-            public void run() {
-                int itemCount = getItemCount();
-                if (itemCount <= 1) {
-                    View newIcon = null;
-                    WorkspaceItemInfo finalItem = null;
-
-                    if (itemCount == 1) {
-                        // Move the item from the folder to the workspace, in the position of the
-                        // folder
-                        CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container,
-                                mInfo.screenId);
-                        finalItem =  mInfo.contents.remove(0);
-                        newIcon = mLauncher.createShortcut(cellLayout, finalItem);
-                        mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
-                                mInfo.container, mInfo.screenId, mInfo.cellX, mInfo.cellY);
-                    }
-
-                    // Remove the folder
-                    mLauncher.removeItem(mFolderIcon, mInfo, true /* deleteFromDb */);
-                    if (mFolderIcon instanceof DropTarget) {
-                        mDragController.removeDropTarget((DropTarget) mFolderIcon);
-                    }
-
-                    if (newIcon != null) {
-                        // We add the child after removing the folder to prevent both from existing
-                        // at the same time in the CellLayout.  We need to add the new item with
-                        // addInScreenFromBind() to ensure that hotseat items are placed correctly.
-                        mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo);
-
-                        // Focus the newly created child
-                        newIcon.requestFocus();
-                    }
-                    if (finalItem != null) {
-                        StatsLogger logger = mStatsLogManager.logger().withItemInfo(finalItem);
-                        mDragController.getLogInstanceId().map(logger::withInstanceId)
-                                .orElse(logger)
-                                .log(LAUNCHER_FOLDER_CONVERTED_TO_ICON);
-                    }
-                }
-            }
-        };
-        View finalChild = mContent.getLastItem();
-        if (finalChild != null) {
-            mFolderIcon.performDestroyAnimation(onCompleteRunnable);
-        } else {
-            onCompleteRunnable.run();
-        }
+        mLauncherDelegate.replaceFolderWithFinalItem(this);
         mDestroyed = true;
     }
 
@@ -1374,6 +1319,10 @@
             mScrollPauseAlarm.cancelAlarm();
         }
         mContent.completePendingPageChanges();
+        Launcher launcher = mLauncherDelegate.getLauncher();
+        if (launcher == null) {
+            return;
+        }
 
         PendingAddShortcutInfo pasi = d.dragInfo instanceof PendingAddShortcutInfo
                 ? (PendingAddShortcutInfo) d.dragInfo : null;
@@ -1383,7 +1332,7 @@
             pasi.container = mInfo.id;
             pasi.rank = mEmptyCellRank;
 
-            mLauncher.addPendingItem(pasi, pasi.container, pasi.screenId, null, pasi.spanX,
+            launcher.addPendingItem(pasi, pasi.container, pasi.screenId, null, pasi.spanX,
                     pasi.spanY);
             d.deferDragViewCleanupPostAnimation = false;
             mRearrangeOnClose = true;
@@ -1405,7 +1354,7 @@
 
                 // Actually move the item in the database if it was an external drag. Call this
                 // before creating the view, so that WorkspaceItemInfo is updated appropriately.
-                mLauncher.getModelWriter().addOrMoveItemInDatabase(
+                mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(
                         si, mInfo.id, 0, si.cellX, si.cellY);
                 mIsExternalDrag = false;
             } else {
@@ -1420,7 +1369,7 @@
                 float scaleY = getScaleY();
                 setScaleX(1.0f);
                 setScaleY(1.0f);
-                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, null);
+                launcher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, null);
                 setScaleX(scaleX);
                 setScaleY(scaleY);
             } else {
@@ -1448,10 +1397,11 @@
 
         if (mContent.getPageCount() > 1) {
             // The animation has already been shown while opening the folder.
-            mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher.getModelWriter());
+            mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true,
+                    mLauncherDelegate.getModelWriter());
         }
 
-        mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+        launcher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
         if (d.stateAnnouncer != null) {
             d.stateAnnouncer.completeAction(R.string.item_moved);
         }
@@ -1480,7 +1430,7 @@
         FolderGridOrganizer verifier = new FolderGridOrganizer(
                 mActivityContext.getDeviceProfile().inv).setFolderInfo(mInfo);
         verifier.updateRankAndPos(item, rank);
-        mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
+        mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
                 item.cellY);
         updateItemLocationsInDatabaseBatch(false);
 
@@ -1713,17 +1663,9 @@
                     return true;
                 }
                 return false;
-            } else if (!dl.isEventOverView(this, ev)) {
-                if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
-                    // Do not close the container if in drag and drop.
-                    if (!dl.isEventOverView(mLauncher.getDropTargetBar(), ev)) {
-                        return true;
-                    }
-                } else {
-                    // TODO: add ww log if need to gather tap outside to close folder
-                    close(true);
-                    return true;
-                }
+            } else if (!dl.isEventOverView(this, ev)
+                    && mLauncherDelegate.interceptOutsideTouch(ev, dl, this)) {
+                return true;
             }
         }
         return false;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 6b02021..279c445 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -169,14 +169,11 @@
     public static <T extends Context & ActivityContext> FolderIcon inflateFolderAndIcon(int resId,
             T activityContext, ViewGroup group, FolderInfo folderInfo) {
         Folder folder = Folder.fromXml(activityContext);
-        folder.setDragController(folder.mLauncher.getDragController());
 
         FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo);
         folder.setFolderIcon(icon);
         folder.bind(folderInfo);
         icon.setFolder(folder);
-
-        icon.setOnFocusChangeListener(folder.mLauncher.getFocusHandler());
         icon.mBackground.paddingY = icon.isInHotseat()
                 ? 0 : activityContext.getDeviceProfile().cellYPaddingPx;
         return icon;
@@ -467,7 +464,7 @@
         CharSequence newTitle = nameInfos.getLabels()[0];
         FromState fromState = mInfo.getFromLabelState();
 
-        mInfo.setTitle(newTitle, mFolder.mLauncher.getModelWriter());
+        mInfo.setTitle(newTitle, mFolder.mLauncherDelegate.getModelWriter());
         onTitleChanged(mInfo.title);
         mFolder.mFolderName.setText(mInfo.title);
 
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index a4e8be6..7fc3740 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -31,7 +31,6 @@
 import android.view.ViewDebug;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
@@ -49,6 +48,7 @@
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewCache;
+import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -102,7 +102,7 @@
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
 
         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
-        mViewCache = BaseActivity.fromContext(context).getViewCache();
+        mViewCache = ActivityContext.lookupContext(context).getViewCache();
     }
 
     public void setFolder(Folder folder) {
diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java
new file mode 100644
index 0000000..f7d8e8c
--- /dev/null
+++ b/src/com/android/launcher3/folder/LauncherDelegate.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2021 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.folder;
+
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.views.BaseDragLayer.LayoutParams;
+import com.android.launcher3.widget.LocalColorExtractor;
+
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * Wrapper around Launcher methods to allow folders in non-launcher context
+ */
+public class LauncherDelegate {
+
+    private final Launcher mLauncher;
+    private final Rect mTempRect = new Rect();
+    private final RectF mTempRectF = new RectF();
+
+    private LauncherDelegate(Launcher launcher) {
+        mLauncher = launcher;
+    }
+
+    void init(Folder folder, FolderIcon icon) {
+        folder.setDragController(mLauncher.getDragController());
+        icon.setOnFocusChangeListener(mLauncher.getFocusHandler());
+    }
+
+    boolean isDraggingEnabled() {
+        return mLauncher.isDraggingEnabled();
+    }
+
+    void beginDragShared(View child, DragSource source, DragOptions options) {
+        mLauncher.getWorkspace().beginDragShared(child, source, options);
+    }
+
+    ModelWriter getModelWriter() {
+        return mLauncher.getModelWriter();
+    }
+
+    void forEachVisibleWorkspacePage(Consumer<View> callback) {
+        mLauncher.getWorkspace().forEachVisiblePage(callback);
+    }
+
+    @Nullable
+    Launcher getLauncher() {
+        return mLauncher;
+    }
+
+    void addRectForColorExtraction(BaseDragLayer.LayoutParams lp, LocalColorExtractor target) {
+        mTempRect.set(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
+        target.getExtractedRectForViewRect(mLauncher,
+                mLauncher.getWorkspace().getCurrentPage(), mTempRect, mTempRectF);
+        if (!mTempRectF.isEmpty()) {
+            target.addLocation(Arrays.asList(mTempRectF));
+        }
+    }
+
+    void replaceFolderWithFinalItem(Folder folder) {
+        // Add the last remaining child to the workspace in place of the folder
+        Runnable onCompleteRunnable = new Runnable() {
+            @Override
+            public void run() {
+                int itemCount = folder.getItemCount();
+                FolderInfo info = folder.mInfo;
+                if (itemCount <= 1) {
+                    View newIcon = null;
+                    WorkspaceItemInfo finalItem = null;
+
+                    if (itemCount == 1) {
+                        // Move the item from the folder to the workspace, in the position of the
+                        // folder
+                        CellLayout cellLayout = mLauncher.getCellLayout(info.container,
+                                info.screenId);
+                        finalItem =  info.contents.remove(0);
+                        newIcon = mLauncher.createShortcut(cellLayout, finalItem);
+                        mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
+                                info.container, info.screenId, info.cellX, info.cellY);
+                    }
+
+                    // Remove the folder
+                    mLauncher.removeItem(folder.mFolderIcon, info, true /* deleteFromDb */);
+                    if (folder.mFolderIcon instanceof DropTarget) {
+                        folder.mDragController.removeDropTarget((DropTarget) folder.mFolderIcon);
+                    }
+
+                    if (newIcon != null) {
+                        // We add the child after removing the folder to prevent both from existing
+                        // at the same time in the CellLayout.  We need to add the new item with
+                        // addInScreenFromBind() to ensure that hotseat items are placed correctly.
+                        mLauncher.getWorkspace().addInScreenFromBind(newIcon, info);
+
+                        // Focus the newly created child
+                        newIcon.requestFocus();
+                    }
+                    if (finalItem != null) {
+                        StatsLogger logger = mLauncher.getStatsLogManager().logger()
+                                .withItemInfo(finalItem);
+                        ((Optional<InstanceId>) folder.mDragController.getLogInstanceId())
+                                .map(logger::withInstanceId)
+                                .orElse(logger)
+                                .log(LAUNCHER_FOLDER_CONVERTED_TO_ICON);
+                    }
+                }
+            }
+        };
+        View finalChild = folder.mContent.getLastItem();
+        if (finalChild != null) {
+            folder.mFolderIcon.performDestroyAnimation(onCompleteRunnable);
+        } else {
+            onCompleteRunnable.run();
+        }
+    }
+
+
+    boolean interceptOutsideTouch(MotionEvent ev, BaseDragLayer dl, Folder folder) {
+        if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
+            // Do not close the container if in drag and drop.
+            if (!dl.isEventOverView(mLauncher.getDropTargetBar(), ev)) {
+                return true;
+            }
+        } else {
+            // TODO: add ww log if need to gather tap outside to close folder
+            folder.close(true);
+            return true;
+        }
+        return false;
+    }
+
+    private static class FallbackDelegate extends LauncherDelegate {
+
+        private final ActivityContext mContext;
+        private ModelWriter mWriter;
+
+        FallbackDelegate(ActivityContext context) {
+            super(null);
+            mContext = context;
+        }
+
+        @Override
+        void init(Folder folder, FolderIcon icon) {
+            folder.setDragController(mContext.getDragController());
+        }
+
+        @Override
+        boolean isDraggingEnabled() {
+            return false;
+        }
+
+        @Override
+        void beginDragShared(View child, DragSource source, DragOptions options) { }
+
+        @Override
+        ModelWriter getModelWriter() {
+            if (mWriter == null) {
+                mWriter = LauncherAppState.getInstance((Context) mContext).getModel()
+                        .getWriter(false, false);
+            }
+            return mWriter;
+        }
+
+        @Override
+        void forEachVisibleWorkspacePage(Consumer<View> callback) { }
+
+        @Override
+        Launcher getLauncher() {
+            return null;
+        }
+
+        @Override
+        void replaceFolderWithFinalItem(Folder folder) { }
+
+        @Override
+        boolean interceptOutsideTouch(MotionEvent ev, BaseDragLayer dl, Folder folder) {
+            folder.close(true);
+            return true;
+        }
+
+        @Override
+        void addRectForColorExtraction(LayoutParams lp, LocalColorExtractor target) { }
+    }
+
+    static LauncherDelegate from(ActivityContext context) {
+        return context instanceof Launcher
+                ? new LauncherDelegate((Launcher) context)
+                : new FallbackDelegate(context);
+    }
+}
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
index f5e1234..523f3d6 100644
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -23,11 +23,12 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
+import android.view.View;
 import android.view.WindowInsets;
 import android.view.inputmethod.InputMethodManager;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.views.ActivityContext;
 
 /**
  * Utility class for offloading some class from UI thread
@@ -43,21 +44,21 @@
     private static final int MSG_RUN_COMMAND = 3;
 
     @SuppressLint("NewApi")
-    public static void hideKeyboardAsync(Launcher launcher, IBinder token) {
+    public static void hideKeyboardAsync(ActivityContext activityContext, IBinder token) {
+        View root = activityContext.getDragLayer();
         if (Utilities.ATLEAST_R) {
-            WindowInsets rootInsets = launcher.getRootView().getRootWindowInsets();
+            WindowInsets rootInsets = root.getRootWindowInsets();
             boolean isImeShown = rootInsets != null && rootInsets.isVisible(
                     WindowInsets.Type.ime());
             if (isImeShown) {
                 // this call is already asynchronous
-                launcher.getAppsView().getWindowInsetsController().hide(
-                        WindowInsets.Type.ime()
-                );
+                root.getWindowInsetsController().hide(WindowInsets.Type.ime());
             }
             return;
         }
 
-        Message.obtain(HANDLER.get(launcher), MSG_HIDE_KEYBOARD, token).sendToTarget();
+        Message.obtain(HANDLER.get(root.getContext()),
+                MSG_HIDE_KEYBOARD, token).sendToTarget();
     }
 
     public static void setOrientationAsync(Activity activity, int orientation) {
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 71aa4ac..646b669 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -23,7 +23,9 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.ViewCache;
 
 /**
  * An interface to be used along with a context for various activities in Launcher. This allows a
@@ -86,6 +88,17 @@
 
     DeviceProfile getDeviceProfile();
 
+    default ViewCache getViewCache() {
+        return new ViewCache();
+    }
+
+    /**
+     * Controller for supporting item drag-and-drop
+     */
+    default <T extends DragController> T getDragController() {
+        return null;
+    }
+
     /**
      * Returns the ActivityContext associated with the given Context.
      */