Merge "Add margin to all apps recycler container view b/29509347 > Last row inside recycler view was obstructed by nav bar making it difficult to interact with before." into ub-launcher3-calgary
diff --git a/build.gradle b/build.gradle
index 899767f..f98021c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -18,7 +18,7 @@
     defaultConfig {
         applicationId "com.android.launcher3"
         minSdkVersion 21
-        targetSdkVersion 23
+        targetSdkVersion 'N'
         versionCode 1
         versionName "1.0"
 
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 8ebc303..27ff789 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -36,7 +36,8 @@
             android:id="@+id/workspace"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:layout_gravity="center" />
+            android:layout_gravity="center"
+            launcher:pageIndicator="@id/page_indicator" />
 
         <!-- DO NOT CHANGE THE ID -->
         <include layout="@layout/hotseat"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9f011e5..4e5fcff 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -38,7 +38,7 @@
 
     <!-- Widgets -->
     <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
-    <string name="long_press_widget_to_add">Tap &amp; hold to pick up a widget.</string>
+    <string name="long_press_widget_to_add">Touch &amp; hold to pick up a widget.</string>
     <!-- Accessibility spoken hint message in widget picker, which allows user to add a widget. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=100] -->
     <string name="long_accessible_way_to_add">Double-tap &amp; hold to pick up a widget or use custom actions.</string>
     <!-- The format string for the dimensions of a widget in the drawer -->
@@ -135,7 +135,7 @@
     <!-- The title text for workspace longpress action [CHAR_LIMIT=40] -->
     <string name="workspace_cling_longpress_title">Wallpapers, widgets, &amp; settings</string>
     <!-- The description of how to use the workspace [CHAR_LIMIT=70] -->
-    <string name="workspace_cling_longpress_description">Tap &amp; hold background to customize</string>
+    <string name="workspace_cling_longpress_description">Touch &amp; hold background to customize</string>
     <!-- The description of the button to dismiss the cling [CHAR_LIMIT=30] -->
     <string name="workspace_cling_longpress_dismiss">GOT IT</string>
 
@@ -162,6 +162,8 @@
     <string name="settings_button_text">Settings</string>
     <!-- Message shown when a feature is disabled by the administrator -->
     <string name="msg_disabled_by_admin">Disabled by your admin</string>
+    <!-- Text for custom accessibility action to go to the overview mode, where users can look and change the overall UI of the launcher. -->
+    <string name="accessibility_action_overview">Overview</string>
 
     <!-- Strings for settings -->
     <!-- Title for Allow Rotation setting. [CHAR LIMIT=50] -->
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 3287015..1762ca4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -158,7 +158,7 @@
         if (info.isDisabled()) {
             iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
         }
-        setIcon(iconDrawable, mIconSize);
+        setIcon(iconDrawable);
         if (info.contentDescription != null) {
             setContentDescription(info.contentDescription);
         }
@@ -175,7 +175,7 @@
         if (info.isDisabled()) {
             iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
         }
-        setIcon(iconDrawable, mIconSize);
+        setIcon(iconDrawable);
         setText(info.title);
         if (info.contentDescription != null) {
             setContentDescription(info.contentDescription);
@@ -188,7 +188,7 @@
     }
 
     public void applyFromPackageItemInfo(PackageItemInfo info) {
-        setIcon(mLauncher.createIconDrawable(info.iconBitmap), mIconSize);
+        setIcon(mLauncher.createIconDrawable(info.iconBitmap));
         setText(info.title);
         if (info.contentDescription != null) {
             setContentDescription(info.contentDescription);
@@ -205,7 +205,7 @@
      */
     public void applyDummyInfo() {
         ColorDrawable d = new ColorDrawable();
-        setIcon(mLauncher.resizeIconDrawable(d), mIconSize);
+        setIcon(mLauncher.resizeIconDrawable(d));
         setText("");
     }
 
@@ -477,7 +477,7 @@
                     preloadDrawable = (PreloadIconDrawable) mIcon;
                 } else {
                     preloadDrawable = new PreloadIconDrawable(mIcon, getPreloaderTheme());
-                    setIcon(preloadDrawable, mIconSize);
+                    setIcon(preloadDrawable);
                 }
 
                 preloadDrawable.setLevel(progressLevel);
@@ -506,10 +506,10 @@
      * Sets the icon for this view based on the layout direction.
      */
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    private Drawable setIcon(Drawable icon, int iconSize) {
+    public void setIcon(Drawable icon) {
         mIcon = icon;
-        if (iconSize != -1) {
-            mIcon.setBounds(0, 0, iconSize, iconSize);
+        if (mIconSize != -1) {
+            mIcon.setBounds(0, 0, mIconSize, mIconSize);
         }
         if (mLayoutHorizontal) {
             if (Utilities.ATLEAST_JB_MR1) {
@@ -520,7 +520,6 @@
         } else {
             setCompoundDrawables(null, mIcon, null, null);
         }
-        return icon;
     }
 
     @Override
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 286a7f1..f54a2d4 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -32,7 +32,7 @@
     /**
      * Intent extra to store the profile. Format: UserHandle
      */
-    static final String EXTRA_PROFILE = "profile";
+    public static final String EXTRA_PROFILE = "profile";
 
     public static final int NO_ID = -1;
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 271c69b..d25eca0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -119,6 +119,7 @@
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Thunk;
@@ -150,7 +151,6 @@
     static final boolean DEBUG_WIDGETS = false;
     static final boolean DEBUG_STRICT_MODE = false;
     static final boolean DEBUG_RESUME_TIME = false;
-    static final boolean DEBUG_LOGGING = false;
 
     private static final int REQUEST_CREATE_SHORTCUT = 1;
     private static final int REQUEST_CREATE_APPWIDGET = 5;
@@ -235,7 +235,6 @@
 
     @Thunk Workspace mWorkspace;
     private View mLauncherView;
-    private PageIndicator mPageIndicator;
     @Thunk DragLayer mDragLayer;
     private DragController mDragController;
 
@@ -294,6 +293,9 @@
     private boolean mHasFocus = false;
     private boolean mAttached = false;
 
+    /** Maps launcher activity components to their list of shortcut ids. */
+    private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
+
     private LauncherClings mClings;
 
     private View.OnTouchListener mHapticFeedbackTouchListener;
@@ -510,9 +512,7 @@
         if (mExtractedColors != null && Utilities.isNycOrAbove()) {
             mExtractedColors.load(this);
             mHotseat.updateColor(mExtractedColors, !mPaused);
-            if (mPageIndicator != null) {
-                mPageIndicator.updateColor(mExtractedColors);
-            }
+            mWorkspace.getPageIndicator().updateColor(mExtractedColors);
         }
     }
 
@@ -626,29 +626,6 @@
         }
     }
 
-    /**
-     * Logger object is a singleton and does not have to be coupled with the foreground activity.
-     * Since most user event logging is done on the UI, the object is retrieved from the
-     * callback for convenience.
-     */
-    private UserEventDispatcher createUserEventDispatcher() {
-        return new UserEventDispatcher() {
-            @Override
-            public void dispatchUserEvent(LauncherLogProto.LauncherEvent ev, Intent intent) {
-                if (!DEBUG_LOGGING) {
-                    return;
-                }
-                Log.d("UserEvent", String.format(Locale.US,
-                        "action:%s\nchild:%s\nparent:%s\nelapsed container %d ms session %d ms",
-                        LoggerUtils.getActionStr(ev.action),
-                        LoggerUtils.getTargetStr(ev.srcTarget[0]),
-                        LoggerUtils.getTargetStr(ev.srcTarget[1]),
-                        ev.elapsedContainerMillis,
-                        ev.elapsedSessionMillis));
-            }
-        };
-    }
-
     public UserEventDispatcher getUserEventDispatcher() {
         if (mLauncherCallbacks != null) {
             UserEventDispatcher dispatcher = mLauncherCallbacks.getUserEventDispatcher();
@@ -657,8 +634,11 @@
             }
         }
 
+        // Logger object is a singleton and does not have to be coupled with the foreground
+        // activity. Since most user event logging is done on the UI, the object is retrieved
+        // from the callback for convenience.
         if (mUserEventDispatcher == null) {
-            mUserEventDispatcher = createUserEventDispatcher();
+            mUserEventDispatcher = new UserEventDispatcher();
         }
         return mUserEventDispatcher;
     }
@@ -1346,9 +1326,8 @@
         mLauncherView = findViewById(R.id.launcher);
         mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
         mFocusHandler = mDragLayer.getFocusIndicatorHelper();
-
         mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
