Add WidgetsAndMore bottom sheet

- Contains two rows, one for widgets, and one for "configurable
  shortcuts" that have customization activities
- Extends AbstractFloatingView and uses VerticalPullDetector for
  touch interactions
- No way to show this currently; will add options to popup in followup

Bug: 34940468
Change-Id: Iab62c2cb89428f91119c9c86f9db886496c321fd
diff --git a/res/layout/widgets_and_more.xml b/res/layout/widgets_and_more.xml
new file mode 100644
index 0000000..7a6d006
--- /dev/null
+++ b/res/layout/widgets_and_more.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<com.android.launcher3.widget.WidgetsAndMore
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="8dp"
+    android:background="?android:attr/colorPrimary"
+    android:elevation="@dimen/deep_shortcuts_elevation"
+    android:layout_gravity="bottom">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:gravity="center_horizontal|bottom"
+        android:fontFamily="sans-serif"
+        android:textStyle="bold"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="20sp"/>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:gravity="center_horizontal|top"
+        android:fontFamily="sans-serif"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="16sp"
+        android:text="@string/long_press_widget_to_add"/>
+
+    <TextView
+        android:id="@+id/widgets_header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="sans-serif"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="16sp"
+        android:textStyle="bold"
+        android:text="@string/widget_button_text"/>
+
+    <include layout="@layout/widgets_scroll_container"
+         android:id="@+id/widgets"
+         android:layout_width="wrap_content"
+         android:layout_height="wrap_content"
+         android:layout_marginTop="@dimen/widget_row_padding"
+         android:layout_marginBottom="@dimen/widget_row_padding"/>
+
+    <TextView
+        android:id="@+id/shortcuts_header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="sans-serif"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="16sp"
+        android:textStyle="bold"
+        android:text="@string/widgets_bottom_sheet_custom_shortcuts_section_title"/>
+
+    <include layout="@layout/widgets_scroll_container"
+             android:id="@+id/shortcuts"
+             android:layout_width="wrap_content"
+             android:layout_height="wrap_content"
+             android:layout_marginTop="@dimen/widget_row_padding"
+             android:layout_marginBottom="@dimen/widget_row_padding" />
+
+</com.android.launcher3.widget.WidgetsAndMore>
\ No newline at end of file
diff --git a/res/layout/widgets_scroll_container.xml b/res/layout/widgets_scroll_container.xml
new file mode 100644
index 0000000..33c981a
--- /dev/null
+++ b/res/layout/widgets_scroll_container.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<HorizontalScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/widgets_scroll_container"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/colorPrimaryDark"
+    android:scrollbars="none">
+    <LinearLayout
+        android:id="@+id/widgets_cell_list"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingStart="0dp"
+        android:paddingEnd="0dp"
+        android:orientation="horizontal"
+        android:showDividers="none"/>
+</HorizontalScrollView>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 423a772..79277b6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -203,6 +203,10 @@
     <!-- Title for an app whose download has been started. -->
     <string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
 
+    <!-- Strings for widgets & more in the popup container/bottom sheet -->
+    <string name="widgets_and_more" translatable="false">Widgets &amp; more</string>
+    <string name="widgets_bottom_sheet_custom_shortcuts_section_title" translatable="false">Custom shortcuts</string>
+
 <!-- Strings for accessibility actions -->
     <!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] -->
     <string name="action_add_to_workspace">Add to Home screen</string>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index bd12686..dbef054 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -16,9 +16,11 @@
 
 package com.android.launcher3;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.support.annotation.IntDef;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.LinearLayout;
 
@@ -32,11 +34,16 @@
  */
 public abstract class AbstractFloatingView extends LinearLayout {
 
-    @IntDef(flag = true, value = {TYPE_FOLDER, TYPE_POPUP_CONTAINER_WITH_ARROW})
+    @IntDef(flag = true, value = {
+            TYPE_FOLDER,
+            TYPE_POPUP_CONTAINER_WITH_ARROW,
+            TYPE_WIDGETS_AND_MORE
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FloatingViewType {}
     public static final int TYPE_FOLDER = 1 << 0;
     public static final int TYPE_POPUP_CONTAINER_WITH_ARROW = 1 << 1;
+    public static final int TYPE_WIDGETS_AND_MORE = 1 << 2;
 
     protected boolean mIsOpen;
 
@@ -48,6 +55,15 @@
         super(context, attrs, defStyleAttr);
     }
 
+    /**
+     * We need to handle touch events to prevent them from falling through to the workspace below.
+     */
+    @SuppressLint("ClickableViewAccessibility")
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return true;
+    }
+
     public final void close(boolean animate) {
         animate &= !Utilities.isPowerSaverOn(getContext());
         handleClose(animate);
@@ -119,7 +135,8 @@
     }
 
     public static AbstractFloatingView getTopOpenView(Launcher launcher) {
-        return getOpenView(launcher, TYPE_FOLDER | TYPE_POPUP_CONTAINER_WITH_ARROW);
+        return getOpenView(launcher, TYPE_FOLDER | TYPE_POPUP_CONTAINER_WITH_ARROW
+                | TYPE_WIDGETS_AND_MORE);
     }
 
     public abstract int getLogContainerType();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 84a5930..1ad97cc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -89,7 +89,6 @@
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PinItemRequestCompat;
-import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dragndrop.DragController;
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 23a2577..b04d5b7 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -61,6 +61,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.widget.WidgetsAndMore;
 
 import java.util.ArrayList;
 
@@ -246,6 +247,12 @@
             return true;
         }
 
