Folder support in Taskbar

- Add TaskbarActivityContext which allows shared Launcher elements to
  "just work" using existing generic ActivityContext.
- TaskbarContainerView extends BaseDragLayer<TaskbarActivityContext>.
- Inflate FolderIcon and Folder using TaskbarActivityContext to be
  shown in TaskbarContainerView.
- Use TaskbarActivityContext's DeviceProfile to determine icon size
  instead of overriding in styles. This also ensures that normal
  BubbleTextView icons have the same size as FolderIcons.

Test: Place a folder in home screen hotseat, ensure it shows up in
taskbar and can be opened, and that apps inside it can be launched
or dragged.
Bug: 171917176

Change-Id: Ic25d2f84bcd7e3399c88989305ea565497c030d9
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 68c3851..a0e87cf 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -130,4 +130,5 @@
     <dimen name="taskbar_icon_spacing">14dp</dimen>
     <dimen name="taskbar_divider_thickness">1dp</dimen>
     <dimen name="taskbar_divider_height">24dp</dimen>
+    <dimen name="taskbar_folder_margin">16dp</dimen>
 </resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 5a353f0..df089f6 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -89,6 +89,5 @@
     <!-- Icon displayed on the taskbar -->
     <style name="BaseIcon.Workspace.Taskbar" >
         <item name="iconDisplay">taskbar</item>
-        <item name="iconSizeOverride">@dimen/taskbar_icon_size</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index edcd0a2..d1fa2fd 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -30,7 +30,6 @@
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.CancellationSignal;
-import android.view.LayoutInflater;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -43,7 +42,7 @@
 import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
-import com.android.launcher3.taskbar.TaskbarContainerView;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarController;
 import com.android.launcher3.taskbar.TaskbarStateHandler;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
@@ -207,6 +206,7 @@
         mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
 
         addTaskbarIfNecessary();
+        addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary());
     }
 
     @Override
@@ -223,9 +223,9 @@
             mTaskbarController = null;
         }
         if (FeatureFlags.ENABLE_TASKBAR.get() && mDeviceProfile.isTablet) {
-            TaskbarContainerView taskbarContainer = (TaskbarContainerView) LayoutInflater.from(this)
-                    .inflate(R.layout.taskbar, null, false);
-            mTaskbarController = new TaskbarController(this, taskbarContainer);
+            TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this);
+            mTaskbarController = new TaskbarController(this,
+                    taskbarActivityContext.getTaskbarContainerView());
             mTaskbarController.init();
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
new file mode 100644
index 0000000..06372fe
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -0,0 +1,78 @@
+/*
+ * 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.taskbar;
+
+import android.content.ContextWrapper;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
+
+/**
+ * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
+ * that are used by both Launcher and Taskbar (such as Folder) to reference a generic
+ * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer.
+ */
+public class TaskbarActivityContext extends ContextWrapper implements ActivityContext {
+
+    private final DeviceProfile mDeviceProfile;
+    private final LayoutInflater mLayoutInflater;
+    private final TaskbarContainerView mTaskbarContainerView;
+
+    public TaskbarActivityContext(BaseQuickstepLauncher launcher) {
+        super(launcher);
+        mDeviceProfile = launcher.getDeviceProfile().copy(this);
+        float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
+        float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
+        mDeviceProfile.updateIconSize(iconScale, getResources());
+
+        mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
+
+        mTaskbarContainerView = (TaskbarContainerView) mLayoutInflater
+                .inflate(R.layout.taskbar, null, false);
+    }
+
+    public TaskbarContainerView getTaskbarContainerView() {
+        return mTaskbarContainerView;
+    }
+
+    /**
+     * @return A LayoutInflater to use in this Context. Views inflated with this LayoutInflater will
+     * be able to access this TaskbarActivityContext via ActivityContext.lookupContext().
+     */
+    public LayoutInflater getLayoutInflater() {
+        return mLayoutInflater;
+    }
+
+    @Override
+    public BaseDragLayer<TaskbarActivityContext> getDragLayer() {
+        return mTaskbarContainerView;
+    }
+
+    @Override
+    public DeviceProfile getDeviceProfile() {
+        return mDeviceProfile;
+    }
+
+    @Override
+    public Rect getFolderBoundingBox() {
+        return mTaskbarContainerView.getFolderBoundingBox();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
index 3b361c4..ddd0d15 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -19,19 +19,29 @@
 import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.widget.FrameLayout;
+import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.R;
 import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
 import com.android.systemui.shared.system.ViewTreeObserverWrapper;
 
 /**
  * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
  */
-public class TaskbarContainerView extends FrameLayout {
+public class TaskbarContainerView extends BaseDragLayer<TaskbarActivityContext> {
+
+    private final int[] mTempLoc = new int[2];
+    private final int mFolderMargin;
+
+    // Initialized in TaskbarController constructor.
+    private TaskbarController.TaskbarContainerViewCallbacks mControllerCallbacks;
 
     // Initialized in init.
     private TaskbarView mTaskbarView;
@@ -52,12 +62,23 @@
 
     public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
+        super(context, attrs, 1 /* alphaChannelCount */);
+        mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin);
+    }
+
+    protected void construct(TaskbarController.TaskbarContainerViewCallbacks callbacks) {
+        mControllerCallbacks = callbacks;
     }
 
     protected void init(TaskbarView taskbarView) {
         mTaskbarView = taskbarView;
         mTaskbarInsetsComputer = createTaskbarInsetsComputer();
+        recreateControllers();
+    }
+
+    @Override
+    public void recreateControllers() {
+        mControllers = new TouchController[0];
     }
 
     private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() {
@@ -70,6 +91,17 @@
                  // We're visible again, accept touches anywhere in our bounds.
                 insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
             }
+
+            // TaskbarContainerView provides insets to other apps based on contentInsets. These
+            // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g.
+            // to show a floating view like Folder. Thus, we set the contentInsets to be where
+            // mTaskbarView is, since its position never changes and insets rather than overlays.
+            int[] loc = mTempLoc;
+            mTaskbarView.getLocationInWindow(loc);
+            insetsInfo.contentInsets.left = loc[0];
+            insetsInfo.contentInsets.top = loc[1];
+            insetsInfo.contentInsets.right = getWidth() - (loc[0] + mTaskbarView.getWidth());
+            insetsInfo.contentInsets.bottom = getHeight() - (loc[1] + mTaskbarView.getHeight());
         };
     }
 
