Merge "Fixing Virtual nodes getting wrong screen bounds, when dragging in spring loaded mode" into ub-launcher3-calgary
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 27ff789..632aff0 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -60,6 +60,12 @@
             android:layout_height="@dimen/dynamic_grid_page_indicator_height"
             android:layout_gravity="bottom|left"/>
 
+        <!-- A place holder view instead of the QSB in transposed layout -->
+        <View
+            android:layout_width="0dp"
+            android:layout_height="10dp"
+            android:id="@+id/workspace_blocked_row" />
+
         <include layout="@layout/widgets_view"
             android:id="@+id/widgets_view"
             android:layout_width="match_parent"
@@ -71,6 +77,7 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:visibility="invisible" />
+
     </com.android.launcher3.dragndrop.DragLayer>
 
 </com.android.launcher3.LauncherRootView>
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index 6b5bf63..0321631 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -37,6 +37,7 @@
             android:id="@+id/workspace"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
+            android:layout_gravity="center"
             launcher:pageIndicator="@+id/page_indicator">
         </com.android.launcher3.Workspace>
 
@@ -61,6 +62,10 @@
             android:id="@+id/drop_target_bar"
             layout="@layout/drop_target_bar_horz" />
 
+        <include
+            layout="@layout/qsb_container"
+            android:id="@+id/qsb_container" />
+
         <include layout="@layout/widgets_view"
             android:id="@+id/widgets_view"
             android:layout_width="match_parent"
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 33ad323..86544d3 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -33,6 +33,7 @@
         <!-- The workspace contains 5 screens of cells -->
         <!-- DO NOT CHANGE THE ID -->
         <com.android.launcher3.Workspace
+            android:layout_gravity="center"
             android:id="@+id/workspace"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
@@ -60,6 +61,10 @@
             android:layout_width="match_parent"
             android:layout_height="@dimen/dynamic_grid_page_indicator_height" />
 
+        <include
+            layout="@layout/qsb_container"
+            android:id="@+id/qsb_container" />
+
         <include layout="@layout/widgets_view"
             android:id="@+id/widgets_view"
             android:layout_width="match_parent"
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index d55fda7..c4c6aab 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -31,7 +31,6 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_gravity="center"
-        android:elevation="2dp"
         android:focusable="false"
         android:visibility="invisible" />
 
@@ -41,7 +40,6 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_gravity="center"
-        android:elevation="15dp"
         android:focusable="true"
         android:focusableInTouchMode="true"
         android:saveEnabled="false"
diff --git a/res/layout/qsb_container.xml b/res/layout/qsb_container.xml
index 55c7390..b75e3b5 100644
--- a/res/layout/qsb_container.xml
+++ b/res/layout/qsb_container.xml
@@ -18,7 +18,7 @@
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:orientation="vertical"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_height="0dp"
         android:id="@+id/qsb_container"
         android:padding="0dp" >
 
diff --git a/res/values/config.xml b/res/values/config.xml
index f69fb2e..0bb3799 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -74,6 +74,9 @@
     <!-- View ID to use for QSB widget -->
     <item type="id" name="qsb_widget" />
 
+    <!-- View ID to use for blocked area on the first screen -->
+    <item type="id" name="workspace_blocked_row" />
+
     <!-- View ID used by cell layout to jail its content -->
     <item type="id" name="cell_layout_jail_id" />
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1ac6620..7bd9ff7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -112,6 +112,8 @@
     <string name="folder_hint_text">Unnamed Folder</string>
 
     <!-- Accessibility -->
+    <!-- The format string for when an app is temporarily disabled. -->
+    <string name="disabled_app_label">Disabled <xliff:g id="app_name" example="Messenger">%1$s</xliff:g></string>
     <skip />
 
     <!-- The format string for default page scroll text [CHAR_LIMIT=none] -->
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ca60d5c..33e4e2a 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -153,34 +153,16 @@
 
     public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
             boolean promiseStateChanged) {
-        Bitmap b = info.getIcon(iconCache);
-
-        FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(b);
-        if (info.isDisabled()) {
-            iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
-        }
-        setIcon(iconDrawable);
-        if (info.contentDescription != null) {
-            setContentDescription(info.contentDescription);
-        }
-        setText(info.title);
+        applyIconAndLabel(info.getIcon(iconCache), info);
         setTag(info);
-
         if (promiseStateChanged || info.isPromise()) {
             applyState(promiseStateChanged);
         }
     }
 
     public void applyFromApplicationInfo(AppInfo info) {
-        FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(info.iconBitmap);
-        if (info.isDisabled()) {
-            iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
-        }
-        setIcon(iconDrawable);
-        setText(info.title);
-        if (info.contentDescription != null) {
-            setContentDescription(info.contentDescription);
-        }
+        applyIconAndLabel(info.iconBitmap, info);
+
         // We don't need to check the info since it's not a ShortcutInfo
         super.setTag(info);
 
@@ -189,11 +171,7 @@
     }
 
     public void applyFromPackageItemInfo(PackageItemInfo info) {
-        setIcon(mLauncher.createIconDrawable(info.iconBitmap));
-        setText(info.title);
-        if (info.contentDescription != null) {
-            setContentDescription(info.contentDescription);
-        }
+        applyIconAndLabel(info.iconBitmap, info);
         // We don't need to check the info since it's not a ShortcutInfo
         super.setTag(info);
 
@@ -201,6 +179,20 @@
         verifyHighRes();
     }
 
+    private void applyIconAndLabel(Bitmap icon, ItemInfo info) {
+        FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(icon);
+        if (info.isDisabled()) {
+            iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
+        }
+        setIcon(iconDrawable);
+        setText(info.title);
+        if (info.contentDescription != null) {
+            setContentDescription(info.isDisabled()
+                    ? getContext().getString(R.string.disabled_app_label, info.contentDescription)
+                    : info.contentDescription);
+        }
+    }
+
     /**
      * Used for measurement only, sets some dummy values on this view.
      */
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 6755ff7..9030dae 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -918,10 +918,6 @@
                 bottom + mTempRect.bottom);
     }
 
