Merge "Temporary workaround for long failure messages when running from AS" into ub-launcher3-master
diff --git a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
deleted file mode 100644
index 42b1194..0000000
--- a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2018 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.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.notification.NotificationKeyData;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
- */
-public class DeepShortcutManager {
-
-    private static final DeepShortcutManager sInstance = new DeepShortcutManager();
-
-    public static DeepShortcutManager getInstance(Context context) {
-        return sInstance;
-    }
-
-    private final QueryResult mFailure = new QueryResult();
-
-    private DeepShortcutManager() { }
-
-    /**
-     * 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 QueryResult queryForFullDetails(String packageName,
-            List<String> shortcutIds, UserHandle user) {
-        return mFailure;
-    }
-
-    /**
-     * Gets all the manifest and dynamic shortcuts associated with the given package and user,
-     * to be displayed in the shortcuts container on long press.
-     */
-    public QueryResult queryForShortcutsContainer(ComponentName activity,
-            UserHandle user) {
-        return mFailure;
-    }
-
-    /**
-     * Removes the given shortcut from the current list of pinned shortcuts.
-     * (Runs on background thread)
-     */
-    public void unpinShortcut(final ShortcutKey key) {
-    }
-
-    /**
-     * Adds the given shortcut to the current list of pinned shortcuts.
-     * (Runs on background thread)
-     */
-    public void pinShortcut(final ShortcutKey key) {
-    }
-
-    public void startShortcut(String packageName, String id, Rect sourceBounds,
-            Bundle startActivityOptions, UserHandle user) {
-    }
-
-    public Drawable getShortcutIconDrawable(ShortcutInfo shortcutInfo, int density) {
-        return null;
-    }
-
-    /**
-     * 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 QueryResult queryForPinnedShortcuts(String packageName, UserHandle user) {
-        return mFailure;
-    }
-
-    public QueryResult queryForPinnedShortcuts(String packageName,
-            List<String> shortcutIds, UserHandle user) {
-        return mFailure;
-    }
-
-    public QueryResult queryForAllShortcuts(UserHandle user) {
-        return mFailure;
-    }
-
-    public boolean hasHostPermission() {
-        return false;
-    }
-
-
-    public static class QueryResult extends ArrayList<ShortcutInfo> {
-
-        public boolean wasSuccess() {
-            return true;
-        }
-    }
-}
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 8f413dc..0560d68 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -93,7 +93,7 @@
   TASKSWITCHER = 12; // Recents UI Container (QuickStep)
   APP = 13; // Foreground activity is another app (QuickStep)
   TIP = 14; // Onboarding texts (QuickStep)
-  SIDELOADED_LAUNCHER = 15;
+  OTHER_LAUNCHER_APP = 15;
 }
 
 // Used to define what type of control a Target would represent.
diff --git a/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml b/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml
deleted file mode 100644
index cfc6d48..0000000
--- a/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:inset="@dimen/predicted_icon_background_inset">
-    <shape>
-        <solid android:color="?attr/folderFillColor" />
-        <corners android:radius="@dimen/predicted_icon_background_corner_radius" />
-    </shape>
-</inset>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
index f7e71f3..b94142a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -61,7 +62,7 @@
 public class HotseatPredictionController implements DragController.DragListener,
         View.OnAttachStateChangeListener, SystemShortcut.Factory<QuickstepLauncher>,
         InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener,
-        IconCache.ItemInfoUpdateReceiver {
+        IconCache.ItemInfoUpdateReceiver, DragSource {
 
     private static final String TAG = "PredictiveHotseat";
     private static final boolean DEBUG = false;
@@ -72,6 +73,9 @@
     private static final String APP_LOCATION_HOTSEAT = "hotseat";
     private static final String APP_LOCATION_WORKSPACE = "workspace";
 
+    private static final String BUNDLE_KEY_HOTSEAT = "hotseat_apps";
+    private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps";
+
     private static final String PREDICTION_CLIENT = "hotseat";
 
     private DropTarget.DragObject mDragObject;
@@ -79,7 +83,7 @@
     private int mPredictedSpotsCount = 0;
 
     private Launcher mLauncher;
-    private Hotseat mHotseat;
+    private final Hotseat mHotseat;
 
     private List<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
 
@@ -87,10 +91,18 @@
 
     private AppPredictor mAppPredictor;
     private AllAppsStore mAllAppsStore;
+    private AnimatorSet mIconRemoveAnimators;
+
 
     private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
 
-    private static HotseatPredictionController sInstance;
+    private final View.OnLongClickListener mPredictionLongClickListener = v -> {
+        if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+        if (mLauncher.getWorkspace().isSwitchingState()) return false;
+        // Start the drag
+        mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
+        return false;
+    };
 
     public HotseatPredictionController(Launcher launcher) {
         mLauncher = launcher;
@@ -101,7 +113,9 @@
         mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
         launcher.getDeviceProfile().inv.addOnChangeListener(this);
         mHotseat.addOnAttachStateChangeListener(this);
-        sInstance = this;
+        if (mHotseat.isAttachedToWindow()) {
+            onViewAttachedToWindow(mHotseat);
+        }
     }
 
     @Override
@@ -125,6 +139,17 @@
         List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
         int predictionIndex = 0;
         ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
+        // make sure predicted icon removal and filling predictions don't step on each other
+        if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
+            mIconRemoveAnimators.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    fillGapsWithPrediction(animate, callback);
+                    mIconRemoveAnimators.removeListener(this);
+                }
+            });
+            return;
+        }
         for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
             View child = mHotseat.getChildAt(
                     mHotseat.getCellXFromOrder(rank),
@@ -140,12 +165,11 @@
                 }
                 continue;
             }
-
             WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
             if (isPredictedIcon(child) && child.isEnabled()) {
                 PredictedAppIcon icon = (PredictedAppIcon) child;
                 icon.applyFromWorkspaceItem(predictedItem);
-                icon.finishBinding();
+                icon.finishBinding(mPredictionLongClickListener);
             } else {
                 newItems.add(predictedItem);
             }
@@ -160,7 +184,7 @@
         for (WorkspaceItemInfo item : itemsToAdd) {
             PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
             mLauncher.getWorkspace().addInScreenFromBind(icon, item);
-            icon.finishBinding();
+            icon.finishBinding(mPredictionLongClickListener);
             if (animate) {
                 animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
             }
@@ -215,9 +239,9 @@
 
     private Bundle getAppPredictionContextExtra() {
         Bundle bundle = new Bundle();
-        bundle.putParcelableArrayList(APP_LOCATION_HOTSEAT,
+        bundle.putParcelableArrayList(BUNDLE_KEY_HOTSEAT,
                 getPinnedAppTargetsInViewGroup((mHotseat.getShortcutsAndWidgets())));
-        bundle.putParcelableArrayList(APP_LOCATION_WORKSPACE, getPinnedAppTargetsInViewGroup(
+        bundle.putParcelableArrayList(BUNDLE_KEY_WORKSPACE, getPinnedAppTargetsInViewGroup(
                 mLauncher.getWorkspace().getScreenWithId(
                         Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets()));
         return bundle;
@@ -285,9 +309,12 @@
             ItemInfoWithIcon info = mapper.getApp(allAppsStore);
             if (info instanceof AppInfo) {
                 WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((AppInfo) info);
+                predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
                 predictedApps.add(predictedApp);
             } else if (info instanceof WorkspaceItemInfo) {
-                predictedApps.add(new WorkspaceItemInfo((WorkspaceItemInfo) info));
+                WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((WorkspaceItemInfo) info);
+                predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+                predictedApps.add(predictedApp);
             } else {
                 if (DEBUG) {
                     Log.e(TAG, "Predicted app not found: " + mapper);
@@ -313,13 +340,27 @@
         return icons;
     }
 
-    private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines) {
+    private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines,
+            ItemInfo draggedInfo) {
+        if (mIconRemoveAnimators != null) {
+            mIconRemoveAnimators.end();
+        }
+        mIconRemoveAnimators = new AnimatorSet();
+        removeOutlineDrawings();
         for (PredictedAppIcon icon : getPredictedIcons()) {
+            if (!icon.isEnabled()) {
+                continue;
+            }
+            if (icon.getTag().equals(draggedInfo)) {
+                mHotseat.removeView(icon);
+                continue;
+            }
             int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
             outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
                     mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
             icon.setEnabled(false);
-            icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() {
+            ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
+            animator.addListener(new AnimationSuccessListener() {
                 @Override
                 public void onAnimationSuccess(Animator animator) {
                     if (icon.getParent() != null) {
@@ -327,10 +368,11 @@
                     }
                 }
             });
+            mIconRemoveAnimators.play(animator);
         }
+        mIconRemoveAnimators.start();
     }
 
-
     private void notifyItemAction(AppTarget target, String location, int action) {
         if (mAppPredictor != null) {
             mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target,
@@ -340,7 +382,7 @@
 
     @Override
     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-        removePredictedApps(mOutlineDrawings);
+        removePredictedApps(mOutlineDrawings, dragObject.dragInfo);
         mDragObject = dragObject;
         if (mOutlineDrawings.isEmpty()) return;
         for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
@@ -354,14 +396,25 @@
         if (mDragObject == null) {
             return;
         }
+
         ItemInfo dragInfo = mDragObject.dragInfo;
-        if (dragInfo instanceof WorkspaceItemInfo && dragInfo.getTargetComponent() != null) {
+        ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets();
+        ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId(
+                Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets();
+
+        if (dragInfo instanceof WorkspaceItemInfo
+                && dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+                && dragInfo.getTargetComponent() != null) {
             AppTarget appTarget = getAppTargetFromItemInfo(dragInfo);
             if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) {
-                notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
+                if (!getPinnedAppTargetsInViewGroup(hotseatVG).contains(appTarget)) {
+                    notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
+                }
             }
             if (!isInFirstPage(dragInfo) && isInFirstPage(mDragObject.originalDragInfo)) {
-                notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
+                if (!getPinnedAppTargetsInViewGroup(firstScreenVG).contains(appTarget)) {
+                    notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
+                }
             }
             if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) {
                 notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
@@ -371,14 +424,7 @@
             }
         }
         mDragObject = null;
-        fillGapsWithPrediction(true, () -> {
-            if (mOutlineDrawings.isEmpty()) return;
-            for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
-                mHotseat.removeDelegatedCellDrawing(outlineDrawing);
-            }
-            mHotseat.invalidate();
-            mOutlineDrawings.clear();
-        });
+        fillGapsWithPrediction(true, this::removeOutlineDrawings);
     }
 
     @Nullable
@@ -394,11 +440,20 @@
     private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
         itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
         itemInfo.rank = rank;
-        itemInfo.cellX = rank;
-        itemInfo.cellY = mHotSeatItemsCount - rank - 1;
+        itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
+        itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
         itemInfo.screenId = rank;
     }
 
+    private void removeOutlineDrawings() {
+        if (mOutlineDrawings.isEmpty()) return;
+        for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
+            mHotseat.removeDelegatedCellDrawing(outlineDrawing);
+        }
+        mHotseat.invalidate();
+        mOutlineDrawings.clear();
+    }
+
     @Override
     public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
         this.mHotSeatItemsCount = profile.numHotseatIcons;
@@ -415,6 +470,17 @@
 
     }
 
+    @Override
+    public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
+        //Does nothing
+    }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
+        mHotseat.fillInLogContainerData(v, info, target, targetParent);
+    }
+
     private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
 
         private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
@@ -435,18 +501,22 @@
      */
     public static void fillInHybridHotseatRank(
             @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
-        if (sInstance == null || itemInfo.getTargetComponent() == null
+        QuickstepLauncher launcher = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
+        if (launcher == null || launcher.getHotseatPredictionController() == null
+                || itemInfo.getTargetComponent() == null
                 || itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
             return;
         }
+        HotseatPredictionController controller = launcher.getHotseatPredictionController();
+
         final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
 
-        final List<ComponentKeyMapper> predictedApps = sInstance.mComponentKeyMappers;
+        final List<ComponentKeyMapper> predictedApps = controller.mComponentKeyMappers;
         IntStream.range(0, predictedApps.size())
                 .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
                 .findFirst()
                 .ifPresent((rank) -> target.predictedRank =
-                        Integer.parseInt(sInstance.mPredictedSpotsCount + "0" + rank));
+                        Integer.parseInt(controller.mPredictedSpotsCount + "0" + rank));
     }
 
     private static boolean isPredictedIcon(View view) {
@@ -461,8 +531,7 @@
         }
         ItemInfo info = (ItemInfo) view.getTag();
         return info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION && (
-                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
-                        || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
+                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION);
     }
 
     private static boolean isInHotseat(ItemInfo itemInfo) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