@@ -91,4 +123,30 @@
 
         cleanup();
     }
+
+    @Override
+    protected boolean canFindActiveController() {
+        // Unlike super class, we want to be able to find controllers when touches occur in the
+        // gesture area. For example, this allows Folder to close itself when touching the Taskbar.
+        return true;
+    }
+
+    @Override
+    public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
+        mControllerCallbacks.onViewRemoved();
+    }
+
+    /**
+     * @return Bounds (in our coordinates) where an opened Folder can display.
+     */
+    protected Rect getFolderBoundingBox() {
+        Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight());
+        boundingBox.inset(mFolderMargin, mFolderMargin);
+        return boundingBox;
+    }
+
+    protected TaskbarActivityContext getTaskbarActivityContext() {
+        return mActivity;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 260428d..ab05fbf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -34,11 +34,15 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.ItemClickHandler;
@@ -81,8 +85,9 @@
             TaskbarContainerView taskbarContainerView) {
         mLauncher = launcher;
         mTaskbarContainerView = taskbarContainerView;
+        mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks());
         mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
-        mTaskbarView.setCallbacks(createTaskbarViewCallbacks());
+        mTaskbarView.construct(createTaskbarViewCallbacks());
         mWindowManager = mLauncher.getWindowManager();
         mTaskbarSize = new Point(MATCH_PARENT,
                 mLauncher.getResources().getDimensionPixelSize(R.dimen.taskbar_size));
@@ -110,6 +115,18 @@
         };
     }
 