-    public Rect getBackgroundBounds() {
-        return mBackground.getBounds();
-    }
-
     /**
      * Returns the amount of space left over after subtracting padding and cells. This space will be
      * very small, a few pixels at most, and is a result of rounding down when calculating the cell
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 72bb343..2b130e5 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -296,7 +296,7 @@
     }
 
     public Point getTotalWorkspacePadding() {
-        Rect padding = getWorkspacePadding();
+        Rect padding = getWorkspacePadding(null);
         return new Point(padding.left + padding.right, padding.top + padding.bottom);
     }
 
@@ -306,8 +306,8 @@
      * this value is not reliable.
      * Use {@link #getTotalWorkspacePadding()} instead.
      */
-    public Rect getWorkspacePadding() {
-        Rect padding = new Rect();
+    public Rect getWorkspacePadding(Rect recycle) {
+        Rect padding = recycle == null ? new Rect() : recycle;
         if (isVerticalBarLayout()) {
             // in case of isVerticalBarLayout, the hotseat is always on the right and the drop
             // target bar is on the left, independent of the layout direction.
@@ -348,7 +348,7 @@
             // In portrait, we want the pages spaced such that there is no
             // overhang of the previous / next page into the current page viewport.
             // We assume symmetrical padding in portrait mode.
-            return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
+            return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding(null).left);
         }
     }
 
@@ -405,13 +405,15 @@
 
         // Layout the workspace
         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
-        lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
-        lp.gravity = Gravity.CENTER;
-        Rect padding = getWorkspacePadding();
-        workspace.setLayoutParams(lp);
+        Rect padding = getWorkspacePadding(null);
         workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
         workspace.setPageSpacing(getWorkspacePageSpacing());
 
+        View qsbContainer = launcher.getQsbContainer();
+        lp = (FrameLayout.LayoutParams) qsbContainer.getLayoutParams();
+        lp.topMargin = padding.top;
+        qsbContainer.setLayoutParams(lp);
+
         // Layout the hotseat
         View hotseat = launcher.findViewById(R.id.hotseat);
         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 763daf4..b7f033e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -116,6 +116,7 @@
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -234,6 +235,7 @@
     private View mLauncherView;
     @Thunk DragLayer mDragLayer;
     private DragController mDragController;
+    private View mQsbContainer;
 
     public View mWeightWatcher;
 
@@ -1245,6 +1247,9 @@
                 // Close any open folders
                 closeFolder();
 
+                // Close any shortcuts containers
+                closeShortcutsContainer();
+
                 // Stop resizing any widgets
                 mWorkspace.exitWidgetResizeMode();
 
@@ -1329,6 +1334,8 @@
         mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
         mFocusHandler = mDragLayer.getFocusIndicatorHelper();
         mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
+        mQsbContainer = mDragLayer.findViewById(mDeviceProfile.isVerticalBarLayout()
+                ? R.id.workspace_blocked_row : R.id.qsb_container);
         mWorkspace.initParentViews(mDragLayer);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -1779,6 +1786,10 @@
         return mWorkspace;
     }
 
+    public View getQsbContainer() {
+        return mQsbContainer;
+    }
+
     public Hotseat getHotseat() {
         return mHotseat;
     }
@@ -1840,6 +1851,7 @@
             mWorkspace.exitWidgetResizeMode();
 
             closeFolder(alreadyOnHome);
+            closeShortcutsContainer();
             exitSpringLoadedDragMode();
 
             // If we are already on home, then just animate back to the workspace,
@@ -1927,6 +1939,8 @@
         // TODO: Move folderInfo.isOpened out of the model and make it a UI state.
         closeFolder(false);
 
+        closeShortcutsContainer();
+
         if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
                 mWaitingForResult) {
             ContentValues itemValues = new ContentValues();
@@ -2423,7 +2437,9 @@
             return;
         }
 
-        if (isAppsViewVisible()) {
+        if (getOpenShortcutsContainer() != null) {
+            closeShortcutsContainer();
+        } else if (isAppsViewVisible()) {
             showWorkspace(true);
         } else if (isWidgetsViewVisible())  {
             showOverviewMode(true);
@@ -3090,6 +3106,21 @@
         getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
     }
 
+    public void closeShortcutsContainer() {
+        DeepShortcutsContainer deepShortcutsContainer = getOpenShortcutsContainer();
+        if (deepShortcutsContainer != null) {
+            mDragController.removeDragListener(deepShortcutsContainer);
+            mDragLayer.removeView(deepShortcutsContainer);
+        }
+    }
+
+    /**
+     * @return The open shortcuts container, or null if there is none
+     */
+    public DeepShortcutsContainer getOpenShortcutsContainer() {
+        return (DeepShortcutsContainer) mDragLayer.findViewById(R.id.deep_shortcuts_container);
+    }
+
     @Override
     public boolean onLongClick(View v) {
         if (!isDraggingEnabled()) return false;
@@ -3351,6 +3382,7 @@
         mUserPresent = false;
         updateAutoAdvanceState();
         closeFolder();
+        closeShortcutsContainer();
 
         // Send an accessibility event to announce the context change
         getWindow().getDecorView()
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index 7c8bfab..ed1079f 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -30,7 +30,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.RemoteViews;
 
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener;
 
 import java.util.ArrayList;
@@ -47,7 +46,6 @@
     private Context mContext;
     @ViewDebug.ExportedProperty(category = "launcher")
     private int mPreviousOrientation;
-    private DragLayer mDragLayer;
 
     private float mSlop;
 
@@ -62,9 +60,7 @@
         mLongPressHelper = new CheckLongPressHelper(this);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
         mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mDragLayer = Launcher.getLauncher(context).getDragLayer();
         setAccessibilityDelegate(Launcher.getLauncher(context).getAccessibilityDelegate());
-
         setBackgroundResource(R.drawable.widget_internal_focus_bg);
     }
 
@@ -117,7 +113,7 @@
                 if (!mStylusEventHelper.inStylusButtonPressed()) {
                     mLongPressHelper.postCheckForLongPress();
                 }
-                mDragLayer.setTouchCompleteListener(this);
+                Launcher.getLauncher(getContext()).getDragLayer().setTouchCompleteListener(this);
                 break;
             }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 39e28c0..a75edb7 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -71,10 +71,10 @@
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.ManagedProfileHeuristic;
+import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.StringFilter;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 
@@ -1942,7 +1942,16 @@
                                         List<ShortcutInfoCompat> fullDetails = mDeepShortcutManager
                                                 .queryForFullDetails(packageName,
                                                 Collections.singletonList(shortcutId), user);
