Merge "Import translations. DO NOT MERGE" into ub-launcher3-master
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index b2ed709..60131e4 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -22,7 +22,7 @@
 
     <com.android.launcher3.shortcuts.DeepShortcutTextView
         style="@style/BaseIcon"
-        android:id="@+id/deep_shortcut"
+        android:id="@+id/bubble_text"
         android:background="?android:attr/selectableItemBackground"
         android:gravity="start|center_vertical"
         android:textAlignment="viewStart"
@@ -34,8 +34,7 @@
         android:fontFamily="sans-serif"
         launcher:layoutHorizontal="true"
         launcher:iconDisplay="shortcut_popup"
-        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
-        android:elevation="@dimen/deep_shortcuts_elevation" />
+        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
 
     <View
         android:id="@+id/icon"
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
new file mode 100644
index 0000000..3baddc4
--- /dev/null
+++ b/res/layout/system_shortcut.xml
@@ -0,0 +1,50 @@
+<?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.shortcuts.DeepShortcutView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="@dimen/bg_popup_item_width"
+    android:layout_height="@dimen/bg_popup_item_height" >
+
+    <com.android.launcher3.BubbleTextView
+        style="@style/BaseIcon"
+        android:id="@+id/bubble_text"
+        android:background="?android:attr/selectableItemBackground"
+        android:gravity="start|center_vertical"
+        android:textAlignment="viewStart"
+        android:paddingStart="@dimen/bg_popup_item_height"
+        android:paddingEnd="@dimen/deep_shortcut_padding_end"
+        android:textSize="14sp"
+        android:fontFamily="sans-serif"
+        launcher:layoutHorizontal="true" />
+
+    <View
+        android:id="@+id/icon"
+        android:layout_width="@dimen/system_shortcut_icon_size"
+        android:layout_height="@dimen/system_shortcut_icon_size"
+        android:layout_margin="@dimen/system_shortcut_padding_start"
+        android:layout_gravity="start" />
+
+    <View
+        android:id="@+id/divider"
+        android:layout_width="@dimen/deep_shortcuts_divider_width"
+        android:layout_height="@dimen/popup_item_divider_height"
+        android:layout_gravity="end|bottom"
+        android:visibility="gone"
+        android:background="?android:attr/listDivider" />
+
+</com.android.launcher3.shortcuts.DeepShortcutView>
diff --git a/res/layout/system_shortcut_icon_only.xml b/res/layout/system_shortcut_icon_only.xml
new file mode 100644
index 0000000..02d4ef8
--- /dev/null
+++ b/res/layout/system_shortcut_icon_only.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/deep_shortcut_icon_size"
+    android:layout_height="@dimen/deep_shortcut_icon_size"
+    android:background="?android:attr/selectableItemBackground"
+    android:layout_marginEnd="@dimen/deep_shortcut_padding_start"
+    android:padding="4dp" />
diff --git a/res/layout/system_shortcut_icons.xml b/res/layout/system_shortcut_icons.xml
new file mode 100644
index 0000000..bf0a251
--- /dev/null
+++ b/res/layout/system_shortcut_icons.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/system_shortcut_icons"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/bg_popup_item_height"
+    android:paddingEnd="@dimen/deep_shortcut_padding_start"
+    android:orientation="horizontal"
+    android:gravity="end|center_vertical"
+    android:background="@color/notification_header_background_color" />
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_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index 4067503..b6e0a0b 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -47,19 +47,5 @@
         launcher:iconSizeOverride="@dimen/widget_section_icon_size"
         launcher:layoutHorizontal="true" />
 
-    <HorizontalScrollView
-        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="@dimen/widget_row_padding"
-            android:paddingEnd="0dp"
-            android:orientation="horizontal"
-            android:showDividers="none"/>
-    </HorizontalScrollView>
+    <include layout="@layout/widgets_scroll_container" />
 </LinearLayout>
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/colors.xml b/res/values/colors.xml
index 455729c..cc78b41 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -30,6 +30,7 @@
     <color name="spring_loaded_panel_color">#40FFFFFF</color>
     <color name="spring_loaded_highlighted_panel_border_color">#FFF</color>
 
+    <!-- Notifications -->
     <color name="notification_icon_default_color">#757575</color> <!-- Gray 600 -->
     <color name="notification_header_background_color">#F5F5F5</color> <!-- Gray 100 -->
     <color name="notification_background_color">#FFF</color>
@@ -37,4 +38,7 @@
     <color name="divider_color">@color/notification_color_beneath</color>
     <color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
     <color name="legacy_icon_background">#FFFFFF</color>
+
+    <!-- System shortcuts -->
+    <color name="system_shortcuts_icon_color">@android:color/tertiary_text_light</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 132ae07..7b1d247 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -172,6 +172,8 @@
     <dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen>
     <!-- popup_item_width - icon_size - padding_start - drawable_padding -->
     <dimen name="deep_shortcuts_divider_width">158dp</dimen>