-        mPageIndicator = (PageIndicator) mDragLayer.findViewById(R.id.page_indicator);
+        mWorkspace.initParentViews(mDragLayer);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
@@ -1468,7 +1447,7 @@
     }
 
     public View getStartViewForAllAppsRevealAnimation() {
-        return FeatureFlags.NO_ALL_APPS_ICON ? mPageIndicator : mAllAppsButton;
+        return FeatureFlags.NO_ALL_APPS_ICON ? mWorkspace.getPageIndicator() : mAllAppsButton;
     }
 
     public View getWidgetsButton() {
@@ -2354,7 +2333,7 @@
      * @param itemInfo the {@link ItemInfo} for this view.
      * @param deleteFromDb whether or not to delete this item from the db.
      */
-    public boolean removeItem(View v, ItemInfo itemInfo, boolean deleteFromDb) {
+    public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb) {
         if (itemInfo instanceof ShortcutInfo) {
             // Remove the shortcut from the folder before removing it from launcher
             View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
@@ -2382,7 +2361,6 @@
             if (deleteFromDb) {
                 deleteWidgetInfo(widgetInfo);
             }
-
         } else {
             return false;
         }
@@ -2701,7 +2679,7 @@
      * Event handler for the (Add) Widgets button that appears after a long press
      * on the home screen.
      */
-    protected void onClickAddWidgetButton(View view) {
+    public void onClickAddWidgetButton(View view) {
         if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
         if (mIsSafeModeEnabled) {
             Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
@@ -2714,7 +2692,7 @@
      * Event handler for the wallpaper picker button that appears after a long press
      * on the home screen.
      */
-    protected void onClickWallpaperPicker(View v) {
+    public void onClickWallpaperPicker(View v) {
         if (!Utilities.isWallapaperAllowed(this)) {
             Toast.makeText(this, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
             return;
@@ -2737,7 +2715,7 @@
      * Event handler for a click on the settings button that appears after a long press
      * on the home screen.
      */
-    private void onClickSettingsButton(View v) {
+    public void onClickSettingsButton(View v) {
         if (LOGD) Log.d(TAG, "onClickSettingsButton");
         startActivity(new Intent(Utilities.ACTION_APPLICATION_PREFERENCES)
                 .setPackage(getPackageName()));
@@ -2819,8 +2797,16 @@
                 // is enabled by default on NYC.
                 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll()
                         .penaltyLog().build());
-                // Could be launching some bookkeeping activity
-                startActivity(intent, optsBundle);
+
+                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                    String id = ((ShortcutInfo) info).getDeepShortcutId();
+                    String packageName = intent.getPackage();
+                    LauncherAppsCompat.getInstance(this).startShortcut(
+                                packageName, id, intent.getSourceBounds(), optsBundle, info.user);
+                } else {
+                    // Could be launching some bookkeeping activity
+                    startActivity(intent, optsBundle);
+                }
             } finally {
                 StrictMode.setVmPolicy(oldPolicy);
             }
@@ -2896,8 +2882,9 @@
                     new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight()));
         }
         try {
-            if (Utilities.ATLEAST_MARSHMALLOW &&
-                    item != null && item.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
+            if (Utilities.ATLEAST_MARSHMALLOW && item != null
+                    && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
+                    || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
                 // Shortcuts need some special checks due to legacy reasons.
                 startShortcutIntentSafely(intent, optsBundle, item);
             } else if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
@@ -3271,7 +3258,7 @@
     /**
      * Shows the overview button.
      */
-    void showOverviewMode(boolean animated) {
+    public void showOverviewMode(boolean animated) {
         showOverviewMode(animated, false);
     }
 
@@ -3709,6 +3696,7 @@
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                     ShortcutInfo info = (ShortcutInfo) item;
                     view = createShortcut(info);
                     break;
@@ -4065,6 +4053,16 @@
     }
 
     /**
+     * Copies LauncherModel's map of activities to shortcut ids to Launcher's. This is necessary
+     * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
+     */
+    @Override
+    public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
+        mDeepShortcutMap = deepShortcutMapCopy;
+        if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
+    }
+
+    /**
      * A package was updated.
      *
      * Implementation of the method from LauncherModel.Callbacks.
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index fe65b31..4bc76fb 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -28,6 +28,8 @@
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutCache;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Thunk;
@@ -42,6 +44,7 @@
     @Thunk final LauncherModel mModel;
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
+    private final DeepShortcutManager mDeepShortcutManager;
 
     @Thunk boolean mWallpaperChangedSinceLastCheck;
 
@@ -95,9 +98,10 @@
         mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
         mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
         mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
+        mDeepShortcutManager = new DeepShortcutManager(sContext, new ShortcutCache());
 
         mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
-        mModel = new LauncherModel(this, mIconCache, mAppFilter);
+        mModel = new LauncherModel(this, mIconCache, mAppFilter, mDeepShortcutManager);
 
         LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
 
@@ -168,6 +172,10 @@
         return mWidgetCache;
     }
 
+    public DeepShortcutManager getShortcutManager() {
+        return mDeepShortcutManager;
+    }
+
     public boolean hasWallpaperChangedSinceLastCheck() {
         boolean result = mWallpaperChangedSinceLastCheck;
         mWallpaperChangedSinceLastCheck = false;
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index bca2ffb..e987a9b 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -560,7 +560,8 @@
 
         // Don't backup apps in other profiles for now.
         String where = "(" + Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
-                Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT + ") AND " +
+                Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT + " OR " +
+                Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_DEEP_SHORTCUT + ") AND " +
                 getUserSelectionArg();
         Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
                 where, null, null);
@@ -798,7 +799,8 @@
         return favorite.container == Favorites.CONTAINER_HOTSEAT
                 && favorite.intent != null
                 && (favorite.itemType == Favorites.ITEM_TYPE_APPLICATION
-                || favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT);
+                || favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT
+                || favorite.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT);
     }
 
     /** Serialize a Favorite for persistence, including a checksum wrapper. */
@@ -835,7 +837,8 @@
             if (!TextUtils.isEmpty(appWidgetProvider)) {
                 favorite.appWidgetProvider = appWidgetProvider;
             }
-        } else if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
+        } else if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT
+                || favorite.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
             String iconPackage = c.getString(ICON_PACKAGE_INDEX);
             String iconResource = c.getString(ICON_RESOURCE_INDEX);
             if (!TextUtils.isEmpty(iconPackage) && !TextUtils.isEmpty(iconResource)) {
@@ -897,7 +900,8 @@
         values.put(Favorites.SPANY, favorite.spanY);
         values.put(Favorites.RANK, favorite.rank);
 
-        if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
+        if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT
+                || favorite.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
             values.put(Favorites.ICON_PACKAGE, favorite.iconPackage);
             values.put(Favorites.ICON_RESOURCE, favorite.iconResource);
             values.put(Favorites.ICON, favorite.icon);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 68a9c7e..8e404a7 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -42,6 +42,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.LongSparseArray;
+import android.util.MutableInt;
 import android.util.Pair;
 
 import com.android.launcher3.compat.AppWidgetManagerCompat;
@@ -60,6 +61,9 @@
 import com.android.launcher3.model.GridSizeMigrationTask;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.provider.LauncherDbUtils;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.CursorIconInfo;
 import com.android.launcher3.util.FlagOp;
@@ -69,6 +73,7 @@
 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;
 
@@ -83,6 +88,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -119,8 +125,9 @@
     // We start off with everything not loaded.  After that, we assume that
     // our monitoring of the package manager provides all updates and we never
     // need to do a requery.  These are only ever touched from the loader thread.
-    @Thunk boolean mWorkspaceLoaded;
-    @Thunk boolean mAllAppsLoaded;
+    private boolean mWorkspaceLoaded;
+    private boolean mAllAppsLoaded;
+    private boolean mDeepShortcutsLoaded;
 
     /**
      * Set of runnables to be called on the background thread after the workspace binding
@@ -135,6 +142,9 @@
     // Entire list of widgets.
     private final WidgetsModel mBgWidgetsModel;
 
+    // Maps all launcher activities to the id's of their shortcuts (if they have any).
+    private final MultiHashMap<ComponentKey, String> mBgDeepShortcutMap = new MultiHashMap<>();
+
     // The lock that must be acquired before referencing any static bg data structures.  Unlike
     // other locks, this one can generally be held long-term because we never expect any of these
     // static data structures to be referenced outside of the worker thread except on the first
@@ -160,16 +170,21 @@
     // sBgWorkspaceScreens is the ordered set of workspace screens.
     static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
 
+    // sBgPinnedShortcutCounts is the ComponentKey representing a pinned shortcut to the number of
+    // times it is pinned.
+    static final Map<ShortcutKey, MutableInt> sBgPinnedShortcutCounts = new HashMap<>();
+
     // sPendingPackages is a set of packages which could be on sdcard and are not available yet
     static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
             new HashMap<UserHandleCompat, HashSet<String>>();
 
     // </ only access in worker thread >
 
-    @Thunk IconCache mIconCache;
+    private IconCache mIconCache;
+    private DeepShortcutManager mDeepShortcutManager;
 
-    @Thunk final LauncherAppsCompat mLauncherApps;
-    @Thunk final UserManagerCompat mUserManager;
+    private final LauncherAppsCompat mLauncherApps;
+    private final UserManagerCompat mUserManager;
 
     public interface Callbacks {
         public boolean setLoadOnResume();
@@ -199,18 +214,21 @@
         public void bindWidgetsModel(WidgetsModel model);
         public void onPageBoundSynchronously(int page);
         public void executeOnNextDraw(ViewOnDrawExecutor executor);
+        public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
     }
 
     public interface ItemInfoFilter {
         public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
     }
 
-    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
+    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter,
+            DeepShortcutManager deepShortcutManager) {
         Context context = app.getContext();
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
         mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
         mIconCache = iconCache;
+        mDeepShortcutManager = deepShortcutManager;
 
         mLauncherApps = LauncherAppsCompat.getInstance(context);
         mUserManager = UserManagerCompat.getInstance(context);
@@ -679,6 +697,7 @@
                 switch (modelItem.itemType) {
                     case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                     case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                    case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                     case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                         if (!sBgWorkspaceItems.contains(modelItem)) {
                             sBgWorkspaceItems.add(modelItem);
@@ -892,6 +911,7 @@
                             // Fall through
                         case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                         case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                        case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                             if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
                                     item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                                 sBgWorkspaceItems.add(item);
@@ -903,6 +923,14 @@
                                     Log.e(TAG, msg);
                                 }
                             }
+                            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                                ShortcutInfo shortcutInfo = (ShortcutInfo) item;
+                                ShortcutKey shortcutToPin = new ShortcutKey(
+                                        shortcutInfo.intent.getPackage(),
+                                        shortcutInfo.user,
+                                        shortcutInfo.getDeepShortcutId());
+                                incrementPinnedShortcutCount(shortcutToPin, true /* shouldPin */);
+                            }
                             break;
                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                             sBgAppWidgets.add((LauncherAppWidgetInfo) item);
@@ -969,6 +997,14 @@
                                 }
                                 sBgWorkspaceItems.remove(item);
                                 break;
+                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                                ShortcutInfo shortcutInfo = ((ShortcutInfo) item);
+                                ShortcutKey pinnedShortcut = new ShortcutKey(
+                                        shortcutInfo.intent.getPackage(),
+                                        shortcutInfo.user,
+                                        shortcutInfo.getDeepShortcutId());
+                                decrementPinnedShortcutCount(pinnedShortcut);
+                                // Fall through.
                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                                 sBgWorkspaceItems.remove(item);
@@ -986,6 +1022,39 @@
     }
 
     /**
+     * Decrement the count for the given pinned shortcut, unpinning it if the count becomes 0.
+     */
+    private static void decrementPinnedShortcutCount(final ShortcutKey pinnedShortcut) {
+        synchronized (sBgLock) {
+            MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
+            if (count == null || --count.value == 0) {
+                LauncherAppState.getInstance().getShortcutManager().unpinShortcut(pinnedShortcut);
+            }
+        }
+    }
+
+    /**
+     * Increment the count for the given shortcut, pinning it if the count becomes 1.
+     *
+     * As an optimization, the caller can pass shouldPin == false to avoid
+     * unnecessary RPC's if the shortcut is already pinned.
+     */
+    private static void incrementPinnedShortcutCount(ShortcutKey pinnedShortcut, boolean shouldPin) {
+        synchronized (sBgLock) {
+            MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
+            if (count == null) {
+                count = new MutableInt(1);
+                sBgPinnedShortcutCounts.put(pinnedShortcut, count);
+            } else {
+                count.value++;
+            }
+            if (shouldPin && count.value == 1) {
+                LauncherAppState.getInstance().getShortcutManager().pinShortcut(pinnedShortcut);
+            }
+        }
+    }
+
+    /**
      * Update the order of the workspace screens in the database. The array list contains
      * a list of screen ids in the order that they should appear.
      */
@@ -1077,28 +1146,28 @@
     @Override
     public void onPackageChanged(String packageName, UserHandleCompat user) {
         int op = PackageUpdatedTask.OP_UPDATE;
-        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
                 user));
     }
 
     @Override
     public void onPackageRemoved(String packageName, UserHandleCompat user) {
         int op = PackageUpdatedTask.OP_REMOVE;
-        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
                 user));
     }
 
     @Override
     public void onPackageAdded(String packageName, UserHandleCompat user) {
         int op = PackageUpdatedTask.OP_ADD;
-        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
                 user));
     }
 
     @Override
     public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
             boolean replacing) {
-        enqueuePackageUpdated(
+        enqueueItemUpdatedTask(
                 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
     }
 
@@ -1106,7 +1175,7 @@
     public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
             boolean replacing) {
         if (!replacing) {
-            enqueuePackageUpdated(new PackageUpdatedTask(
+            enqueueItemUpdatedTask(new PackageUpdatedTask(
                     PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
                     user));
         }
@@ -1114,18 +1183,24 @@
 
     @Override
     public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
-        enqueuePackageUpdated(new PackageUpdatedTask(
+        enqueueItemUpdatedTask(new PackageUpdatedTask(
                 PackageUpdatedTask.OP_SUSPEND, packageNames,
                 user));
     }
 
     @Override
     public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
-        enqueuePackageUpdated(new PackageUpdatedTask(
+        enqueueItemUpdatedTask(new PackageUpdatedTask(
                 PackageUpdatedTask.OP_UNSUSPEND, packageNames,
                 user));
     }
 
+    @Override
+    public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
+            UserHandleCompat user) {
+        enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user));
+    }
+
     /**
      * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
      * ACTION_PACKAGE_CHANGED.
@@ -1146,7 +1221,7 @@
                 LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
             UserHandleCompat user = UserHandleCompat.fromIntent(intent);
             if (user != null) {
-                enqueuePackageUpdated(new PackageUpdatedTask(
+                enqueueItemUpdatedTask(new PackageUpdatedTask(
                         PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
                         new String[0], user));
             }
@@ -1171,6 +1246,8 @@
             stopLoaderLocked();
             if (resetAllAppsLoaded) mAllAppsLoaded = false;
             if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
+            // Always reset deep shortcuts loaded.
+            mDeepShortcutsLoaded = false;
         }
     }
 
@@ -1257,6 +1334,7 @@
      *   - workspace icons
      *   - widgets
      *   - all apps icons
+     *   - deep shortcuts within apps
      */
     private class LoaderTask implements Runnable {
         private Context mContext;
@@ -1388,6 +1466,12 @@
                 // second step
                 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                 loadAndBindAllApps();
+
+                waitForIdle();
+
+                // third step
+                if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
+                loadAndBindDeepShortcuts();
             }
 
             // Clear out this reference, otherwise we end up holding it until all of the
@@ -1539,6 +1623,7 @@
                 sBgFolders.clear();
                 sBgItemsIdMap.clear();
                 sBgWorkspaceScreens.clear();
+                sBgPinnedShortcutCounts.clear();
             }
         }
 
@@ -1585,6 +1670,7 @@
 
                 final ArrayList<Long> itemsToRemove = new ArrayList<>();
                 final ArrayList<Long> restoredRows = new ArrayList<>();
+                Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
                 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
                 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
                 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
@@ -1635,6 +1721,13 @@
                         long serialNo = mUserManager.getSerialNumberForUser(user);
                         allUsers.put(serialNo, user);
                         quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
+
+                        List<ShortcutInfoCompat> pinnedShortcuts = mDeepShortcutManager
+                                .queryForPinnedShortcuts(null, user);
+                        for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
+                            shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
+                                    shortcut);
+                        }
                     }
 
                     ShortcutInfo info;