+        WidgetsAndMore widgetsAndMore = WidgetsAndMore.getOpen(mLauncher);
+        if (widgetsAndMore != null && widgetsAndMore.onControllerInterceptTouchEvent(ev)) {
+            mActiveController = widgetsAndMore;
+            return true;
+        }
+
         if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) {
             // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
             mActiveController = mPinchListener;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 3d2ffb4..93c9ea8 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -388,15 +388,6 @@
         return mFolderIcon;
     }
 
-    /**
-     * We need to handle touch events to prevent them from falling through to the workspace below.
-     */
-    @SuppressLint("ClickableViewAccessibility")
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return true;
-    }
-
     public void setDragController(DragController dragController) {
         mDragController = dragController;
     }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index b2018b9..fffcb71 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -531,15 +531,6 @@
     }
 
     /**
-     * We need to handle touch events to prevent them from falling through to the workspace below.
-     */
-    @SuppressLint("ClickableViewAccessibility")
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return true;
-    }
-
-    /**
      * Updates the notification header to reflect the badge info. Since this can be called
      * for any badge info (not necessarily the one associated with this app), we first
      * check that the ItemInfo matches the one of this popup.
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 3bf622e..72effd4 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -73,6 +73,7 @@
     private StylusEventHelper mStylusEventHelper;
 
     protected CancellationSignal mActiveRequest;
+    private boolean mAnimatePreview = true;
 
     protected final BaseActivity mActivity;
 
@@ -149,13 +150,21 @@
         return mWidgetImage;
     }
 
+    public void setAnimatePreview(boolean shouldAnimate) {
+        mAnimatePreview = shouldAnimate;
+    }
+
     public void applyPreview(Bitmap bitmap) {
         if (bitmap != null) {
             mWidgetImage.setBitmap(bitmap,
                     DrawableFactory.get(getContext()).getBadgeForUser(mItem.user, getContext()));
-            mWidgetImage.setAlpha(0f);
-            ViewPropertyAnimator anim = mWidgetImage.animate();
-            anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
+            if (mAnimatePreview) {
+                mWidgetImage.setAlpha(0f);
+                ViewPropertyAnimator anim = mWidgetImage.animate();
+                anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
+            } else {
+                mWidgetImage.setAlpha(1f);
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/widget/WidgetsAndMore.java b/src/com/android/launcher3/widget/WidgetsAndMore.java
new file mode 100644
index 0000000..3ed2530
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsAndMore.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.widget.TextView;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.VerticalPullDetector;
+import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.TouchController;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Bottom sheet for the "Widgets & more" long-press option.
+ */
+public class WidgetsAndMore extends AbstractFloatingView implements Insettable, TouchController,
+        VerticalPullDetector.Listener, View.OnClickListener, View.OnLongClickListener,
+        DragController.DragListener {
+
+    private int mTranslationYOpen;
+    private int mTranslationYClosed;
+    private float mTranslationYRange;
+
+    private Launcher mLauncher;
+    private ObjectAnimator mOpenCloseAnimator;
+    private Interpolator mFastOutSlowInInterpolator;
+    private VerticalPullDetector.ScrollInterpolator mScrollInterpolator;
+    private Rect mInsets;
+    private boolean mWasNavBarLight;
+    private VerticalPullDetector mVerticalPullDetector;
+
+    public WidgetsAndMore(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public WidgetsAndMore(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme), attrs, defStyleAttr);
+        setWillNotDraw(false);
+        mLauncher = Launcher.getLauncher(context);
+        mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
+        mFastOutSlowInInterpolator = new FastOutSlowInInterpolator();
+        mScrollInterpolator = new VerticalPullDetector.ScrollInterpolator();
+        mInsets = new Rect();
+        mVerticalPullDetector = new VerticalPullDetector(context);
+        mVerticalPullDetector.setListener(this);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mTranslationYOpen = 0;
+        mTranslationYClosed = getMeasuredHeight();
+        mTranslationYRange = mTranslationYClosed - mTranslationYOpen;
+    }
+
+    public void populateAndShow(ItemInfo itemInfo, List<WidgetItem> widgets) {
+        ((TextView) findViewById(R.id.title)).setText(itemInfo.title);
+
+        List<WidgetItem> shortcuts = new ArrayList<>();
+        // Transfer configurable widgets to shortcuts
+        Iterator<WidgetItem> widgetsIter = widgets.iterator();
+        WidgetItem nextWidget;
+        while (widgetsIter.hasNext()) {
+            nextWidget = widgetsIter.next();
+            if (nextWidget.activityInfo != null) {
+                shortcuts.add(nextWidget);
+                widgetsIter.remove();
+            }
+        }
+
+        ViewGroup widgetRow = (ViewGroup) findViewById(R.id.widgets);
+        ViewGroup widgetCells = (ViewGroup) widgetRow.findViewById(R.id.widgets_cell_list);
+
+        ViewGroup shortcutRow = (ViewGroup) findViewById(R.id.shortcuts);
+        ViewGroup shortcutCells = (ViewGroup) shortcutRow.findViewById(R.id.widgets_cell_list);
+
+        for (int i = 0; i < widgets.size(); i++) {
+            addItemCell(widgetCells);
+            if (i < widgets.size() - 1) {
+                addDivider(widgetCells);
+            }
+        }
+        for (int i = 0; i < shortcuts.size(); i++) {
+            addItemCell(shortcutCells);
+            if (i < shortcuts.size() - 1) {
+                addDivider(shortcutCells);
+            }
+        }
+
+        // Bind the views in the horizontal tray regions.
+        if (widgetCells.getChildCount() > 0) {
+            for (int i = 0; i < widgets.size(); i++) {
+                WidgetCell widget = (WidgetCell) widgetCells.getChildAt(i*2); // skip dividers
+                widget.applyFromCellItem(widgets.get(i), LauncherAppState.getInstance(mLauncher)
+                        .getWidgetCache());
+                widget.ensurePreview();
+                widget.setVisibility(View.VISIBLE);
+            }
+        } else {
+            removeView(findViewById(R.id.widgets_header));
+        }
+        if (shortcutCells.getChildCount() > 0) {
+            for (int i = 0; i < shortcuts.size(); i++) {
+                WidgetCell shortcut = (WidgetCell) shortcutCells.getChildAt(i*2); // skip dividers
+                shortcut.applyFromCellItem(shortcuts.get(i), LauncherAppState.getInstance(mLauncher)
+                        .getWidgetCache());
+                shortcut.ensurePreview();
+                shortcut.setVisibility(View.VISIBLE);
+            }
+        } else {
+            removeView(findViewById(R.id.shortcuts_header));
+        }
+
+        mWasNavBarLight = (mLauncher.getWindow().getDecorView().getSystemUiVisibility()
+                & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0;
+        mLauncher.getDragLayer().addView(this);
+        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        setTranslationY(mTranslationYClosed);
+        mIsOpen = false;
+        open(true);
+    }
+
+    private void addDivider(ViewGroup parent) {
+        LayoutInflater.from(getContext()).inflate(R.layout.widget_list_divider, parent, true);
+    }
+
+    private void addItemCell(ViewGroup parent) {
+        WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()).inflate(
+                R.layout.widget_cell, parent, false);
+
+        widget.setOnClickListener(this);
+        widget.setOnLongClickListener(this);
+        widget.setAnimatePreview(false);
+
+        parent.addView(widget);
+    }
+
+    @Override
+    public void onClick(View view) {
+        mLauncher.getWidgetsView().handleClick();
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        mLauncher.getDragController().addDragListener(this);
+        return mLauncher.getWidgetsView().handleLongClick(view);
+    }
+
+    private void open(boolean animate) {
+        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        mIsOpen = true;
+        setLightNavBar(true);
+        if (animate) {
+            mOpenCloseAnimator.setValues(new PropertyListBuilder()
+                    .translationY(mTranslationYOpen).build());
+            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mVerticalPullDetector.finishedScrolling();
+                }
+            });
+            mOpenCloseAnimator.setInterpolator(mFastOutSlowInInterpolator);
+            mOpenCloseAnimator.start();
+        } else {
+            setTranslationY(mTranslationYOpen);
+        }
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        if (animate) {
+            mOpenCloseAnimator.setValues(new PropertyListBuilder()
+                    .translationY(mTranslationYClosed).build());
+            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mIsOpen = false;
+                    mVerticalPullDetector.finishedScrolling();
+                    ((ViewGroup) getParent()).removeView(WidgetsAndMore.this);
+                    setLightNavBar(mWasNavBarLight);
+                }
+            });
+            mOpenCloseAnimator.setInterpolator(mVerticalPullDetector.isIdleState()
+                    ? mFastOutSlowInInterpolator : mScrollInterpolator);
+            mOpenCloseAnimator.start();
+        } else {
+            setTranslationY(mTranslationYClosed);
+            setLightNavBar(mWasNavBarLight);
+            mIsOpen = false;
+        }
+    }
+
+    private void setLightNavBar(boolean lightNavBar) {
+        mLauncher.activateLightSystemBars(lightNavBar, false /* statusBar */, true /* navBar */);
+    }
+
+    @Override
+    protected boolean isOfType(@FloatingViewType int type) {
+        return (type & TYPE_WIDGETS_AND_MORE) != 0;
+    }
+
+    @Override
+    public int getLogContainerType() {
+        return LauncherLogProto.ContainerType.WIDGETS; // TODO: be more specific
+    }
+
+    /**
+     * Returns a WidgetsAndMore which is already open or null
+     */
+    public static WidgetsAndMore getOpen(Launcher launcher) {
+        return getOpenView(launcher, TYPE_WIDGETS_AND_MORE);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        // Extend behind left, right, and bottom insets.
+        int leftInset = insets.left - mInsets.left;
+        int rightInset = insets.right - mInsets.right;
+        int bottomInset = insets.bottom - mInsets.bottom;
+        mInsets.set(insets);
+        setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
+                getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
+    }
+
+    /* VerticalPullDetector.Listener */
+
+    @Override
+    public void onDragStart(boolean start) {
+    }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        setTranslationY(Utilities.boundToRange(displacement, mTranslationYOpen,
+                mTranslationYClosed));
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        if ((fling && velocity > 0) || getTranslationY() > (mTranslationYRange) / 2) {
+            mScrollInterpolator.setVelocityAtZero(velocity);
+            mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity,
+                    (mTranslationYClosed - getTranslationY()) / mTranslationYRange));
+            close(true);
+        } else {
+            mIsOpen = false;
+            mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity,
+                    (getTranslationY() - mTranslationYOpen) / mTranslationYRange));
+            open(true);
+        }
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return mVerticalPullDetector.onTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        int directionsToDetectScroll = mVerticalPullDetector.isIdleState() ?
+                VerticalPullDetector.DIRECTION_DOWN : 0;
+        mVerticalPullDetector.setDetectableScrollConditions(
+                directionsToDetectScroll, false);
+        mVerticalPullDetector.onTouchEvent(ev);
+        return mVerticalPullDetector.isDraggingOrSettling();
+    }
+
+    /* DragListener */
+
+    @Override
+    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+        // A widget or custom shortcut was dragged.
+        close(true);
+    }
+
+    @Override
+    public void onDragEnd() {
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index b2321a7..ba6ed41 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -29,13 +29,10 @@
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.IconCache;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.model.PackageItemInfo;
@@ -55,8 +52,6 @@
 
     /* Global instances that are used inside this container. */
     @Thunk Launcher mLauncher;
-    private DragController mDragController;
-    private IconCache mIconCache;
 
     /* Recycler view related member variables */
     private WidgetsRecyclerView mRecyclerView;
@@ -76,9 +71,7 @@
     public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mLauncher = Launcher.getLauncher(context);
-        mDragController = mLauncher.getDragController();
         mAdapter = new WidgetsListAdapter(this, this, context);
-        mIconCache = LauncherAppState.getInstance(context).getIconCache();
         if (LOGD) {
             Log.d(TAG, "WidgetsContainerView constructor");
         }
@@ -116,6 +109,10 @@
                 || mLauncher.getWorkspace().isSwitchingState()
                 || !(v instanceof WidgetCell)) return;
 
+        handleClick();
+    }
+
+    public void handleClick() {
         // Let the user know that they have to long press to add a widget
         if (mWidgetInstructionToast != null) {
             mWidgetInstructionToast.cancel();
@@ -130,14 +127,19 @@
 
     @Override
     public boolean onLongClick(View v) {
+        // When we have exited the widget tray, disregard long clicks
+        if (!mLauncher.isWidgetsViewVisible()) return false;
+        return handleLongClick(v);
+    }
+
+    public boolean handleLongClick(View v) {
         if (LOGD) {
             Log.d(TAG, String.format("onLongClick [v=%s]", v));
         }
         // Return early if this is not initiated from a touch
         if (!v.isInTouchMode()) return false;
-        // When we have exited all apps or are in transition, disregard long clicks
-        if (!mLauncher.isWidgetsViewVisible() ||
-                mLauncher.getWorkspace().isSwitchingState()) return false;
+        // When we  are in transition, disregard long clicks
+        if (mLauncher.getWorkspace().isSwitchingState()) return false;
         // Return if global dragging is not enabled
         if (!mLauncher.isDraggingEnabled()) return false;