+    <dimen name="system_shortcut_icon_size">28dp</dimen>
+    <dimen name="system_shortcut_padding_start">10dp</dimen>
 
 <!-- Icon badges (with notification counts) -->
     <dimen name="badge_size">24dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9d9e1df..99ff581 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..1f12034 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);
@@ -76,6 +92,9 @@
         return mIsOpen;
     }
 
+    protected void onWidgetsBound() {
+    }
+
     protected abstract boolean isOfType(@FloatingViewType int type);
 
     protected static <T extends AbstractFloatingView> T getOpenView(
@@ -119,7 +138,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 d007fc3..200cc82 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1455,7 +1455,7 @@
         ShortcutInfo info = null;
         if (Utilities.isAtLeastO()) {
             info = LauncherAppsCompat.createShortcutInfoFromPinItemRequest(
-                    this, PinItemRequestCompat.getPinItemRequest(data));
+                    this, PinItemRequestCompat.getPinItemRequest(data), 0);
         }
 
         if (info == null) {
@@ -3938,11 +3938,24 @@
             mWidgetsView.setWidgets(allWidgets);
             mAllWidgets = null;
         }
+
+        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
+        if (topView != null) {
+            topView.onWidgetsBound();
+        }
+    }
+
+    public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
+        return mWidgetsView.getWidgetsForPackageUser(packageUserKey);
     }
 
     @Override
     public void notifyWidgetProvidersChanged() {
-        if (mWorkspace.getState().shouldUpdateWidget) {
+        notifyWidgetProvidersChanged(false);
+    }
+
+    public void notifyWidgetProvidersChanged(boolean force) {
+        if (force || mWorkspace.getState().shouldUpdateWidget) {
             mModel.refreshAndBindWidgetsAndShortcuts(this, mWidgetsView.isEmpty());
         }
     }
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 5e859c7..a3ad616 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -91,11 +91,12 @@
      * @return a request id which can be used to cancel the request.
      */
     public CancellationSignal getPreview(WidgetItem item, int previewWidth,
-            int previewHeight, WidgetCell caller) {
+            int previewHeight, WidgetCell caller, boolean animate) {
         String size = previewWidth + "x" + previewHeight;
         WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
 
-        PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller);
+        PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller,
+                animate);
         task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
 
         CancellationSignal signal = new CancellationSignal();
@@ -522,17 +523,19 @@
         private final int mPreviewHeight;
         private final int mPreviewWidth;
         private final WidgetCell mCaller;