@@ -1657,6 +1750,7 @@
                             switch (itemType) {
                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                                 id = c.getLong(idIndex);
                                 intentDescription = c.getString(intentIndex);
                                 serialNumber = c.getInt(profileIdIndex);
@@ -1819,7 +1913,37 @@
                                     info = getAppShortcutInfo(intent, user, context, c,
                                             cursorIconInfo.iconIndex, titleIndex,
                                             allowMissingTarget, useLowResIcon);
-                                } else {
+                                } else if (itemType ==
+                                        LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                                    String shortcutId = intent.getStringExtra(
+                                            ShortcutInfoCompat.EXTRA_SHORTCUT_ID);
+                                    String packageName = intent.getPackage();
+                                    ShortcutKey key = new ShortcutKey(intent.getPackage(),
+                                            user, shortcutId);
+                                    ShortcutInfoCompat pinnedShortcut =
+                                            shortcutKeyToPinnedShortcuts.get(key);
+                                    boolean shouldPin = false; // It's already pinned.
+                                    if (pinnedShortcut == null) {
+                                        // It shouldn't be possible for a shortcut to be on the
+                                        // workspace without being pinned, but if one somehow is,
+                                        // we should pin it now to get back to a good state.
+                                        Log.w(TAG, "Shortcut was on workspace but wasn't pinned");
+                                        // Get full details; incrementing the count will pin it.
+                                        List<ShortcutInfoCompat> fullDetails = mDeepShortcutManager
+                                                .queryForFullDetails(packageName,
+                                                Collections.singletonList(shortcutId), user);
+                                        if (fullDetails == null || fullDetails.isEmpty()) {
+                                            itemsToRemove.add(id);
+                                            continue;
+                                        } else {
+                                            pinnedShortcut = fullDetails.get(0);
+                                            shouldPin = true;
+                                        }
+                                    }
+                                    incrementPinnedShortcutCount(key, shouldPin);
+                                    info = ShortcutInfo.fromDeepShortcutInfo(pinnedShortcut,
+                                            context, launcherApps);
+                                } else { // item type == ITEM_TYPE_SHORTCUT
                                     info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
 
                                     // Shortcuts are only available on the primary profile
@@ -2098,6 +2222,15 @@
                     }
                 }
 