index 38bb180..76972af 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -44,8 +44,8 @@
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.InstantAppResolver;
 
 import java.util.ArrayList;
@@ -167,11 +167,7 @@
 
     @WorkerThread
     private WorkspaceItemInfo loadShortcutWorker(ShortcutKey shortcutKey) {
-        DeepShortcutManager mgr = DeepShortcutManager.getInstance(mContext);
-        List<ShortcutInfo> details = mgr.queryForFullDetails(
-                shortcutKey.componentName.getPackageName(),
-                Collections.<String>singletonList(shortcutKey.getId()),
-                shortcutKey.user);
+        List<ShortcutInfo> details = shortcutKey.buildRequest(mContext).query(ShortcutRequest.ALL);
         if (!details.isEmpty()) {
             WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext);
             try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 1dcbffb..27ac284 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -29,7 +29,6 @@
 
 import androidx.core.graphics.ColorUtils;
 
-import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -37,7 +36,6 @@
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.icons.IconNormalizer;
-import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.views.DoubleShadowBubbleTextView;
@@ -47,14 +45,13 @@
  */
 public class PredictedAppIcon extends DoubleShadowBubbleTextView {
 
-    private static final float RING_EFFECT_RATIO = 0.12f;
+    private static final float RING_EFFECT_RATIO = 0.11f;
 
     private DeviceProfile mDeviceProfile;
     private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private boolean mIsPinned = false;
     private int mNormalizedIconRadius;
 
-
     public PredictedAppIcon(Context context) {
         this(context, null, 0);
     }
@@ -105,14 +102,8 @@
     /**
      * prepares prediction icon for usage after bind
      */
-    public void finishBinding() {
-        setOnLongClickListener((v) -> {
-            PopupContainerWithArrow.showForIcon((BubbleTextView) v);
-            if (getParent() != null) {
-                getParent().requestDisallowInterceptTouchEvent(true);
-            }
-            return true;
-        });
+    public void finishBinding(OnLongClickListener longClickListener) {
+        setOnLongClickListener(longClickListener);
         ((CellLayout.LayoutParams) getLayoutParams()).canReorder = false;
         setTextVisibility(false);
         verifyHighRes();
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 37a3929..cae01ae 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -178,6 +178,13 @@
     }
 
     /**
+     * Returns Prediction controller for hybrid hotseat
+     */
+    public HotseatPredictionController getHotseatPredictionController() {
+        return mHotseatPredictionController;
+    }
+
+    /**
      * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
      */
     private void onStateOrResumeChanged() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index f889bc1..6574d22 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -226,7 +226,7 @@
         RecentsActivity activity = getCreatedActivity();
         boolean visible = activity != null && activity.isStarted() && activity.hasWindowFocus();
         return visible
-                ? LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER
+                ? LauncherLogProto.ContainerType.OTHER_LAUNCHER_APP
                 : LauncherLogProto.ContainerType.APP;
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index bafb2ef..29df5cc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -484,7 +484,9 @@
                 base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat);
             }
 
-            if (mOverscrollPlugin != null) {
+            if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()
+                    && (mOverscrollPlugin != null)
+                    && mOverscrollPlugin.isActive()) {
                 // Put the overscroll gesture as higher priority than the Assistant or base gestures
                 base = new OverscrollInputConsumer(this, newGestureState, base, mInputMonitorCompat,
                         mOverscrollPlugin);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
index e3da98b..0a21413 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
@@ -26,14 +26,17 @@
 
 import android.content.Context;
 import android.graphics.PointF;
+import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.R;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
+import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.plugins.OverscrollPlugin;
 import com.android.systemui.shared.system.InputMonitorCompat;
@@ -47,12 +50,12 @@
 
     private static final String TAG = "OverscrollInputConsumer";
 
-    private static final int ANGLE_THRESHOLD = 35; // Degrees
-
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
     private final PointF mStartDragPos = new PointF();
+    private final int mAngleThreshold;
 
+    private final float mFlingThresholdPx;
     private int mActivePointerId = -1;
     private boolean mPassedSlop = false;
 
@@ -60,19 +63,28 @@
 
     private final Context mContext;
     private final GestureState mGestureState;
-    @Nullable private final OverscrollPlugin mPlugin;
+    @Nullable
+    private final OverscrollPlugin mPlugin;
+    private final GestureDetector mGestureDetector;
 
     private RecentsView mRecentsView;
 
     public OverscrollInputConsumer(Context context, GestureState gestureState,
             InputConsumer delegate, InputMonitorCompat inputMonitor, OverscrollPlugin plugin) {
         super(delegate, inputMonitor);
+
+        mAngleThreshold = context.getResources()
+                .getInteger(R.integer.assistant_gesture_corner_deg_threshold);
+        mFlingThresholdPx = context.getResources()
+            .getDimension(R.dimen.gestures_overscroll_fling_threshold);
         mContext = context;
         mGestureState = gestureState;
         mPlugin = plugin;
 
         float slop = ViewConfiguration.get(context).getScaledTouchSlop();
+
         mSquaredSlop = slop * slop;
+        mGestureDetector = new GestureDetector(context, new FlingGestureListener());
 
         gestureState.getActivityInterface().createActivityInitListener(this::onActivityInit)
                 .register();
@@ -139,21 +151,29 @@
 
                         mPassedSlop = true;
                         mStartDragPos.set(mLastPos.x, mLastPos.y);
-
                         if (isOverscrolled()) {
                             setActive(ev);
+
+                            if (mPlugin != null) {
+                                mPlugin.onTouchStart(getDeviceState(), getUnderlyingActivity());
+                            }
                         } else {
                             mState = STATE_DELEGATE_ACTIVE;
                         }
                     }
                 }
 
+                if (mPassedSlop && mState != STATE_DELEGATE_ACTIVE && isOverscrolled()
+                        && mPlugin != null) {
+                    mPlugin.onTouchTraveled(getDistancePx());
+                }
+
                 break;
             }
             case ACTION_CANCEL:
             case ACTION_UP:
                 if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) {
-                    mPlugin.onOverscroll(getDeviceState());
+                    mPlugin.onTouchEnd(getDistancePx());
                 }
 
                 mPassedSlop = false;
@@ -161,6 +181,10 @@
                 break;
         }
 
+        if (mState != STATE_DELEGATE_ACTIVE) {
+            mGestureDetector.onTouchEvent(ev);
+        }
+
         if (mState != STATE_ACTIVE) {
             mDelegate.onMotionEvent(ev);
         }