+        private final boolean mAnimatePreviewIn;
         private final BaseActivity mActivity;
         @Thunk long[] mVersions;
         @Thunk Bitmap mBitmapToRecycle;
 
         PreviewLoadTask(WidgetCacheKey key, WidgetItem info, int previewWidth,
-                int previewHeight, WidgetCell caller) {
+                int previewHeight, WidgetCell caller, boolean animate) {
             mKey = key;
             mInfo = info;
             mPreviewHeight = previewHeight;
             mPreviewWidth = previewWidth;
             mCaller = caller;
+            mAnimatePreviewIn = animate;
             mActivity = BaseActivity.fromContext(mCaller.getContext());
             if (DEBUG) {
                 Log.d(TAG, String.format("%s, %s, %d, %d",
@@ -588,7 +591,7 @@
 
         @Override
         protected void onPostExecute(final Bitmap preview) {
-            mCaller.applyPreview(preview);
+            mCaller.applyPreview(preview, mAnimatePreviewIn);
 
             // Write the generated preview to the DB in the worker thread
             if (mVersions != null) {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 2eb5b02..b03a302 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -27,10 +27,12 @@
 import android.support.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.LooperExecutor;
 
 import java.util.List;
 
@@ -100,10 +102,32 @@
      */
     @Nullable
     public static ShortcutInfo createShortcutInfoFromPinItemRequest(
-            Context context, PinItemRequestCompat request) {
+            Context context, final PinItemRequestCompat request, final long acceptDelay) {
         if (request != null &&
                 request.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT &&
-                request.isValid() && request.accept()) {
+                request.isValid()) {
+
+            if (acceptDelay <= 0) {
+                if (!request.accept()) {
+                    return null;
+                }
+            } else {
+                // Block the worker thread until the accept() is called.
+                new LooperExecutor(LauncherModel.getWorkerLooper()).execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            Thread.sleep(acceptDelay);
+                        } catch (InterruptedException e) {
+                            // Ignore
+                        }
+                        if (request.isValid()) {
+                            request.accept();
+                        }
+                    }
+                });
+            }
+
             ShortcutInfoCompat compat = new ShortcutInfoCompat(request.getShortcutInfo());
             ShortcutInfo info = new ShortcutInfo(compat, context);
             // Apply the unbadged icon and fetch the actual icon asynchronously.
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index e1a9834..705262e 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -60,6 +60,7 @@
 import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.widget.WidgetsAndMore;
 
 import java.util.ArrayList;
 
@@ -245,6 +246,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/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 26460d7..bb5ac5b 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -26,8 +26,10 @@
 import android.os.Build;
 
 import com.android.launcher3.IconCache;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PinItemRequestCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
@@ -73,7 +75,13 @@
 
     @Override
     public com.android.launcher3.ShortcutInfo createShortcutInfo() {
-        return LauncherAppsCompat.createShortcutInfoFromPinItemRequest(mContext, mRequest);
+        // Total duration for the drop animation to complete.
+        long duration = mContext.getResources().getInteger(R.integer.config_dropAnimMaxDuration) +
+                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT +
+                mContext.getResources().getInteger(R.integer.config_overlayTransitionTime) / 2;
+        // Delay the actual accept() call until the drop animation is complete.
+        return LauncherAppsCompat.createShortcutInfoFromPinItemRequest(
+                mContext, mRequest, duration);
     }
 
     @Override
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 3ee475a..a0ceb49 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -392,15 +392,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..b92814f 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -22,7 +22,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
@@ -66,6 +65,7 @@
 import com.android.launcher3.graphics.TriangleShape;
 import com.android.launcher3.notification.NotificationItemView;
 import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutsItemView;
 import com.android.launcher3.util.PackageUserKey;
@@ -138,19 +138,21 @@
             return null;
         }
         ItemInfo itemInfo = (ItemInfo) icon.getTag();
+        if (!DeepShortcutManager.supportsShortcuts(itemInfo)) {
+            return null;
+        }
+
         List<String> shortcutIds = launcher.getPopupDataProvider().getShortcutIdsForItem(itemInfo);
         List<NotificationKeyData> notificationKeys = launcher.getPopupDataProvider()
                 .getNotificationKeysForItem(itemInfo);
-        if (shortcutIds.size() > 0 || notificationKeys.size() > 0) {
-            final PopupContainerWithArrow container =
-                    (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
-                            R.layout.popup_container, launcher.getDragLayer(), false);
-            container.setVisibility(View.INVISIBLE);
-            launcher.getDragLayer().addView(container);
-            container.populateAndShow(icon, shortcutIds, notificationKeys);
-            return container;
-        }
-        return null;
+
+        final PopupContainerWithArrow container =
+                (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
+                        R.layout.popup_container, launcher.getDragLayer(), false);
+        container.setVisibility(View.INVISIBLE);
+        launcher.getDragLayer().addView(container);
+        container.populateAndShow(icon, shortcutIds, notificationKeys);
+        return container;
     }
 
     public void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
@@ -187,6 +189,9 @@
         List<DeepShortcutView> shortcutViews = mShortcutsItemView == null
                 ? Collections.EMPTY_LIST
                 : mShortcutsItemView.getDeepShortcutViews(reverseOrder);
+        List<View> systemShortcutViews = mShortcutsItemView == null
+                ? Collections.EMPTY_LIST
+                : mShortcutsItemView.getSystemShortcutViews(reverseOrder);
         if (mNotificationItemView != null) {
             BadgeInfo badgeInfo = mLauncher.getPopupDataProvider()
                     .getBadgeInfoForItem(originalItemInfo);
@@ -208,7 +213,8 @@
         final Looper workerLooper = LauncherModel.getWorkerLooper();
         new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
                 mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
-                this, shortcutIds, shortcutViews, notificationKeys, mNotificationItemView));
+                this, shortcutIds, shortcutViews, notificationKeys, mNotificationItemView,
+                systemShortcutViews));
     }
 
     private void addDummyViews(BubbleTextView originalIcon,
@@ -216,9 +222,12 @@
         final Resources res = getResources();
         final int spacing = res.getDimensionPixelSize(R.dimen.popup_items_spacing);
         final LayoutInflater inflater = mLauncher.getLayoutInflater();
+
         int numItems = itemTypesToPopulate.length;
         for (int i = 0; i < numItems; i++) {
             PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
+            PopupPopulator.Item nextItemTypeToPopulate =
+                    i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
             final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
 
             if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
@@ -228,23 +237,23 @@
                 item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
             }
 
-            boolean itemIsFollowedByDifferentType = i < numItems - 1
-                    && itemTypesToPopulate[i + 1] != itemTypeToPopulate;
+            boolean shouldAddBottomMargin = nextItemTypeToPopulate != null
+                    && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
 
             item.setAccessibilityDelegate(mAccessibilityDelegate);
-            if (itemTypeToPopulate == PopupPopulator.Item.SHORTCUT) {
+            if (itemTypeToPopulate.isShortcut) {
                 if (mShortcutsItemView == null) {
                     mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
                             R.layout.shortcuts_item, this, false);
                     addView(mShortcutsItemView);
                 }
-                mShortcutsItemView.addDeepShortcutView((DeepShortcutView) item);
-                if (itemIsFollowedByDifferentType) {
+                mShortcutsItemView.addShortcutView(item, itemTypeToPopulate, mIsAboveIcon);
+                if (shouldAddBottomMargin) {
                     ((LayoutParams) mShortcutsItemView.getLayoutParams()).bottomMargin = spacing;
                 }
             } else {
                 addView(item);
-                if (itemIsFollowedByDifferentType) {
+                if (shouldAddBottomMargin) {
                     ((LayoutParams) item.getLayoutParams()).bottomMargin = spacing;
                 }
             }
@@ -531,15 +540,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.
@@ -611,6 +611,16 @@
                 badgeInfo.getNotificationKeys()));
     }
 