+                // Unpin shortcuts that don't exist on the workspace.
+                for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
+                    MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key);
+                    if (numTimesPinned == null || numTimesPinned.value == 0) {
+                        // Shortcut is pinned but doesn't exist on the workspace; unpin it.
+                        mDeepShortcutManager.unpinShortcut(key);
+                    }
+                }
+
                 // Sort all the folder items and make sure the first 3 items are high resolution.
                 for (FolderInfo folder : sBgFolders) {
                     Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
@@ -2629,6 +2762,27 @@
             }
         }
 
+        private void loadAndBindDeepShortcuts() {
+            if (DEBUG_LOADERS) {
+                Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
+            }
+            if (!mDeepShortcutsLoaded) {
+                mBgDeepShortcutMap.clear();
+                for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+                    List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager
+                            .queryForAllShortcuts(user);
+                    updateDeepShortcutMap(null, shortcuts);
+                }
+                synchronized (LoaderTask.this) {
+                    if (mStopped) {
+                        return;
+                    }
+                    mDeepShortcutsLoaded = true;
+                }
+            }
+            bindDeepShortcutMapOnMainThread();
+        }
+
         public void dumpState() {
             synchronized (sBgLock) {
                 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
@@ -2639,6 +2793,40 @@
         }
     }
 
+    // Clear all the shortcuts for the given package, and re-add the new shortcuts.
+    private void updateDeepShortcutMap(String packageName, List<ShortcutInfoCompat> shortcuts) {
+        // Remove all keys associated with the given package.
+        if (packageName != null) {
+            Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
+            while (keysIter.hasNext()) {
+                if (keysIter.next().componentName.getPackageName().equals(packageName)) {
+                    keysIter.remove();
+                }
+            }
+        }
+
+        // Now add the new shortcuts to the map.
+        for (ShortcutInfoCompat shortcut : shortcuts) {
+            ComponentKey targetComponent
+                    = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
+            mBgDeepShortcutMap.addToList(targetComponent, shortcut.getId());
+        }
+    }
+
+    private void bindDeepShortcutMapOnMainThread() {
+        final MultiHashMap<ComponentKey, String> shortcutMapCopy = new MultiHashMap<>();
+        shortcutMapCopy.putAll(mBgDeepShortcutMap);
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                Callbacks callbacks = getCallback();
+                if (callbacks != null) {
+                    callbacks.bindDeepShortcutMap(shortcutMapCopy);
+                }
+            }
+        });
+    }
+
     /**
      * Called when the icons for packages have been updated in the icon cache.
      */
@@ -2664,19 +2852,7 @@
             mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
         }
 
-        if (!updatedShortcuts.isEmpty()) {
-            final UserHandleCompat userFinal = user;
-            mHandler.post(new Runnable() {
-
-                public void run() {
-                    Callbacks cb = getCallback();
-                    if (cb != null && callbacks == cb) {
-                        cb.bindShortcutsChanged(updatedShortcuts,
-                                new ArrayList<ShortcutInfo>(), userFinal);
-                    }
-                }
-            });
-        }
+        bindUpdatedShortcuts(updatedShortcuts, user);
 
         if (!updatedApps.isEmpty()) {
             mHandler.post(new Runnable() {
@@ -2691,7 +2867,25 @@
         }
     }
 
-    void enqueuePackageUpdated(PackageUpdatedTask task) {
+    private void bindUpdatedShortcuts(final ArrayList<ShortcutInfo> updatedShortcuts,
+            UserHandleCompat user) {
+        if (!updatedShortcuts.isEmpty()) {
+            final Callbacks callbacks = getCallback();
+            final UserHandleCompat userFinal = user;
+            mHandler.post(new Runnable() {
+
+                public void run() {
+                    Callbacks cb = getCallback();
+                    if (cb != null && callbacks == cb) {
+                        cb.bindShortcutsChanged(updatedShortcuts,
+                                new ArrayList<ShortcutInfo>(), userFinal);
+                    }
+                }
+            });
+        }
+    }
+
+    void enqueueItemUpdatedTask(Runnable task) {
         sWorker.post(task);
     }
 
@@ -2719,11 +2913,11 @@
                         }
                     }
                     if (!packagesRemoved.isEmpty()) {
-                        enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
+                        enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
                                 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
                     }
                     if (!packagesUnavailable.isEmpty()) {
-                        enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
+                        enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
                                 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user));
                     }
                 }
@@ -3081,6 +3275,60 @@
         }
     }
 