-                                        if (fullDetails != null && !fullDetails.isEmpty()) {
+                                        if (fullDetails == null || fullDetails.isEmpty()) {
+                                            // There are no details for the shortcut. If this is due
+                                            // to a SecurityException, keep it in the database so
+                                            // we can restore the icon when the launcher regains
+                                            // permission. Otherwise remove the icon from the db.
+                                            if (!mDeepShortcutManager.wasLastCallSuccess()) {
+                                                itemsToRemove.add(id);
+                                                continue;
+                                            }
+                                        } else {
                                             pinnedShortcut = fullDetails.get(0);
                                             shouldPin = true;
                                         }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index a37fe5b..2758a7c 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -145,9 +145,6 @@
 
     protected int mActivePointerId = INVALID_POINTER;
 
-    // If true, modify alpha of neighboring pages as user scrolls left/right
-    protected boolean mFadeInAdjacentScreens = false;
-
     protected boolean mIsPageMoving = false;
 
     protected boolean mWasInOverscroll = false;
@@ -756,10 +753,6 @@
 
                     childWidth = getViewportWidth() - horizontalPadding
                             - mInsets.left - mInsets.right;
-
-                    if (lp.matchStartEdge) {
-                        childWidth += getPaddingStart();
-                    }
                     childHeight = getViewportHeight() - verticalPadding
                             - mInsets.top - mInsets.bottom;
                     mNormalChildHeight = childHeight;
@@ -809,8 +802,7 @@
         LayoutParams lp = (LayoutParams) getChildAt(startIndex).getLayoutParams();
         LayoutParams nextLp;
 
-        int childLeft = offsetX +
-                ((lp.isFullScreenPage || (!mIsRtl && lp.matchStartEdge)) ? 0 : getPaddingLeft());
+        int childLeft = offsetX + (lp.isFullScreenPage ? 0 : getPaddingLeft());
         if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
             mPageScrolls = new int[childCount];
         }
@@ -834,8 +826,7 @@
                 child.layout(childLeft, childTop,
                         childLeft + child.getMeasuredWidth(), childTop + childHeight);
 
-                int scrollOffsetLeft = (lp.isFullScreenPage || (!mIsRtl & lp.matchStartEdge)) ?
-                        0 : getPaddingLeft();
+                int scrollOffsetLeft = lp.isFullScreenPage ? 0 : getPaddingLeft();
                 mPageScrolls[i] = childLeft - scrollOffsetLeft - offsetX;
 
                 int pageGap = mPageSpacing;
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
index c1c2519..3807945 100644
--- a/src/com/android/launcher3/PinchAnimationManager.java
+++ b/src/com/android/launcher3/PinchAnimationManager.java
@@ -51,8 +51,8 @@
     private static final int THRESHOLD_ANIM_DURATION = 150;
     private static final LinearInterpolator INTERPOLATOR = new LinearInterpolator();
 
-    private static final int INDEX_PAGE_INDICATOR = 0;
-    private static final int INDEX_HOTSEAT = 1;
+    private static final int INDEX_HOTSEAT = 0;
+    private static final int INDEX_QSB = 1;
     private static final int INDEX_OVERVIEW_PANEL_BUTTONS = 2;
     private static final int INDEX_SCRIM = 3;
 
@@ -189,11 +189,10 @@
     }
 
     private void animateHotseatAndPageIndicator(boolean show) {
-        animateShowHideView(INDEX_HOTSEAT, mLauncher.getHotseat(), show);
-        if (mWorkspace.getPageIndicator() != null) {
-            // There aren't page indicators in landscape mode on phones, hence the null check.
-            animateShowHideView(INDEX_PAGE_INDICATOR, mWorkspace.getPageIndicator(), show);
-        }
+        startAnimator(INDEX_HOTSEAT,
+                mWorkspace.createHotseatAlphaAnimator(show ? 1 : 0), THRESHOLD_ANIM_DURATION);
+        startAnimator(INDEX_QSB, mWorkspace.mQsbAlphaController.animateAlphaAtIndex(
+                show ? 1 : 0, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE), THRESHOLD_ANIM_DURATION);
     }
 
     private void animateOverviewPanelButtons(boolean show) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 1b3f5df..8d46719 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -53,9 +53,11 @@
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
+import android.widget.Space;
 import android.widget.TextView;
 
 import com.android.launcher3.Launcher.CustomContentCallbacks;
@@ -82,6 +84,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.MultiStateAlphaController;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperOffsetInterpolator;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -227,6 +230,12 @@
      */
     private float[] mHotseatAlpha = new float[] {1, 1, 1};
 
+    public static final int QSB_ALPHA_INDEX_STATE_CHANGE = 0;
+    public static final int QSB_ALPHA_INDEX_Y_TRANSLATION = 1;
+    public static final int QSB_ALPHA_INDEX_PAGE_SCROLL = 2;
+
+    MultiStateAlphaController mQsbAlphaController;
+
     @ViewDebug.ExportedProperty(category = "launcher")
     private State mState = State.NORMAL;
     private boolean mIsSwitchingState = false;
@@ -305,6 +314,7 @@
     private boolean mForceDrawAdjacentPages = false;
     // Total over scrollX in the overlay direction.
     private float mOverlayTranslation;
+    private int mFirstPageScrollX;
 
     // Handles workspace state transitions
     private WorkspaceStateTransitionAnimation mStateTransitionAnimation;
@@ -339,7 +349,6 @@
         final Resources res = getResources();
         DeviceProfile grid = mLauncher.getDeviceProfile();
         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
-        mFadeInAdjacentScreens = false;
         mWallpaperManager = WallpaperManager.getInstance(context);
 
         mWallpaperOffset = new WallpaperOffsetInterpolator(this);
@@ -484,6 +493,7 @@
     public void initParentViews(View parent) {
         super.initParentViews(parent);
         mPageIndicator.setAccessibilityDelegate(new OverviewAccessibilityDelegate());
+        mQsbAlphaController = new MultiStateAlphaController(mLauncher.getQsbContainer(), 3);
     }
 
     private int getDefaultPage() {
@@ -548,6 +558,11 @@
         return mTouchState != TOUCH_STATE_REST;
     }
 