@@ -168,12 +192,19 @@
 
     private boolean isOverscrolled() {
         // Make sure there isn't an app to quick switch to on our right
-        boolean atRightMostApp = (mRecentsView == null || mRecentsView.getRunningTaskIndex() <= 0);
+        int maxIndex = 0;
+        if ((mRecentsView instanceof LauncherRecentsView)
+                && ((LauncherRecentsView) mRecentsView).hasRecentsExtraCard()) {
+            maxIndex = 1;
+        }
+
+        boolean atRightMostApp = (mRecentsView == null
+                || mRecentsView.getRunningTaskIndex() <= maxIndex);
 
         // Check if the gesture is within our angle threshold of horizontal
         float deltaY = Math.abs(mLastPos.y - mDownPos.y);
         float deltaX = mDownPos.x - mLastPos.x; // Positive if this is a gesture to the left
-        boolean angleInBounds = Math.toDegrees(Math.atan2(deltaY, deltaX)) < ANGLE_THRESHOLD;
+        boolean angleInBounds = Math.toDegrees(Math.atan2(deltaY, deltaX)) < mAngleThreshold;
 
         return atRightMostApp && angleInBounds;
     }
@@ -193,4 +224,36 @@
 
         return deviceState;
     }
+
+    private int getDistancePx() {
+        return (int) Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y);
+    }
+
+    private String getUnderlyingActivity() {
+        return mGestureState.getRunningTask().topActivity.flattenToString();
+    }
+
+    private class FlingGestureListener extends GestureDetector.SimpleOnGestureListener {
+        @Override
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+            if (isValidAngle(velocityX, -velocityY)
+                    && getDistancePx() >= mFlingThresholdPx
+                    && mState != STATE_DELEGATE_ACTIVE) {
+
+                if (mPlugin != null) {
+                    mPlugin.onFling(-velocityX);
+                }
+            }
+            return true;
+        }
+
+        private boolean isValidAngle(float deltaX, float deltaY) {
+            float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+            // normalize so that angle is measured clockwise from horizontal in the bottom right
+            // corner and counterclockwise from horizontal in the bottom left corner
+
+            angle = angle > 90 ? 180 - angle : angle;
+            return (angle < mAngleThreshold);
+        }
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 82fbbc6..1bbb3f5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -378,6 +378,11 @@
     }
 
     @Override