+    private class ShortcutsChangedTask implements Runnable {
+        private String mPackageName;
+        private List<ShortcutInfoCompat> mShortcuts;
+        private UserHandleCompat mUser;
+
+        public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
+                UserHandleCompat user) {
+            mPackageName = packageName;
+            mShortcuts = shortcuts;
+            mUser = user;
+        }
+
+        @Override
+        public void run() {
+            mDeepShortcutManager.onShortcutsChanged(mShortcuts);
+
+            Map<String, ShortcutInfoCompat> idsToShortcuts = new HashMap<>();
+            for (ShortcutInfoCompat shortcut : mShortcuts) {
+                idsToShortcuts.put(shortcut.getId(), shortcut);
+            }
+
+            // Find ShortcutInfo's that have changed on the workspace.
+            MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
+            for (ItemInfo itemInfo : sBgItemsIdMap) {
+                if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                    ShortcutInfo si = (ShortcutInfo) itemInfo;
+                    String shortcutId = si.getDeepShortcutId();
+                    if (idsToShortcuts.containsKey(shortcutId)) {
+                        idsToWorkspaceShortcutInfos.addToList(shortcutId, si);
+                    }
+                }
+            }
+
+            // Update the workspace to reflect the changes to updated shortcuts residing on it.
+            List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager.queryForFullDetails(
+                    mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
+            ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
+            Context context = LauncherAppState.getInstance().getContext();
+            for (ShortcutInfoCompat fullDetails : shortcuts) {
+                List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
+                        .get(fullDetails.getId());
+                for (ShortcutInfo shortcutInfo : shortcutInfos) {
+                    shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context, mLauncherApps);
+                    updatedShortcutInfos.add(shortcutInfo);
+                }
+            }
+            bindUpdatedShortcuts(updatedShortcutInfos, mUser);
+
+            // Update the deep shortcut map, in case the list of ids has changed for an activity.
+            updateDeepShortcutMap(mPackageName, mShortcuts);
+            bindDeepShortcutMapOnMainThread();
+        }
+    }
+
     private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) {
         mHandler.post(new Runnable() {
             @Override
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 52668d7..c213c8d 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -211,6 +211,11 @@
         public static final int ITEM_TYPE_CUSTOM_APPWIDGET = 5;
 
         /**
+         * The gesture is an application created deep shortcut
+         */
+        public static final int ITEM_TYPE_DEEP_SHORTCUT = 6;
+
+        /**
          * The appWidgetId of the widget
          *
          * <P>Type: INTEGER</P>
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 9266793..5ac3f0b 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -154,7 +154,7 @@
 
     // Page Indicator
     @Thunk int mPageIndicatorViewId;
-    @Thunk PageIndicator mPageIndicator;
+    protected PageIndicator mPageIndicator;
     // The viewport whether the pages are to be contained (the actual view may be larger than the
     // viewport)
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -247,39 +247,14 @@
         mScroller.setInterpolator(mDefaultInterpolator);
     }
 
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        // Hook up the page indicator
-        ViewGroup parent = (ViewGroup) getParent();
-        ViewGroup grandParent = (ViewGroup) parent.getParent();
-        if (mPageIndicator == null && mPageIndicatorViewId > -1) {
-            mPageIndicator = (PageIndicator) grandParent.findViewById(mPageIndicatorViewId);
+    public void initParentViews(View parent) {
+        if (mPageIndicatorViewId > -1) {
+            mPageIndicator = (PageIndicator) parent.findViewById(mPageIndicatorViewId);
             mPageIndicator.setMarkersCount(getChildCount());
-
-            OnClickListener listener = getPageIndicatorClickListener();
-            if (listener != null) {
-                mPageIndicator.setOnClickListener(listener);
-            }
-            mPageIndicator.setContentDescription(getPageIndicatorDescription());
+            mPageIndicator.setContentDescription(getCurrentPageDescription());
         }
     }
 
-    protected String getPageIndicatorDescription() {
-        return getCurrentPageDescription();
-    }
-
-    protected OnClickListener getPageIndicatorClickListener() {
-        return null;
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        // Unhook the page indicator
-        mPageIndicator = null;
-    }
-
     // Convenience methods to map points from self to parent and vice versa
     private float[] mapPointFromViewToParent(View v, float x, float y) {
         sTmpPoint[0] = x;
@@ -467,7 +442,7 @@
     private void updatePageIndicator() {
         // Update the page indicator (when we aren't reordering)
         if (mPageIndicator != null) {
-            mPageIndicator.setContentDescription(getPageIndicatorDescription());
+            mPageIndicator.setContentDescription(getCurrentPageDescription());
             if (!isReordering(false)) {
                 mPageIndicator.setActiveMarker(getNextPage());
             }
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index d051665..63f49e0 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -16,20 +16,23 @@
 
 package com.android.launcher3;
 
+import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
-import android.util.Log;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.TextUtils;
 
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.folder.FolderIcon;
-
-import java.util.ArrayList;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 
 /**
  * Represents a launchable icon on the workspaces and in folders.
@@ -274,6 +277,46 @@
         return shortcut;
     }
 
+    /**
+     * Creates a {@link ShortcutInfo} from a {@link ShortcutInfoCompat}. Pardon the overloaded name.
+     */
+    @TargetApi(Build.VERSION_CODES.N)
+    public static ShortcutInfo fromDeepShortcutInfo(ShortcutInfoCompat shortcutInfo,
+            Context context, LauncherAppsCompat launcherApps) {
+        ShortcutInfo si = new ShortcutInfo();
+        si.user = shortcutInfo.getUserHandle();
+        si.itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+        si.intent = shortcutInfo.makeIntent(context);
+        si.flags = 0;
+        si.updateFromDeepShortcutInfo(shortcutInfo, context, launcherApps);
+        return si;
+    }
+
+    public void updateFromDeepShortcutInfo(ShortcutInfoCompat shortcutInfo,
+            Context context, LauncherAppsCompat launcherApps) {
+        title = shortcutInfo.getShortLabel();
+
+        CharSequence label = shortcutInfo.getLongLabel();
+        if (TextUtils.isEmpty(label)) {
+            label = shortcutInfo.getShortLabel();
+        }
+        this.contentDescription = UserManagerCompat.getInstance(context)
+                .getBadgedLabelForUser(label, user);
+
+        LauncherAppState launcherAppState = LauncherAppState.getInstance();
+        Drawable unbadgedIcon = launcherApps.getShortcutIconDrawable(shortcutInfo, launcherAppState
+                .getInvariantDeviceProfile().fillResIconDpi);
+        Bitmap icon = unbadgedIcon == null ? null
+                : Utilities.createBadgedIconBitmap(unbadgedIcon, user, context);
+        setIcon(icon != null ? icon : launcherAppState.getIconCache().getDefaultIcon(user));
+    }
+
+    /** Returns the ShortcutInfo id associated with the deep shortcut. */
+    public String getDeepShortcutId() {
+        return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT ?
+                intent.getStringExtra(ShortcutInfoCompat.EXTRA_SHORTCUT_ID) : null;
+    }
+
     @Override
     public boolean isDisabled() {
         return isDisabled != 0;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 24736d4..416da3a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -59,8 +59,8 @@
 import com.android.launcher3.Launcher.CustomContentCallbacks;
 import com.android.launcher3.Launcher.LauncherOverlay;
 import com.android.launcher3.UninstallDropTarget.DropTargetSource;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource;
+import com.android.launcher3.accessibility.OverviewAccessibilityDelegate;
 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
@@ -458,6 +458,12 @@
         setEdgeGlowColor(getResources().getColor(R.color.workspace_edge_effect_color));
     }
 
+    @Override
+    public void initParentViews(View parent) {
+        super.initParentViews(parent);
+        mPageIndicator.setAccessibilityDelegate(new OverviewAccessibilityDelegate());
+    }
+
     private int getDefaultPage() {
         return numCustomPages();
     }
@@ -1445,10 +1451,9 @@
         mHotseatAlpha[direction.ordinal()] = alpha;
         float finalAlpha = mHotseatAlpha[0] * mHotseatAlpha[1];
 
-        View pageIndicator = getPageIndicator();
-        if (pageIndicator != null) {
-            property.set(pageIndicator, translation);
-            pageIndicator.setAlpha(finalAlpha);
+        if (mPageIndicator != null) {
+            property.set(mPageIndicator, translation);
+            mPageIndicator.setAlpha(alpha);
         }
 
         property.set(mLauncher.getHotseat(), translation);
@@ -1647,8 +1652,8 @@
             mLauncher.getHotseat().setTranslationX(translationX);
         }
 
-        if (getPageIndicator() != null) {
-            getPageIndicator().setTranslationX(translationX);
+        if (mPageIndicator != null) {
+            mPageIndicator.setTranslationX(translationX);
         }
 
         if (mCustomContentCallbacks != null) {
@@ -1657,21 +1662,6 @@
     }
 
     @Override
-    protected OnClickListener getPageIndicatorClickListener() {
-        AccessibilityManager am = (AccessibilityManager)
-                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-        if (!am.isTouchExplorationEnabled()) {
-            return null;
-        }
-        return new OnClickListener() {
-            @Override
-            public void onClick(View arg0) {
-                mLauncher.showOverviewMode(true);
-            }
-        };
-    }
-
-    @Override
     protected void screenScrolled(int screenCenter) {
         updatePageAlphaValues(screenCenter);
         updateStateForCustomContent(screenCenter);
@@ -1692,15 +1682,6 @@
     }
 
     protected void onResume() {
-        if (getPageIndicator() != null) {
-            // In case accessibility state has changed, we need to perform this on every
-            // attach to window
-            OnClickListener listener = getPageIndicatorClickListener();
-            if (listener != null) {
-                getPageIndicator().setOnClickListener(listener);
-            }
-        }
-
         // Update wallpaper dimensions if they were changed since last onResume
         // (we also always set the wallpaper dimensions in the constructor)
         if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
@@ -2086,10 +2067,13 @@
                     IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
             page.setContentDescription(getPageDescription(pageNo));
 
-            if (mPagesAccessibilityDelegate == null) {
-                mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this);
+            // No custom action for the first page.
+            if (!FeatureFlags.QSB_ON_FIRST_SCREEN || pageNo > 0) {
+                if (mPagesAccessibilityDelegate == null) {
+                    mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this);
+                }
+                page.setAccessibilityDelegate(mPagesAccessibilityDelegate);
             }
-            page.setAccessibilityDelegate(mPagesAccessibilityDelegate);
         } else {
             int accessible = mState == State.NORMAL ?
                     IMPORTANT_FOR_ACCESSIBILITY_AUTO :
@@ -2118,7 +2102,7 @@
 
     @Override
     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
-        if (mPageIndicator instanceof PageIndicator) {
+        if (mPageIndicator != null) {
             boolean isNewStateSpringLoaded = mState == State.SPRING_LOADED;
             mPageIndicator.setShouldAutoHide(!isNewStateSpringLoaded);
             if (isNewStateSpringLoaded) {
@@ -2575,7 +2559,8 @@
         boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
         boolean willBecomeShortcut =
                 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
-                        info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
+                        info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
+                        info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
 
         return (aboveShortcut && willBecomeShortcut);
     }
@@ -3509,6 +3494,7 @@
             switch (info.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                 if (info.container == NO_ID && info instanceof AppInfo) {
                     // Came from all apps -- make a copy
                     info = ((AppInfo) info).makeShortcut();
@@ -4195,7 +4181,7 @@
 
     public interface ItemOperator {
         /**
-         * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}.
+         * Process the next itemInfo, possibly with side-effect on the next item.
          *
          * @param info info for the shortcut
          * @param view view for the shortcut
@@ -4379,11 +4365,7 @@
         exitWidgetResizeMode();
     }
 
-    protected String getPageIndicatorDescription() {
-        String settings = getResources().getString(R.string.settings_button_text);
-        return getCurrentPageDescription() + ", " + settings;
-    }
-
+    @Override
     protected String getCurrentPageDescription() {
         if (hasCustomContent() && getNextPage() == 0) {
             return mCustomContentDescription;
diff --git a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
new file mode 100644
index 0000000..64559f2
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 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.accessibility;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
+/**
+ * Accessibility delegate with actions pointing to various Overview entry points.
+ */
+public class OverviewAccessibilityDelegate extends AccessibilityDelegate {
+
+    private static final int OVERVIEW = R.string.accessibility_action_overview;
+    private static final int WALLPAPERS = R.string.wallpaper_button_text;
+    private static final int WIDGETS = R.string.widget_button_text;
+    private static final int SETTINGS = R.string.settings_button_text;
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(host, info);
+
+        Context context = host.getContext();
+        info.addAction(new AccessibilityAction(OVERVIEW, context.getText(OVERVIEW)));
+
+        if (Utilities.isWallapaperAllowed(context)) {
+            info.addAction(new AccessibilityAction(WALLPAPERS, context.getText(WALLPAPERS)));
+        }
+        info.addAction(new AccessibilityAction(WIDGETS, context.getText(WIDGETS)));
+        info.addAction(new AccessibilityAction(SETTINGS, context.getText(SETTINGS)));
+    }
+
+    @Override
+    public boolean performAccessibilityAction(View host, int action, Bundle args) {
+        Launcher launcher = (Launcher) host.getContext();
+        if (action == OVERVIEW) {
+            launcher.showOverviewMode(true);
+            return true;
+        } else if (action == WALLPAPERS) {
+            launcher.onClickWallpaperPicker(host);
+            return true;
+        } else if (action == WIDGETS) {
+            launcher.onClickAddWidgetButton(host);
+            return true;
+        } else if (action == SETTINGS) {
+            launcher.onClickSettingsButton(host);
+            return true;
+        }
+        return super.performAccessibilityAction(host, action, args);
+    }
+}
diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
index c5b52de..5f68f90 100644
--- a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
@@ -28,6 +28,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.config.FeatureFlags;
 
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate {
@@ -88,7 +89,9 @@
         if (index < mWorkspace.getChildCount() - 1) {
             info.addAction(mActions.get(MOVE_FORWARD));
         }
-        if (index > mWorkspace.numCustomPages()) {
+
+        int startIndex = mWorkspace.numCustomPages() + (FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0);
+        if (index > startIndex) {
             info.addAction(mActions.get(MOVE_BACKWARD));
         }
     }
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 237a9e9..3381064 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -19,10 +19,13 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 
 import java.util.List;
 
@@ -45,6 +48,8 @@
         void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing);
         void onPackagesSuspended(String[] packageNames, UserHandleCompat user);
         void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user);
+        void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
+                UserHandleCompat user);
     }
 
     protected LauncherAppsCompat() {
@@ -56,7 +61,9 @@
     public static LauncherAppsCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.ATLEAST_LOLLIPOP) {
+                if (Utilities.isNycOrAbove()) {
+                    sInstance = new LauncherAppsCompatVNMR1(context.getApplicationContext());
+                } else if (Utilities.ATLEAST_LOLLIPOP) {
                     sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
                 } else {
                     sInstance = new LauncherAppsCompatV16(context.getApplicationContext());
@@ -79,4 +86,11 @@
     public abstract boolean isActivityEnabledForProfile(ComponentName component,
             UserHandleCompat user);
     public abstract boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user);
+    public abstract List<ShortcutInfoCompat> getShortcuts(LauncherApps.ShortcutQuery q,
+            UserHandleCompat userHandle);
+    public abstract void pinShortcuts(String packageName, List<String> pinnedIds,
+            UserHandleCompat userHandle);
+    public abstract void startShortcut(String packageName, String id, Rect sourceBounds,
+            Bundle startActivityOptions, UserHandleCompat user);
+    public abstract Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density);
 }
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
index 4e2fc05..1a144e8 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
@@ -22,15 +22,18 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Settings;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Thunk;
 
@@ -130,6 +133,29 @@
         return false;
     }
 
+    @Override
+    public List<ShortcutInfoCompat> getShortcuts(LauncherApps.ShortcutQuery q,
+            UserHandleCompat userHandle) {
+        return null;
+    }
+
+    @Override
+    public void pinShortcuts(String packageName, List<String> pinnedIds,
+            UserHandleCompat userHandle) {
+        // Not supported, so do nothing.
+    }
+
+    @Override
+    public void startShortcut(String packageName, String id, Rect sourceBounds,
+            Bundle startActivityOptions, UserHandleCompat user) {
+        // Not supported, so do nothing.
+    }
+
+    @Override
+    public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
+        return null;
+    }
+
     private void unregisterForPackageIntents() {
         mContext.unregisterReceiver(mPackageMonitor);
     }
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
index 7270d02..d97bf2f 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -22,11 +22,14 @@
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.UserHandle;
 
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -34,7 +37,7 @@
 import java.util.Map;
 
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class LauncherAppsCompatVL extends LauncherAppsCompat {
+public class LauncherAppsCompatVL extends LauncherAppsCompatV16 {
 
     protected LauncherApps mLauncherApps;
 
@@ -42,7 +45,7 @@
             = new HashMap<OnAppsChangedCallbackCompat, WrappedCallback>();
 
     LauncherAppsCompatVL(Context context) {
-        super();
+        super(context);
         mLauncherApps = (LauncherApps) context.getSystemService("launcherapps");
     }
 
@@ -146,6 +149,18 @@
         public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
             mCallback.onPackagesUnsuspended(packageNames, UserHandleCompat.fromUser(user));
         }
+
+        @Override
+        public void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
+                UserHandle user) {
+            List<ShortcutInfoCompat> shortcutInfoCompats = new ArrayList<>(shortcuts.size());
+            for (ShortcutInfo shortcutInfo : shortcuts) {
+                shortcutInfoCompats.add(new ShortcutInfoCompat(shortcutInfo));
+            }
+
+            mCallback.onShortcutsChanged(packageName, shortcutInfoCompats,
+                    UserHandleCompat.fromUser(user));
+        }
     }
 }
 
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVNMR1.java b/src/com/android/launcher3/compat/LauncherAppsCompatVNMR1.java
new file mode 100644
index 0000000..0c1db13
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVNMR1.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 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.compat;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@TargetApi(Build.VERSION_CODES.N)
+public class LauncherAppsCompatVNMR1 extends LauncherAppsCompatVL {
+
+    LauncherAppsCompatVNMR1(Context context) {
+        super(context);
+    }
+
+    @Override
+    public List<ShortcutInfoCompat> getShortcuts(LauncherApps.ShortcutQuery q,
+            UserHandleCompat userHandle) {
+        List<ShortcutInfo> shortcutInfos = mLauncherApps.getShortcuts(q, userHandle.getUser());
+        if (shortcutInfos == null) {
+            return null;
+        }
+        List<ShortcutInfoCompat> shortcutInfoCompats = new ArrayList<>(shortcutInfos.size());
+        for (ShortcutInfo shortcutInfo : shortcutInfos) {
+            shortcutInfoCompats.add(new ShortcutInfoCompat(shortcutInfo));
+        }
+        return shortcutInfoCompats;
+    }
+
+    @Override
+    public void pinShortcuts(String packageName, List<String> pinnedIds,
+            UserHandleCompat userHandle) {
+        mLauncherApps.pinShortcuts(packageName, pinnedIds, userHandle.getUser());
+    }
+
+    @Override
+    public void startShortcut(String packageName, String id, Rect sourceBounds,
+            Bundle startActivityOptions, UserHandleCompat user) {
+        mLauncherApps.startShortcut(packageName, id, sourceBounds,
+                startActivityOptions, user.getUser());
+    }
+
+    @Override
+    public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
+        return mLauncherApps.getShortcutIconDrawable(shortcutInfo.getShortcutInfo(), density);
+    }
+
+    private static class WrappedCallback extends LauncherApps.Callback {
+        private OnAppsChangedCallbackCompat mCallback;
+
+        public WrappedCallback(OnAppsChangedCallbackCompat callback) {
+            mCallback = callback;
+        }
+
+        public void onPackageRemoved(String packageName, UserHandle user) {
+            mCallback.onPackageRemoved(packageName, UserHandleCompat.fromUser(user));
+        }
+
+        public void onPackageAdded(String packageName, UserHandle user) {
+            mCallback.onPackageAdded(packageName, UserHandleCompat.fromUser(user));
+        }
+
+        public void onPackageChanged(String packageName, UserHandle user) {
+            mCallback.onPackageChanged(packageName, UserHandleCompat.fromUser(user));
+        }
+
+        public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
+            mCallback.onPackagesAvailable(packageNames, UserHandleCompat.fromUser(user), replacing);
+        }
+
+        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+            mCallback.onPackagesUnavailable(packageNames, UserHandleCompat.fromUser(user),
+                    replacing);
+        }
+
+        public void onPackagesSuspended(String[] packageNames, UserHandle user) {
+            mCallback.onPackagesSuspended(packageNames, UserHandleCompat.fromUser(user));
+        }
+
+        public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
+            mCallback.onPackagesUnsuspended(packageNames, UserHandleCompat.fromUser(user));
+        }
+
+        @Override
+        public void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
+                UserHandle user) {
+            List<ShortcutInfoCompat> shortcutInfoCompats = new ArrayList<>(shortcuts.size());
+            for (ShortcutInfo shortcutInfo : shortcuts) {
+                shortcutInfoCompats.add(new ShortcutInfoCompat(shortcutInfo));
+            }
+
+            mCallback.onShortcutsChanged(packageName, shortcutInfoCompats,
+                    UserHandleCompat.fromUser(user));
+        }
+    }
+}
+
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 93238de..2035f99 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -510,7 +510,7 @@
         }
 
         // This is set to true in close(), but isn't reset to false until onDropCompleted(). This