+    private int getEmbeddedQsbId() {
+        return mLauncher.getDeviceProfile().isVerticalBarLayout()
+                ? R.id.qsb_container : R.id.workspace_blocked_row;
+    }
+
     /**
      * Initializes and binds the first page
      * @param qsb an exisitng qsb to recycle or null.
@@ -559,31 +574,45 @@
         // Add the first page
         CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0);
 
-        if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            // Let the cell layout extend the start padding. On transposed layout, there is page
-            // indicator on left and hotseat on right, as such workspace does not touch the edge.
-            ((LayoutParams) firstPage.getLayoutParams()).matchStartEdge = true;
-            firstPage.setPaddingRelative(getPaddingStart(), 0, 0, 0);
-        }
-
+        // Always add a QSB on the first screen.
         if (qsb == null) {
-            // Always add a QSB on the first screen.
-            qsb = mLauncher.getLayoutInflater().inflate(R.layout.qsb_container,
-                    firstPage, false);
+            // In transposed layout, we add the QSB in the Grid. As workspace does not touch the
+            // edges, we do not need a full width QSB.
+            if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+                qsb = mLauncher.getLayoutInflater().inflate(R.layout.qsb_container, firstPage, false);
+            } else {
+                qsb = new Space(getContext());
+            }
         }
 
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) qsb.getLayoutParams();
-        lp.cellX = 0;
-        lp.cellY = 0;
-        lp.cellHSpan = firstPage.getCountX();
-        lp.cellVSpan = 1;
+        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 1);
         lp.canReorder = false;
-
-        if (!firstPage.addViewToCellLayout(qsb, 0, R.id.qsb_container, lp, true)) {
+        if (!firstPage.addViewToCellLayout(qsb, 0, getEmbeddedQsbId(), lp, true)) {
             Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
         }
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        // Update the QSB to match the cell height. This is treating the QSB essentially as a child
+        // of workspace despite that it's not a true child.
+        // Note that it relies on the strict ordering of measuring the workspace before the QSB
+        // at the dragLayer level.
+        if (getChildCount() > 0) {
+            CellLayout firstPage = (CellLayout) getChildAt(0);
+            int cellHeight = firstPage.getCellHeight();
+
+            View qsbContainer = mLauncher.getQsbContainer();
+            ViewGroup.LayoutParams lp = qsbContainer.getLayoutParams();
+            if (cellHeight > 0 && lp.height != cellHeight) {
+                lp.height = cellHeight;
+            }
+            qsbContainer.setLayoutParams(lp);
+        }
+    }
+
     public void removeAllWorkspaceScreens() {
         // Disable all layout transitions before removing all pages to ensure that we don't get the
         // transition animations competing with us changing the scroll when we add pages or the
@@ -597,7 +626,7 @@
         }
 
         // Recycle the QSB widget
-        View qsb = findViewById(R.id.qsb_container);
+        View qsb = findViewById(getEmbeddedQsbId());
         if (qsb != null) {
             ((ViewGroup) qsb.getParent()).removeView(qsb);
         }
@@ -1366,9 +1395,15 @@
         super.scrollTo(x, y);
     }
 
+    private void onWorkspaceOverallScrollChanged() {
+        mLauncher.getQsbContainer().setTranslationX(
+                mOverlayTranslation + mFirstPageScrollX - getScrollX());
+    }
+
     @Override
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
         super.onScrollChanged(l, t, oldl, oldt);
+        onWorkspaceOverallScrollChanged();
 
         // Update the page indicator progress.
         boolean isTransitioning = mIsSwitchingState
@@ -1438,6 +1473,19 @@
         // device I've tried, translating the launcher causes things to get quite laggy.
         setWorkspaceTranslationAndAlpha(Direction.X, transX, alpha);
         setHotseatTranslationAndAlpha(Direction.X, transX, alpha);
+        onWorkspaceOverallScrollChanged();
+    }
+
+    /**
+     * Moves the workspace UI in the Y direction.
+     * @param translation the amount of shift.
+     * @param alpha the alpha for the workspace page
+     */
+    public void setWorkspaceYTranslationAndAlpha(float translation, float alpha) {
+        setWorkspaceTranslationAndAlpha(Direction.Y, translation, alpha);
+
+        mLauncher.getQsbContainer().setTranslationY(translation);
+        mQsbAlphaController.setAlphaAtIndex(alpha, QSB_ALPHA_INDEX_Y_TRANSLATION);
     }
 
     /**
@@ -1446,7 +1494,7 @@
      * @param translation the amount of shift.
      * @param alpha the alpha for the workspace page
      */
-    public void setWorkspaceTranslationAndAlpha(Direction direction, float translation, float alpha) {
+    private void setWorkspaceTranslationAndAlpha(Direction direction, float translation, float alpha) {
         Property<View, Float> property = direction.viewProperty;
         mPageAlpha[direction.ordinal()] = alpha;
         float finalAlpha = mPageAlpha[0] * mPageAlpha[1];
@@ -1630,6 +1678,10 @@
                     float scrollProgress = getScrollProgress(screenCenter, child, i);
                     float alpha = 1 - Math.abs(scrollProgress);
                     child.getShortcutsAndWidgets().setAlpha(alpha);
+
+                    if (isQsbContainerPage(i)) {
+                        mQsbAlphaController.setAlphaAtIndex(alpha, QSB_ALPHA_INDEX_PAGE_SCROLL);
+                    }
                 }
             }
         }
@@ -1751,6 +1803,8 @@
             mWallpaperOffset.jumpToFinal();
         }
         super.onLayout(changed, left, top, right, bottom);
+        mFirstPageScrollX = getScrollForPage(0);
+        onWorkspaceOverallScrollChanged();
     }
 
     @Override
@@ -2027,10 +2081,10 @@
 
     int getOverviewModeTranslationY() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        Rect workspacePadding = grid.getWorkspacePadding();
         int overviewButtonBarHeight = grid.getOverviewModeButtonBarHeight();
 
         int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
+        Rect workspacePadding = grid.getWorkspacePadding(sTempRect);
         int workspaceTop = mInsets.top + workspacePadding.top;
         int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom;
         int overviewTop = mInsets.top;
@@ -2045,12 +2099,12 @@
         if (grid.isVerticalBarLayout() || getChildCount() == 0) {
             return 0;
         }
-        Rect workspacePadding = grid.getWorkspacePadding();
 
         float scaledHeight = grid.workspaceSpringLoadShrinkFactor * getNormalChildHeight();
         float shrunkTop = mInsets.top + grid.dropTargetBarSizePx;
         float shrunkBottom = getViewportHeight() - mInsets.bottom