+    @Override
+    protected void onWidgetsBound() {
+        enableWidgets();
+    }
+
+    public boolean enableWidgets() {
+        return mShortcutsItemView != null && mShortcutsItemView.enableWidgets(
+                (ItemInfo) mOriginalIcon.getTag());
+    }
+
     private ObjectAnimator createArrowScaleAnim(float scale) {
         return LauncherAnimUtils.ofPropertyValuesHolder(
                 mArrow, new PropertyListBuilder().scale(scale).build());
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 9c4faed..b101bc5 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -48,6 +48,12 @@
     private static final boolean LOGD = false;
     private static final String TAG = "PopupDataProvider";
 
+    /** Note that these are in order of priority. */
+    public static final SystemShortcut[] SYSTEM_SHORTCUTS = new SystemShortcut[] {
+            new SystemShortcut.Widgets(),
+            new SystemShortcut.AppInfo(),
+    };
+
     private final Launcher mLauncher;
 
     /** Maps launcher activity components to their list of shortcut ids. */
diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java
index eeece88..2484426 100644
--- a/src/com/android/launcher3/popup/PopupItemView.java
+++ b/src/com/android/launcher3/popup/PopupItemView.java
@@ -156,7 +156,8 @@
      * Returns the position of the center of the icon relative to the container.
      */
     public Point getIconCenter() {
-        sTempPoint.y = sTempPoint.x = getMeasuredHeight() / 2;
+        sTempPoint.y = getMeasuredHeight() / 2;
+        sTempPoint.x = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height) / 2;
         if (Utilities.isRtl(getResources())) {
             sTempPoint.x = getMeasuredWidth() - sTempPoint.x;
         }
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 9b2141f..a9f219b 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -17,12 +17,15 @@
 package com.android.launcher3.popup;
 
 import android.content.ComponentName;
+import android.content.res.Resources;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
+import android.view.View;
+import android.widget.ImageView;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
@@ -52,13 +55,17 @@
     @VisibleForTesting static final int NUM_DYNAMIC = 2;
 
     public enum Item {
-        SHORTCUT(R.layout.deep_shortcut),
-        NOTIFICATION(R.layout.notification);
+        SHORTCUT(R.layout.deep_shortcut, true),
+        NOTIFICATION(R.layout.notification, false),
+        SYSTEM_SHORTCUT(R.layout.system_shortcut, true),
+        SYSTEM_SHORTCUT_ICON(R.layout.system_shortcut_icon_only, true);
 
         public final int layoutId;
+        public final boolean isShortcut;
 
-        Item(int layoutId) {
+        Item(int layoutId, boolean isShortcut) {
             this.layoutId = layoutId;
+            this.isShortcut = isShortcut;
         }
     }
 
@@ -66,7 +73,8 @@
             @NonNull List<NotificationKeyData> notificationKeys) {
         boolean hasNotifications = notificationKeys.size() > 0;
         int numNotificationItems = hasNotifications ? 1 : 0;
-        int numItems = Math.min(MAX_ITEMS, shortcutIds.size() + numNotificationItems);
+        int numItems = Math.min(MAX_ITEMS, shortcutIds.size() + numNotificationItems)
+                + PopupDataProvider.SYSTEM_SHORTCUTS.length;
         Item[] items = new Item[numItems];
         for (int i = 0; i < numItems; i++) {
             items[i] = Item.SHORTCUT;
@@ -75,6 +83,11 @@
             // The notification layout is always first.
             items[0] = Item.NOTIFICATION;
         }
+        // The system shortcuts are always last.
+        boolean iconsOnly = !shortcutIds.isEmpty();
+        for (int i = 0; i < PopupDataProvider.SYSTEM_SHORTCUTS.length; i++) {
+            items[numItems - 1 - i] = iconsOnly ? Item.SYSTEM_SHORTCUT_ICON : Item.SYSTEM_SHORTCUT;
+        }
         return items;
     }
 
@@ -159,11 +172,11 @@
         return filteredShortcuts;
     }
 