+    public boolean hasRecentsExtraCard() {
+        return mRecentsExtraViewContainer != null;
+    }
+
+    @Override
     public void setContentAlpha(float alpha) {
         super.setContentAlpha(alpha);
         if (mRecentsExtraViewContainer != null) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index bcaa126..47bc31a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -830,6 +830,11 @@
 
     public abstract void startHome();
 
+    /** `true` if there is a +1 space available in overview. */
+    public boolean hasRecentsExtraCard() {
+        return false;
+    }
+
     public void reset() {
         setCurrentTask(-1);
         mIgnoreResetTaskId = -1;
diff --git a/quickstep/res/drawable-v28/back_gesture_tutorial_action_button_background.xml b/quickstep/res/drawable-v28/back_gesture_tutorial_action_button_background.xml
new file mode 100644
index 0000000..cd30ef7
--- /dev/null
+++ b/quickstep/res/drawable-v28/back_gesture_tutorial_action_button_background.xml
@@ -0,0 +1,20 @@
+<!--
+    Copyright (C) 2020 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="?android:attr/dialogCornerRadius"/>
+    <solid android:color="@color/back_gesture_tutorial_primary_color"/>
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/back_gesture.xml b/quickstep/res/drawable/back_gesture.xml
new file mode 100644
index 0000000..a5c57b4
--- /dev/null
+++ b/quickstep/res/drawable/back_gesture.xml
@@ -0,0 +1,367 @@
+<!--
+    Copyright (C) 2020 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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="206dp"
+            android:height="435dp"
+            android:viewportWidth="206"
+            android:viewportHeight="435">
+            <group android:name="edgeGroup"
+                android:translateX="197"
+                android:translateY="0">
+                <path
+                    android:name="edge"
+                    android:fillAlpha="0"
+                    android:fillType="nonZero"
+                    android:fillColor="#1a73eb"
+                    android:pathData=" M0,0 h9 v435 h-9 z " />
+            </group>
+            <group
+                android:name="trailGroup"
+                android:translateX="226"
+                android:translateY="200">
+                <path
+                    android:name="trail"
+                    android:fillAlpha="1"
+                    android:fillType="nonZero"
+                    android:pathData=" M0,0 h55 v36 h-55 z ">
+                    <aapt:attr name="android:fillColor">
+                        <gradient
+                            android:startX="0"
+                            android:endX="55"
+                            android:type="linear">
+                            <item
+                                android:color="#991a73eb"
+                                android:offset="0" />
+                            <item
+                                android:color="#401a73eb"
+                                android:offset="0.5" />
+                            <item
+                                android:color="#001a73eb"
+                                android:offset="1" />
+                        </gradient>
+                    </aapt:attr>
+                </path>
+            </group>
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G_T_1"
+                    android:rotation="11"
+                    android:scaleX="0.9"
+                    android:scaleY="0.9"
+                    android:translateX="309"
+                    android:translateY="422.5">
+                    <group
+                        android:name="_R_G_L_0_G"
+                        android:translateX="-145"
+                        android:translateY="-208">
+                        <path
+                            android:name="_R_G_L_0_G_D_0_P_0"
+                            android:fillAlpha="1"
+                            android:fillColor="#d2e3fc"
+                            android:fillType="nonZero"
+                            android:pathData=" M12.5 -47 C-7.93,-41.24 -3,-20.5 -1.5,-7 C0,6.5 2.5,22 9,39.5 C13.52,51.67 17.06,63.52 19,113 C21,164 53.5,243.5 53.5,243.5 C53.5,243.5 59,275.5 123.5,326 C188,376.5 283.5,236 290.5,199 C297.5,162 194.5,80 149,73 C103.5,66 90.5,57.5 77,50 C63.5,42.5 57,27 54.5,13.5 C52,0 43.5,-15 40,-25 C36.5,-35 32,-52.5 12.5,-47c " />
+                        <path
+                            android:name="_R_G_L_0_G_D_1_P_0"
+                            android:pathData=" M4.45 -34.66 C4.45,-34.66 10.5,-12.66 10.5,-12.66 C11.24,-9.98 13.98,-8.38 16.67,-9.04 C16.67,-9.04 29.72,-12.27 29.72,-12.27 C32.39,-12.93 34.05,-15.59 33.47,-18.28 C33.47,-18.28 32.11,-24.57 32.11,-24.57 "
+                            android:strokeWidth="4"
+                            android:strokeAlpha="1"
+                            android:strokeColor="#a0c2f9" />
+                        <path
+                            android:name="_R_G_L_0_G_D_2_P_0"
+                            android:pathData=" M18.35 21.81 C21.41,17.24 36.97,10.77 44.63,13.55 "
+                            android:strokeWidth="4"
+                            android:strokeAlpha="1"
+                            android:strokeColor="#a0c2f9" />
+                    </group>
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="edge">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="333"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="0.2"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="917"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="333"
+                    android:valueFrom="0.2"
+                    android:valueTo="0.2"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="583"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="1250"
+                    android:valueFrom="0.2"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="trail">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="2000"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="850"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="2000"
+                    android:valueFrom="1"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="trailGroup">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="83"
+                    android:propertyName="translateX"
+                    android:startOffset="1250"
+                    android:valueFrom="226"
+                    android:valueTo="226"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="1000"
+                    android:propertyName="translateX"
+                    android:startOffset="1333"
+                    android:valueFrom="226"
+                    android:valueTo="151"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="517"
+                    android:propertyName="translateX"
+                    android:startOffset="2333"
+                    android:valueFrom="151"
+                    android:valueTo="151"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="50"
+                    android:propertyName="translateX"
+                    android:startOffset="2850"
+                    android:valueFrom="226"
+                    android:valueTo="226"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="1833"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="1250"
+                    android:valueFrom="1"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="167"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="3083"
+                    android:valueFrom="1"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="1833"
+                    android:propertyName="strokeAlpha"
+                    android:startOffset="1250"
+                    android:valueFrom="1"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="100"
+                    android:propertyName="strokeAlpha"
+                    android:startOffset="3083"
+                    android:valueFrom="1"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="1833"
+                    android:propertyName="strokeAlpha"
+                    android:startOffset="1250"
+                    android:valueFrom="1"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="100"
+                    android:propertyName="strokeAlpha"
+                    android:startOffset="3083"
+                    android:valueFrom="1"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="83"
+                    android:propertyName="translateX"
+                    android:startOffset="1250"
+                    android:valueFrom="309"
+                    android:valueTo="309"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="1417"
+                    android:propertyName="translateX"
+                    android:startOffset="1333"
+                    android:valueFrom="309"
+                    android:valueTo="251"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="83"
+                    android:propertyName="rotation"
+                    android:startOffset="1250"
+                    android:valueFrom="11"
+                    android:valueTo="11"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.277,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="1417"
+                    android:propertyName="rotation"
+                    android:startOffset="1333"
+                    android:valueFrom="11"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.277,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="2183"
+                    android:propertyName="translateX"
+                    android:startOffset="1250"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/back_gesture_tutorial_action_button_background.xml b/quickstep/res/drawable/back_gesture_tutorial_action_button_background.xml
new file mode 100644
index 0000000..d7b9102
--- /dev/null
+++ b/quickstep/res/drawable/back_gesture_tutorial_action_button_background.xml
@@ -0,0 +1,20 @@
+<!--
+    Copyright (C) 2020 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/default_dialog_corner_radius"/>
+    <solid android:color="@color/back_gesture_tutorial_primary_color"/>
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/back_gesture_tutorial_close_button.xml b/quickstep/res/drawable/back_gesture_tutorial_close_button.xml
new file mode 100644
index 0000000..0702042
--- /dev/null
+++ b/quickstep/res/drawable/back_gesture_tutorial_close_button.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2020 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="13dp"
+    android:viewportHeight="14"
+    android:viewportWidth="14"
+    android:width="13dp">
+    <path
+        android:fillColor="#000000"
+        android:fillType="evenOdd"
+        android:pathData="M14,1.41L12.59,0L7,5.59L1.41,0L0,1.41L5.59,7L0,12.59L1.41,14L7,8.41L12.59,14L14,12.59L8.41,7L14,1.41Z"/>
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_bulb_outline.xml b/quickstep/res/drawable/ic_bulb_outline.xml
new file mode 100644
index 0000000..ef7bf9a
--- /dev/null
+++ b/quickstep/res/drawable/ic_bulb_outline.xml
@@ -0,0 +1,29 @@
+<!--
+    Copyright (C) 2020 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="192dp"
+    android:height="192dp"
+    android:viewportWidth="192"
+    android:viewportHeight="192">
+    <path
+        android:pathData="M96,90.01"
+        android:strokeWidth="2.4297"
+        android:fillColor="#00000000"
+        android:strokeColor="#FFC800"/>
+    <path
+        android:pathData="M153.24,48.09c-0.5,-1.51 -0.99,-2.86 -1.51,-4.12c-3.03,-7.1 -7.26,-13.45 -12.59,-18.88C127.68,13.42 112.4,7 96.1,7c-14,0 -27.66,4.93 -38.45,13.87C47,29.69 39.61,41.99 36.82,55.51c-0.76,3.9 -1.14,7.86 -1.14,11.78c0,16.39 6.36,31.77 17.9,43.3c2.65,2.65 5.59,5.08 8.74,7.23l0.09,24.14v22.03c0,5.33 4.82,10.01 10.32,10.01h0.41h3.85v0c0,6.23 4.53,10.93 10.53,10.93h16.94c6.01,0 10.54,-4.7 10.54,-10.93v0h4.31c5.56,0 10.26,-4.5 10.26,-9.83v-22.01c0.01,-0.06 0.01,-0.13 0.01,-0.2v-23.74c16.75,-11.15 26.73,-30.15 26.73,-50.94C156.31,60.76 155.28,54.3 153.24,48.09zM118.51,111.08l-0.46,0.29l-0.46,0.29v0.55v0.55v4.38v22.77l-14.12,0V95.9h14h2v-2v-8.5v-2h-2H74.53h-2v2v8.5v2h2h14v44.02l-14.13,0l-0.09,-23.21l-0.02,-4.3l0,-0.54l0,-0.54l-0.45,-0.29l-0.45,-0.29l-3.6,-2.36c-2.81,-1.84 -5.41,-3.95 -7.73,-6.28c-9.27,-9.26 -14.38,-21.63 -14.38,-34.82c0,-3.14 0.3,-6.31 0.9,-9.43C53.25,35.35 73.23,19 96.1,19c13.05,0 25.3,5.15 34.48,14.5c4.27,4.34 7.66,9.43 10.08,15.1c0.39,0.96 0.78,2.03 1.18,3.24c1.64,5 2.47,10.2 2.47,15.45c0,17.1 -8.27,32.58 -22.11,41.43L118.51,111.08z"
+        android:fillColor="#4285F4"/>
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/layout/back_gesture_tutorial_activity.xml b/quickstep/res/layout/back_gesture_tutorial_activity.xml
new file mode 100644
index 0000000..e894e89
--- /dev/null
+++ b/quickstep/res/layout/back_gesture_tutorial_activity.xml
@@ -0,0 +1,19 @@
+<!--
+    Copyright (C) 2020 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/back_gesture_tutorial_fragment_container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/quickstep/res/layout/back_gesture_tutorial_fragment.xml b/quickstep/res/layout/back_gesture_tutorial_fragment.xml
new file mode 100644
index 0000000..294e46e
--- /dev/null
+++ b/quickstep/res/layout/back_gesture_tutorial_fragment.xml
@@ -0,0 +1,121 @@
+<!--
+    Copyright (C) 2020 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layerType="software"
+    android:background="@color/back_gesture_tutorial_background_color">
+    <!--The layout is rendered on the software layer to avoid b/136158117-->
+
+    <ImageView
+        android:id="@+id/back_gesture_tutorial_fragment_hand_coaching"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="centerCrop"/>
+
+    <ImageButton
+        android:id="@+id/back_gesture_tutorial_fragment_close_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="18dp"
+        android:layout_marginTop="30dp"
+        android:layout_marginStart="4dp"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:background="@android:color/transparent"
+        android:accessibilityTraversalAfter="@id/back_gesture_tutorial_fragment_titles_container"
+        android:contentDescription="@string/back_gesture_tutorial_close_button_content_description"
+        android:src="@drawable/back_gesture_tutorial_close_button"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginTop="70dp"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/back_gesture_tutorial_fragment_titles_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:focusable="true">
+
+            <TextView
+                android:id="@+id/back_gesture_tutorial_fragment_title_view"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginStart="@dimen/back_gesture_tutorial_title_margin_start_end"
+                android:layout_marginEnd="@dimen/back_gesture_tutorial_title_margin_start_end"
+                style="@style/TextAppearance.BackGestureTutorial.Title"/>
+
+            <TextView
+                android:id="@+id/back_gesture_tutorial_fragment_subtitle_view"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginTop="10dp"
+                android:layout_marginStart="@dimen/back_gesture_tutorial_subtitle_margin_start_end"
+                android:layout_marginEnd="@dimen/back_gesture_tutorial_subtitle_margin_start_end"
+                style="@style/TextAppearance.BackGestureTutorial.Subtitle"/>
+
+        </LinearLayout>
+
+        <Space
+            android:layout_width="wrap_content"
+            android:layout_weight="1"
+            android:layout_height="0dp"
+            android:layout_marginTop="48dp"
+            android:layout_gravity="center_horizontal"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"/>
+
+        <!-- android:stateListAnimator="@null" removes shadow and normal on click behavior (increase
+             of elevation and shadow) which is replaced by ripple effect in android:foreground -->
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="46dp"
+            android:layout_marginBottom="48dp"
+            android:layout_gravity="center_horizontal">
+
+            <Button
+                android:id="@+id/back_gesture_tutorial_fragment_action_button"
+                android:layout_width="142dp"
+                android:layout_height="49dp"
+                android:layout_marginEnd="@dimen/back_gesture_tutorial_button_margin_start_end"
+                android:layout_alignParentEnd="true"
+                android:stateListAnimator="@null"
+                android:background="@drawable/back_gesture_tutorial_action_button_background"
+                android:foreground="?android:attr/selectableItemBackgroundBorderless"
+                style="@style/TextAppearance.BackGestureTutorial.ButtonLabel"/>
+
+            <Button
+                android:id="@+id/back_gesture_tutorial_fragment_action_text_button"
+                android:layout_width="142dp"
+                android:layout_height="49dp"
+                android:layout_marginStart="@dimen/back_gesture_tutorial_button_margin_start_end"
+                android:layout_alignParentStart="true"
+                android:stateListAnimator="@null"
+                android:background="@null"
+                android:foreground="?android:attr/selectableItemBackgroundBorderless"
+                style="@style/TextAppearance.BackGestureTutorial.TextButtonLabel"/>
+
+        </RelativeLayout>
+
+    </LinearLayout>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 9ff1350..988c78d 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -77,4 +77,12 @@
 
     <!-- Distance to move elements when swiping up to go home from launcher -->
     <dimen name="home_pullback_distance">28dp</dimen>
+
+    <!-- Overscroll Gesture -->
+    <dimen name="gestures_overscroll_fling_threshold">40dp</dimen>
+
+    <!-- Tips Gesture Tutorial -->
+    <dimen name="back_gesture_tutorial_title_margin_start_end">40dp</dimen>
+    <dimen name="back_gesture_tutorial_subtitle_margin_start_end">16dp</dimen>
+    <dimen name="back_gesture_tutorial_button_margin_start_end">18dp</dimen>
 </resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 4319b5d..378858f 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -66,5 +66,33 @@
     <!-- Text of the tip when user lands in all apps view for the first time, indicating where the tip toast points to is the predicted apps section. [CHAR_LIMIT=50] -->
     <string name="all_apps_prediction_tip">Your predicted apps</string>
 
+    <!-- Content description for a close button. [CHAR LIMIT=NONE] -->
+    <string name="back_gesture_tutorial_close_button_content_description" translatable="false">Close</string>
 
+    <!-- Title shown on the notification of Back gesture tutorial. [CHAR LIMIT=30] -->
+    <string name="back_gesture_tutorial_notification_title" translatable="false">Try the new back gesture</string>
+    <!-- Subtitle shown on the notification of Back gesture tutorial. [CHAR LIMIT=60] -->
+    <string name="back_gesture_tutorial_notification_subtitle" translatable="false">Learn how to go back while using your apps</string>
+    <!-- Action text shown on the notification of Back gesture tutorial. [CHAR LIMIT=14] -->
+    <string name="back_gesture_tutorial_notification_action_label" translatable="false">Try it</string>
+
+    <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
+    <string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
+    <!-- Subtitle shown during interactive parts of Back gesture tutorial for right edge. [CHAR LIMIT=60] -->
+    <string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge" translatable="false">Start at the right edge and swipe toward the middle</string>
+
+    <!-- Title shown during interactive part of Back gesture tutorial for left edge. [CHAR LIMIT=30] -->
+    <string name="back_gesture_tutorial_playground_title_swipe_inward_left_edge" translatable="false">Try the other side</string>
+    <!-- Subtitle shown during interactive parts of Back gesture tutorial for left edge. [CHAR LIMIT=60] -->
+    <string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge" translatable="false">That\'s it! Now try swiping from the left edge.</string>
+
+    <!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
+    <string name="back_gesture_tutorial_confirm_title" translatable="false">All set</string>
+    <!-- Subtitle shown on the confirmation screen after successful gesture. [CHAR LIMIT=60] -->
+    <string name="back_gesture_tutorial_confirm_subtitle" translatable="false">To change the sensitivity of the back gesture, go to Settings</string>
+
+    <!-- Button text shown on a button on the confirm screen. [CHAR LIMIT=14] -->
+    <string name="back_gesture_tutorial_action_button_label" translatable="false">Done</string>
+    <!-- Button text shown on a text button on the confirm screen. [CHAR LIMIT=14] -->
+    <string name="back_gesture_tutorial_action_text_button_label" translatable="false">Settings</string>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index bb364ff..c8d7777 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -25,4 +25,39 @@
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
     </style>
+
+    <style name="TextAppearance.BackGestureTutorial"
+        parent="android:TextAppearance.Material.Body1" />
+
+    <style name="TextAppearance.BackGestureTutorial.CallToAction"
+        parent="android:TextAppearance.Material.Body2" />
+
+    <style name="TextAppearance.BackGestureTutorial.Title"
+        parent="TextAppearance.BackGestureTutorial">
+        <item name="android:gravity">center</item>
+        <item name="android:textColor">@color/back_gesture_tutorial_title_color</item>
+        <item name="android:textSize">28sp</item>
+    </style>
+
+    <style name="TextAppearance.BackGestureTutorial.Subtitle"
+        parent="TextAppearance.BackGestureTutorial">
+        <item name="android:gravity">center</item>
+        <item name="android:textColor">@color/back_gesture_tutorial_subtitle_color</item>
+        <item name="android:letterSpacing">0.03</item>
+        <item name="android:textSize">21sp</item>
+    </style>
+
+    <style name="TextAppearance.BackGestureTutorial.ButtonLabel"
+        parent="TextAppearance.BackGestureTutorial.CallToAction">
+        <item name="android:gravity">center</item>
+        <item name="android:textColor">@color/back_gesture_tutorial_action_button_label_color</item>
+        <item name="android:letterSpacing">0.02</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.BackGestureTutorial.TextButtonLabel"
+        parent="TextAppearance.BackGestureTutorial.ButtonLabel">
+        <item name="android:textColor">@color/back_gesture_tutorial_primary_color</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/xml/notification_action.xml b/quickstep/res/xml/notification_action.xml
new file mode 100644
index 0000000..cc3612e
--- /dev/null
+++ b/quickstep/res/xml/notification_action.xml
@@ -0,0 +1,19 @@
+<!--
+    Copyright (C) 2020 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.
+-->
+<alias xmlns:android="http://schemas.android.com/apk/res/android">
+    <intent
+        android:action="com.android.quickstep.action.BACK_GESTURE_TUTORIAL" />
+</alias>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 3c8fe1e..815ae21 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -37,4 +37,10 @@
 
     <color name="all_apps_bg_hand_fill">#E5E5E5</color>
     <color name="all_apps_bg_hand_fill_dark">#9AA0A6</color>
+
+    <color name="back_gesture_tutorial_background_color">#FFFFFFFF</color>
+    <color name="back_gesture_tutorial_subtitle_color">#99000000</color> <!-- 60% black -->
+    <color name="back_gesture_tutorial_title_color">#FF000000</color>
+    <color name="back_gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
+    <color name="back_gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
 </resources>
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index f319ae1..ea63fa7 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -24,7 +25,12 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
 import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
 import android.view.ContextThemeWrapper;
 
 import androidx.annotation.IntDef;
@@ -47,6 +53,8 @@
 public abstract class BaseActivity extends Activity
         implements UserEventDelegate, LogStateProvider, ActivityContext {
 
+    private static final String TAG = "BaseActivity";
+
     public static final int INVISIBLE_BY_STATE_HANDLER = 1 << 0;
     public static final int INVISIBLE_BY_APP_TRANSITIONS = 1 << 1;
     public static final int INVISIBLE_BY_PENDING_FLAGS = 1 << 2;
@@ -312,6 +320,22 @@
         writer.println(prefix + "mForceInvisible: " + mForceInvisible);
     }
 
+    /**
+     * A wrapper around the platform method with Launcher specific checks
+     */
+    public void startShortcut(String packageName, String id, Rect sourceBounds,
+            Bundle startActivityOptions, UserHandle user) {
+        if (GO_DISABLE_WIDGETS) {
+            return;
+        }
+        try {
+            getSystemService(LauncherApps.class).startShortcut(packageName, id, sourceBounds,
+                    startActivityOptions, user);
+        } catch (SecurityException | IllegalStateException e) {
+            Log.e(TAG, "Failed to start shortcut", e);
+        }
+    }
+
     public static <T extends BaseActivity> T fromContext(Context context) {
         if (context instanceof BaseActivity) {
             return (T) context;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 21c819a..df15fc1 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -35,7 +35,6 @@
 
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.uioverrides.DisplayRotationListener;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -198,8 +197,7 @@
                 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                     String id = ((WorkspaceItemInfo) info).getDeepShortcutId();
                     String packageName = intent.getPackage();
-                    DeepShortcutManager.getInstance(this).startShortcut(
-                            packageName, id, intent.getSourceBounds(), optsBundle, info.user);
+                    startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user);
                     AppLaunchTracker.INSTANCE.get(this).onStartShortcut(packageName, id, info.user,
                             sourceContainer);
                 } else {
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 52a393f..8b6d209 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -108,6 +108,7 @@
     @Override
     public void onCommitCompletion(CompletionInfo text) {
         setText(text.getText());
+        setSelection(text.getText().length());
     }
 
     /**
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index df03027..3eb02b3 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -49,8 +49,8 @@
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Thunk;
@@ -538,12 +538,9 @@
                     return new PendingInstallShortcutInfo(info, context);
                 }
             } else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
-                DeepShortcutManager sm = DeepShortcutManager.getInstance(context);
-                List<ShortcutInfo> si = sm.queryForFullDetails(
-                        decoder.launcherIntent.getPackage(),
-                        Arrays.asList(decoder.launcherIntent.getStringExtra(
-                                ShortcutKey.EXTRA_SHORTCUT_ID)),
-                        decoder.user);
+                List<ShortcutInfo> si = ShortcutKey.fromIntent(decoder.launcherIntent, decoder.user)
+                        .buildRequest(context)
+                        .query(ShortcutRequest.ALL);
                 if (si.isEmpty()) {
                     return null;
                 } else {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 67fd7db..0bdf8fd 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
 
 import android.content.Context;
 import android.content.Intent;
@@ -54,7 +55,7 @@
 import com.android.launcher3.pm.InstallSessionTracker;
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
@@ -113,12 +114,9 @@
     private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
         @Override
         public void run() {
-            if (mModelLoaded) {
-                boolean hasShortcutHostPermission =
-                        DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
-                if (hasShortcutHostPermission != sBgDataModel.hasShortcutHostPermission) {
-                    forceReload();
-                }
+            if (mModelLoaded && hasShortcutsPermission(mApp.getContext())
+                    != sBgDataModel.hasShortcutHostPermission) {
+                forceReload();
             }
         }
     };
@@ -220,8 +218,8 @@
         Context context = mApp.getContext();
         onPackageChanged(packageName, user);
 
-        List<ShortcutInfo> pinnedShortcuts = DeepShortcutManager.getInstance(context)
-                .queryForPinnedShortcuts(packageName, user);
+        List<ShortcutInfo> pinnedShortcuts = new ShortcutRequest(context, user)
+                .forPackage(packageName).query(ShortcutRequest.PINNED);
         if (!pinnedShortcuts.isEmpty()) {
             enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user,
                     false));
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 2bec0ba..af9a1b4 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -65,9 +65,10 @@
 import com.android.launcher3.graphics.TintedDrawableSpan;
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.ShortcutCachingLogic;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.views.Transposable;
@@ -540,15 +541,14 @@
                 outObj[0] = activityInfo;
                 return activityInfo.getFullResIcon(appState.getIconCache());
             }
-            ShortcutKey key = ShortcutKey.fromItemInfo(info);
-            DeepShortcutManager sm = DeepShortcutManager.getInstance(launcher);
-            List<ShortcutInfo> si = sm.queryForFullDetails(
-                    key.componentName.getPackageName(), Arrays.asList(key.getId()), key.user);
+            List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
+                    .buildRequest(launcher)
+                    .query(ShortcutRequest.ALL);
             if (si.isEmpty()) {
                 return null;
             } else {
                 outObj[0] = si.get(0);
-                return sm.getShortcutIconDrawable(si.get(0),
+                return ShortcutCachingLogic.getIcon(launcher, si.get(0),
                         appState.getInvariantDeviceProfile().fillResIconDpi);
             }
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 0c4be62..10e2821 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -15,13 +15,14 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LabelComparator;
@@ -398,7 +399,7 @@
 
     private boolean shouldShowWorkFooter() {
         return mIsWork && Utilities.ATLEAST_P &&
-                (DeepShortcutManager.getInstance(mLauncher).hasHostPermission()
+                (hasShortcutsPermission(mLauncher)
                         || mLauncher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
                         == PackageManager.PERMISSION_GRANTED);
     }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 7334964..003ca82 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -114,7 +114,7 @@
             "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
 
     public static final TogglableFlag ENABLE_QUICK_CAPTURE_GESTURE = new TogglableFlag(
-            "ENABLE_QUICK_CAPTURE_GESTURE", false, "Swipe from right to left to quick capture");
+            "ENABLE_QUICK_CAPTURE_GESTURE", true, "Swipe from right to left to quick capture");
 
     public static final TogglableFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = new TogglableFlag(
             "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index b72fd98..dcdf5d6 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.Utilities.ATLEAST_Q;
 
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
@@ -56,6 +57,12 @@
 public class DragController implements DragDriver.EventListener, TouchController {
     private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
 
+    /**
+     * When a drag is started from a deep press, you need to drag this much farther than normal to
+     * end a pre-drag. See {@link DragOptions.PreDragCondition#shouldStartDrag(double)}.
+     */
+    private static final int DEEP_PRESS_DISTANCE_FACTOR = 3;
+
     @Thunk Launcher mLauncher;
     private FlingToDeleteHelper mFlingToDeleteHelper;
 
@@ -91,9 +98,10 @@
 
     private DropTarget mLastDropTarget;
 
-    @Thunk int mLastTouch[] = new int[2];
-    @Thunk long mLastTouchUpTime = -1;
-    @Thunk int mDistanceSinceScroll = 0;
+    private final int[] mLastTouch = new int[2];
+    private long mLastTouchUpTime = -1;
+    private int mLastTouchClassification;
+    private int mDistanceSinceScroll = 0;
 
     private int mTmpPoint[] = new int[2];
     private Rect mDragLayerRect = new Rect();
@@ -204,7 +212,7 @@
         }
 
         mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-        dragView.show(mMotionDownX, mMotionDownY);
+        dragView.show(mLastTouch[0], mLastTouch[1]);
         mDistanceSinceScroll = 0;
 
         if (!mIsInPreDrag) {
@@ -213,9 +221,7 @@
             mOptions.preDragCondition.onPreDragStart(mDragObject);
         }
 
-        mLastTouch[0] = mMotionDownX;
-        mLastTouch[1] = mMotionDownY;
-        handleMoveEvent(mMotionDownX, mMotionDownY);
+        handleMoveEvent(mLastTouch[0], mLastTouch[1]);
         mLauncher.getUserEventDispatcher().resetActionDurationMillis();
         return dragView;
     }
@@ -430,6 +436,11 @@
         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
         final int dragLayerX = dragLayerPos[0];
         final int dragLayerY = dragLayerPos[1];
+        mLastTouch[0] = dragLayerX;
+        mLastTouch[1] = dragLayerY;
+        if (ATLEAST_Q) {
+            mLastTouchClassification = ev.getClassification();
+        }
 
         switch (action) {
             case MotionEvent.ACTION_DOWN:
@@ -488,8 +499,12 @@
         mLastTouch[0] = x;
         mLastTouch[1] = y;
 
+        int distanceDragged = mDistanceSinceScroll;
+        if (ATLEAST_Q && mLastTouchClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS) {
+            distanceDragged /= DEEP_PRESS_DISTANCE_FACTOR;
+        }
         if (mIsInPreDrag && mOptions.preDragCondition != null
-                && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
+                && mOptions.preDragCondition.shouldStartDrag(distanceDragged)) {
             callOnDragStart();
         }
     }
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 4d3599e..e67d244 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -35,7 +35,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.util.Themes;
 
 import java.util.function.Supplier;
@@ -133,8 +132,8 @@
 
     public BitmapInfo createShortcutIconLegacy(ShortcutInfo shortcutInfo, boolean badged,
             @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
-        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
-                .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
+        Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon(
+                mContext, shortcutInfo, mFillResIconDpi);
         IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
         final Bitmap unbadgedBitmap;
         if (unbadgedDrawable != null) {
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
index 5c21470..b856dd1 100644
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
@@ -16,19 +16,22 @@
 
 package com.android.launcher3.icons;
 
+import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.cache.CachingLogic;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.Themes;
 
@@ -37,6 +40,8 @@
  */
 public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> {
 
+    private static final String TAG = "ShortcutCachingLogic";
+
     @Override
     public ComponentName getComponent(ShortcutInfo info) {
         return ShortcutKey.fromInfo(info).componentName;
@@ -56,8 +61,8 @@
     @Override
     public BitmapInfo loadIcon(Context context, ShortcutInfo info) {
         try (LauncherIcons li = LauncherIcons.obtain(context)) {
-            Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
-                    .getShortcutIconDrawable(info, LauncherAppState.getIDP(context).fillResIconDpi);
+            Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon(
+                    context, info, LauncherAppState.getIDP(context).fillResIconDpi);
             if (unbadgedDrawable == null) return BitmapInfo.LOW_RES_INFO;
             return new BitmapInfo(li.createScaledBitmapWithoutShadow(
                     unbadgedDrawable, 0), Themes.getColorAccent(context));
@@ -76,4 +81,21 @@
     public boolean addToMemCache() {
         return false;
     }
+
+    /**
+     * Similar to {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)} with additional
+     * Launcher specific checks
+     */
+    public static Drawable getIcon(Context context, ShortcutInfo shortcutInfo, int density) {
+        if (GO_DISABLE_WIDGETS) {
+            return null;
+        }
+        try {
+            return context.getSystemService(LauncherApps.class)
+                    .getShortcutIconDrawable(shortcutInfo, density);
+        } catch (SecurityException | IllegalStateException e) {
+            Log.e(TAG, "Failed to get shortcut icon", e);
+            return null;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 04cf20a..2c972a0 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -42,6 +42,8 @@
     private static Handler sHandler = null;
     private static File sLogsDirectory = null;
 
+    private static final int LOG_DAYS = 2;
+
     public static void setDir(File logsDir) {
         if (ENABLED) {
             synchronized (DATE_FORMAT) {
@@ -147,7 +149,7 @@
                 case MSG_WRITE: {
                     Calendar cal = Calendar.getInstance();
                     // suffix with 0 or 1 based on the day of the year.
-                    String fileName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) & 1);
+                    String fileName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) % LOG_DAYS);
 
                     if (!fileName.equals(mCurrentFileName)) {
                         closeWriter();
@@ -195,8 +197,9 @@
                             (Pair<PrintWriter, CountDownLatch>) msg.obj;
 
                     if (p.first != null) {
-                        dumpFile(p.first, FILE_NAME_PREFIX + 0);
-                        dumpFile(p.first, FILE_NAME_PREFIX + 1);
+                        for (int i = 0; i < LOG_DAYS; i++) {
+                            dumpFile(p.first, FILE_NAME_PREFIX + i);
+                        }
                     }
                     p.second.countDown();
                     return true;
@@ -226,4 +229,15 @@
             }
         }
     }
+
+    /**
+     * Gets files used for FileLog
+     */
+    public static File[] getLogFiles() {
+        File[] files = new File[LOG_DAYS];
+        for (int i = 0; i < LOG_DAYS; i++) {
+            files[i] = new File(sLogsDirectory, FILE_NAME_PREFIX + i);
+        }
+        return files;
+    }
 }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 499cd2a..8289da9 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -147,6 +147,12 @@
             }
             fillIntentInfo(event.srcTarget[0], intent, userHandle);
         }
+        ItemInfo info = (ItemInfo) v.getTag();
+        if (Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
+            FileLog.d(TAG, "appLaunch: packageName:" + info.getTargetComponent().getPackageName()
+                    + ",isWorkApp:" + (info.user != null && !Process.myUserHandle().equals(
+                    userHandle)) + ",launchLocation:" + info.container);
+        }
         dispatchUserEvent(event, intent);
         mAppOrTaskLaunch = true;
     }
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 0e20270..88f2a09 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -15,7 +15,11 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
+
 import android.content.Context;
+import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -29,15 +33,15 @@
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.PromiseAppInfo;
-import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.DumpTargetWrapper;
 import com.android.launcher3.model.nano.LauncherDumpProto;
 import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
 import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
@@ -59,6 +63,8 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
 
 /**
  * All the data stored in-memory and managed by the LauncherModel
@@ -287,7 +293,7 @@
                     if ((count == null || --count.value == 0)
                             && !InstallShortcutReceiver.getPendingShortcuts(context)
                                 .contains(pinnedShortcut)) {
-                        DeepShortcutManager.getInstance(context).unpinShortcut(pinnedShortcut);
+                        unpinShortcut(context, pinnedShortcut);
                     }
                     // Fall through.
                 }
@@ -324,7 +330,7 @@
 
                 // Since this is a new item, pin the shortcut in the system server.
                 if (newItem && count.value == 1) {
-                    DeepShortcutManager.getInstance(context).pinShortcut(pinnedShortcut);
+                    updatePinnedShortcuts(context, pinnedShortcut, List::add);
                 }
                 // Fall through
             }
@@ -355,6 +361,36 @@
     }
 
     /**
+     * Removes the given shortcut from the current list of pinned shortcuts.
+     * (Runs on background thread)
+     */
+    public void unpinShortcut(Context context, ShortcutKey key) {
+        updatePinnedShortcuts(context, key, List::remove);
+    }
+
+    private void updatePinnedShortcuts(Context context, ShortcutKey key,
+            BiConsumer<List<String>, String> idOp) {
+        if (GO_DISABLE_WIDGETS) {
+            return;
+        }
+        String packageName = key.componentName.getPackageName();
+        String id = key.getId();
+        UserHandle user = key.user;
+        List<String> pinnedIds = new ShortcutRequest(context, user)
+                .forPackage(packageName)
+                .query(PINNED)
+                .stream()
+                .map(ShortcutInfo::getId)
+                .collect(Collectors.toCollection(ArrayList::new));
+        idOp.accept(pinnedIds, id);
+        try {
+            context.getSystemService(LauncherApps.class).pinShortcuts(packageName, pinnedIds, user);
+        } catch (SecurityException | IllegalStateException e) {
+            Log.w(TAG, "Failed to pin shortcut", e);
+        }
+    }
+
+    /**
      * Return an existing FolderInfo object if we have encountered this ID previously,
      * or make a new one.
      */
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 605bb75..571d41a 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
 import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
 
 import android.appwidget.AppWidgetProviderInfo;
@@ -72,8 +73,9 @@
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.provider.ImportDataTask;
 import com.android.launcher3.qsb.QsbContainerView;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IOUtils;
 import com.android.launcher3.util.LooperIdleLock;
@@ -114,7 +116,6 @@
     private final UserManager mUserManager;
     private final UserCache mUserCache;
 
-    private final DeepShortcutManager mShortcutManager;
     private final InstallSessionHelper mSessionHelper;
     private final IconCache mIconCache;
 
@@ -130,7 +131,6 @@
         mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
         mUserManager = mApp.getContext().getSystemService(UserManager.class);
         mUserCache = UserCache.INSTANCE.get(mApp.getContext());
-        mShortcutManager = DeepShortcutManager.getInstance(mApp.getContext());
         mSessionHelper = InstallSessionHelper.INSTANCE.get(mApp.getContext());
         mIconCache = mApp.getIconCache();
     }
@@ -349,8 +349,8 @@
 
                     // We can only query for shortcuts when the user is unlocked.
                     if (userUnlocked) {
-                        DeepShortcutManager.QueryResult pinnedShortcuts =
-                                mShortcutManager.queryForPinnedShortcuts(null, user);
+                        QueryResult pinnedShortcuts = new ShortcutRequest(context, user)
+                                .query(ShortcutRequest.PINNED);
                         if (pinnedShortcuts.wasSuccess()) {
                             for (ShortcutInfo shortcut : pinnedShortcuts) {
                                 shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
@@ -786,7 +786,7 @@
                 if ((numTimesPinned == null || numTimesPinned.value == 0)
                         && !pendingShortcuts.contains(key)) {
                     // Shortcut is pinned but doesn't exist on the workspace; unpin it.
-                    mShortcutManager.unpinShortcut(key);
+                    mBgDataModel.unpinShortcut(context, key);
                 }
             }
 
@@ -884,12 +884,12 @@
     private List<ShortcutInfo> loadDeepShortcuts() {
         List<ShortcutInfo> allShortcuts = new ArrayList<>();
         mBgDataModel.deepShortcutMap.clear();
-        mBgDataModel.hasShortcutHostPermission = mShortcutManager.hasHostPermission();
+        mBgDataModel.hasShortcutHostPermission = hasShortcutsPermission(mApp.getContext());
         if (mBgDataModel.hasShortcutHostPermission) {
             for (UserHandle user : mUserCache.getUserProfiles()) {
                 if (mUserManager.isUserUnlocked(user)) {
-                    List<ShortcutInfo> shortcuts =
-                            mShortcutManager.queryForAllShortcuts(user);
+                    List<ShortcutInfo> shortcuts = new ShortcutRequest(mApp.getContext(), user)
+                            .query(ShortcutRequest.ALL);
                     allShortcuts.addAll(shortcuts);
                     mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts);
                 }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 3361ff0..48c56e9 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -41,7 +41,7 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntSparseArrayMap;
@@ -208,10 +208,11 @@
                             if (si.isPromise() && isNewApkAvailable) {
                                 boolean isTargetValid = true;
                                 if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                                    List<ShortcutInfo> shortcut = DeepShortcutManager
-                                            .getInstance(context).queryForPinnedShortcuts(
-                                                    cn.getPackageName(),
-                                                    Arrays.asList(si.getDeepShortcutId()), mUser);
+                                    List<ShortcutInfo> shortcut =
+                                            new ShortcutRequest(context, mUser)
+                                                    .forPackage(cn.getPackageName(),
+                                                            si.getDeepShortcutId())
+                                                    .query(ShortcutRequest.PINNED);
                                     if (shortcut.isEmpty()) {
                                         isTargetValid = false;
                                     } else {
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 05225d4..b0e7a69 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -24,8 +24,8 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
 
@@ -54,8 +54,6 @@
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
         final Context context = app.getContext();
-        DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
-
         // Find WorkspaceItemInfo's that have changed on the workspace.
         HashSet<ShortcutKey> removedKeys = new HashSet<>();
         MultiHashMap<ShortcutKey, WorkspaceItemInfo> keyToShortcutInfo = new MultiHashMap<>();
@@ -74,8 +72,9 @@
         final ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
         if (!keyToShortcutInfo.isEmpty()) {
             // Update the workspace to reflect the changes to updated shortcuts residing on it.
-            List<ShortcutInfo> shortcuts = deepShortcutManager.queryForFullDetails(
-                    mPackageName, new ArrayList<>(allIds), mUser);
+            List<ShortcutInfo> shortcuts = new ShortcutRequest(context, mUser)
+                    .forPackage(mPackageName, new ArrayList<>(allIds))
+                    .query(ShortcutRequest.ALL);
             for (ShortcutInfo fullDetails : shortcuts) {
                 ShortcutKey key = ShortcutKey.fromInfo(fullDetails);
                 List<WorkspaceItemInfo> workspaceItemInfos = keyToShortcutInfo.remove(key);
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 694ae1a..d527423 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -27,8 +27,9 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 
@@ -52,12 +53,11 @@
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
         Context context = app.getContext();
         boolean isUserUnlocked = context.getSystemService(UserManager.class).isUserUnlocked(mUser);
-        DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
 
         HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>();
         if (isUserUnlocked) {
-            DeepShortcutManager.QueryResult shortcuts =
-                    deepShortcutManager.queryForPinnedShortcuts(null, mUser);
+            QueryResult shortcuts = new ShortcutRequest(context, mUser)
+                    .query(ShortcutRequest.PINNED);
             if (shortcuts.wasSuccess()) {
                 for (ShortcutInfo shortcut : shortcuts) {
                     pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
@@ -115,7 +115,8 @@
 
         if (isUserUnlocked) {
             dataModel.updateDeepShortcutCounts(
-                    null, mUser, deepShortcutManager.queryForAllShortcuts(mUser));
+                    null, mUser,
+                    new ShortcutRequest(context, mUser).query(ShortcutRequest.ALL));
         }
         bindDeepShortcuts(dataModel);
     }
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 80c6683..947f49d 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -31,8 +31,8 @@
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
@@ -144,8 +144,9 @@
                 uiHandler.post(() -> container.applyNotificationInfos(infos));
             }
 
-            List<ShortcutInfo> shortcuts = DeepShortcutManager.getInstance(launcher)
-                    .queryForShortcutsContainer(activity, user);
+            List<ShortcutInfo> shortcuts = new ShortcutRequest(launcher, user)
+                    .withContainer(activity)
+                    .query(ShortcutRequest.PUBLISHED);
             String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
                     : notificationKeys.get(0).shortcutId;
             shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index fb33551..36ff07e 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -34,8 +34,8 @@
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherProvider.DatabaseHelper;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.util.IntArray;
@@ -112,9 +112,18 @@
             whereClause.append(" AND profileId != ?");
             profileIds[i] = Long.toString(profileMapping.keyAt(i));
         }
-        int itemsDeleted = db.delete(Favorites.TABLE_NAME, whereClause.toString(), profileIds);
-        if (itemsDeleted > 0) {
-            FileLog.d(TAG, itemsDeleted + " items from unrestored user(s) were deleted");
+        try {
+            int itemsDeleted = db.delete(Favorites.TABLE_NAME, whereClause.toString(), profileIds);
+            if (itemsDeleted > 0) {
+                FileLog.d(TAG, itemsDeleted + " items from unrestored user(s) were deleted");
+            }
+        } catch (IllegalArgumentException exception) {
+            // b/147114476
+            FileLog.e(TAG, new StringBuilder("Failed to execute delete, where clause: '")
+                    .append(whereClause).append("', profile Id size:").append(profileIds.length)
+                    .append("profileIds: ").append(String.join(", ", profileIds)).toString()
+            );
+            throw exception;
         }
 
         // Mark all items as restored.
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index 70665ca..fa1a85f 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -1,6 +1,7 @@
 package com.android.launcher3.shortcuts;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
@@ -29,6 +30,14 @@
         return componentName.getClassName();
     }
 
+    /**
+     * Creates a {@link ShortcutRequest} for this key
+     */
+    public ShortcutRequest buildRequest(Context context) {
+        return new ShortcutRequest(context, user)
+                .forPackage(componentName.getPackageName(), getId());
+    }
+
     public static ShortcutKey fromInfo(ShortcutInfo shortcutInfo) {
         return new ShortcutKey(shortcutInfo.getPackage(), shortcutInfo.getUserHandle(),
                 shortcutInfo.getId());
diff --git a/src/com/android/launcher3/shortcuts/ShortcutRequest.java b/src/com/android/launcher3/shortcuts/ShortcutRequest.java
new file mode 100644
index 0000000..e6203b4
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutRequest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 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 static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility class to streamline Shortcut query
+ */
+public class ShortcutRequest {
+
+    private static final String TAG = "ShortcutRequest";
+
+    public static final int ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC
+            | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
+    public static final int PUBLISHED = ShortcutQuery.FLAG_MATCH_DYNAMIC
+            | ShortcutQuery.FLAG_MATCH_MANIFEST;
+    public static final int PINNED = ShortcutQuery.FLAG_MATCH_PINNED;
+
+    private final ShortcutQuery mQuery = GO_DISABLE_WIDGETS ? null : new ShortcutQuery();
+
+    private final Context mContext;
+    private final UserHandle mUserHandle;
+
+    boolean mFailed = false;
+
+    public ShortcutRequest(Context context, UserHandle userHandle) {
+        mContext = context;
+        mUserHandle = userHandle;
+    }
+
+    public ShortcutRequest forPackage(String packageName, String... shortcutIds) {
+        return forPackage(packageName, Arrays.asList(shortcutIds));
+    }
+
+    public ShortcutRequest forPackage(String packageName, @Nullable List<String> shortcutIds) {
+        if (!GO_DISABLE_WIDGETS && packageName != null) {
+            mQuery.setPackage(packageName);
+            mQuery.setShortcutIds(shortcutIds);
+        }
+        return this;
+    }
+
+    public ShortcutRequest withContainer(@Nullable ComponentName activity) {
+        if (!GO_DISABLE_WIDGETS) {
+            if (activity == null) {
+                mFailed = true;
+            } else {
+                mQuery.setActivity(activity);
+            }
+        }
+        return this;
+    }
+
+    public QueryResult query(int flags) {
+        if (GO_DISABLE_WIDGETS || mFailed) {
+            return QueryResult.DEFAULT;
+        }
+        mQuery.setQueryFlags(flags);
+
+        try {
+            return new QueryResult(mContext.getSystemService(LauncherApps.class)
+                    .getShortcuts(mQuery, mUserHandle));
+        } catch (SecurityException | IllegalStateException e) {
+            Log.e(TAG, "Failed to query for shortcuts", e);
+            return QueryResult.DEFAULT;
+        }
+    }
+
+    public static class QueryResult extends ArrayList<ShortcutInfo> {
+
+        static final QueryResult DEFAULT = new QueryResult(GO_DISABLE_WIDGETS);
+
+        private final boolean mWasSuccess;
+
+        QueryResult(List<ShortcutInfo> result) {
+            super(result == null ? Collections.emptyList() : result);
+            mWasSuccess = true;
+        }
+
+        QueryResult(boolean wasSuccess) {
+            mWasSuccess = wasSuccess;
+        }
+
+
+        public boolean wasSuccess() {
+            return mWasSuccess;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 2d56ce7..8b2ee36 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -332,4 +332,17 @@
         }
         return false;
     }
+
+    /**
+     * Returns true if Launcher has the permission to access shortcuts.
+     * @see LauncherApps#hasShortcutHostPermission()
+     */
+    public static boolean hasShortcutsPermission(Context context) {
+        try {
+            return context.getSystemService(LauncherApps.class).hasShortcutHostPermission();
+        } catch (SecurityException | IllegalStateException e) {
+            Log.e(TAG, "Failed to make shortcut manager call", e);
+        }
+        return false;
+    }
 }
diff --git a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java
index 60eb304..28a9193 100644
--- a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java
@@ -24,11 +24,11 @@
  * the user to a more recent app).
  */
 @ProvidesInterface(action = com.android.systemui.plugins.OverscrollPlugin.ACTION,
-        version = com.android.systemui.plugins.OverlayPlugin.VERSION)
+        version = com.android.systemui.plugins.OverscrollPlugin.VERSION)
 public interface OverscrollPlugin extends Plugin {
 
     String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERSCROLL";
-    int VERSION = 1;
+    int VERSION = 3;
 
     String DEVICE_STATE_LOCKED = "Locked";
     String DEVICE_STATE_LAUNCHER = "Launcher";
@@ -36,9 +36,38 @@
     String DEVICE_STATE_UNKNOWN = "Unknown";
 
     /**
-     * Called when the user completed a right to left swipe in the gesture area.
-     *
-     * @param deviceState One of the DEVICE_STATE_* constants.
+     * @return true if the plugin is active and will accept overscroll gestures
      */
-    void onOverscroll(String deviceState);
+    boolean isActive();
+
+    /**
+     * Called when a touch is down and has been recognized as an overscroll gesture.
+     * A call of this method will always result in `onTouchUp` being called, and possibly
+     * `onFling` as well.
+     *
+     * @param deviceState String representing the current device state
+     * @param underlyingActivity String representing the currently active Activity
+     */
+    void onTouchStart(String deviceState, String underlyingActivity);
+
+    /**
+     * Called when a touch that was previously recognized has moved.
+     *
+     * @param px distance between the position of touch on this update and the position of the
+     * touch when it was initially recognized.
+     */
+    void onTouchTraveled(int px);
+
+    /**
+     * Called when a touch that was previously recognized has ended.
+     *
+     * @param px distance between the position of touch on this update and the position of the
+     * touch when it was initially recognized.
+     */
+    void onTouchEnd(int px);
+
+    /**
+     * Called when the user starts Compose with a fling. `onTouchUp` will also be called.
+     */
+    void onFling(float velocity);
 }
diff --git a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
deleted file mode 100644
index 57f4164..0000000
--- a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * 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.content.ComponentName;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.ShortcutQuery;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
- */
-public class DeepShortcutManager {
-    private static final String TAG = "DeepShortcutManager";
-
-    private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC
-            | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
-
-    private static DeepShortcutManager sInstance;
-
-    public static DeepShortcutManager getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new DeepShortcutManager(context.getApplicationContext());
-        }
-        return sInstance;
-    }
-
-    private final LauncherApps mLauncherApps;
-
-    private DeepShortcutManager(Context context) {
-        mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
-    }
-
-    /**
-     * 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 QueryResult queryForFullDetails(String packageName,
-            List<String> shortcutIds, UserHandle user) {
-        return query(FLAG_GET_ALL, packageName, null, shortcutIds, user);
-    }
-
-    /**
-     * Gets all the manifest and dynamic shortcuts associated with the given package and user,
-     * to be displayed in the shortcuts container on long press.
-     */
-    public QueryResult queryForShortcutsContainer(@Nullable ComponentName activity,
-            UserHandle user) {
-        if (activity == null) return QueryResult.FAILURE;
-        return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
-                activity.getPackageName(), activity, null, 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.getId();
-        UserHandle user = key.user;
-        List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
-        pinnedIds.remove(id);
-        try {
-            mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
-        } catch (SecurityException|IllegalStateException e) {
-            Log.w(TAG, "Failed to unpin shortcut", e);
-        }
-    }
-
-    /**
-     * 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.getId();
-        UserHandle user = key.user;
-        List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
-        pinnedIds.add(id);
-        try {
-            mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
-        } catch (SecurityException|IllegalStateException e) {
-            Log.w(TAG, "Failed to pin shortcut", e);
-        }
-    }
-
-    public void startShortcut(String packageName, String id, Rect sourceBounds,
-          Bundle startActivityOptions, UserHandle user) {
-        try {
-            mLauncherApps.startShortcut(packageName, id, sourceBounds,
-                    startActivityOptions, user);
-        } catch (SecurityException|IllegalStateException e) {
-            Log.e(TAG, "Failed to start shortcut", e);
-        }
-    }
-
-    public Drawable getShortcutIconDrawable(ShortcutInfo shortcutInfo, int density) {
-        try {
-            return mLauncherApps.getShortcutIconDrawable(shortcutInfo, density);
-        } catch (SecurityException|IllegalStateException e) {
-            Log.e(TAG, "Failed to get shortcut icon", e);
-            return null;
-        }
-    }
-
-    /**
-     * 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 QueryResult queryForPinnedShortcuts(String packageName, UserHandle user) {
-        return queryForPinnedShortcuts(packageName, null, user);
-    }
-
-    public QueryResult queryForPinnedShortcuts(String packageName, List<String> shortcutIds,
-            UserHandle user) {
-        return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, shortcutIds, user);
-    }
-
-    public QueryResult queryForAllShortcuts(UserHandle user) {
-        return query(FLAG_GET_ALL, null, null, null, user);
-    }
-
-    private static List<String> extractIds(List<ShortcutInfo> shortcuts) {
-        List<String> shortcutIds = new ArrayList<>(shortcuts.size());
-        for (ShortcutInfo 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 QueryResult query(int flags, String packageName, ComponentName activity,
-            List<String> shortcutIds, UserHandle user) {
-        ShortcutQuery q = new ShortcutQuery();
-        q.setQueryFlags(flags);
-        if (packageName != null) {
-            q.setPackage(packageName);
-            q.setActivity(activity);
-            q.setShortcutIds(shortcutIds);
-        }
-        try {
-            return new QueryResult(mLauncherApps.getShortcuts(q, user));
-        } catch (SecurityException|IllegalStateException e) {
-            Log.e(TAG, "Failed to query for shortcuts", e);
-            return QueryResult.FAILURE;
-        }
-    }
-
-    public boolean hasHostPermission() {
-        try {
-            return mLauncherApps.hasShortcutHostPermission();
-        } catch (SecurityException|IllegalStateException e) {
-            Log.e(TAG, "Failed to make shortcut manager call", e);
-        }
-        return false;
-    }
-
-    public static class QueryResult extends ArrayList<ShortcutInfo> {
-
-        static QueryResult FAILURE = new QueryResult();
-
-        private final boolean mWasSuccess;
-
-        QueryResult(List<ShortcutInfo> result) {
-            super(result == null ? Collections.emptyList() : result);
-            mWasSuccess = true;
-        }
-
-        QueryResult() {
-            mWasSuccess = false;
-        }
-
-
-        public boolean wasSuccess() {
-            return mWasSuccess;
-        }
-    }
-}
diff --git a/tests/Android.mk b/tests/Android.mk
index 83fdddc..d1a6c06 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -57,7 +57,7 @@
     LOCAL_PRIVATE_PLATFORM_APIS := true
     LOCAL_STATIC_JAVA_LIBRARIES += launcher-aosp-tapl
 else
-    LOCAL_SDK_VERSION := 28
+    LOCAL_SDK_VERSION := system_28
     LOCAL_MIN_SDK_VERSION := 21
     LOCAL_STATIC_JAVA_LIBRARIES += ub-launcher-aosp-tapl
 endif