Merge "Move the cursor to the end after accepting gboard selection Bug: 146909183" into ub-launcher3-master
diff --git a/proguard.flags b/proguard.flags
index 272ab7a..3e12283 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -23,7 +23,10 @@
 # support jar.
 -keep class androidx.recyclerview.widget.RecyclerView { *; }
 
-# Preference fragments
+# Fragments
+-keep class ** extends androidx.fragment.app.Fragment {
+    public <init>(...);
+}
 -keep class ** extends android.app.Fragment {
     public <init>(...);
 }
@@ -50,4 +53,4 @@
 -dontwarn android.app.**
 -dontwarn android.view.**
 -dontwarn android.os.**
--dontwarn android.graphics.**
\ No newline at end of file
+-dontwarn android.graphics.**
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 055ade5..0560d68 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -57,6 +57,7 @@
   optional TargetExtension extension = 16;
   optional TipType tip_type = 17;
   optional int32 search_query_length = 18;
+  optional bool is_work_app = 19;
 }
 
 // Used to define what type of item a Target would represent.
@@ -92,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/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/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index 888ea9c..700feef 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -453,8 +453,10 @@
 
     @Override
     public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-        super.onRecentsAnimationCanceled(thumbnailData);
         mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+
+        // Defer clearing the controller and the targets until after we've updated the state
+        super.onRecentsAnimationCanceled(thumbnailData);
     }
 
     /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 8f75c79..1b60404 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -610,10 +610,12 @@
 
     @Override
     public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-        super.onRecentsAnimationCanceled(thumbnailData);
+        ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
         mActivityInitListener.unregister();
         mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
-        ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
+
+        // Defer clearing the controller and the targets until after we've updated the state
+        super.onRecentsAnimationCanceled(thumbnailData);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewActionsFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewActionsFactory.java
deleted file mode 100644
index 6d17b27..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewActionsFactory.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2019 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.quickstep;
-
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import android.view.View;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Overview actions are shown in overview underneath the task snapshot. This factory class is
- * overrideable in an overlay. The {@link OverviewActions} class provides the view that should be
- * shown in the Overview.
- */
-public class OverviewActionsFactory implements ResourceBasedOverride {
-
-    public static final MainThreadInitializedObject<OverviewActionsFactory> INSTANCE =
-            forOverride(OverviewActionsFactory.class, R.string.overview_actions_factory_class);
-
-    /** Create a new Overview Actions for interacting between the actions and overview. */
-    public OverviewActions createOverviewActions() {
-        return new OverviewActions();
-    }
-
-    /** Overlay overrideable, base class does nothing. */
-    public static class OverviewActions {
-        /** Get the view to show in the overview. */
-        public View getView() {
-            return null;
-        }
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index b5441df..ec7cddf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -19,6 +19,9 @@
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 
 import android.graphics.Matrix;
+import android.view.View;
+
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
@@ -75,6 +78,11 @@
          */
         public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) { }
 
+        @Nullable
+        public View getActionsView() {
+            return null;
+        }
+
         /**
          * Called when the overlay is no longer used.
          */
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 0f0fda9..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;
@@ -1310,7 +1315,7 @@
     }
 
     private void dismissCurrentTask() {
-        TaskView taskView = getTaskView(getNextPage());
+        TaskView taskView = getNextPageTaskView();
         if (taskView != null) {
             dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 4e0fdea..3bc1509 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -197,6 +197,10 @@
         updateThumbnailPaintFilter();
     }
 
+    public TaskOverlay getTaskOverlay() {
+        return mOverlay;
+    }
+
     public float getDimAlpha() {
         return mDimAlpha;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 94cec72..6e1b24a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -62,7 +62,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.ViewPool.Reusable;
-import com.android.quickstep.OverviewActionsFactory;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskOverlayFactory;
@@ -164,7 +163,6 @@
     private final float mWindowCornerRadius;
     private final BaseDraggingActivity mActivity;
 
-    private OverviewActionsFactory.OverviewActions mOverviewActions;
     @Nullable private View mActionsView;
 
     private ObjectAnimator mIconAndDimAnimator;
@@ -222,7 +220,6 @@
         mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius);
         mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
 
-        mOverviewActions = OverviewActionsFactory.INSTANCE.get(context).createOverviewActions();
         mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams);
         setOutlineProvider(mOutlineProvider);
     }
@@ -239,7 +236,7 @@
 
 
         if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