-                - workspacePadding.bottom - grid.workspaceSpringLoadedBottomSpace;
+                - grid.getWorkspacePadding(sTempRect).bottom
+                - grid.workspaceSpringLoadedBottomSpace;
         float totalShrunkSpace = shrunkBottom - shrunkTop;
 
         float desiredCellTop = shrunkTop + (totalShrunkSpace - scaledHeight) / 2;
@@ -4523,4 +4577,8 @@
          */
         void prepareStateChange(State toState, AnimatorSet targetAnim);
     }
+
+    public static final boolean isQsbContainerPage(int pageNo) {
+        return pageNo == 0;
+    }
 }
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index a73f3ec..c2631b0 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -276,6 +276,8 @@
         float finalHotseatAlpha = (states.stateIsNormal || states.stateIsSpringLoaded ||
                 (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && states.stateIsNormalHidden)) ? 1f : 0f;
         float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f;
+        float finalQsbAlpha = (states.stateIsNormal ||
+                (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && states.stateIsNormalHidden)) ? 1f : 0f;
 
         float finalWorkspaceTranslationY = 0;
         if (states.stateIsOverview || states.stateIsOverviewHidden) {
@@ -355,9 +357,28 @@
                 cl.setBackgroundAlpha(finalBackgroundAlpha);
                 cl.setShortcutAndWidgetAlpha(finalAlpha);
             }
+
+            if (Workspace.isQsbContainerPage(i)) {
+                if (animated) {
+                    Animator anim = mWorkspace.mQsbAlphaController
+                            .animateAlphaAtIndex(finalAlpha, Workspace.QSB_ALPHA_INDEX_PAGE_SCROLL);
+                    anim.setDuration(duration);
+                    anim.setInterpolator(mZoomInInterpolator);
+                    mStateAnimator.play(anim);
+                } else {
+                    mWorkspace.mQsbAlphaController.setAlphaAtIndex(
+                            finalAlpha, Workspace.QSB_ALPHA_INDEX_PAGE_SCROLL);
+                }
+            }
         }
 
         final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
+
+        final View qsbContainer = mLauncher.getQsbContainer();
+
+        Animator qsbAlphaAnimation = mWorkspace.mQsbAlphaController
+                .animateAlphaAtIndex(finalQsbAlpha, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE);
+
         if (animated) {
             LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(mWorkspace);
             scale.scaleX(mNewScale)
@@ -376,10 +397,13 @@
             // For animation optimations, we may need to provide the Launcher transition
             // with a set of views on which to force build layers in certain scenarios.
             overviewPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            qsbContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
             if (layerViews != null) {
                 // If layerViews is not null, we add these views, and indicate that
                 // the caller can manage layer state.
                 layerViews.put(overviewPanel, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+                layerViews.put(qsbContainer, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+
                 layerViews.put(mLauncher.getHotseat(),
                         LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
                 layerViews.put(mWorkspace.getPageIndicator(),
@@ -399,9 +423,11 @@
 
             overviewPanelAlpha.setDuration(duration);
             hotseatAlpha.setDuration(duration);
+            qsbAlphaAnimation.setDuration(duration);
 
             mStateAnimator.play(overviewPanelAlpha);
             mStateAnimator.play(hotseatAlpha);
+            mStateAnimator.play(qsbAlphaAnimation);
             mStateAnimator.addListener(new AnimatorListenerAdapter() {
                 boolean canceled = false;
                 @Override
@@ -422,6 +448,8 @@
         } else {
             overviewPanel.setAlpha(finalOverviewPanelAlpha);
             AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled);
+
+            qsbAlphaAnimation.end();
             mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha).end();
             mWorkspace.updateCustomContentVisibility();
             mWorkspace.setScaleX(mNewScale);
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 028f065..e7108a1 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -291,7 +291,7 @@
                 mDecelInterpolator.getInterpolation(alpha))));
         mAppsView.getContentView().setAlpha(alpha);
         mAppsView.setTranslationY(progress);
-        mWorkspace.setWorkspaceTranslationAndAlpha(Direction.Y,
+        mWorkspace.setWorkspaceYTranslationAndAlpha(
                 PARALLAX_COEFFICIENT * (-mShiftRange + progress),
                 mAccelInterpolator.getInterpolation(workspaceHotseatAlpha));
         if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index ce97536..5f8faab 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -231,8 +231,7 @@
         }
 
         // Remove the shortcuts container when touching outside of it.
-        DeepShortcutsContainer deepShortcutsContainer = (DeepShortcutsContainer)
-                findViewById(R.id.deep_shortcuts_container);
+        DeepShortcutsContainer deepShortcutsContainer = mLauncher.getOpenShortcutsContainer();
         if (deepShortcutsContainer != null) {
             if (isEventOverView(deepShortcutsContainer, ev)) {
                 // Let the container handle the event.
@@ -244,7 +243,7 @@
                         return true;
                     }
                 } else {
-                    removeView(deepShortcutsContainer);
+                    mLauncher.closeShortcutsContainer();
                     // We let touches on the original icon go through so that users can launch
                     // the app with one tap if they don't find a shortcut they want.
                     return !isEventOverView(deepShortcutsContainer.getDeferredDragIcon(), ev);
@@ -547,10 +546,6 @@
         return new LayoutParams(p);
     }
 
-    public void setController(TouchController controller) {
-        mActiveController = controller;
-    }
-
     public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
         public int x, y;
         public boolean customPosition = false;
@@ -752,13 +747,14 @@
      *        location doesn't account for scaling, and so should be centered about the desired
      *        final location (including scaling).
      * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
-     * @param finalScale The final scale of the view. The view is scaled about its center.
+     * @param finalScaleX The final scale of the view. The view is scaled about its center.
+     * @param finalScaleY The final scale of the view. The view is scaled about its center.
      * @param duration The duration of the animation.
      * @param motionInterpolator The interpolator to use for the location of the view.
      * @param alphaInterpolator The interpolator to use for the alpha of the view.
      * @param onCompleteRunnable Optional runnable to run on animation completion.
-     * @param fadeOut Whether or not to fade out the view once the animation completes. If true,
-     *        the runnable will execute after the view is faded out.
+     * @param animationEndStyle Whether or not to fade out the view once the animation completes.
+     *        {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}.
      * @param anchorView If not null, this represents the view which the animated view stays
      *        anchored to in case scrolling is currently taking place. Note: currently this is
      *        only used for the X dimension for the case of the workspace.
@@ -993,12 +989,7 @@
             canvas.save();
             if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
                 // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
-                float scale = getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
-                Rect backBounds = currCellLayout.getBackgroundBounds();
-                mHighlightRect.left += (int) (backBounds.left * scale);
-                mHighlightRect.top += (int) (backBounds.top * scale);
-                mHighlightRect.right = (int) (mHighlightRect.left + backBounds.width() * scale);
-                mHighlightRect.bottom = (int) (mHighlightRect.top + backBounds.height() * scale);
+                getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
                 canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
             }
             canvas.drawColor((alpha << 24) | SCRIM_COLOR);
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 8bceda7..66e98cd 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -25,7 +25,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.launcher3.ItemInfo;
@@ -33,7 +32,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserHandleCompat;
 
-import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -53,14 +51,18 @@
             FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST;
 
     private final LauncherApps mLauncherApps;
+    private boolean mWasLastCallSuccess;
 
     public DeepShortcutManager(Context context, ShortcutCache shortcutCache) {
         mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
     }
 
     public static boolean supportsShortcuts(ItemInfo info) {
-        return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
-                || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+        return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+    }
+
+    public boolean wasLastCallSuccess() {
+        return mWasLastCallSuccess;
     }
 
     public void onShortcutsChanged(List<ShortcutInfoCompat> shortcuts) {
@@ -100,8 +102,10 @@
             pinnedIds.remove(id);
             try {
                 mLauncherApps.pinShortcuts(packageName, pinnedIds, user.getUser());
+                mWasLastCallSuccess = true;
             } catch (SecurityException e) {
-                Log.e(TAG, Log.getStackTraceString(e));
+                Log.w(TAG, "Failed to unpin shortcut", e);
+                mWasLastCallSuccess = false;
             }
         }
     }
@@ -120,8 +124,10 @@
             pinnedIds.add(id);
             try {
                 mLauncherApps.pinShortcuts(packageName, pinnedIds, user.getUser());
+                mWasLastCallSuccess = true;
             } catch (SecurityException e) {
-                Log.e(TAG, Log.getStackTraceString(e));
+                Log.w(TAG, "Failed to pin shortcut", e);
+                mWasLastCallSuccess = false;
             }
         }
     }