-    public static Runnable createUpdateRunnable(final Launcher launcher, ItemInfo originalInfo,
+    public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
             final Handler uiHandler, final PopupContainerWithArrow container,
             final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
             final List<NotificationKeyData> notificationKeys,
-            final NotificationItemView notificationView) {
+            final NotificationItemView notificationView, final List<View> systemShortcutViews) {
         final ComponentName activity = originalInfo.getTargetComponent();
         final UserHandle user = originalInfo.user;
         return new Runnable() {
@@ -195,11 +208,20 @@
                     uiHandler.post(new UpdateShortcutChild(container, shortcutViews.get(i),
                             si, shortcut));
                 }
+
+                // This ensures that mLauncher.getWidgetsForPackageUser()
+                // doesn't return null (it puts all the widgets in memory).
+                launcher.notifyWidgetProvidersChanged(true /* force */);
+                for (int i = 0; i < PopupDataProvider.SYSTEM_SHORTCUTS.length; i++) {
+                    final SystemShortcut systemShortcut = PopupDataProvider.SYSTEM_SHORTCUTS[i];
+                    uiHandler.post(new UpdateSystemShortcutChild(container,
+                            systemShortcutViews.get(i), systemShortcut, launcher, originalInfo));
+                }
             }
         };
     }
 
-    /** Updates the child of this container at the given index based on the given shortcut info. */
+    /** Updates the shortcut child of this container based on the given shortcut info. */
     private static class UpdateShortcutChild implements Runnable {
         private final PopupContainerWithArrow mContainer;
         private final DeepShortcutView mShortcutChild;
@@ -221,7 +243,7 @@
         }
     }
 
-    /** Updates the child of this container at the given index based on the given shortcut info. */
+    /** Updates the notification child based on the given notification info. */
     private static class UpdateNotificationChild implements Runnable {
         private NotificationItemView mNotificationView;
         private List<NotificationInfo> mNotificationInfos;
@@ -237,4 +259,50 @@
             mNotificationView.applyNotificationInfos(mNotificationInfos);
         }
     }