-        // leads to an consistent state if you drag out of the folder and drag back in without
+        // leads to an inconsistent state if you drag out of the folder and drag back in without
         // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
         mDeleteFolderOnDropCompleted = false;
 
@@ -737,7 +737,8 @@
         final ItemInfo item = d.dragInfo;
         final int itemType = item.itemType;
         return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
-                    itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
+                itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
+                itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
                     !isFull());
     }
 
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index d7952c5..d08cf54 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -213,7 +213,8 @@
     private boolean willAcceptItem(ItemInfo item) {
         final int itemType = item.itemType;
         return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
-                itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
+                itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
+                itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
                 !mFolder.isFull() && item != mInfo && !mInfo.opened);
     }
 
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index c56e4e5..c6fc4cb 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -120,6 +120,7 @@
         mFolder = folder;
         mKeyListener = new PagedFolderKeyEventListener(folder);
         mPageIndicator = (PageIndicator) folder.findViewById(R.id.folder_page_indicator);
+        initParentViews(folder);
     }
 
     /**
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index e9a897e..0deee57 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewParent;
 
@@ -28,11 +29,14 @@
 import com.android.launcher3.util.ComponentKey;
 
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Manages the creation of {@link LauncherEvent}.
  */
-public abstract class UserEventDispatcher {
+public class UserEventDispatcher {
+
+    private static final boolean DEBUG_LOGGING = false;
 
     private final static int MAXIMUM_VIEW_HIERARCHY_LEVEL = 5;
     /**
@@ -155,7 +159,17 @@
         mActionDurationMillis = System.currentTimeMillis();
     }
 
-    public abstract void dispatchUserEvent(LauncherEvent ev, Intent intent);
+    public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
+        if (DEBUG_LOGGING) {
+            Log.d("UserEvent", String.format(Locale.US,
+                    "action:%s\nchild:%s\nparent:%s\nelapsed container %d ms session %d ms",
+                    LoggerUtils.getActionStr(ev.action),
+                    LoggerUtils.getTargetStr(ev.srcTarget[0]),
+                    LoggerUtils.getTargetStr(ev.srcTarget[1]),
+                    ev.elapsedContainerMillis,
+                    ev.elapsedSessionMillis));
+        }
+    }
 
     public int getPredictedRank(ComponentKey key) {
         if (mPredictedApps == null) return -1;
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index 8e8e551..cb1acaa 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -150,7 +150,7 @@
     protected boolean migrateHotseat() throws Exception {
         ArrayList<DbEntry> items = loadHotseatEntries();
 
-        int requiredCount = mDestHotseatSize - 1;
+        int requiredCount = FeatureFlags.NO_ALL_APPS_ICON ? mDestHotseatSize : mDestHotseatSize - 1;
 
         while (items.size() > requiredCount) {
             // Pick the center item by default.
@@ -641,10 +641,11 @@
                 // calculate weight
                 switch (entry.itemType) {
                     case Favorites.ITEM_TYPE_SHORTCUT:
+                    case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                     case Favorites.ITEM_TYPE_APPLICATION: {
                         verifyIntent(c.getString(indexIntent));
-                        entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT
-                                ? WT_SHORTCUT : WT_APPLICATION;
+                        entry.weight = entry.itemType == Favorites.ITEM_TYPE_APPLICATION ?
+                                WT_APPLICATION : WT_SHORTCUT;
                         break;
                     }
                     case Favorites.ITEM_TYPE_FOLDER: {
@@ -715,10 +716,11 @@
                 // calculate weight
                 switch (entry.itemType) {
                     case Favorites.ITEM_TYPE_SHORTCUT:
+                    case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                     case Favorites.ITEM_TYPE_APPLICATION: {
                         verifyIntent(c.getString(indexIntent));
-                        entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT
-                            ? WT_SHORTCUT : WT_APPLICATION;
+                        entry.weight = entry.itemType == Favorites.ITEM_TYPE_APPLICATION ?
+                                WT_APPLICATION : WT_SHORTCUT;
                         break;
                     }
                     case Favorites.ITEM_TYPE_APPWIDGET: {
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
new file mode 100644
index 0000000..e2e06af
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 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.shortcuts;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.os.Build;
+import android.os.Process;
+import android.util.Log;
+
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class DeepShortcutManager {
+    private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_GET_DYNAMIC
+            | ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_MANIFEST;
+
+    private final LauncherAppsCompat mLauncherApps;
+
+    public DeepShortcutManager(Context context, ShortcutCache shortcutCache) {
+        mLauncherApps = LauncherAppsCompat.getInstance(context);
+    }
+
+    public void onShortcutsChanged(List<ShortcutInfoCompat> shortcuts) {
+        // mShortcutCache.removeShortcuts(shortcuts);
+    }
+
+    /**
+     * Queries for the shortcuts with the package name and provided ids.
+     *
+     * This method is intended to get the full details for shortcuts when they are added or updated,
+     * because we only get "key" fields in onShortcutsChanged().
+     */
+    public List<ShortcutInfoCompat> queryForFullDetails(String packageName,
+            List<String> shortcutIds, UserHandleCompat user) {
+        return query(FLAG_GET_ALL, packageName, null, shortcutIds, user);
+    }
+
+    /**
+     * Gets all the shortcuts associated with the given package and user.
+     */
+    public List<ShortcutInfoCompat> queryForAllAppShortcuts(ComponentName activity,
+            List<String> ids, UserHandleCompat user) {
+        return query(FLAG_GET_ALL, activity.getPackageName(), activity, ids, user);
+    }
+
+    /**
+     * Removes the given shortcut from the current list of pinned shortcuts.
+     * (Runs on background thread)
+     */
+    public void unpinShortcut(final ShortcutKey key) {
+        String packageName = key.componentName.getPackageName();
+        String id = key.id;
+        UserHandleCompat user = key.user;
+        List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
+        pinnedIds.remove(id);
+        mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
+    }
+
+    /**
+     * Adds the given shortcut to the current list of pinned shortcuts.
+     * (Runs on background thread)
+     */
+    public void pinShortcut(final ShortcutKey key) {
+        String packageName = key.componentName.getPackageName();
+        String id = key.id;
+        UserHandleCompat user = key.user;
+        List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
+        pinnedIds.add(id);
+        mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
+    }
+
+    /**
+     * Returns the id's of pinned shortcuts associated with the given package and user.
+     *
+     * If packageName is null, returns all pinned shortcuts regardless of package.
+     */
+    public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName,
+            UserHandleCompat user) {
+        return query(ShortcutQuery.FLAG_GET_PINNED, packageName, null, null, user);
+    }
+
+    public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandleCompat user) {
+        return query(FLAG_GET_ALL, null, null, null, user);
+    }
+
+    private List<String> extractIds(List<ShortcutInfoCompat> shortcuts) {
+        List<String> shortcutIds = new ArrayList<>(shortcuts.size());
+        for (ShortcutInfoCompat shortcut : shortcuts) {
+            shortcutIds.add(shortcut.getId());
+        }
+        return shortcutIds;
+    }
+
+    /**
+     * Query the system server for all the shortcuts matching the given parameters.
+     * If packageName == null, we query for all shortcuts with the passed flags, regardless of app.
+     *
+     * TODO: Use the cache to optimize this so we don't make an RPC every time.
+     */
+    private List<ShortcutInfoCompat> query(int flags, String packageName,
+            ComponentName activity, List<String> shortcutIds, UserHandleCompat user) {
+        ShortcutQuery q = new ShortcutQuery();
+        q.setQueryFlags(flags);
+        if (packageName != null) {
+            q.setPackage(packageName);
+            q.setActivity(activity);
+            q.setShortcutIds(shortcutIds);
+        }
+        return mLauncherApps.getShortcuts(q, user);
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutCache.java b/src/com/android/launcher3/shortcuts/ShortcutCache.java
new file mode 100644
index 0000000..fc118a8
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutCache.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 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.shortcuts;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.UserHandle;
+import android.util.LruCache;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Loads {@link ShortcutInfoCompat}s on demand (e.g. when launcher
+ * loads for pinned shortcuts and on long-press for dynamic shortcuts), and caches them
+ * for handful of apps in an LruCache while launcher lives.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class ShortcutCache {
+    private static final String TAG = "ShortcutCache";
+    private static final boolean LOGD = false;
+
+    private static final int CACHE_SIZE = 30; // Max number shortcuts we cache.
+
+    private LruCache<ShortcutKey, ShortcutInfoCompat> mCachedShortcuts;
+    // We always keep pinned shortcuts in the cache.
+    private HashMap<ShortcutKey, ShortcutInfoCompat> mPinnedShortcuts;
+
+    public ShortcutCache() {
+        mCachedShortcuts = new LruCache<>(CACHE_SIZE);
+        mPinnedShortcuts = new HashMap<>();
+    }
+
+    /**
+     * Removes shortcuts from the cache when shortcuts change for a given package.
+     *
+     * Returns a map of ids to their evicted shortcuts.
+     *
+     * @see android.content.pm.LauncherApps.Callback#onShortcutsChanged(String, List, UserHandle).
+     */
+    public void removeShortcuts(List<ShortcutInfoCompat> shortcuts) {
+        for (ShortcutInfoCompat shortcut : shortcuts) {
+            ShortcutKey key = ShortcutKey.fromInfo(shortcut);
+            mCachedShortcuts.remove(key);
+        }
+    }
+
+    public ShortcutInfoCompat get(ShortcutKey key) {
+        if (mPinnedShortcuts.containsKey(key)) {
+            return mPinnedShortcuts.get(key);
+        }
+        return mCachedShortcuts.get(key);
+    }
+
+    public void put(ShortcutKey key, ShortcutInfoCompat shortcut) {
+        if (shortcut.isPinned()) {
+            mPinnedShortcuts.put(key, shortcut);
+        } else {
+            mCachedShortcuts.put(key, shortcut);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
new file mode 100644
index 0000000..8dbeaa7
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2016 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.shortcuts;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.os.Build;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ComponentKey;
+
+/**
+ * Wrapper class for {@link android.content.pm.ShortcutInfo}, representing deep shortcuts into apps.
+ *
+ * Not to be confused with {@link com.android.launcher3.ShortcutInfo}.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class ShortcutInfoCompat {
+    private static final String INTENT_CATEGORY = "com.android.launcher3.DEEP_SHORTCUT";
+    public static final String EXTRA_SHORTCUT_ID = "shortcut_id";
+
+    private ShortcutInfo mShortcutInfo;
+
+    public ShortcutInfoCompat(ShortcutInfo shortcutInfo) {
+        mShortcutInfo = shortcutInfo;
+    }
+
+    @TargetApi(Build.VERSION_CODES.N)
+    public Intent makeIntent(Context context) {
+        long serialNumber = UserManagerCompat.getInstance(context)
+                .getSerialNumberForUser(getUserHandle());
+        return new Intent(Intent.ACTION_MAIN)
+                .addCategory(INTENT_CATEGORY)
+                .setComponent(getActivity())
+                .setPackage(getPackage())
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+                .putExtra(ItemInfo.EXTRA_PROFILE, serialNumber)
+                .putExtra(EXTRA_SHORTCUT_ID, getId());
+    }
+
+    public ShortcutInfo getShortcutInfo() {
+        return mShortcutInfo;
+    }
+
+    public String getPackage() {
+        return mShortcutInfo.getPackage();
+    }
+
+    public String getId() {
+        return mShortcutInfo.getId();
+    }
+
+    public CharSequence getShortLabel() {
+        return mShortcutInfo.getShortLabel();
+    }
+
+    public CharSequence getLongLabel() {
+        return mShortcutInfo.getLongLabel();
+    }
+
+    public long getLastChangedTimestamp() {
+        return mShortcutInfo.getLastChangedTimestamp();
+    }
+
+    public ComponentName getActivity() {
+        return mShortcutInfo.getActivity();
+    }
+
+    public UserHandleCompat getUserHandle() {
+        return UserHandleCompat.fromUser(mShortcutInfo.getUserHandle());
+    }
+
+    public boolean hasKeyFieldsOnly() {
+        return mShortcutInfo.hasKeyFieldsOnly();
+    }
+
+    public boolean isPinned() {
+        return mShortcutInfo.isPinned();
+    }
+
+    @Override
+    public String toString() {
+        return mShortcutInfo.toString();
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
new file mode 100644
index 0000000..c9d66eb
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -0,0 +1,30 @@
+package com.android.launcher3.shortcuts;
+
+import android.content.ComponentName;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ComponentKey;
+
+/**
+ * A key that uniquely identifies a shortcut using its package, id, and user handle.
+ */
+public class ShortcutKey extends ComponentKey {
+    final String id;
+
+    public ShortcutKey(String packageName, UserHandleCompat user, String id) {
+        // Use the id as the class name.
+        super(new ComponentName(packageName, id), user);
+        this.id = id;
+    }
+
+    public static ShortcutKey fromInfo(ShortcutInfoCompat shortcutInfo) {
+        return new ShortcutKey(shortcutInfo.getPackage(), shortcutInfo.getUserHandle(),
+                shortcutInfo.getId());
+    }
+
+    @Override
+    public String toString() {
+        return flattenToString(LauncherAppState.getInstance().getContext());
+    }
+}
diff --git a/src/com/android/launcher3/util/CachedPackageTracker.java b/src/com/android/launcher3/util/CachedPackageTracker.java
index d55d573..293714e 100644
--- a/src/com/android/launcher3/util/CachedPackageTracker.java
+++ b/src/com/android/launcher3/util/CachedPackageTracker.java
@@ -170,7 +170,7 @@
      */
     protected abstract void onLauncherPackageRemoved(String packageName, UserHandleCompat user);
 
-    protected static class LauncherActivityInstallInfo
+    public static class LauncherActivityInstallInfo
             implements Comparable<LauncherActivityInstallInfo> {
         public final LauncherActivityInfoCompat info;
         public final long installTime;
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index df23abe..7dbc0e7a 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -29,6 +29,7 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 
@@ -180,6 +181,12 @@
                 saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
             }
         }
+
+        @Override
+        public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
+                UserHandleCompat user) {
+            // Do nothing
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/util/MultiHashMap.java b/src/com/android/launcher3/util/MultiHashMap.java
new file mode 100644
index 0000000..f54ab88
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiHashMap.java
@@ -0,0 +1,20 @@
+package com.android.launcher3.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A utility map from keys to an ArrayList of values.
+ */
+public class MultiHashMap<K, V> extends HashMap<K, ArrayList<V>> {
+    public void addToList(K key, V value) {
+        ArrayList<V> list = get(key);
+        if (list == null) {
+            list = new ArrayList<>();
+            list.add(value);
+            put(key, list);
+        } else {
+            list.add(value);
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
index 08a81f0..c250cb2 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -10,6 +10,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.util.TestLauncherProvider;
 
@@ -61,8 +62,13 @@
         mIdp.numHotseatIcons = 3;
         new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, 5, 3)
                 .migrateHotseat();
-        // First & last items are dropped as they have the least weight.
-        verifyHotseat(hotseatItems[1], -1, hotseatItems[3]);
+        if (FeatureFlags.NO_ALL_APPS_ICON) {
+            // First item is dropped as it has the least weight.
+            verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
+        } else {
+            // First & last items are dropped as they have the least weight.
+            verifyHotseat(hotseatItems[1], -1, hotseatItems[3]);
+        }
     }
 
     public void testHotseatMigration_shortcuts_dropped() throws Exception {
@@ -77,8 +83,13 @@
         mIdp.numHotseatIcons = 3;
         new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, 5, 3)
                 .migrateHotseat();
-        // First & third items are dropped as they have the least weight.
-        verifyHotseat(hotseatItems[1], -1, hotseatItems[4]);
+        if (FeatureFlags.NO_ALL_APPS_ICON) {
+            // First item is dropped as it has the least weight.
+            verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
+        } else {
+            // First & third items are dropped as they have the least weight.
+            verifyHotseat(hotseatItems[1], -1, hotseatItems[4]);
+        }
     }
 
     private void verifyHotseat(long... sortedIds) {