+    private TaskbarContainerViewCallbacks createTaskbarContainerViewCallbacks() {
+        return new TaskbarContainerViewCallbacks() {
+            @Override
+            public void onViewRemoved() {
+                if (mTaskbarContainerView.getChildCount() == 1) {
+                    // Only TaskbarView remains.
+                    setTaskbarWindowFullscreen(false);
+                }
+            }
+        };
+    }
+
     private TaskbarViewCallbacks createTaskbarViewCallbacks() {
         return new TaskbarViewCallbacks() {
             @Override
@@ -120,9 +137,29 @@
                         Task task = (Task) tag;
                         ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
                                 ActivityOptions.makeBasic());
+                    } else if (tag instanceof FolderInfo) {
+                        FolderIcon folderIcon = (FolderIcon) view;
+                        Folder folder = folderIcon.getFolder();
+
+                        setTaskbarWindowFullscreen(true);
+
+                        mTaskbarContainerView.post(() -> {
+                            folder.animateOpen();
+
+                            folder.iterateOverItems((itemInfo, itemView) -> {
+                                itemView.setOnClickListener(getItemOnClickListener());
+                                itemView.setOnLongClickListener(getItemOnLongClickListener());
+                                // To play haptic when dragging, like other Taskbar items do.
+                                itemView.setHapticFeedbackEnabled(true);
+                                return false;
+                            });
+                        });
                     } else {
                         ItemClickHandler.INSTANCE.onClick(view);
                     }
+
+                    AbstractFloatingView.closeAllOpenViews(
+                            mTaskbarContainerView.getTaskbarActivityContext());
                 };
             }
 