+
+    /** Updates the system shortcut child based on the given shortcut info. */
+    private static class UpdateSystemShortcutChild implements Runnable {
+        private static final float DISABLED_ALPHA = 0.38f;
+
+        private final PopupContainerWithArrow mContainer;
+        private final View mSystemShortcutChild;
+        private final SystemShortcut mSystemShortcutInfo;
+        private final Launcher mLauncher;
+        private final ItemInfo mItemInfo;
+
+        public UpdateSystemShortcutChild(PopupContainerWithArrow container, View systemShortcutChild,
+                SystemShortcut systemShortcut, Launcher launcher, ItemInfo originalInfo) {
+            mContainer = container;
+            mSystemShortcutChild = systemShortcutChild;
+            mSystemShortcutInfo = systemShortcut;
+            mLauncher = launcher;
+            mItemInfo = originalInfo;
+        }
+
+        @Override
+        public void run() {
+            final Resources res = mSystemShortcutChild.getResources();
+            if (mSystemShortcutChild instanceof DeepShortcutView) {
+                final DeepShortcutView shortcutView = (DeepShortcutView) mSystemShortcutChild;
+                shortcutView.getIconView().setBackground(mSystemShortcutInfo.getIcon(res));
+                shortcutView.getBubbleText().setText(mSystemShortcutInfo.getLabel(res));
+            } else if (mSystemShortcutChild instanceof ImageView) {
+                final ImageView shortcutIcon = (ImageView) mSystemShortcutChild;
+                shortcutIcon.setImageDrawable(mSystemShortcutInfo.getIcon(res));
+                shortcutIcon.setContentDescription(mSystemShortcutInfo.getLabel(res));
+            }
+            if (!(mSystemShortcutInfo instanceof SystemShortcut.Widgets)) {
+                mSystemShortcutChild.setOnClickListener(mSystemShortcutInfo
+                        .getOnClickListener(mLauncher, mItemInfo));
+            } else {
+                mSystemShortcutChild.setTag(mSystemShortcutInfo);
+                // Widgets might not be enabled right away.
+                if (mContainer.enableWidgets()) {
+                    return;
+                }
+                // Disable Widgets (we might be able to re-enable when widgets are bound).
+                mSystemShortcutChild.setAlpha(DISABLED_ALPHA);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
new file mode 100644
index 0000000..d94db43
--- /dev/null
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -0,0 +1,89 @@
+package com.android.launcher3.popup;
+
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import com.android.launcher3.InfoDropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.WidgetsAndMore;
+
+import java.util.List;
+
+/**
+ * Represents a system shortcut for a given app. The shortcut should have a static label and
+ * icon, and an onClickListener that depends on the item that the shortcut services.
+ *
+ * Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
+ */
+public abstract class SystemShortcut {
+    private final int mIconResId;
+    private final int mLabelResId;
+
+    public SystemShortcut(int iconResId, int labelResId) {
+        mIconResId = iconResId;
+        mLabelResId = labelResId;
+    }
+
+    public Drawable getIcon(Resources resources) {
+        Drawable icon = resources.getDrawable(mIconResId);
+        icon.setTint(resources.getColor(R.color.system_shortcuts_icon_color));
+        return icon;
+    }
+
+    public String getLabel(Resources resources) {
+        return resources.getString(mLabelResId);
+    }
+
+    public abstract View.OnClickListener getOnClickListener(final Launcher launcher,
+            final ItemInfo itemInfo);
+
+
+    public static class Widgets extends SystemShortcut {
+
+        public Widgets() {
+            super(R.drawable.ic_widget, R.string.widgets_and_more);
+        }
+
+        @Override
+        public View.OnClickListener getOnClickListener(final Launcher launcher,
+                final ItemInfo itemInfo) {
+            final List<WidgetItem> widgets = launcher.getWidgetsForPackageUser(new PackageUserKey(
+                    itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
+            if (widgets == null) {
+                return null;
+            }
+            return new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    PopupContainerWithArrow.getOpen(launcher).close(true);
+                    WidgetsAndMore widgetsAndMore =
+                            (WidgetsAndMore) launcher.getLayoutInflater().inflate(
+                                    R.layout.widgets_and_more, launcher.getDragLayer(), false);
+                    widgetsAndMore.populateAndShow(itemInfo);
+                }
+            };
+        }
+    }
+
+    public static class AppInfo extends SystemShortcut {
+        public AppInfo() {
+            super(R.drawable.ic_info_launcher, R.string.app_info_drop_target_label);
+        }
+
+        @Override
+        public View.OnClickListener getOnClickListener(final Launcher launcher,
+                final ItemInfo itemInfo) {
+            return new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    InfoDropTarget.startDetailsActivityForInfo(itemInfo, launcher, null);
+                }
+            };
+        }
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 47a023e..75a4886 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -24,6 +24,7 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
@@ -39,7 +40,7 @@
 
     private final Rect mPillRect;
 
-    private DeepShortcutTextView mBubbleText;
+    private BubbleTextView mBubbleText;
     private View mIconView;
 
     private ShortcutInfo mInfo;
@@ -62,11 +63,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mBubbleText = (DeepShortcutTextView) findViewById(R.id.deep_shortcut);
+        mBubbleText = findViewById(R.id.bubble_text);
         mIconView = findViewById(R.id.icon);
     }
 
-    public DeepShortcutTextView getBubbleText() {
+    public BubbleTextView getBubbleText() {
         return mBubbleText;
     }
 
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
index 349c4c9..2255007 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
@@ -36,21 +36,28 @@
 import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.popup.PopupPopulator;
+import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
- * A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app.
+ * A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app,
+ * as well as the system shortcuts such as Widgets and App Info.
  */
 public class ShortcutsItemView extends PopupItemView implements View.OnLongClickListener,
         View.OnTouchListener, LogContainerProvider {
 
     private Launcher mLauncher;
-    private LinearLayout mDeepShortcutsLayout;
+    private LinearLayout mShortcutsLayout;
+    private LinearLayout mSystemShortcutIcons;
     private final Point mIconShift = new Point();
     private final Point mIconLastTouchPos = new Point();
+    private final List<DeepShortcutView> mDeepShortcutViews = new ArrayList<>();
+    private final List<View> mSystemShortcutViews = new ArrayList<>();
 
     public ShortcutsItemView(Context context) {
         this(context, null, 0);
@@ -69,7 +76,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mDeepShortcutsLayout = (LinearLayout) findViewById(R.id.deep_shortcuts);
+        mShortcutsLayout = findViewById(R.id.deep_shortcuts);
     }
 
     @Override
@@ -111,42 +118,81 @@
         return false;
     }
 
-    public void addDeepShortcutView(DeepShortcutView deepShortcutView) {
-        if (getNumDeepShortcuts() > 0) {
-            getDeepShortcutAt(getNumDeepShortcuts() - 1).findViewById(R.id.divider)
-                    .setVisibility(VISIBLE);
+    public void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType,
+            boolean isAboveIcon) {
+        if (shortcutType == PopupPopulator.Item.SHORTCUT) {
+            mDeepShortcutViews.add((DeepShortcutView) shortcutView);
+        } else {
+            mSystemShortcutViews.add(shortcutView);
         }
-        mDeepShortcutsLayout.addView(deepShortcutView);
-    }
-
-    private DeepShortcutView getDeepShortcutAt(int index) {
-        return (DeepShortcutView) mDeepShortcutsLayout.getChildAt(index);
-    }
-
-    private int getNumDeepShortcuts() {
-        return mDeepShortcutsLayout.getChildCount();
+        if (shortcutType == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
+            // System shortcut icons are added to a header that is separate from the full shortcuts.
+            if (mSystemShortcutIcons == null) {
+                mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate(
+                        R.layout.system_shortcut_icons, mShortcutsLayout, false);
+                if (isAboveIcon) {
+                    mShortcutsLayout.addView(mSystemShortcutIcons, 0);
+                } else {
+                    mShortcutsLayout.addView(mSystemShortcutIcons);
+                }
+            }
+            mSystemShortcutIcons.addView(shortcutView);
+        } else {
+            if (mShortcutsLayout.getChildCount() > 0) {
+                View prevChild = mShortcutsLayout.getChildAt(mShortcutsLayout.getChildCount() - 1);
+                if (prevChild instanceof DeepShortcutView) {
+                    prevChild.findViewById(R.id.divider).setVisibility(VISIBLE);
+                }
+            }
+            mShortcutsLayout.addView(shortcutView);
+        }
     }
 
     public List<DeepShortcutView> getDeepShortcutViews(boolean reverseOrder) {
-        int numDeepShortcuts = getNumDeepShortcuts();
-        List<DeepShortcutView> deepShortcutViews = new ArrayList<>(numDeepShortcuts);
-        for (int i = 0; i < numDeepShortcuts; i++) {
-            DeepShortcutView deepShortcut = getDeepShortcutAt(i);
-            if (reverseOrder) {
-                deepShortcutViews.add(0, deepShortcut);
-            } else {
-                deepShortcutViews.add(deepShortcut);
+        if (reverseOrder) {
+            Collections.reverse(mDeepShortcutViews);
+        }
+        return mDeepShortcutViews;
+    }
+
+    public List<View> getSystemShortcutViews(boolean reverseOrder) {
+        if (reverseOrder) {
+            Collections.reverse(mSystemShortcutViews);
+        }
+        return mSystemShortcutViews;
+    }
+
+    /**
+     * Sets the onClickListener on widgets system shortcut child, and updates alpha to 1.
+     * @return whether widgets is enabled, i.e. the onClickListener is not null.
+     */
+    public boolean enableWidgets(ItemInfo itemInfo) {
+        for (View systemShortcut : mSystemShortcutViews) {
+            if (systemShortcut.getTag() instanceof SystemShortcut.Widgets) {
+                View.OnClickListener onClickListener =
+                        ((SystemShortcut.Widgets) systemShortcut.getTag()).getOnClickListener(
+                                mLauncher, itemInfo);
+                if (onClickListener != null) {
+                    systemShortcut.setAlpha(1f);
+                    systemShortcut.setOnClickListener(onClickListener);
+                    return true;
+                }
+                return false;
             }
         }
-        return deepShortcutViews;
+        return false;
     }
 
     @Override
     public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
         AnimatorSet openAnimation = LauncherAnimUtils.createAnimatorSet();
         openAnimation.play(super.createOpenAnimation(isContainerAboveIcon, pivotLeft));
-        for (int i = 0; i < getNumDeepShortcuts(); i++) {
-            View deepShortcutIcon = getDeepShortcutAt(i).getIconView();
+        for (int i = 0; i < mShortcutsLayout.getChildCount(); i++) {
+            if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) {
+                continue;
+            }
+            DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i));
+            View deepShortcutIcon = shortcutView.getIconView();
             deepShortcutIcon.setScaleX(0);
             deepShortcutIcon.setScaleY(0);
             openAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
@@ -160,8 +206,12 @@
             long duration) {
         AnimatorSet closeAnimation = LauncherAnimUtils.createAnimatorSet();
         closeAnimation.play(super.createCloseAnimation(isContainerAboveIcon, pivotLeft, duration));
-        for (int i = 0; i < getNumDeepShortcuts(); i++) {
-            View deepShortcutIcon = getDeepShortcutAt(i).getIconView();
+        for (int i = 0; i < mShortcutsLayout.getChildCount(); i++) {
+            if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) {
+                continue;
+            }
+            DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i));
+            View deepShortcutIcon = shortcutView.getIconView();
             deepShortcutIcon.setScaleX(1);
             deepShortcutIcon.setScaleY(1);
             closeAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java
index 3fb2401..1ce2822 100644
--- a/src/com/android/launcher3/util/PackageUserKey.java
+++ b/src/com/android/launcher3/util/PackageUserKey.java
@@ -11,8 +11,8 @@
 /** Creates a hash key based on package name and user. */
 public class PackageUserKey {
 
-    private String mPackageName;
-    private UserHandle mUser;
+    public String mPackageName;
+    public UserHandle mUser;
     private int mHashCode;
 
     public static PackageUserKey fromItemInfo(ItemInfo info) {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 3bf622e..40dbd52 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,22 +150,38 @@
         return mWidgetImage;
     }
 
+    public void setAnimatePreview(boolean shouldAnimate) {
+        mAnimatePreview = shouldAnimate;
+    }
+
     public void applyPreview(Bitmap bitmap) {
+        applyPreview(bitmap, true);
+    }
+
+    public void applyPreview(Bitmap bitmap, boolean animate) {
         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);
+            }
         }
     }
 
     public void ensurePreview() {
+        ensurePreview(true);
+    }
+
+    public void ensurePreview(boolean animate) {
         if (mActiveRequest != null) {
             return;
         }
         mActiveRequest = mWidgetPreviewLoader.getPreview(
-                mItem, mPresetPreviewSize, mPresetPreviewSize, this);
+                mItem, mPresetPreviewSize, mPresetPreviewSize, this, animate);
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/WidgetsAndMore.java b/src/com/android/launcher3/widget/WidgetsAndMore.java
new file mode 100644
index 0000000..1aea534
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsAndMore.java
@@ -0,0 +1,341 @@
+/*
+ * 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.PackageUserKey;
+import com.android.launcher3.util.TouchController;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import static android.R.attr.bottom;
+
+/**
+ * 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 ItemInfo mOriginalItemInfo;
+    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) {
+        mOriginalItemInfo = itemInfo;
+        ((TextView) findViewById(R.id.title)).setText(mOriginalItemInfo.title);
+
+        onWidgetsBound();
+
+        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);
+    }
+
+    @Override
+    protected void onWidgetsBound() {
+        List<WidgetItem> widgets = mLauncher.getWidgetsForPackageUser(new PackageUserKey(
+                mOriginalItemInfo.getTargetComponent().getPackageName(), mOriginalItemInfo.user));
+        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);
+
+        widgetCells.removeAllViews();
+        shortcutCells.removeAllViews();
+
+        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));
+        }
+    }
+
+    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..4e296bf 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;
@@ -43,8 +40,11 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Thunk;
 
+import java.util.List;
+
 /**
  * The widgets list view container.
  */
@@ -55,8 +55,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 +74,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 +112,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 +130,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;
 
@@ -241,6 +246,10 @@
         return mAdapter.getItemCount() == 0;
     }
 
+    public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
+        return mAdapter.copyWidgetsForPackageUser(packageUserKey);
+    }
+
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         targetParent.containerType = ContainerType.WIDGETS;
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 5f789a2..a1eb0ab 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -31,10 +31,12 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.LabelComparator;
 import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -98,6 +100,26 @@
         return mEntries.get(pos).titleSectionName;
     }
 