-            mActionsView = mOverviewActions.getView();
+            mActionsView = mSnapshotView.getTaskOverlay().getActionsView();
             if (mActionsView != null) {
                 TaskView.LayoutParams params = new TaskView.LayoutParams(LayoutParams.MATCH_PARENT,
                         getResources().getDimensionPixelSize(R.dimen.overview_actions_height),
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 24ab487..327bb14 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -15,8 +15,6 @@
 -->
 <resources>
     <string name="task_overlay_factory_class" translatable="false"></string>
-    <!-- Class name for factory object that creates the overview actions UI when enabled. -->
-    <string name="overview_actions_factory_class" translatable="false" />
 
     <!-- Activity which blocks home gesture -->
     <string name="gesture_blocking_activity" translatable="false"></string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 9ff1350..24be859 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -77,4 +77,7 @@
 
     <!-- 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>
 </resources>
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 8e5ed1a..a466f12 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -17,30 +17,34 @@
 package com.android.quickstep.logging;
 
 import static android.stats.launcher.nano.Launcher.ALLAPPS;
+import static android.stats.launcher.nano.Launcher.BACKGROUND;
+import static android.stats.launcher.nano.Launcher.DISMISS_TASK;
 import static android.stats.launcher.nano.Launcher.HOME;
 import static android.stats.launcher.nano.Launcher.LAUNCH_APP;
 import static android.stats.launcher.nano.Launcher.LAUNCH_TASK;
-import static android.stats.launcher.nano.Launcher.DISMISS_TASK;
-import static android.stats.launcher.nano.Launcher.BACKGROUND;
 import static android.stats.launcher.nano.Launcher.OVERVIEW;
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.UserHandle;
 import android.stats.launcher.nano.Launcher;
 import android.stats.launcher.nano.LauncherExtension;
 import android.stats.launcher.nano.LauncherTarget;
 import android.util.Log;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogUtils;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ComponentKey;
 import com.android.systemui.shared.system.StatsLogCompat;
+
 import com.google.protobuf.nano.MessageNano;
 
 /**
@@ -60,7 +64,7 @@
     public StatsLogCompatManager(Context context) { }
 
     @Override
-    public void logAppLaunch(View v, Intent intent) {
+    public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) {
         LauncherExtension ext = new LauncherExtension();
         ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
         int srcState = mStateProvider.getCurrentState();
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 5606ac2..b786c8b 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -35,6 +35,7 @@
 
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.tapl.TestHelpers;
+import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -57,6 +58,8 @@
 
     static final String TAG = "QuickStepOnOffRule";
 
+    public static final int WAIT_TIME_MS = 10000;
+
     public enum Mode {
         THREE_BUTTON, TWO_BUTTON, ZERO_BUTTON, ALL
     }
@@ -118,8 +121,8 @@
                         if (mode == THREE_BUTTON || mode == ALL) {
                             evaluateWithThreeButtons();
                         }
-                    } catch (Exception e) {
-                        Log.e(TAG, "Exception", e);
+                    } catch (Throwable e) {
+                        Log.e(TAG, "Error", e);
                         throw e;
                     } finally {
                         assertTrue("Couldn't set overlay",
@@ -195,19 +198,14 @@
                                 currentSysUiNavigationMode() == expectedMode);
                     }
 
-                    for (int i = 0; i != 100; ++i) {
-                        if (mLauncher.getNavigationModel() == expectedMode) break;
-                        Thread.sleep(100);
-                    }
-                    assertTrue("Couldn't switch to " + overlayPackage,
-                            mLauncher.getNavigationModel() == expectedMode);
+                    Wait.atMost("Couldn't switch to " + overlayPackage,
+                            () -> mLauncher.getNavigationModel() == expectedMode, WAIT_TIME_MS,
+                            mLauncher);
 
-                    for (int i = 0; i != 100; ++i) {
-                        if (mLauncher.getNavigationModeMismatchError() == null) break;
-                        Thread.sleep(100);
-                    }
-                    final String error = mLauncher.getNavigationModeMismatchError();
-                    assertTrue("Switching nav mode: " + error, error == null);
+                    Wait.atMost(() -> "Switching nav mode: "
+                                    + mLauncher.getNavigationModeMismatchError(),
+                            () -> mLauncher.getNavigationModeMismatchError() == null, WAIT_TIME_MS,
+                            mLauncher);
 
                     return true;
                 }
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 893f64a..21c819a 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -174,8 +174,8 @@
                 AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), user,
                         sourceContainer);
             }
-            getUserEventDispatcher().logAppLaunch(v, intent);
-            getStatsLogManager().logAppLaunch(v, intent);
+            getUserEventDispatcher().logAppLaunch(v, intent, user);
+            getStatsLogManager().logAppLaunch(v, intent, user);
             return true;
         } catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7265a1a..d8c4c5c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1429,6 +1429,9 @@
 
     @Override
     protected void onNewIntent(Intent intent) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Launcher.onNewIntent: " + intent);
+        }
         Object traceToken = TraceHelper.INSTANCE.beginSection(ON_NEW_INTENT_EVT);
         super.onNewIntent(intent);
 
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index 73e705b..7f327a5 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -29,7 +29,7 @@
         return getDimenByName(resName, res, 48);
     }
 
-    private static int getDimenByName(String resName, Resources res, int defaultValue) {
+    public static int getDimenByName(String resName, Resources res, int defaultValue) {
         final int frameSize;
         final int frameSizeResID = res.getIdentifier(resName, "dimen", "android");
         if (frameSizeResID != 0) {
@@ -40,6 +40,17 @@
         return frameSize;
     }
 
+    public static boolean getBoolByName(String resName, Resources res, boolean defaultValue) {
+        final boolean val;
+        final int resId = res.getIdentifier(resName, "bool", "android");
+        if (resId != 0) {
+            val = res.getBoolean(resId);
+        } else {
+            val = defaultValue;
+        }
+        return val;
+    }
+
     public static int pxFromDp(float size, DisplayMetrics metrics) {
         return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
     }
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index d47a40e..28579c1 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -57,6 +58,7 @@
         parcel.putInt(TestProtocol.STATE_FIELD, stateOrdinal);
 
         sendEventToTest(accessibilityManager, TestProtocol.SWITCHED_TO_STATE_MESSAGE, parcel);
+        Log.d(TestProtocol.PERMANENT_DIAG_TAG, "sendStateEventToTest: " + stateOrdinal);
     }
 
     public static void sendScrollFinishedEventToTest(Context context) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 80c7056..003ca82 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -95,7 +95,7 @@
             "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
 
     public static final TogglableFlag ENABLE_HINTS_IN_OVERVIEW = new TogglableFlag(
-            "ENABLE_HINTS_IN_OVERVIEW", true,
+            "ENABLE_HINTS_IN_OVERVIEW", false,
             "Show chip hints and gleams on the overview screen");
 
     public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag(
@@ -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/logging/DumpTargetWrapper.java b/src/com/android/launcher3/logging/DumpTargetWrapper.java
index 365e8f2..067bdfd 100644
--- a/src/com/android/launcher3/logging/DumpTargetWrapper.java
+++ b/src/com/android/launcher3/logging/DumpTargetWrapper.java
@@ -15,17 +15,22 @@
  */
 package com.android.launcher3.logging;
 
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+
+import android.content.ComponentName;
 import android.os.Process;
 import android.text.TextUtils;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.WorkspaceItemInfo;
 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.model.nano.LauncherDumpProto.ItemType;
 import com.android.launcher3.model.nano.LauncherDumpProto.UserType;
+import com.android.launcher3.util.ShortcutUtil;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -73,20 +78,23 @@
     public DumpTarget newItemTarget(ItemInfo info) {
         DumpTarget dt = new DumpTarget();
         dt.type = DumpTarget.Type.ITEM;
-
+        if (info == null) {
+            return dt;
+        }
         switch (info.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 dt.itemType = ItemType.APP_ICON;
                 break;
-            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
-                break;
             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                 dt.itemType = ItemType.WIDGET;
                 break;
-            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+            case ITEM_TYPE_DEEP_SHORTCUT:
+            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                 dt.itemType = ItemType.SHORTCUT;
                 break;
+            default:
+                dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
+                break;
         }
         return dt;
     }
@@ -120,6 +128,9 @@
     }
 
     private static String getItemStr(DumpTarget t) {
+        if (t == null) {
+            return "";
+        }
         String typeStr = LoggerUtils.getFieldName(t.itemType, ItemType.class);
         if (!TextUtils.isEmpty(t.packageName)) {
             typeStr += ", package=" + t.packageName;
@@ -132,8 +143,15 @@
     }
 
     public DumpTarget writeToDumpTarget(ItemInfo info) {
-        node.component = info.getTargetComponent() == null? "":
-                info.getTargetComponent().flattenToString();
+        if (info == null) {
+            return node;
+        }
+        if (ShortcutUtil.isDeepShortcut(info)) {
+            node.component = ((WorkspaceItemInfo) info).getDeepShortcutId();
+        } else {
+            ComponentName cmp = info.getTargetComponent();
+            node.component = cmp == null ? "" : cmp.flattenToString();
+        }
         node.packageName = info.getTargetComponent() == null? "":
                 info.getTargetComponent().getPackageName();
         if (info instanceof LauncherAppWidgetInfo) {
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/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index 598792a..f352b46 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -142,8 +142,10 @@
             typeStr += ", grid(" + t.gridX + "," + t.gridY + ")";
         } else if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0)
                 && t.itemType != ItemType.TASK) {
-            typeStr += ", predictiveRank=" + t.predictedRank + ", grid(" + t.gridX + "," + t.gridY
-                    + "), span(" + t.spanX + "," + t.spanY + "), pageIdx=" + t.pageIndex;
+            typeStr +=
+                    ", isWorkApp=" + t.isWorkApp + ", predictiveRank=" + t.predictedRank + ", grid("
+                            + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+                            + "), pageIdx=" + t.pageIndex;
         }
         if (t.searchQueryLength != 0) {
             typeStr += ", searchQueryLength=" + t.searchQueryLength;
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index cad95b0..9dfd7ab 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -17,12 +17,15 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.UserHandle;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.R;
+import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
 
 /**
  * Handles the user event logging in Q.
@@ -38,7 +41,10 @@
         return mgr;
     }
 
-    public void logAppLaunch(View v, Intent intent) { }
+    /**
+     * Logs app launches
+     */
+    public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) { }
     public void logTaskLaunch(View v, ComponentKey key) { }
     public void logTaskDismiss(View v, ComponentKey key) { }
     public void logSwipeOnContainer(boolean isSwipingToLeft, int pageId) { }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 99906fe..8289da9 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -33,7 +33,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.os.Process;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
 
@@ -135,7 +137,7 @@
     // --------------------------------------------------------------
 
     @Deprecated
-    public void logAppLaunch(View v, Intent intent) {
+    public void logAppLaunch(View v, Intent intent, @Nullable  UserHandle userHandle) {
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
                 newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
 
@@ -143,7 +145,13 @@
             if (mDelegate != null) {
                 mDelegate.modifyUserEvent(event);
             }
-            fillIntentInfo(event.srcTarget[0], intent);
+            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;
@@ -171,8 +179,9 @@
         mAppOrTaskLaunch = true;
     }
 
-    protected void fillIntentInfo(Target target, Intent intent) {
+    protected void fillIntentInfo(Target target, Intent intent, @Nullable UserHandle userHandle) {
         target.intentHash = intent.hashCode();
+        target.isWorkApp = userHandle != null && !userHandle.equals(Process.myUserHandle());
         fillComponentInfo(target, intent.getComponent());
     }
 
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index 521f511..2a102d2 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -37,6 +39,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.R;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.TopRoundedCornerView;
 
@@ -247,4 +250,10 @@
         anim.play(ObjectAnimator.ofFloat(mRecyclerView, ALPHA, 0.5f));
         return anim;
     }
+
+    @Override
+    protected void onCloseComplete() {
+        super.onCloseComplete();
+        AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
+    }
 }
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/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
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 4243ed0..60dad12 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -92,7 +92,7 @@
     public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
     public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
 
-    public static final long DEFAULT_UI_TIMEOUT = 60000; // b/136278866
+    public static final long DEFAULT_UI_TIMEOUT = 10000;
     private static final String TAG = "AbstractLauncherUiTest";
 
     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
@@ -259,7 +259,7 @@
     protected <T> T getOnUiThread(final Callable<T> callback) {
         try {
             return mMainThreadExecutor.submit(callback).get();
-        } catch (Exception e) {
+        } catch (Throwable e) {
             throw new RuntimeException(e);
         }
     }
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 80bb3ed..1a68122 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -38,8 +38,8 @@
 
                     evaluateInPortrait();
                     evaluateInLandscape();
-                } catch (Exception e) {
-                    Log.e(TAG, "Exception", e);
+                } catch (Throwable e) {
+                    Log.e(TAG, "Error", e);
                     throw e;
                 } finally {
                     mTest.mDevice.setOrientationNatural();
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 0472ce1..62e2a53 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -103,11 +103,11 @@
 
         setResult(acceptConfig);
         if (acceptConfig) {
-            Wait.atMost(null, new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
+            Wait.atMost("", new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
             assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
         } else {
             // Verify that the widget id is deleted.
-            Wait.atMost(null, () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null,
+            Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null,
                     DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
         }
     }
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index d909ad7..59b861c 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -170,7 +170,7 @@
 
         // Go back to home
         mLauncher.pressHome();
-        Wait.atMost(null, new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT,
+        Wait.atMost("", new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT,
                 mLauncher);
     }
 
diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java
index 2663d02..2ab1e00 100644
--- a/tests/src/com/android/launcher3/util/Wait.java
+++ b/tests/src/com/android/launcher3/util/Wait.java
@@ -7,6 +7,8 @@
 
 import org.junit.Assert;
 
+import java.util.function.Supplier;
+
 /**
  * A utility class for waiting for a condition to be true.
  */
@@ -16,10 +18,16 @@
 
     public static void atMost(String message, Condition condition, long timeout,
             LauncherInstrumentation launcher) {
+        atMost(() -> message, condition, timeout, DEFAULT_SLEEP_MS, launcher);
+    }
+
+    public static void atMost(Supplier<String> message, Condition condition, long timeout,
+            LauncherInstrumentation launcher) {
         atMost(message, condition, timeout, DEFAULT_SLEEP_MS, launcher);
     }
 
-    public static void atMost(String message, Condition condition, long timeout, long sleepMillis,
+    public static void atMost(Supplier<String> message, Condition condition, long timeout,
+            long sleepMillis,
             LauncherInstrumentation launcher) {
         final long startTime = SystemClock.uptimeMillis();
         long endTime = startTime + timeout;
@@ -45,6 +53,6 @@
         }
         Log.d("Wait", "atMost: timed out: " + SystemClock.uptimeMillis());
         launcher.checkForAnomaly();
-        Assert.fail(message);
+        Assert.fail(message.get());
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 18f33a2..b715de0 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -308,13 +308,22 @@
         }
     }
 
+    private String getVisiblePackages() {
+        return mDevice.findObjects(By.textStartsWith(""))
+                .stream()
+                .map(object -> object.getApplicationPackage())
+                .distinct()
+                .filter(pkg -> !"com.android.systemui".equals(pkg))
+                .collect(Collectors.joining(", "));
+    }
+
     private String getVisibleStateMessage() {
         if (hasLauncherObject(CONTEXT_MENU_RES_ID)) return "Context Menu";
         if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets";
         if (hasLauncherObject(OVERVIEW_RES_ID)) return "Overview";
         if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace";
         if (hasLauncherObject(APPS_RES_ID)) return "AllApps";
-        return "Background";
+        return "Background (" + getVisiblePackages() + ")";
     }
 
     public void setSystemHealthSupplier(Function<Long, String> supplier) {
@@ -429,12 +438,6 @@
         assertEquals("Unexpected display rotation",
                 mExpectedRotation, mDevice.getDisplayRotation());
 
-        // b/136278866
-        for (int i = 0; i != 100; ++i) {
-            if (getNavigationModeMismatchError() == null) break;
-            sleep(100);
-        }
-
         final String error = getNavigationModeMismatchError();
         assertTrue(error, error == null);
         log("verifyContainerType: " + containerType);
@@ -519,7 +522,9 @@
                     mInstrumentation.getUiAutomation().executeAndWaitForEvent(
                             command, eventFilter, WAIT_TIME_MS);
             assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
-            return event.getParcelableData();
+            final Parcelable parcelableData = event.getParcelableData();
+            event.recycle();
+            return parcelableData;
         } catch (TimeoutException e) {
             fail(message.get());
             return null;
@@ -557,7 +562,7 @@
             if (hasLauncherObject(WORKSPACE_RES_ID)) {
                 log(action = "already at home");
             } else {
-                log("Hierarchy before swiping up to home");
+                log("Hierarchy before swiping up to home:");
                 dumpViewHierarchy();
                 log(action = "swiping up to home from " + getVisibleStateMessage());
 
@@ -569,15 +574,19 @@
                 }
             }
         } else {
-            log(action = "clicking home button");
-            executeAndWaitForEvent(
-                    () -> {
-                        log("LauncherInstrumentation.pressHome before clicking");
-                        waitForSystemUiObject("home").click();
-                    },
-                    event -> true,
-                    () -> "Pressing Home didn't produce any events");
-            mDevice.waitForIdle();
+            log("Hierarchy before clicking home:");
+            dumpViewHierarchy();
+            log(action = "clicking home button from " + getVisibleStateMessage());
+            try (LauncherInstrumentation.Closable c = addContextLayer(action)) {
+                mDevice.waitForIdle();
+                runToState(
+                        () -> waitForSystemUiObject("home").click(),
+                        NORMAL_STATE_ORDINAL,
+                        !hasLauncherObject(WORKSPACE_RES_ID)
+                                && (hasLauncherObject(APPS_RES_ID)
+                                || hasLauncherObject(OVERVIEW_RES_ID)));
+                mDevice.waitForIdle();
+            }
         }
         try (LauncherInstrumentation.Closable c = addContextLayer(
                 "performed action to switch to Home - " + action)) {
@@ -781,12 +790,20 @@
                 + "]";
     }
 
+    void runToState(Runnable command, int expectedState, boolean requireEvent) {
+        if (requireEvent) {
+            runToState(command, expectedState);
+        } else {
+            command.run();
+        }
+    }
+
     void runToState(Runnable command, int expectedState) {
         final List<Integer> actualEvents = new ArrayList<>();
         executeAndWaitForEvent(
                 command,
                 event -> isSwitchToStateEvent(event, expectedState, actualEvents),
-                () -> "Failed to receive an event for the swipe end: expected "
+                () -> "Failed to receive an event for the state change: expected "
                         + TestProtocol.stateOrdinalToString(expectedState)
                         + ", actual: " + eventListToString(actualEvents));
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 8a53ef1..3299d5d 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -22,6 +22,7 @@
 
 import static junit.framework.TestCase.assertTrue;
 
+import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.SystemClock;
@@ -49,6 +50,34 @@
         mHotseat = launcher.waitForLauncherObject("hotseat");
     }
 
+    private static boolean supportsRoundedCornersOnWindows(Resources resources) {
+        return ResourceUtils.getBoolByName(
+                "config_supportsRoundedCornersOnWindows", resources, false);
+    }
+
+    private static float getWindowCornerRadius(Resources resources) {
+        if (!supportsRoundedCornersOnWindows(resources)) {
+            return 0f;
+        }
+
+        // Radius that should be used in case top or bottom aren't defined.
+        float defaultRadius = ResourceUtils.getDimenByName("rounded_corner_radius", resources, 0);
+
+        float topRadius = ResourceUtils.getDimenByName("rounded_corner_radius_top", resources, 0);
+        if (topRadius == 0f) {
+            topRadius = defaultRadius;
+        }
+        float bottomRadius = ResourceUtils.getDimenByName(
+                "rounded_corner_radius_bottom", resources, 0);
+        if (bottomRadius == 0f) {
+            bottomRadius = defaultRadius;
+        }
+
+        // Always use the smallest radius to make sure the rounded corners will
+        // completely cover the display.
+        return Math.min(topRadius, bottomRadius);
+    }
+
     /**
      * Swipes up to All Apps.
      *
@@ -59,13 +88,12 @@
         try (LauncherInstrumentation.Closable c =
                      mLauncher.addContextLayer("want to switch from workspace to all apps")) {
             verifyActiveContainer();
-            final UiObject2 hotseat = mHotseat;
-            final Point start = hotseat.getVisibleCenter();
-            int deviceHeight = mLauncher.getDevice().getDisplayHeight();
-            int bottomGestureMargin = ResourceUtils.getNavbarSize(
+            final int deviceHeight = mLauncher.getDevice().getDisplayHeight();
+            final int bottomGestureMargin = ResourceUtils.getNavbarSize(
                     ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources());
-            int displayBottom = deviceHeight - bottomGestureMargin;
-            start.y = displayBottom - 1;
+            final int windowCornerRadius = (int) Math.ceil(getWindowCornerRadius(
+                    mLauncher.getResources()));
+            final int startY = deviceHeight - Math.max(bottomGestureMargin, windowCornerRadius) - 1;
             final int swipeHeight = mLauncher.getTestInfo(
                     TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT).
                     getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
@@ -74,10 +102,10 @@
                             + mLauncher.getTouchSlop());
 
             mLauncher.swipeToState(
-                    start.x,
-                    start.y,
-                    start.x,
-                    start.y - swipeHeight - mLauncher.getTouchSlop(),
+                    0,
+                    startY,
+                    0,
+                    startY - swipeHeight - mLauncher.getTouchSlop(),
                     12,
                     ALL_APPS_STATE_ORDINAL);