@@ -131,16 +137,12 @@
           Bundle startActivityOptions, UserHandleCompat user) {
         if (Utilities.isNycMR1OrAbove()) {
             try {
-                // TODO: remove reflection once updated SDK is ready.
-                // mLauncherApps.startShortcut(packageName, id, sourceBounds,
-                //        startActivityOptions, user.getUser());
-                mLauncherApps.getClass().getMethod("startShortcut", String.class, String.class,
-                        Rect.class, Bundle.class, UserHandle.class).invoke(mLauncherApps,
-                        packageName, id, sourceBounds, startActivityOptions, user.getUser());
+                mLauncherApps.startShortcut(packageName, id, sourceBounds,
+                        startActivityOptions, user.getUser());
+                mWasLastCallSuccess = true;
             } catch (SecurityException e) {
-                Log.e(TAG, Log.getStackTraceString(e));
-            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
-                e.printStackTrace();
+                Log.e(TAG, "Failed to start shortcut", e);
+                mWasLastCallSuccess = false;
             }
         }
     }
@@ -149,10 +151,13 @@
     public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
         if (Utilities.isNycMR1OrAbove()) {
             try {
-                return mLauncherApps.getShortcutIconDrawable(shortcutInfo.getShortcutInfo(),
-                        density);
+                Drawable icon = mLauncherApps.getShortcutIconDrawable(
+                        shortcutInfo.getShortcutInfo(), density);
+                mWasLastCallSuccess = true;
+                return icon;
             } catch (SecurityException e) {
-                Log.e(TAG, Log.getStackTraceString(e));
+                Log.e(TAG, "Failed to get shortcut icon", e);
+                mWasLastCallSuccess = false;
             }
         }
         return null;