+    /**
+     * Copies and returns the widgets associated with the package and user of the ComponentKey.
+     */
+    public List<WidgetItem> copyWidgetsForPackageUser(PackageUserKey packageUserKey) {
+        for (WidgetListRowEntry entry : mEntries) {
+            if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) {
+                ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets);
+                // Remove widgets not associated with the correct user.
+                Iterator<WidgetItem> iterator = widgets.iterator();
+                while (iterator.hasNext()) {
+                    if (!iterator.next().user.equals(packageUserKey.mUser)) {
+                        iterator.remove();
+                    }
+                }
+                return widgets.isEmpty() ? null : widgets;
+            }
+        }
+        return null;
+    }
+
     @Override
     public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
         WidgetListRowEntry entry = mEntries.get(pos);
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
index ee3a628..3a0b613 100644
--- a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
+++ b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
@@ -60,7 +60,7 @@
         // Verify that launching a shortcut opens a page with the same text
         assertTrue(deepShortcutsContainer.getChildCount() > 0);
         UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0)
-                .findObject(getSelectorForId(R.id.deep_shortcut));
+                .findObject(getSelectorForId(R.id.bubble_text));
         shortcut.click();
         assertTrue(mDevice.wait(Until.hasObject(By.pkg(
                 mSettingsApp.getComponentName().getPackageName())
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
index 061e865..5d86d1e 100644
--- a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
@@ -61,7 +61,7 @@
         // Drag the first shortcut to the home screen.
         assertTrue(deepShortcutsContainer.getChildCount() > 0);
         UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0)
-                .findObject(getSelectorForId(R.id.deep_shortcut));
+                .findObject(getSelectorForId(R.id.bubble_text));
         String shortcutName = shortcut.getText();
         dragToWorkspace(shortcut, false);