@@ -345,6 +382,20 @@
     }
 
     /**
+     * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
+     */
+    private void setTaskbarWindowFullscreen(boolean fullscreen) {
+        if (fullscreen) {
+            mWindowLayoutParams.width = MATCH_PARENT;
+            mWindowLayoutParams.height = MATCH_PARENT;
+        } else {
+            mWindowLayoutParams.width = mTaskbarSize.x;
+            mWindowLayoutParams.height = mTaskbarSize.y;
+        }
+        mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams);
+    }
+
+    /**
      * Contains methods that TaskbarStateHandler can call to interface with TaskbarController.
      */
     protected interface TaskbarStateHandlerCallbacks {
@@ -361,6 +412,13 @@
     }
 
     /**
+     * Contains methods that TaskbarContainerView can call to interface with TaskbarController.
+     */
+    protected interface TaskbarContainerViewCallbacks {
+        void onViewRemoved();
+    }
+
+    /**
      * Contains methods that TaskbarView can call to interface with TaskbarController.
      */
     protected interface TaskbarViewCallbacks {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index d8f3bb5..df77f87 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -22,7 +22,6 @@
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.DragEvent;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -32,10 +31,14 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.views.ActivityContext;
 import com.android.systemui.shared.recents.model.Task;
 
 /**
@@ -51,6 +54,9 @@
     private final RectF mDelegateSlopBounds = new RectF();
     private final int[] mTempOutLocation = new int[2];
 
+    // Initialized in TaskbarController constructor.
+    private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+
     // Initialized in init().
     private int mHotseatStartIndex;
     private int mHotseatEndIndex;
@@ -58,8 +64,6 @@
     private int mRecentsStartIndex;
     private int mRecentsEndIndex;
 
-    private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
-
     // Delegate touches to the closest view if within mIconTouchSize.
     private boolean mDelegateTargeted;
     private View mDelegateView;
@@ -90,7 +94,7 @@
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     }
 
-    protected void setCallbacks(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
+    protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
         mControllerCallbacks = taskbarViewCallbacks;
     }
 
@@ -130,17 +134,37 @@
 
             // Replace any Hotseat views with the appropriate type if it's not already that type.
             final int expectedLayoutResId;
+            boolean isFolder = false;
+            boolean needsReinflate = false;
             if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
                 expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
+            } else if (hotseatItemInfo instanceof FolderInfo) {
+                expectedLayoutResId = R.layout.folder_icon;
+                isFolder = true;
+                // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so
+                // if the info changes we need to reinflate. This should only happen if a new folder
+                // is dragged to the position that another folder previously existed.
+                needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo;
             } else {
                 expectedLayoutResId = R.layout.taskbar_app_icon;
             }
-            if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId) {
+            if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId
+                    || needsReinflate) {
                 removeView(hotseatView);
-                BubbleTextView btv = (BubbleTextView) inflate(expectedLayoutResId);
-                LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize());
+                TaskbarActivityContext activityContext =
+                        ActivityContext.lookupContext(getContext());
+                if (isFolder) {
+                    FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
+                    FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
+                            activityContext, this, folderInfo);
+                    folderIcon.setTextVisible(false);
+                    hotseatView = folderIcon;
+                } else {
+                    hotseatView = inflate(expectedLayoutResId);
+                }
+                int iconSize = activityContext.getDeviceProfile().iconSizePx;
+                LayoutParams lp = new LayoutParams(iconSize, iconSize);
                 lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
-                hotseatView = btv;
                 addView(hotseatView, hotseatIndex, lp);
             }
 
@@ -153,6 +177,11 @@
                 hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
                 hotseatView.setOnLongClickListener(
                         mControllerCallbacks.getItemOnLongClickListener());
+            } else if (isFolder) {
+                hotseatView.setVisibility(VISIBLE);
+                hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
+                hotseatView.setOnLongClickListener(
+                        mControllerCallbacks.getItemOnLongClickListener());
             } else {
                 hotseatView.setVisibility(GONE);
                 hotseatView.setOnClickListener(null);
@@ -345,6 +374,7 @@
         switch (event.getAction()) {
             case DragEvent.ACTION_DRAG_STARTED:
                 mIsDraggingItem = true;
+                AbstractFloatingView.closeAllOpenViews(ActivityContext.lookupContext(getContext()));
                 return true;
             case DragEvent.ACTION_DRAG_ENDED:
                 mIsDraggingItem = false;
@@ -358,6 +388,7 @@
     }
 
     private View inflate(@LayoutRes int layoutResId) {
-        return LayoutInflater.from(getContext()).inflate(layoutResId, this, false);
+        TaskbarActivityContext taskbarActivityContext = ActivityContext.lookupContext(getContext());
+        return taskbarActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
     }
 }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index edc7e9b..e5a4335 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -505,7 +505,7 @@
      * @param canvas The canvas to draw to.
      */
     protected void drawDotIfNecessary(Canvas canvas) {
-        if (mDisplay == DISPLAY_TASKBAR) {
+        if (mActivity instanceof Launcher && ((Launcher) mActivity).isViewInTaskbar(this)) {
             // TODO: support notification dots in Taskbar
             return;
         }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 19915b7..83a7d77 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -375,7 +375,7 @@
      * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
      * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
      */
-    private void updateIconSize(float scale, Resources res) {
+    public void updateIconSize(float scale, Resources res) {
         // Workspace
         final boolean isVerticalLayout = isVerticalBarLayout();
         float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 9efbc7d..504b29e 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -1616,6 +1616,11 @@
         return false;
     }
 
+    @Override
+    public boolean canInterceptEventsInSystemGestureRegion() {
+        return true;
+    }
+
     /**
      * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of
      * rounded rect.
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 65a4fba..75d8f22 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -754,6 +754,9 @@
     }
 
     public void clearLeaveBehindIfExists() {
+        if (!(getLayoutParams() instanceof CellLayout.LayoutParams)) {
+            return;
+        }
         ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
         if (isInHotseat()) {
             CellLayout cl = (CellLayout) getParent().getParent();
@@ -762,6 +765,9 @@
     }
 
     public void drawLeaveBehindIfExists() {
+        if (!(getLayoutParams() instanceof CellLayout.LayoutParams)) {
+            return;
+        }
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
         // While the folder is open, the position of the icon cannot change.
         lp.canReorder = false;
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 15f7730..1939d15 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -206,15 +206,19 @@
 
     protected boolean findActiveController(MotionEvent ev) {
         mActiveController = null;
-        if ((mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION
-                | TOUCH_DISPATCHING_FROM_PROXY)) == 0) {
-            // Only look for controllers if we are not dispatching from gesture area and proxy is
-            // not active
+        if (canFindActiveController()) {
             mActiveController = findControllerToHandleTouch(ev);
         }
         return mActiveController != null;
     }
 
+    protected boolean canFindActiveController() {
+        // Only look for controllers if we are not dispatching from gesture area and proxy is
+        // not active
+        return (mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION
+                | TOUCH_DISPATCHING_FROM_PROXY)) == 0;
+    }
+
     @Override
     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
         // Shortcuts can appear above folder