@@ -200,8 +205,10 @@
             List<ShortcutInfo> shortcutInfos = null;
             try {
                 shortcutInfos = mLauncherApps.getShortcuts(q, user.getUser());
+                mWasLastCallSuccess = true;
             } catch (SecurityException e) {
-                Log.e(TAG, Log.getStackTraceString(e));
+                Log.e(TAG, "Failed to query for shortcuts", e);
+                mWasLastCallSuccess = false;
             }
             if (shortcutInfos == null) {
                 return Collections.EMPTY_LIST;
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index 6f3875c..d9e34a6 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -7,8 +7,10 @@
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.AsyncTask;
 import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -21,30 +23,30 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LogDecelerateInterpolator;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadCircularReveal;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
  * A container for shortcuts to deep links within apps.
  */
 @TargetApi(Build.VERSION_CODES.N)
-public class DeepShortcutsContainer extends LinearLayout implements  View.OnClickListener,
-        View.OnLongClickListener, View.OnTouchListener, DragSource,
-        UserEventDispatcher.LaunchSourceProvider, TouchController {
+public class DeepShortcutsContainer extends LinearLayout implements View.OnLongClickListener,
+        View.OnTouchListener, DragSource, DragController.DragListener,
+        UserEventDispatcher.LaunchSourceProvider {
     private static final String TAG = "ShortcutsContainer";
 
     private Launcher mLauncher;
@@ -98,37 +100,53 @@
         deferDrag(originalIcon);
 
         // Load the shortcuts on a background thread and update the container as it animates.
+        final Looper workerLooper = LauncherModel.getWorkerLooper();
+        final Handler uiHandler = new Handler(Looper.getMainLooper());
         final ItemInfo originalInfo = (ItemInfo) originalIcon.getTag();
         final UserHandleCompat user = originalInfo.user;
         final ComponentName activity = originalInfo.getTargetComponent();
-        new AsyncTask<Void, Void, List<ShortcutInfo>>() {
-            public List<ShortcutInfo> doInBackground(Void ... args) {
-                List<ShortcutInfoCompat> shortcuts = mDeepShortcutsManager
-                        .queryForAllAppShortcuts(activity, ids, user);
-                List<ShortcutInfo> shortcutInfos = new ArrayList<>(shortcuts.size());
-                for (ShortcutInfoCompat shortcut : shortcuts) {
-                    shortcutInfos.add(ShortcutInfo.fromDeepShortcutInfo(shortcut, mLauncher));
-                }
-                return shortcutInfos;
-            }
-
-            // TODO: implement onProgressUpdate() to load shortcuts one at a time.
-
+        new Handler(workerLooper).postAtFrontOfQueue(new Runnable() {
             @Override
-            protected void onPostExecute(List<ShortcutInfo> shortcuts) {
+            public void run() {
+                final List<ShortcutInfoCompat> shortcuts = mDeepShortcutsManager
+                        .queryForAllAppShortcuts(activity, ids, user);
                 for (int i = 0; i < shortcuts.size(); i++) {
-                    DeepShortcutView iconAndText = (DeepShortcutView) getChildAt(i);
-                    ShortcutInfo launcherShortcutInfo = shortcuts.get(i);
-                    iconAndText.applyFromShortcutInfo(launcherShortcutInfo,
-                            LauncherAppState.getInstance().getIconCache());
-                    iconAndText.setOnClickListener(DeepShortcutsContainer.this);
-                    iconAndText.setOnLongClickListener(DeepShortcutsContainer.this);
-                    iconAndText.setOnTouchListener(DeepShortcutsContainer.this);
-                    int viewId = mLauncher.getViewIdForItem(originalInfo);
-                    iconAndText.setId(viewId);
+                    final ShortcutInfoCompat shortcut = shortcuts.get(i);
+                    final ShortcutInfo launcherShortcutInfo = ShortcutInfo
+                            .fromDeepShortcutInfo(shortcut, mLauncher);
+                    CharSequence label = shortcut.getLongLabel();
+                    if (TextUtils.isEmpty(label)) {
+                        label = shortcut.getShortLabel();
+                    }
+                    uiHandler.post(new UpdateShortcutChild(i, launcherShortcutInfo, label));
                 }
             }
-        }.execute();
+        });
+    }
+
+    /** Updates the child of this container at the given index based on the given shortcut info. */
+    private class UpdateShortcutChild implements Runnable {
+        private int mShortcutChildIndex;
+        private ShortcutInfo mShortcutChildInfo;
+        private CharSequence mLabel;
+
+        public UpdateShortcutChild(int shortcutChildIndex, ShortcutInfo shortcutChildInfo,
+                CharSequence label) {
+            mShortcutChildIndex = shortcutChildIndex;
+            mShortcutChildInfo = shortcutChildInfo;
+            mLabel = label;
+        }
+
+        @Override
+        public void run() {
+            DeepShortcutView shortcutView = (DeepShortcutView) getChildAt(mShortcutChildIndex);
+            shortcutView.applyFromShortcutInfo(mShortcutChildInfo,
+                    LauncherAppState.getInstance().getIconCache());
+            shortcutView.setText(mLabel);
+            shortcutView.setOnClickListener(mLauncher);
+            shortcutView.setOnLongClickListener(DeepShortcutsContainer.this);
+            shortcutView.setOnTouchListener(DeepShortcutsContainer.this);
+        }
     }
 
     // TODO: update this animation
@@ -194,6 +212,7 @@
     private void deferDrag(BubbleTextView originalIcon) {
         mDeferredDragIcon = originalIcon;
         showDragView(originalIcon);
+        mLauncher.getDragController().addDragListener(this);
     }
 
     public BubbleTextView getDeferredDragIcon() {
@@ -219,9 +238,9 @@
         mDragView.show(motionDownX, motionDownY);
     }
 
-    public boolean onForwardedEvent(MotionEvent ev, int activePointerId, MotionEvent touchDownEvent) {
-        mTouchDown = new Point((int) touchDownEvent.getX(), (int) touchDownEvent.getY());
+    public boolean onForwardedEvent(MotionEvent ev, int activePointerId, Point touchDown) {
         mActivePointerId = activePointerId;
+        mTouchDown = touchDown;
         return dispatchTouchEvent(ev);
     }
 
@@ -231,7 +250,6 @@
             return false;
         }
 
-
         final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
         if (activePointerIndex < 0) {
             return false;
@@ -256,12 +274,10 @@
 
             boolean containerContainsTouch = x >= 0 && y >= 0 && x < getWidth() && y < getHeight();
             if (shouldStartDeferredDrag((int) x, (int) y, containerContainsTouch)) {
-                mDeferredDragIcon.getParent().requestDisallowInterceptTouchEvent(false);
-                mDeferredDragIcon.setVisibility(VISIBLE);
-                mDeferredDragIcon.getOnLongClickListener().onLongClick(mDeferredDragIcon);
-                mLauncher.getDragLayer().removeView(this);
-                mLauncher.getDragController().onTouchEvent(ev);
                 cleanupDeferredDrag();
+                mDeferredDragIcon.getParent().requestDisallowInterceptTouchEvent(false);
+                mDeferredDragIcon.getOnLongClickListener().onLongClick(mDeferredDragIcon);
+                mLauncher.getDragController().onTouchEvent(ev);
                 return true;
             } else {
                 // Determine whether touch is over a shortcut.
@@ -288,7 +304,6 @@
                 }
             }
         } else if (action == MotionEvent.ACTION_UP) {
-            mDeferredDragIcon.setVisibility(VISIBLE);
             cleanupDeferredDrag();
             // Launch a shortcut if user was hovering over it.
             for (int i = 0; i < childCount; i++) {
@@ -316,9 +331,9 @@
      * @param y the y touch coordinate relative to this container
      */
     private boolean shouldStartDeferredDrag(int x, int y, boolean containerContainsTouch) {
-        Point closestEdge = new Point(mTouchDown.x, mIsAboveIcon ? getMeasuredHeight() : 0);
-        double distToEdge = Math.hypot(mTouchDown.x - closestEdge.x, mTouchDown.y - closestEdge.y);
-        double newDistToEdge = Math.hypot(x - closestEdge.x, y - closestEdge.y);
+        int closestEdgeY = mIsAboveIcon ? getMeasuredHeight() : 0;
+        double distToEdge = Math.abs(mTouchDown.y - closestEdgeY);
+        double newDistToEdge = Math.hypot(x - mTouchDown.x, y - closestEdgeY);
         return  !containerContainsTouch && (newDistToEdge - distToEdge > mStartDragThreshold);
     }
 
@@ -326,6 +341,7 @@
         if (mDragView != null) {
             mDragView.remove();
         }
+        mDeferredDragIcon.setVisibility(VISIBLE);
     }
 
     @Override
@@ -340,13 +356,6 @@
         return false;
     }
 
-    @Override
-    public void onClick(View view) {
-        // Clicked on a shortcut.
-        mLauncher.onClick(view);
-        ((DragLayer) getParent()).removeView(this);
-    }
-
     public boolean onLongClick(View v) {
         // Return early if this is not initiated from a touch
         if (!v.isInTouchMode()) return false;
@@ -354,11 +363,7 @@
         if (!mLauncher.isDraggingEnabled()) return false;
 
         // Long clicked on a shortcut.
-        // TODO remove this hack; it required because DragLayer isn't intercepting touch, so
-        // the controller is not updated from what it was previously.
-        mLauncher.getDragLayer().setController(mLauncher.getDragController());
         mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false);
-        ((DragLayer) getParent()).removeView(this);
         // TODO: support dragging from within folder without having to close it
         mLauncher.closeFolder();
         return false;
@@ -401,6 +406,19 @@
     }
 
     @Override
+    public void onDragStart(DragSource source, ItemInfo info, int dragAction) {
+        // Either the original icon or one of the shortcuts was dragged.
+        // Hide the container, but don't remove it yet because that interferes with touch events.
+        setVisibility(INVISIBLE);
+    }
+
+    @Override
+    public void onDragEnd() {
+        // Now remove the container.
+        mLauncher.closeShortcutsContainer();
+    }
+
+    @Override
     public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
         target.itemType = LauncherLogProto.SHORTCUT; // TODO: change to DYNAMIC_SHORTCUT
         target.gridX = info.cellX;
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java b/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
index 956623e..63c8363 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
@@ -1,6 +1,7 @@
 package com.android.launcher3.shortcuts;
 
 import android.content.Context;
+import android.graphics.Point;
 import android.os.SystemClock;
 import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
@@ -53,6 +54,8 @@
     private Launcher mLauncher;
     private DragLayer mDragLayer;
     private MotionEvent mTouchDownEvent;
+    /** The coordinates of the touch down, relative do the shortcuts container. */
+    private final Point mTouchDown;
 
     public ShortcutsContainerListener(BubbleTextView icon) {
         mSrcIcon = icon;
@@ -64,8 +67,8 @@
         icon.addOnAttachStateChangeListener(this);
 
         mLauncher = Launcher.getLauncher(mSrcIcon.getContext());
-
         mDragLayer = mLauncher.getDragLayer();
+        mTouchDown = new Point();
     }
 
     @Override
@@ -76,6 +79,9 @@
         }
 
         if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            if (mTouchDownEvent != null) {
+                mTouchDownEvent.recycle();
+            }
             mTouchDownEvent = MotionEvent.obtainNoHistory(event);
         }
 
@@ -134,6 +140,10 @@
             deepShortcutsContainer.populateAndShow(mSrcIcon, ids);
             mSrcIcon.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+
+            // Convert touch down event to the container's coordinates.
+            Utilities.translateEventCoordinates(mSrcIcon, deepShortcutsContainer, mTouchDownEvent);
+            mTouchDown.set((int) mTouchDownEvent.getX(), (int) mTouchDownEvent.getY());
             return true;
         }
         return false;
@@ -243,8 +253,7 @@
     private boolean onTouchForwarded(MotionEvent srcEvent) {
         final View src = mSrcIcon;
 
-        final DeepShortcutsContainer dst = (DeepShortcutsContainer)
-                mDragLayer.findViewById(R.id.deep_shortcuts_container);
+        final DeepShortcutsContainer dst = mLauncher.getOpenShortcutsContainer();
         if (dst == null) {
             return false;
         }
@@ -253,16 +262,10 @@
         final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
         Utilities.translateEventCoordinates(src, dst, dstEvent);
 
-        // Convert touch down event to destination-local coordinates.
-        // TODO: only create this once, or just store the x and y.
-        final MotionEvent touchDownEvent = MotionEvent.obtainNoHistory(mTouchDownEvent);
-        Utilities.translateEventCoordinates(src, dst, touchDownEvent);
-
         // Forward converted event to destination view, then recycle it.
         // TODO: don't create objects in onForwardedEvent.
-        final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId, touchDownEvent);
+        final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId, mTouchDown);
         dstEvent.recycle();
-        touchDownEvent.recycle();
 
         // Always cancel forwarding when the touch stream ends.
         final int action = srcEvent.getActionMasked();
diff --git a/src/com/android/launcher3/util/MultiStateAlphaController.java b/src/com/android/launcher3/util/MultiStateAlphaController.java
new file mode 100644
index 0000000..df73bfd
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiStateAlphaController.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008 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.util;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.Arrays;
+
+/**
+ * A utility class which divides the alpha for a view across multiple states.
+ */
+public class MultiStateAlphaController {
+
+    private final View mTargetView;
+    private final float[] mAlphas;
+    private final AccessibilityManager mAm;
+
+    public MultiStateAlphaController(View view, int stateCount) {
+        mTargetView = view;
+        mAlphas = new float[stateCount];
+        Arrays.fill(mAlphas, 1);
+
+        mAm = (AccessibilityManager) view.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+    }
+
+    public void setAlphaAtIndex(float alpha, int index) {
+        mAlphas[index] = alpha;
+        float finalAlpha = 1;
+        for (float a : mAlphas) {
+            finalAlpha = finalAlpha * a;
+        }
+        mTargetView.setAlpha(finalAlpha);
+        mTargetView.setVisibility(alpha > 0 ? View.VISIBLE
+                : (mAm.isEnabled() ? View.GONE : View.INVISIBLE));
+    }
+
+    public Animator animateAlphaAtIndex(float finalAlpha, final int index) {
+        if (Float.compare(finalAlpha, mAlphas[index]) == 0) {
+            // Return a dummy animator to avoid null checks.
+            return ValueAnimator.ofFloat(0, 0);
+        } else {
+            ValueAnimator animator = ValueAnimator.ofFloat(mAlphas[index], finalAlpha);
+            animator.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                    float value = (Float) valueAnimator.getAnimatedValue();
+                    setAlphaAtIndex(value, index);
+                }
+            });
+            return animator;
+        }
+    }
+}